Fix Windows stack-overflow recursion between the log sink and the memory-security allocator - #602
Open
moodysalem wants to merge 1 commit into
Conversation
… locked allocator
On Windows, sqlcipher_fprintf converts console log output to UTF-16 by
allocating through sqlite3_vmprintf and sqlite3_malloc. When
cipher_memory_security is enabled, those allocations run through the
locked allocator, and sqlcipher_mlock logs a warning whenever VirtualLock
fails ("VirtualLock() returned 0 LastError=1453"), which commonly happens
once the process working-set quota is exhausted. Writing that warning
allocates through the same allocator, fails VirtualLock again, and logs
again, recursing until the process dies with STATUS_STACK_OVERFLOW.
With the default WARN-to-stderr log configuration this crashes any
Windows application that enables cipher_memory_security and performs an
allocation larger than the pre-locked startup heap while the working-set
quota is exhausted, for example preparing a moderately large CREATE TABLE
statement.
Drop log messages emitted while the same thread is already inside
sqlcipher_log. The guard is thread-local and Windows-only: the Unix,
Android, and Apple sinks write with plain fprintf or the device logger
and never allocate through sqlite3_malloc, so they cannot re-enter.
moodysalem
added a commit
to EkuboProtocol/wallet
that referenced
this pull request
Aug 4, 2026
The root cause is fixed by the cipher_log_level pragma and recorded in docs/windows-sqlcipher-overflow.md, now linking the upstream report: sqlcipher/sqlcipher#602. The full library suite passed on windows-2025 in the final diagnose run, so the temporary probe tests, the open() instrumentation, and the diagnose workflow are gone. The stack-floor regression test in lib.rs stays: it guards the RUST_MIN_STACK plumbing permanently. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Member
|
Hello @moodysalem - thank you for reporting this issue. We have confirmed the problem and it will be resolved in the next release of SQLCipher. Our fix uses a different approach than this PR by eliminating dynamic allocations in the UTF-16 conversion code, rather than tracking the log state. Even so, I will leave this PR open until the fix is released. As an alternative workaround, SQLCipher 4.16 or higher should not exhibit this problem with default settings since WARN-level logs on lock failure were removed in that version. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
On Windows, enabling
PRAGMA cipher_memory_security = ONcan crash the process withSTATUS_STACK_OVERFLOWthrough unbounded mutual recursion between the logger and the locked allocator:WARNtostderr.cipher_memory_securityroutes everysqlite3_mallocthrough the locked allocator, andsqlcipher_mlocklogs aWARNwheneverVirtualLockfails — commonlyVirtualLock() returned 0 LastError=1453(ERROR_WORKING_SET_QUOTA), the known-benign condition discussed in https://discuss.zetetic.net/t/errors-with-mlock-virtuallock-in-ci-cd-pipelines/6704.sqlcipher_fprintf, converts the message to UTF-16 by allocating throughsqlite3_vmprintfandsqlite3_malloc— the same locked allocator.VirtualLockagain, logs again, and the cycle repeats until the stack is exhausted.Unix, Android, and Apple never loop: their sinks write with plain
fprintfor the device logger and do not allocate throughsqlite3_malloc.The crash needs an allocation larger than the pre-locked startup heap (4.7.0's 48KB pool absorbs small ones) while the working-set quota is exhausted — for example preparing a moderately large
CREATE TABLEon a busy machine or CI runner. Because the trigger is quota exhaustion, raising thread stack sizes does not help; we reproduced the crash with a 256 MiB stack.Fix
Drop messages emitted while the same thread is already inside
sqlcipher_log, via a Windows-only thread-local guard (__declspec(thread)on MSVC,__threadon MinGW). The recursion is same-thread by construction, so a thread-local flag is exactly sufficient, costs nothing on the hot path, and changes no behavior other than suppressing the infinitely-repeating message that was killing the process.Reproduction
Observed with SQLCipher 4.14.0 community (via Rust
libsqlite3-sys0.38.1, vendored OpenSSL, MSVC) onwindows-2025GitHub runners, and present unchanged at currentmaster. Steps:PRAGMA key,PRAGMA cipher_memory_security = ON, leave log settings at their defaults.CREATE TABLEstatements withCHECKconstraints).STATUS_STACK_OVERFLOW(exit 0xc00000fd) inside the prepare.Under the same conditions the crash disappears with any one of:
cipher_memory_securityoff,PRAGMA cipher_log_level = NONE, or pre-growing the working set withSetProcessWorkingSetSize— the last of which confirmed the quota mechanism.The patched amalgamation compiles cleanly; the non-Windows paths are unchanged.