Skip to content

Documentation request: symbol collision with system libsqlite3.so.0 on Linux when used inside a plugin .so (webkit/libsoup) #234

@rossanoparis

Description

@rossanoparis

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions