SQLCipher crash with Shared-Cache

I am experiencing crashes with SQLCipher 4.5.6 on a Debian 12-based OS using Shared-Cache mode when trying to attach a DB concurrently from multiple threads within our application.

It seems like multiple threads are trying to access the same cipher_ctx object instance that is unfortunately freed by one thread, which results in a use-after-free crash.

My issue is similar to this ticket.

Is Shared-Cache mode supported by SQLCipher? Is it our responsibility to synchronize open/attach operations to encrypted databases? I believe I understand that, once a single connection is established to a database, that connection can be used by multiple threads to read/write to the database, given the correct thread safety compiler flags.

Can you confirm if this is an issue within SQLCipher OR this is a known limitation that should be handled by the integrating application OR this should be possible, but we are using the API incorrectly?

I added a thread ID to the sqlcipher debug logs which highlights the use-after-free really nicely.

I can email the full log if needed (new users cannot attach zip files to tickets).

Details

SQLCipher is compiled into a static library with given compiler flags:

-DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_UNLOCK_NOTIFY -DSQLITE_MAX_ATTACHED=125 -DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_THREADSAFE=2 -DSQLITE_ENABLE_MEMSYS5=1 -DSQLITE_DEFAULT_PAGE_SIZE=8192 -DSQLITE_HAS_CODEC=1 -DSQLITE_TEMP_STORE=2
Callstack
#2 <signal handler called>
#3 0x00007f82084cf850 in sqlcipher_codec_ctx_set_error (error=1, ctx=0x7f81dc27ddc8) at sqlite3.c:108407
#4 sqlite3Codec (iCtx=0x7f81dc27ddc8, data=<optimized out>, pgno=1, mode=3) at sqlite3.c:41565
#5 0x00007f82084912fb in readDbPage (pPg=pPg@entry=0x7f81dc290ae0) at sqlite3.c:59744
#6 0x00007f82084f2b17 in getPageNormal (pPager=0x7f81dc289dc8, pgno=1, ppPage=0x7f81fd147780, flags=<optimized out>) at sqlite3.c:62299
#7 0x00007f82084f705a in sqlite3PagerGet (flags=0, ppPage=0x7f81fd147780, pgno=1, pPager=<optimized out>) at sqlite3.c:62428
#8 btreeGetPage (flags=0, ppPage=<synthetic pointer>, pgno=1, pBt=0x7f81c82723d8) at sqlite3.c:6889
#9 lockBtree (pBt=0x7f81c82723d8) at sqlite3.c:7833
#10 btreeBeginTrans (p=p@entry=0x7f81dc285bf8, wrflag=wrflag@entry=0, pSchemaVersion=pSchemaVersion@entry=0x0) at sqlite3.c:8228
#11 0x00007f8208543fc4 in sqlite3BtreeBeginTrans (pSchemaVersion=<optimized out>, wrflag=<optimized out>, p=<optimized out>) at sqlite3.c:73857
#12 sqlite3InitOne (db=db@entry=0x7f81dc001558, iDb=iDb@entry=2, pzErrMsg=pzErrMsg@entry=0x7f81fd147950, mFlags=mFlags@entry=0) at sqlite3.c:14863
#13 0x00007f82085447f8 in sqlite3Init (db=db@entry=0x7f81dc001558, pzErrMsg=pzErrMsg@entry=0x7f81fd147950) at sqlite3.c:146136
#14 0x00007f8208545834 in attachFunc (context=0x7f81dc00d068, NotUsed=<optimized out>, argv=0x7f81dc00d098) at sqlite3.c:124796
#15 0x00007f820855b072 in sqlite3VdbeExec (p=p@entry=0x7f81dc00a7b8) at sqlite3.c:101251
#16 0x00007f820853ce80 in sqlite3Step (p=0x7f81dc00a7b8) at sqlite3.c:90720
#17 sqlite3_step (pStmt=pStmt@entry=0x7f81dc00a7b8) at sqlite3.c:25245
#18 0x00007f8208540178 in sqlite3_exec (db=<optimized out>, zSql=<optimized out>, xCallback=<optimized out>, pArg=<optimized out>, pzErrMsg=<optimized out>) at sqlite3.c:140223
#19 0x00007f82083f5ce6 in sqlite3_blocking_exec (db=0x7f81dc001558, sql=0x7f81dc26a400 "ATTACH DATABASE 'MyDatabase.db3' AS 'MyDatabase' KEY 'SQLCipherTestPassword123456';", callback=callback@entry=0x0, arg=arg@entry=0x0, errmsg=errmsg@entry=0x7f81fd147e28, pCommand=pCommand@entry=0x0, isReadContext=true)

Logs right before the crash:

Hello @TekMate,

It seems like you may be using the SQLite API incorrectly due to a misunderstanding of the thread safety options.

The compiler flag -DSQLITE_THREADSAFE=2, “Multi-Thread” does not make it safe to use SQLite objects like connections across multiple threads:

Multi-thread. In this mode, SQLite can be safely used by multiple threads provided that no single database connection nor any object derived from database connection, such as a prepared statement, is used in two or more threads at the same time.

In other words, the application is strictly required to synchronize access to all SQLite objects so that only one thread is using any individual object. Thus it is not safe to use multiple threads to read/write to the database.

Given the description of your application you should switch to use Serialized mode (-DSQLITE_THREADSAFE=1):

Serialized. In serialized mode, API calls to affect or use any SQLite database connection or any object derived from such a database connection can be made safely from multiple threads.

That seems like the level of thread safety you are expecting. Serialized is the default mode in official SQLCipher builds and the recommended setting. Please give that a try and let us know if it resolves the issue.

Finally, Shared Cache mode is strongly recommended against, both in SQLite and in SQLCipher. To quote the SQLite Documentation:

Shared-cache mode is an obsolete feature. The use of shared-cache mode is discouraged. Most use cases for shared-cache are better served by WAL mode.

and

Shared cache is disabled by default. It is recommended that it stay that way. In other words, do not use this routine. This interface continues to be provided for historical compatibility, but its use is discouraged. Any use of shared cache is discouraged.

While it is probably unrelated to the issue you reported, you should avoid using the shared cache feature.