You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by su...@apache.org on 2006/09/12 19:25:12 UTC

svn commit: r442647 - in /db/derby/code/trunk/java: engine/org/apache/derby/iapi/store/raw/log/ engine/org/apache/derby/impl/store/raw/ engine/org/apache/derby/impl/store/raw/log/ engine/org/apache/derby/loc/ shared/org/apache/derby/shared/common/refer...

Author: suresht
Date: Tue Sep 12 10:25:10 2006
New Revision: 442647

URL: http://svn.apache.org/viewvc?view=rev&rev=442647
Log:
DERBY-1786 (a crash during re-encryption may cause an unrecoverable db)

The problem was when transaction log spans more than one log file during (re)
encryption of the database and if there is a crash just before switching the
database to use the new encryption properties; On recovery checkpoint in the
first log file is used as reference and the next log file is assumed to have
the commit log record for (re) encryption and deleted incorrectly to force the
roll-back , which lead to the incomplete rollback of re-encryption. And that
caused recovery failures on next (re) encryption crashed. 

This patch fixes the problem by ensuring there a checkpoint record in the
last log file before creating a new log file with new encryption properties
and writing the commit log record. Log is also flushed before making the
transaction log use the new encryption key to avoid any part of old log 
records in the buffers getting encrypted with the new encryption key. 
While working on this problem , I noticed error message thrown incase of 
re-encryption failures are confusing, added a new error message to indicate 
failures specific to (re) encryption. 


Modified:
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/store/raw/log/LogFactory.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/RawStore.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/log/LogToFile.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/log/ReadOnly.java
    db/derby/code/trunk/java/engine/org/apache/derby/loc/messages_en.properties
    db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/store/raw/log/LogFactory.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/store/raw/log/LogFactory.java?view=diff&rev=442647&r1=442646&r2=442647
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/store/raw/log/LogFactory.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/store/raw/log/LogFactory.java Tue Sep 12 10:25:10 2006
@@ -284,9 +284,16 @@
 
     /*
      * Set that the database is encrypted , all the transaction log has 
-     * to be encrypted.
+     * to be encrypted, and flush the log if requesed. Log needs to 
+	 * be flushed  first, if this is  being set during (re) encryption 
+	 * of an existing  database. 
+	 *
+	 * @param flushLog  true, if log needs to be flushed, 
+	 *                  otherwise false.  
      */
-    public void setDatabaseEncrypted();
+    public  void setDatabaseEncrypted(boolean flushLog)
+		throws StandardException;
+
     
     /*
      * set up a new log file to start writing 

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/RawStore.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/RawStore.java?view=diff&rev=442647&r1=442646&r2=442647
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/RawStore.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/RawStore.java Tue Sep 12 10:25:10 2006
@@ -302,7 +302,7 @@
 
         if (databaseEncrypted) {
             // let log factory know if the database is encrypted . 
-            logFactory.setDatabaseEncrypted();
+            logFactory.setDatabaseEncrypted(false);
             // let data factory know if the database is encrypted. 
             dataFactory.setDatabaseEncrypted();
         }
@@ -1398,7 +1398,8 @@
      * when the input debug flag is set, an expception 
      * is throw when run in the debug mode.
      */
-    private void crashOnDebugFlag(String debugFlag) 
+    private void crashOnDebugFlag(String debugFlag, 
+                                  boolean reEncrypt) 
         throws StandardException
     {
         if (SanityManager.DEBUG)
@@ -1407,11 +1408,12 @@
             // exception to simulate error cases.
             if (SanityManager.DEBUG_ON(debugFlag))
             {
-                StandardException se= StandardException.newException(
-                       SQLState.LOG_IO_ERROR, 
-                       new IOException(debugFlag));
-                markCorrupt(se);
-                throw se;
+               StandardException se = StandardException.newException(
+                                      (reEncrypt ? SQLState.DATABASE_REENCRYPTION_FAILED :
+                                      SQLState.DATABASE_ENCRYPTION_FAILED),
+                                      debugFlag);
+               markCorrupt(se);
+               throw se;
             }
         }
     }
@@ -1488,178 +1490,202 @@
                    this,
                     ContextService.getFactory().getCurrentContextManager(),
                     AccessFactoryGlobals.USER_TRANS_NAME);
-        boolean error = true;
-        try {
+
+        try 
+		{
+			
             dataFactory.encryptAllContainers(transaction);
-            error = false;
-        }finally {
+
+            // all the containers are (re) encrypted, now mark the database as
+            // encrypted if a plain database is getting configured for encryption
+            // or update the encryption the properties, in the 
+            // service.properties ..etc.
+
             
-            // if (re) encryption failed, abort the transaction.
-            if (error) { 
-                transaction.abort();
-            }
-            else {
-
-                // (re) encryption of all the containers is complete 
-                // update the encryption properties in the 
-                // service.properties ..etc.
+            if (SanityManager.DEBUG) {
+                crashOnDebugFlag(TEST_REENCRYPT_CRASH_BEFORE_COMMT, reEncrypt);
+            }
 
-                if (SanityManager.DEBUG) {
-                    crashOnDebugFlag(TEST_REENCRYPT_CRASH_BEFORE_COMMT);
-                }
+            // check if the checkpoint is currently in the last log file, 
+            // otherwise force a checkpoint and then do a log switch, 
+            // after setting up a new encryption key
+            if (!logFactory.isCheckpointInLastLogFile()) 
+            {
+                // perfrom a checkpoint, this is a reference checkpoint 
+                // to find if the re(encryption) is complete. 
+                logFactory.checkpoint(this, dataFactory, xactFactory, true);
+            }
                 
-                // let the log factory and data factory know that 
-                // database is encrypted.
-                if (!reEncrypt) {
-                    // mark in the raw store that the database is 
-                    // encrypted. 
-                    encryptDatabase = false;
-                    databaseEncrypted = true;
-                    dataFactory.setDatabaseEncrypted();
-                    logFactory.setDatabaseEncrypted();
 
+            encryptDatabase = false;
 
-                } else {
-                    // switch the encryption/decryption engine to the new ones.
-                    decryptionEngine = newDecryptionEngine;  
-                    encryptionEngine = newEncryptionEngine;
-                    currentCipherFactory = newCipherFactory;
-                }
+            // let the log factory know that database is 
+            // (re) encrypted and ask it to flush the log, 
+            // before enabling encryption of the log with 
+            // the new key.
+            logFactory.setDatabaseEncrypted(true);
+            
+            // let the log factory and data factory know that 
+            // database is encrypted.
+            if (!reEncrypt) {
+                // mark in the raw store that the database is 
+                // encrypted. 
+                databaseEncrypted = true;
+                dataFactory.setDatabaseEncrypted();
+            } else {
+                // switch the encryption/decryption engine to the new ones.
+                decryptionEngine = newDecryptionEngine;  
+                encryptionEngine = newEncryptionEngine;
+                currentCipherFactory = newCipherFactory;
+            }
 
-  
-                // make the log factory ready to encrypt
-                // the transaction log with the new encryption 
-                // key by switching to a new log file. 
-                // If re-encryption is aborted for any reason, 
-                // this new log file will be deleted, during
-                // recovery.
-
-                logFactory.startNewLogFile();
-
-                // mark that re-encryption is in progress in the 
-                // service.properties, so that (re) encryption 
-                // changes that can not be undone using the transaction 
-                // log can be un-done before recovery starts.
-                // (like the changes to service.properties and 
-                // any log files the can not be understood by the
-                // old encryption key), incase engine crashes
-                // after this point. 
-
-                // if the crash occurs before this point, recovery
-                // will rollback the changes using the transaction 
-                // log.
+            
+            // make the log factory ready to encrypt
+            // the transaction log with the new encryption 
+            // key by switching to a new log file. 
+            // If re-encryption is aborted for any reason, 
+            // this new log file will be deleted, during
+            // recovery.
+
+            logFactory.startNewLogFile();
+
+            // mark that re-encryption is in progress in the 
+            // service.properties, so that (re) encryption 
+            // changes that can not be undone using the transaction 
+            // log can be un-done before recovery starts.
+            // (like the changes to service.properties and 
+            // any log files the can not be understood by the
+            // old encryption key), incase engine crashes
+            // after this point. 
+
+            // if the crash occurs before this point, recovery
+            // will rollback the changes using the transaction 
+            // log.
 
-                properties.put(RawStoreFactory.DB_ENCRYPTION_STATUS,
-                               String.valueOf(
+            properties.put(RawStoreFactory.DB_ENCRYPTION_STATUS,
+                           String.valueOf(
                                RawStoreFactory.DB_ENCRYPTION_IN_PROGRESS));
 
-                if (reEncrypt) 
-                {
-                    // incase re-encryption, save the old 
-                    // encryption related properties, before
-                    // doing updates with new values.
+            if (reEncrypt) 
+            {
+                // incase re-encryption, save the old 
+                // encryption related properties, before
+                // doing updates with new values.
 
-                    if (externalKeyEncryption) 
-                    {
-                        // save the current copy of verify key file.
-                        StorageFile verifyKeyFile = 
-                            storageFactory.newStorageFile(
+                if (externalKeyEncryption) 
+                {
+                    // save the current copy of verify key file.
+                    StorageFile verifyKeyFile = 
+                        storageFactory.newStorageFile(
                                  Attribute.CRYPTO_EXTERNAL_KEY_VERIFY_FILE);
-                        StorageFile oldVerifyKeyFile = 
-                          storageFactory.newStorageFile(
+                    StorageFile oldVerifyKeyFile = 
+                        storageFactory.newStorageFile(
                           RawStoreFactory.CRYPTO_OLD_EXTERNAL_KEY_VERIFY_FILE);
 
-                        if(!privCopyFile(verifyKeyFile, oldVerifyKeyFile))
-                            throw StandardException.
-                              newException(SQLState.RAWSTORE_ERROR_COPYING_FILE,
-                                           verifyKeyFile, oldVerifyKeyFile); 
-
-                        // update the verify key file with the new key info.
-                        currentCipherFactory.verifyKey(reEncrypt, 
-                                                       storageFactory, 
-                                                       properties);
-                    } else 
-                    {
-                        // save the current generated encryption key 
-                        String keyString = 
-                            properties.getProperty(
-                                           RawStoreFactory.ENCRYPTED_KEY);
-                        if (keyString != null)
-                            properties.put(RawStoreFactory.OLD_ENCRYPTED_KEY,
-                                           keyString);
-                    }
+                    if(!privCopyFile(verifyKeyFile, oldVerifyKeyFile))
+                        throw StandardException.
+                            newException(SQLState.RAWSTORE_ERROR_COPYING_FILE,
+                                         verifyKeyFile, oldVerifyKeyFile); 
+
+                    // update the verify key file with the new key info.
+                    currentCipherFactory.verifyKey(reEncrypt, 
+                                                   storageFactory, 
+                                                   properties);
                 } else 
                 {
-                    // save the encryption block size;
-                    properties.put(RawStoreFactory.ENCRYPTION_BLOCKSIZE,
-                                   String.valueOf(encryptionBlockSize));
+                    // save the current generated encryption key 
+                    String keyString = 
+                        properties.getProperty(
+                                               RawStoreFactory.ENCRYPTED_KEY);
+                    if (keyString != null)
+                        properties.put(RawStoreFactory.OLD_ENCRYPTED_KEY,
+                                       keyString);
                 }
+            } else 
+            {
+                // save the encryption block size;
+                properties.put(RawStoreFactory.ENCRYPTION_BLOCKSIZE,
+                               String.valueOf(encryptionBlockSize));
+            }
 
-                // save the new encryption properties into service.properties
-                currentCipherFactory.saveProperties(properties) ;
+            // save the new encryption properties into service.properties
+            currentCipherFactory.saveProperties(properties) ;
  
-                if (SanityManager.DEBUG) {
-                    crashOnDebugFlag(
-                                 TEST_REENCRYPT_CRASH_AFTER_SWITCH_TO_NEWKEY);
-                }
+            if (SanityManager.DEBUG) {
+                crashOnDebugFlag(
+                                 TEST_REENCRYPT_CRASH_AFTER_SWITCH_TO_NEWKEY,
+                                 reEncrypt);
+            }
 
-                // commit the transaction that is used to 
-                // (re) encrypt the database. Note that 
-                // this will be logged with newly generated 
-                // encryption key in the new log file created 
-                // above.
-                transaction.commit();
+            // commit the transaction that is used to 
+            // (re) encrypt the database. Note that 
+            // this will be logged with newly generated 
+            // encryption key in the new log file created 
+            // above.
+            transaction.commit();
 
-                if (SanityManager.DEBUG) {
-                    crashOnDebugFlag(TEST_REENCRYPT_CRASH_AFTER_COMMT);
-                }
+            if (SanityManager.DEBUG) {
+                crashOnDebugFlag(TEST_REENCRYPT_CRASH_AFTER_COMMT, 
+                                 reEncrypt);
+            }
 
-                // force the checkpoint with new encryption key.
-                logFactory.checkpoint(this, dataFactory, xactFactory, true);
+            // force the checkpoint with new encryption key.
+            logFactory.checkpoint(this, dataFactory, xactFactory, true);
 
-                if (SanityManager.DEBUG) {
-                    crashOnDebugFlag(TEST_REENCRYPT_CRASH_AFTER_CHECKPOINT);
-                }
+            if (SanityManager.DEBUG) {
+                crashOnDebugFlag(TEST_REENCRYPT_CRASH_AFTER_CHECKPOINT, 
+                                 reEncrypt);
+            }
 
-                // once the checkpont makes it to the log, re-encrption 
-                // is complete. only cleanup is remaining ; update the 
-                // re-encryption status flag to cleanup. 
-                properties.put(RawStoreFactory.DB_ENCRYPTION_STATUS,
-                               String.valueOf(
+            // once the checkpont makes it to the log, re-encrption 
+            // is complete. only cleanup is remaining ; update the 
+            // re-encryption status flag to cleanup. 
+            properties.put(RawStoreFactory.DB_ENCRYPTION_STATUS,
+                           String.valueOf(
                                RawStoreFactory.DB_ENCRYPTION_IN_CLEANUP));
 
-                // database is (re)encrypted successfuly, 
-                // remove the old version of the container files.
-                dataFactory.removeOldVersionOfContainers(false);
+            // database is (re)encrypted successfuly, 
+            // remove the old version of the container files.
+            dataFactory.removeOldVersionOfContainers(false);
                 
-                if (reEncrypt) 
+            if (reEncrypt) 
+            {
+                if (externalKeyEncryption)
                 {
-                    if (externalKeyEncryption)
-                    {
-                        // remove the saved copy of the verify.key file
-                        StorageFile oldVerifyKeyFile = 
+                    // remove the saved copy of the verify.key file
+                    StorageFile oldVerifyKeyFile = 
                         storageFactory.newStorageFile(
-                        RawStoreFactory.CRYPTO_OLD_EXTERNAL_KEY_VERIFY_FILE);
-                        if (!privDelete(oldVerifyKeyFile))
-                            throw StandardException.newException(
+                          RawStoreFactory.CRYPTO_OLD_EXTERNAL_KEY_VERIFY_FILE);
+                    if (!privDelete(oldVerifyKeyFile))
+                        throw StandardException.newException(
                                     SQLState.UNABLE_TO_DELETE_FILE, 
                                     oldVerifyKeyFile);
-                    } else 
-                    {
-                        // remove the old encryption key property.
-                        properties.remove(RawStoreFactory.OLD_ENCRYPTED_KEY);
-                    }
+                } else 
+                {
+                    // remove the old encryption key property.
+                    properties.remove(RawStoreFactory.OLD_ENCRYPTED_KEY);
                 }
+            }
 
-                // (re) encrypion is done,  remove the (re) 
-                // encryption status property. 
-                properties.remove(RawStoreFactory.DB_ENCRYPTION_STATUS);
+            // (re) encrypion is done,  remove the (re) 
+            // encryption status property. 
 
-            }                
+            properties.remove(RawStoreFactory.DB_ENCRYPTION_STATUS);
+
+            // close the transaction. 
+            transaction.close(); 
 
+        } catch (StandardException se) {
+
+            throw StandardException.newException(
+                      (reEncrypt ? SQLState.DATABASE_REENCRYPTION_FAILED :
+                      SQLState.DATABASE_ENCRYPTION_FAILED),
+                      se,
+                      se.getMessage()); 
+        } finally {
+            // clear the new encryption engines.
             newDecryptionEngine = null;   
             newEncryptionEngine = null;
-            transaction.close(); 
         }
     }
 
@@ -1741,7 +1767,8 @@
                 
             if (SanityManager.DEBUG) {
                 crashOnDebugFlag(
-                   TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_LOGFILE_DELETE);
+                   TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_LOGFILE_DELETE, 
+                   reEncryption);
             }
 
             // Note : If a crash occurs at this point, then on reboot 
@@ -1825,7 +1852,8 @@
 
             if (SanityManager.DEBUG) {
                 crashOnDebugFlag(
-                    TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_REVERTING_KEY);
+                    TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_REVERTING_KEY, 
+                    reEncryption);
             }
 
         } // end of UNDO
@@ -1839,7 +1867,8 @@
         
         if (SanityManager.DEBUG) {
                 crashOnDebugFlag(
-                   TEST_REENCRYPT_CRASH_BEFORE_RECOVERY_FINAL_CLEANUP);
+                   TEST_REENCRYPT_CRASH_BEFORE_RECOVERY_FINAL_CLEANUP, 
+                   reEncryption);
         }
 
         // either the (re) encryption was complete , 

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/log/LogToFile.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/log/LogToFile.java?view=diff&rev=442647&r1=442646&r2=442647
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/log/LogToFile.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/log/LogToFile.java Tue Sep 12 10:25:10 2006
@@ -3973,11 +3973,19 @@
 
 
     /*
-     * Set that the database is encrypted, all the data in the 
-     * transaction log  should be encrypted. 
+     * Set that the database is encrypted , all the transaction log has 
+     * to be encrypted, and flush the log if requesed. Log needs to 
+     * be flushed  first, if this is  being set during (re) encryption 
+     * of an existing  database. 
+     *
+     * @param flushLog  true, if log needs to be flushed, 
+     *                  otherwise false.  
      */
-    public  void setDatabaseEncrypted() 
+    public  void setDatabaseEncrypted(boolean flushLog) 
+        throws StandardException
     {
+        if (flushLog) 
+            flushAll();
         databaseEncrypted = true;
     }
 

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/log/ReadOnly.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/log/ReadOnly.java?view=diff&rev=442647&r1=442646&r2=442647
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/log/ReadOnly.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/log/ReadOnly.java Tue Sep 12 10:25:10 2006
@@ -360,7 +360,7 @@
      * Set that the database is encrypted. Read-only database can not 
      * be reencrypted, nothing to do in this case. 
      */
-    public void setDatabaseEncrypted()
+    public void setDatabaseEncrypted(boolean flushLog)
     {
         // nothing to do for a read-only database.
     }

Modified: db/derby/code/trunk/java/engine/org/apache/derby/loc/messages_en.properties
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/loc/messages_en.properties?view=diff&rev=442647&r1=442646&r2=442647
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/loc/messages_en.properties (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/loc/messages_en.properties Tue Sep 12 10:25:10 2006
@@ -102,13 +102,14 @@
 XBCXL.S=The verification process for the encryption key was not successful. This could have been caused by an error when accessing the appropriate file to do the verification process.  See next exception for details.  
 XBCXM.S=The length of the external encryption key must be an even number.
 XBCXN.S=The external encryption key contains one or more illegal characters. Allowed characters for a hexadecimal number are 0-9, a-f and A-F.
-XBCXO.S= Cannot encrypt the database when there is a global transaction in the prepared state.
-XBCXP.S= Cannot re-encrypt the database with a new boot password or an external encryption key when there is a global transaction in the prepared state.
-XBCXQ.S= Cannot configure a read-only database for encryption.
-XBCXR.S= Cannot re-encrypt a read-only database with a new boot password or an external encryption key .
-XBCXS.S= Cannot configure a database for encryption, when database is in the log archive mode.
-XBCXT.S= Cannot re-encrypt a database with a new boot password or an external encryption key, when database is in the log archive mode.
-
+XBCXO.S=Cannot encrypt the database when there is a global transaction in the prepared state.
+XBCXP.S=Cannot re-encrypt the database with a new boot password or an external encryption key when there is a global transaction in the prepared state.
+XBCXQ.S=Cannot configure a read-only database for encryption.
+XBCXR.S=Cannot re-encrypt a read-only database with a new boot password or an external encryption key .
+XBCXS.S=Cannot configure a database for encryption, when database is in the log archive mode.
+XBCXT.S=Cannot re-encrypt a database with a new boot password or an external encryption key, when database is in the log archive mode.
+XBCXU.S=Encryption of an un-encrypted database failed: {0}.
+XBCXV.S=Encryption of an encrypted database with a new key or a new password failed: {0}.
 
 #../java/com/ibm/db2j/impl/BasicServices/CacheService/Generic/messages.properties
 

Modified: db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java?view=diff&rev=442647&r1=442646&r2=442647
==============================================================================
--- db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java (original)
+++ db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java Tue Sep 12 10:25:10 2006
@@ -229,7 +229,8 @@
     String CANNOT_REENCRYPT_READONLY_DATABASE         = "XBCXR.S";
     String CANNOT_ENCRYPT_LOG_ARCHIVED_DATABASE       = "XBCXS.S";
     String CANNOT_REENCRYPT_LOG_ARCHIVED_DATABASE     = "XBCXT.S";
-
+    String DATABASE_ENCRYPTION_FAILED                 = "XBCXU.S";
+    String DATABASE_REENCRYPTION_FAILED               = "XBCXV.S";
 
 	/*
 	** Cache Service