From f94b47851b2bbfd19d1e5c48837b30373a47984e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Tro=C3=9Fbach?= Date: Tue, 3 Nov 2026 16:35:52 +0100 Subject: [PATCH 1/4] Generate a random salt for a new database again Since c969cdf, mcReadDatabaseHeader() returns the zero-filled header buffer for an empty file, and the key generation takes it as the salt. Every new database got a salt of 16 zero bytes, so all new databases with the same password had the same key. Return NULL for an empty file again, so that a random salt is generated. The all-zero salt also made a keyless open delete the hot journal of such a database after a crash. rekeytest now checks that two databases created from empty files with the same key get different salts. --- src/cipher_common.c | 4 ++++ test/rekeytest.c | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/cipher_common.c b/src/cipher_common.c index 38e0fba..40f42b5 100644 --- a/src/cipher_common.c +++ b/src/cipher_common.c @@ -688,7 +688,11 @@ SQLITE_PRIVATE unsigned char* mcReadDatabaseHeader(Codec* codec, unsigned char* sqlite3_int64 fileSize = 0; int ret = sqlite3OsFileSize(fd, &fileSize); if (fileSize == 0) + { + /* Empty file: there is no salt yet, a random salt will be generated */ *rc = SQLITE_OK; + return NULL; + } } if (*rc == SQLITE_OK) hdr = dbHeader; diff --git a/test/rekeytest.c b/test/rekeytest.c index 4eb8ade..2d87af9 100644 --- a/test/rekeytest.c +++ b/test/rekeytest.c @@ -1765,6 +1765,25 @@ static void t8_short_header(void) rc = open_state("chacha20", "k", &db, &n); CHECK(rc == SQLITE_OK && n == 1, "reopened and readable without cipher_salt"); if (db) sqlite3_close(db); + + /* A second new DB with the same key must get a different salt */ + { + unsigned char h2[16]; + remove_files(); + write_bytes(DBFILE, 0); + rc = sqlite3_open_v2(DBFILE, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, "multipleciphers-faulty"); + if (rc == SQLITE_OK) + { + set_params(db, "chacha20"); + keyRc = sqlite3_key(db, "k", 1); + rc = sqlite3_exec(db, "CREATE TABLE t(a INTEGER PRIMARY KEY, b TEXT)", 0, 0, 0); + if (keyRc != SQLITE_OK) rc = keyRc; + } + sqlite3_close(db); + db = 0; + CHECK(rc == SQLITE_OK && read_header_salt(h2) && memcmp(h, h2, 16) != 0, + "second empty file + same key: different salt"); + } } for (i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++) From d3d325802eaa1f5b6037768915b67f1528665fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Tro=C3=9Fbach?= Date: Tue, 3 Nov 2026 16:35:52 +0100 Subject: [PATCH 2/4] Check the schema name in mcFindVfs Since c969cdf, mcFindVfs() indexes db->aDb with the result of sqlite3FindDbName() without checking for -1 or a missing btree. sqlite3_key_v2() and sqlite3_rekey_v2() with an unknown schema name or "temp", and sqlite3_backup_init() with "temp" as source, crashed. The key functions return an error again, and the backup works as before. rekeytest checks these calls. --- src/sqlite3mc_vfs.c | 4 ++-- test/rekeytest.c | 31 ++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/sqlite3mc_vfs.c b/src/sqlite3mc_vfs.c index a718209..3e8f4da 100644 --- a/src/sqlite3mc_vfs.c +++ b/src/sqlite3mc_vfs.c @@ -723,8 +723,8 @@ static sqlite3mc_file* mcFindDbMainFileName(sqlite3mc_vfs* mcVfs, const char* zF static sqlite3mc_vfs* mcFindVfs(sqlite3* db, const char* zDbName) { int dbIndex = (zDbName) ? sqlite3FindDbName(db, zDbName) : 0; - Pager* pager = sqlite3BtreePager(db->aDb[dbIndex].pBt); - sqlite3_vfs* dbVfs = pager->pVfs; + Btree* pBt = (dbIndex >= 0) ? db->aDb[dbIndex].pBt : NULL; + sqlite3_vfs* dbVfs = (pBt != NULL) ? sqlite3BtreePager(pBt)->pVfs : NULL; sqlite3mc_vfs* pVfsMC = NULL; if (dbVfs && dbVfs->xOpen == mcVfsOpen) diff --git a/test/rekeytest.c b/test/rekeytest.c index 2d87af9..62a241e 100644 --- a/test/rekeytest.c +++ b/test/rekeytest.c @@ -21,7 +21,8 @@ ** always be supplied (rekey, failure case, plain -> encrypted) ** Part 8 : salt management (random salt, salt in memory across several rekeys, ** rekey_salt=on/off) and behaviour when reading the file header -** (unencrypted/empty/too short file, I/O error while reading the header) +** (unencrypted/empty/too short file, I/O error while reading the header), +** key, rekey and backup for a schema name without a database file ** ** Invariant in parts 3 and 6 (after every rekey, whether it failed or not): ** exactly ONE of the two states (old/new) is completely readable @@ -1846,6 +1847,33 @@ static void t8_header_read_error(void) CHECK(state_ok("chacha20", "old-key", g_nrows), "afterwards completely readable with the correct key"); } +/* Key, rekey and backup for a schema name without a database file +** (unknown name, "temp" before it is used): an error, no crash */ +static void t8_schema_without_file(void) +{ + sqlite3* db = 0; + sqlite3* dbDest = 0; + sqlite3_backup* backup; + int rc; + + printf("\n== key, rekey and backup for a schema without database file ==\n"); + snprintf(g_ctx, sizeof(g_ctx), "Part 8: schema_without_file"); + remove_files(); + rc = sqlite3_open_v2(DBFILE, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, "multipleciphers-faulty"); + CHECK(rc == SQLITE_OK, "DB opened"); + if (rc != SQLITE_OK) return; + CHECK(sqlite3_key_v2(db, "nosuchdb", "k", 1) != SQLITE_OK, "key for an unknown schema fails"); + CHECK(sqlite3_key_v2(db, "temp", "k", 1) != SQLITE_OK, "key for temp fails"); + CHECK(sqlite3_rekey_v2(db, "nosuchdb", "k", 1) != SQLITE_OK, "rekey for an unknown schema fails"); + CHECK(sqlite3_rekey_v2(db, "temp", "k", 1) != SQLITE_OK, "rekey for temp fails"); + rc = sqlite3_open(":memory:", &dbDest); + backup = (rc == SQLITE_OK) ? sqlite3_backup_init(dbDest, "main", db, "temp") : NULL; + if (backup) sqlite3_backup_finish(backup); + CHECK(rc == SQLITE_OK, "backup from temp returns without a crash"); + sqlite3_close(dbDest); + sqlite3_close(db); +} + static void run_part8(void) { t8_random_salt(); @@ -1856,6 +1884,7 @@ static void run_part8(void) t8_plain_with_key(); t8_short_header(); t8_header_read_error(); + t8_schema_without_file(); } /* ------------------------------------------------------------------ */ From 489784c882b866c024f6af4a827e209770eba652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Tro=C3=9Fbach?= Date: Tue, 3 Nov 2026 16:35:52 +0100 Subject: [PATCH 3/4] Do not wrap the secure memory allocator twice Since 654feca, mcMemoryShutdown() resets mcSecureMemoryInitialized. After sqlite3_shutdown(), SQLITE_CONFIG_GETMALLOC still returns the secure memory methods, so the next sqlite3_initialize() saved them as the default methods, the allocator called itself, and sqlite3_initialize() did not return (SQLITE3MC_SECURE_MEMORY=1). If the secure memory methods are still installed, they are kept now. An allocator configured after the shutdown is wrapped as before. --- src/memory_secure.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/memory_secure.c b/src/memory_secure.c index 937149d..adb99c4 100644 --- a/src/memory_secure.c +++ b/src/memory_secure.c @@ -209,16 +209,26 @@ SQLITE_PRIVATE void sqlite3mcInitMemoryMethods() #if SQLITE3MC_SECURE_MEMORY if (!mcSecureMemoryInitialized) { - if (sqlite3_config(SQLITE_CONFIG_GETMALLOC, &mcDefaultMemoryMethods) == SQLITE_OK) + sqlite3_mem_methods currentMemoryMethods; + if (sqlite3_config(SQLITE_CONFIG_GETMALLOC, ¤tMemoryMethods) == SQLITE_OK) { - mcSecureMemoryMethods.pAppData = mcDefaultMemoryMethods.pAppData; - if (sqlite3_config(SQLITE_CONFIG_MALLOC, &mcSecureMemoryMethods) == SQLITE_OK) + if (currentMemoryMethods.xMalloc == mcSecureMemoryMethods.xMalloc) { + /* Still installed after sqlite3_shutdown(), do not wrap it a second time */ mcSecureMemoryInitialized = 1; } else { - mcSecureMemoryFlag = mcSecureMemoryInitialized = 0; + mcDefaultMemoryMethods = currentMemoryMethods; + mcSecureMemoryMethods.pAppData = mcDefaultMemoryMethods.pAppData; + if (sqlite3_config(SQLITE_CONFIG_MALLOC, &mcSecureMemoryMethods) == SQLITE_OK) + { + mcSecureMemoryInitialized = 1; + } + else + { + mcSecureMemoryFlag = mcSecureMemoryInitialized = 0; + } } } else From e3d26dda50c110090bc6119f11ab4bd89f84f42c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Tro=C3=9Fbach?= Date: Tue, 3 Nov 2026 16:35:52 +0100 Subject: [PATCH 4/4] Remove SQLITE_ENABLE_BATCH_ATOMIC_WRITE from the Android build Since da92132 the option stops the build with #error. --- packaging/android/Android.mk | 1 - 1 file changed, 1 deletion(-) diff --git a/packaging/android/Android.mk b/packaging/android/Android.mk index ddcb9b8..b03a1fd 100644 --- a/packaging/android/Android.mk +++ b/packaging/android/Android.mk @@ -12,7 +12,6 @@ LOCAL_CFLAGS += -DHAVE_USLEEP=1 LOCAL_CFLAGS += -DSQLITE_ENABLE_FTS5 LOCAL_CFLAGS += -DSQLITE_ENABLE_RTREE LOCAL_CFLAGS += -DSQLITE_ENABLE_FTS3 -LOCAL_CFLAGS += -DSQLITE_ENABLE_BATCH_ATOMIC_WRITE # Select default encryption scheme # ChaCha20-Poly1305 is the default