Hello, and thanks for maintaining SQLite3 Multiple Ciphers - it has been working perfectly on Windows in my project for a long time.
I recently ported my application to Linux and ran into a subtle problem that cost me significant time to diagnose. I'd like to share it here, partly as a possible documentation improvement and partly so other users running into the same issue can find this thread.
Environment
- Linux (Linux Mint / Ubuntu derivative), x86_64
- GCC, wxWidgets 3.3.3 with GTK3
- Application uses
wxWebView, which on Linux pulls in libwebkit2gtk-4.0 and libsoup-2.4
- Architecture: main executable loads a plugin shared library (
Connectors.Db.Sqlite.so) which statically links a library (libLibraries.Db.Sqlite.a) built from sqlite3mc_amalgamation.c
- Identical source code on Windows works correctly - databases are encrypted as expected
Symptom
On Linux:
PRAGMA key and sqlite3_key() were silently accepted
- The resulting database file was written as plaintext (
SQLite format 3 header visible with a hex dump)
PRAGMA cipher_version returned no row / was not recognised
sqlite3_sourceid() returned the stock sqlite.org check-in hash, not an MC-specific one
On Windows, the same source code produced an encrypted database without any special configuration.
Root cause
Linux ELF shared libraries use a flat global symbol namespace by default. libwebkit2gtk and libsoup both declare DT_NEEDED on libsqlite3.so.0, so the system stock SQLite library is loaded into the process before my plugin is loaded.
Because stock libsqlite3.so.0 appears first in the global symbol resolution scope, every sqlite3_* call in the entire process - including those made from inside my plugin which had MC linked in statically - was resolved against the system stock SQLite. MC's code was loaded but never called. Stock SQLite silently ignores unknown pragmas, which is why PRAGMA key appeared to succeed while writing plaintext.
This does not happen on Windows because DLL imports are resolved per-module against explicit DLL names, not against a shared global namespace.
How to diagnose it
For anyone else hitting this, these diagnostics were decisive:
ldd /path/to/app | grep -i sqlite
Showed libsqlite3.so.0 being loaded from /usr/lib/x86_64-linux-gnu even though nothing in my build references it.
LD_DEBUG=bindings /path/to/app 2>/tmp/ld.log
grep "sqlite3_open" /tmp/ld.log
Showed sqlite3_* symbol lookups binding to libsqlite3.so.0 rather than to the MC-containing plugin.
# Confirm who transitively pulls in libsqlite3.so.0
for lib in $(ldd /path/to/app | awk '/=>/ {print $3}'); do
if [ -f "$lib" ] && ldd "$lib" 2>/dev/null | grep -q libsqlite3; then
echo "PULLS SQLITE: $lib"
fi
done
In my case this printed:
PULLS SQLITE: /usr/lib/x86_64-linux-gnu/libwebkit2gtk-4.0.so.37
PULLS SQLITE: /usr/lib/x86_64-linux-gnu/libsoup-2.4.so.1
Running with LD_PRELOAD of an MC-built libsqlite3mc.so successfully routed calls into MC (including webkit's and libsoup's internal calls, which continued working fine - MC is a correct drop-in replacement for stock SQLite when no key is set).
The fix that works for a plugin-only deployment
My constraint was that MC must stay inside the plugin and not be linked into the main executable. The robust solution was to rename all SQLite public symbols inside the plugin so they no longer collide with stock SQLite's symbols in the process:
1. Create a header sqlite3_rename.h with #define lines mapping every sqlite3_* function the plugin uses to a prefixed name:
#ifndef SQLITE3_RENAME_H
#define SQLITE3_RENAME_H
#define sqlite3_open mc_sqlite3_open
#define sqlite3_open_v2 mc_sqlite3_open_v2
#define sqlite3_close mc_sqlite3_close
#define sqlite3_prepare_v2 mc_sqlite3_prepare_v2
#define sqlite3_step mc_sqlite3_step
#define sqlite3_finalize mc_sqlite3_finalize
#define sqlite3_key mc_sqlite3_key
#define sqlite3_key_v2 mc_sqlite3_key_v2
#define sqlite3_rekey mc_sqlite3_rekey
/* ... every sqlite3_* function the plugin uses ... */
#endif
2. Compile both sqlite3mc_amalgamation.c and all plugin source files with -include sqlite3_rename.h. This renames definitions and call sites consistently via the preprocessor, without changing any source code.
3. Link the plugin with -Wl,--exclude-libs=libLibraries.Db.Sqlite.a to prevent the renamed symbols from being exported from the plugin, keeping them fully internal.
After this:
nm -D on the plugin shows no sqlite3_* exports
- Webkit and libsoup continue to use the system
libsqlite3.so.0 untouched
- The plugin uses MC internally via the
mc_sqlite3_* names
PRAGMA cipher_version returns the MC version string as expected
- The database file is correctly encrypted
Verification commands
# Should return nothing (no sqlite3_* exported from the plugin)
nm -D /path/to/plugin.so | grep -E "sqlite3_(open|key|libversion|prepare_v2)$"
# Should find the renamed internal symbols
nm /path/to/plugin.so | grep -E " [tT] mc_sqlite3_(open|key)$"
# First 16 bytes of the DB file should be random (encrypted), not "SQLite format 3"
head -c 16 /path/to/db.db | xxd
Suggestion
It might be worth adding a section to the installation / build documentation covering this scenario, since:
- It is specific to Linux (the Windows path in the docs works out of the box)
- It is very likely to be hit by any application that uses
wxWebView, Qt WebEngine, or any webkit/GNOME stack component in the same process as an MC-enabled plugin or shared library
- The symptom (silent plaintext writes with no error) is dangerous - a user could ship a product believing encryption is active when it is not
- The fix (symbol rename via
-include + --exclude-libs) is not intuitive and took significant effort to arrive at
If a documentation PR with a "Linux plugin deployment" appendix would be welcome, I'd be happy to contribute one based on what I learned.
Thank you again for the excellent work on this project.
Hello, and thanks for maintaining SQLite3 Multiple Ciphers - it has been working perfectly on Windows in my project for a long time.
I recently ported my application to Linux and ran into a subtle problem that cost me significant time to diagnose. I'd like to share it here, partly as a possible documentation improvement and partly so other users running into the same issue can find this thread.
Environment
wxWebView, which on Linux pulls inlibwebkit2gtk-4.0andlibsoup-2.4Connectors.Db.Sqlite.so) which statically links a library (libLibraries.Db.Sqlite.a) built fromsqlite3mc_amalgamation.cSymptom
On Linux:
PRAGMA keyandsqlite3_key()were silently acceptedSQLite format 3header visible with a hex dump)PRAGMA cipher_versionreturned no row / was not recognisedsqlite3_sourceid()returned the stock sqlite.org check-in hash, not an MC-specific oneOn Windows, the same source code produced an encrypted database without any special configuration.
Root cause
Linux ELF shared libraries use a flat global symbol namespace by default.
libwebkit2gtkandlibsoupboth declareDT_NEEDEDonlibsqlite3.so.0, so the system stock SQLite library is loaded into the process before my plugin is loaded.Because stock
libsqlite3.so.0appears first in the global symbol resolution scope, everysqlite3_*call in the entire process - including those made from inside my plugin which had MC linked in statically - was resolved against the system stock SQLite. MC's code was loaded but never called. Stock SQLite silently ignores unknown pragmas, which is whyPRAGMA keyappeared to succeed while writing plaintext.This does not happen on Windows because DLL imports are resolved per-module against explicit DLL names, not against a shared global namespace.
How to diagnose it
For anyone else hitting this, these diagnostics were decisive:
ldd /path/to/app | grep -i sqliteShowed
libsqlite3.so.0being loaded from/usr/lib/x86_64-linux-gnueven though nothing in my build references it.Showed
sqlite3_*symbol lookups binding tolibsqlite3.so.0rather than to the MC-containing plugin.In my case this printed:
Running with
LD_PRELOADof an MC-builtlibsqlite3mc.sosuccessfully routed calls into MC (including webkit's and libsoup's internal calls, which continued working fine - MC is a correct drop-in replacement for stock SQLite when no key is set).The fix that works for a plugin-only deployment
My constraint was that MC must stay inside the plugin and not be linked into the main executable. The robust solution was to rename all SQLite public symbols inside the plugin so they no longer collide with stock SQLite's symbols in the process:
1. Create a header
sqlite3_rename.hwith#definelines mapping everysqlite3_*function the plugin uses to a prefixed name:2. Compile both
sqlite3mc_amalgamation.cand all plugin source files with-include sqlite3_rename.h. This renames definitions and call sites consistently via the preprocessor, without changing any source code.3. Link the plugin with
-Wl,--exclude-libs=libLibraries.Db.Sqlite.ato prevent the renamed symbols from being exported from the plugin, keeping them fully internal.After this:
nm -Don the plugin shows nosqlite3_*exportslibsqlite3.so.0untouchedmc_sqlite3_*namesPRAGMA cipher_versionreturns the MC version string as expectedVerification commands
Suggestion
It might be worth adding a section to the installation / build documentation covering this scenario, since:
wxWebView, Qt WebEngine, or any webkit/GNOME stack component in the same process as an MC-enabled plugin or shared library-include+--exclude-libs) is not intuitive and took significant effort to arrive atIf a documentation PR with a "Linux plugin deployment" appendix would be welcome, I'd be happy to contribute one based on what I learned.
Thank you again for the excellent work on this project.