diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java index 2202e6e99f..3e62c0b17d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java @@ -101,7 +101,7 @@ public class JobDatabase extends SQLiteOpenHelper implements SignalDatabase { } public JobDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) { - super(application, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook()); + super(application, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook(), new SqlCipherErrorHandler(DATABASE_NAME)); this.application = application; this.databaseSecret = databaseSecret; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/KeyValueDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/KeyValueDatabase.java index 4931a9a947..9039a4ccba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/KeyValueDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/KeyValueDatabase.java @@ -61,7 +61,7 @@ public class KeyValueDatabase extends SQLiteOpenHelper implements SignalDatabase } public KeyValueDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) { - super(application, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook()); + super(application, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook(), new SqlCipherErrorHandler(DATABASE_NAME)); this.application = application; this.databaseSecret = databaseSecret; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MegaphoneDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MegaphoneDatabase.java index c00919ba78..824e8532dd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MegaphoneDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MegaphoneDatabase.java @@ -64,7 +64,7 @@ public class MegaphoneDatabase extends SQLiteOpenHelper implements SignalDatabas } public MegaphoneDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) { - super(application, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook()); + super(application, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook(), new SqlCipherErrorHandler(DATABASE_NAME)); this.application = application; this.databaseSecret = databaseSecret; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SqlCipherErrorHandler.java b/app/src/main/java/org/thoughtcrime/securesms/database/SqlCipherErrorHandler.java new file mode 100644 index 0000000000..0555f3e5cd --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SqlCipherErrorHandler.java @@ -0,0 +1,53 @@ +package org.thoughtcrime.securesms.database; + +import android.database.Cursor; + +import androidx.annotation.NonNull; + +import net.sqlcipher.DatabaseErrorHandler; +import net.sqlcipher.database.SQLiteDatabase; + +import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.util.CursorUtil; + +/** + * The default error handler wipes the file. This one instead prints some diagnostics and then crashes so the original corrupt file isn't lost. + */ +public final class SqlCipherErrorHandler implements DatabaseErrorHandler { + + private static final String TAG = Log.tag(SqlCipherErrorHandler.class); + + private final String tableName; + + public SqlCipherErrorHandler(@NonNull String tableName) { + this.tableName = tableName; + } + + @Override + public void onCorruption(SQLiteDatabase db) { + Log.e(TAG, "Database '" + tableName + "' corrupted! Going to try to run some diagnostics."); + + Log.w(TAG, " ===== PRAGMA integrity_check ====="); + try (Cursor cursor = db.rawQuery("PRAGMA integrity_check", null)) { + while (cursor.moveToNext()) { + Log.w(TAG, CursorUtil.readRowAsString(cursor)); + } + } catch (Throwable t) { + Log.e(TAG, "Failed to do integrity_check!", t); + } + + Log.w(TAG, "===== PRAGMA cipher_integrity_check ====="); + try (Cursor cursor = db.rawQuery("PRAGMA cipher_integrity_check", null)) { + while (cursor.moveToNext()) { + Log.w(TAG, CursorUtil.readRowAsString(cursor)); + } + } catch (Throwable t) { + Log.e(TAG, "Failed to do cipher_integrity_check!", t); + } + + throw new DatabaseCorruptedError(); + } + + public static final class DatabaseCorruptedError extends Error { + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index be75935876..c74cd54220 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -52,6 +52,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignedPreKeyDatabase; import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.SqlCipherDatabaseHook; +import org.thoughtcrime.securesms.database.SqlCipherErrorHandler; import org.thoughtcrime.securesms.database.StickerDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.UnknownStorageIdDatabase; @@ -205,7 +206,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab private final DatabaseSecret databaseSecret; public SQLCipherOpenHelper(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) { - super(context, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook()); + super(context, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook(), new SqlCipherErrorHandler(DATABASE_NAME)); this.context = context.getApplicationContext(); this.databaseSecret = databaseSecret; diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java index 842fea48d8..2503a5cfce 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java @@ -77,4 +77,20 @@ public final class CursorUtil { return Optional.fromNullable(requireBlob(cursor, column)); } } + + /** + * Reads each column as a string, and concatenates them together into a single string separated by | + */ + public static String readRowAsString(@NonNull Cursor cursor) { + StringBuilder row = new StringBuilder(); + + for (int i = 0, len = cursor.getColumnCount(); i < len; i++) { + row.append(cursor.getString(i)); + if (i < len - 1) { + row.append(" | "); + } + } + + return row.toString(); + } }