From f8221d8a167fcadfe07c6dd6f55588312367c44e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Tro=C3=9Fbach?= Date: Mon, 2 Nov 2026 08:36:32 +0100 Subject: [PATCH 1/3] Register sqlite3mc_config and sqlite3mc_codec_data as direct-only Both functions change or reveal the encryption settings of the connection, but they were registered as deterministic and could be called from the schema of any database file. With the default trusted_schema=ON, a view or trigger in a foreign database file could switch the connection to RC4, turn off the HMAC check or lower kdf_iter, and a database attached afterwards with ATTACH ... KEY was created with these settings without an error. Being deterministic also allowed their use in expression indexes, although the result changes at run time. Register them with SQLITE_DIRECTONLY instead of SQLITE_DETERMINISTIC, as the VLE functions already are. --- src/sqlite3mc.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sqlite3mc.c b/src/sqlite3mc.c index a365e2e..6872809 100644 --- a/src/sqlite3mc.c +++ b/src/sqlite3mc.c @@ -437,27 +437,27 @@ mcRegisterCodecExtensions(sqlite3* db, char** pzErrMsg, const sqlite3_api_routin rc = (codecParameterTable != NULL) ? SQLITE_OK : SQLITE_NOMEM; if (rc == SQLITE_OK) { - rc = sqlite3_create_function(db, "sqlite3mc_config", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, + rc = sqlite3_create_function(db, "sqlite3mc_config", 1, SQLITE_UTF8 | SQLITE_DIRECTONLY, codecParameterTable, sqlite3mcConfigParams, 0, 0); } if (rc == SQLITE_OK) { - rc = sqlite3_create_function(db, "sqlite3mc_config", 2, SQLITE_UTF8 | SQLITE_DETERMINISTIC, + rc = sqlite3_create_function(db, "sqlite3mc_config", 2, SQLITE_UTF8 | SQLITE_DIRECTONLY, codecParameterTable, sqlite3mcConfigParams, 0, 0); } if (rc == SQLITE_OK) { - rc = sqlite3_create_function(db, "sqlite3mc_config", 3, SQLITE_UTF8 | SQLITE_DETERMINISTIC, + rc = sqlite3_create_function(db, "sqlite3mc_config", 3, SQLITE_UTF8 | SQLITE_DIRECTONLY, codecParameterTable, sqlite3mcConfigParams, 0, 0); } if (rc == SQLITE_OK) { - rc = sqlite3_create_function(db, "sqlite3mc_codec_data", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, + rc = sqlite3_create_function(db, "sqlite3mc_codec_data", 1, SQLITE_UTF8 | SQLITE_DIRECTONLY, NULL, sqlite3mcCodecDataSql, 0, 0); } if (rc == SQLITE_OK) { - rc = sqlite3_create_function(db, "sqlite3mc_codec_data", 2, SQLITE_UTF8 | SQLITE_DETERMINISTIC, + rc = sqlite3_create_function(db, "sqlite3mc_codec_data", 2, SQLITE_UTF8 | SQLITE_DIRECTONLY, NULL, sqlite3mcCodecDataSql, 0, 0); } if (rc == SQLITE_OK) From 7129fa60ca761c41835ace4a9ed6bdfbb27d9442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Tro=C3=9Fbach?= Date: Mon, 2 Nov 2026 08:36:39 +0100 Subject: [PATCH 2/3] Fix the order of two entry pairs in the core dispatch table The initializer of sqlite3mcCoreApis listed sqlite3_preupdate_new before sqlite3_preupdate_old, and sqlite3_win32_set_directory16 before sqlite3_win32_set_directory8, while struct sqlite3mc_core_routines declares them the other way round. With SQLITE3MC_USE_DISPATCH_TABLE, sqlite3_preupdate_old() therefore returned the new value and sqlite3_preupdate_new() the old one, and on Windows the two set_directory functions were swapped. --- src/sqlite3mc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sqlite3mc.c b/src/sqlite3mc.c index 6872809..e0dbfae 100644 --- a/src/sqlite3mc.c +++ b/src/sqlite3mc.c @@ -931,8 +931,8 @@ static const sqlite3mc_core_routines sqlite3mcCoreApis = sqlite3_preupdate_count, sqlite3_preupdate_depth, sqlite3_preupdate_hook, - sqlite3_preupdate_new, sqlite3_preupdate_old, + sqlite3_preupdate_new, #else 0, 0, @@ -965,8 +965,8 @@ static const sqlite3mc_core_routines sqlite3mcCoreApis = #endif #if SQLITE_OS_WIN == 1 sqlite3_win32_set_directory, - sqlite3_win32_set_directory16, sqlite3_win32_set_directory8, + sqlite3_win32_set_directory16, #else 0, 0, From 4f122ab58db75c09e15b81cc3257ef1af6976457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Tro=C3=9Fbach?= Date: Mon, 2 Nov 2026 08:44:01 +0100 Subject: [PATCH 3/3] Check the codec that encrypted the journal page mcWriteMainJournal and mcWriteSubJournal encrypt the page with the codec of the main database, but checked the last error of the codec of the journal file, which is always NULL. A failed encryption therefore went unnoticed, and the page buffer was written to the journal anyway. If the cipher fails before it encrypts, that buffer still holds the plaintext copy of the page. Check the codec that was used, as 4afae8a already does in sqlite3mcPagerCodec and mcWriteWal. --- src/sqlite3mc_vfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sqlite3mc_vfs.c b/src/sqlite3mc_vfs.c index 683d50f..a415217 100644 --- a/src/sqlite3mc_vfs.c +++ b/src/sqlite3mc_vfs.c @@ -1680,7 +1680,7 @@ static int mcWriteMainJournal(sqlite3_file* pFile, const void* buffer, int count ** Encrypt the page buffer, but only if the page number is valid */ void* bufferEncrypted = sqlite3mcCodec(codec, (char*) buffer, pageNo, 7); - rc = sqlite3mcGetCodecLastError(mcFile->codec); + rc = sqlite3mcGetCodecLastError(codec); if (rc == SQLITE_OK) { rc = REALFILE(pFile)->pMethods->xWrite(REALFILE(pFile), bufferEncrypted, pageSize, offset); @@ -1741,7 +1741,7 @@ static int mcWriteSubJournal(sqlite3_file* pFile, const void* buffer, int count, ** Encrypt the page buffer, but only if the page number is valid */ void* bufferEncrypted = sqlite3mcCodec(codec, (char*) buffer, pageNo, 7); - rc = sqlite3mcGetCodecLastError(mcFile->codec); + rc = sqlite3mcGetCodecLastError(codec); if (rc == SQLITE_OK) { rc = REALFILE(pFile)->pMethods->xWrite(REALFILE(pFile), bufferEncrypted, pageSize, offset);