Following up on #621, where the conclusion was "we don't yet know what the right fix is". I think I have the root cause, and it explains why the earlier fix attempt (bumping LOAD alignment) didn't resolve it.
Summary
libe_sqlcipher.so is correctly built with max-page-size=16384 — every LOAD segment reports align=0x4000, which is why it passes zipalign -c -P 16 and the Android Studio APK analyzer, and why this looks fixed. The problem is one segment further down: GNU_RELRO ends on a 4 KB boundary but not a 16 KB one.
From SQLitePCLRaw.lib.e_sqlcipher.android 2.1.10, jni/arm64-v8a/libe_sqlcipher.so:
Type Offset VirtAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x0000000000000000 0x1a5e58 0x1a5e58 R E 0x4000
LOAD 0x1a63b0 0x00000000001aa3b0 0x007100 0x00f950 RW 0x4000
GNU_RELRO 0x1a63b0 0x00000000001aa3b0 0x003c50 0x003c50 R 0x1
- Writable data spans
0x1aa3b0 .. 0x1b9d00
- RELRO covers
0x1aa3b0 .. 0x1ae000
After relocation the dynamic linker makes the RELRO range read-only, rounding the end up to a page boundary:
| Page size |
Marked read-only |
Over-protects |
| 4 KB |
0x1aa000 .. 0x1ae000 |
nothing — lands exactly on the RELRO end |
| 16 KB |
0x1a8000 .. 0x1b0000 |
8 KB of genuinely writable .data/.bss |
So on a 16 KB device, memory that SQLite expects to write becomes read-only, and the first write kills the process.
Crash
sqlite3_initialize writes into that over-protected window:
Page size: 16384 bytes
signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x000075c70e1ee078
backtrace:
#00 pc 00000000000156e0 libe_sqlcipher.so (sqlite3_initialize+120)
#01 pc 000000000003a13c libe_sqlcipher.so
Subtracting the load bias (0x75c70e0556e0 - 0x156e0 = 0x75c70e040000) puts the faulting address at vaddr 0x1ae078 — i.e. 0x78 bytes past the RELRO end, squarely inside the 8 KB the linker over-protected. SEGV_ACCERR is a permission violation rather than a bad pointer, which matches.
The symptom varies by API level and is easy to misattribute:
- API 35 + 16 KB → raw
SIGSEGV in sqlite3_initialize
- API 37 + 16 KB → surfaces to .NET as
System.DllNotFoundException: e_sqlcipher
Both are this same root cause.
Likely fix
The native build appears to pass -Wl,-z,max-page-size=16384 but not -Wl,-z,common-page-size=16384. max-page-size governs LOAD alignment (already correct); common-page-size is what makes the linker pad sections so the RELRO boundary lands on a 16 KB boundary too. Setting both should resolve it.
Note on e_sqlite3
SQLitePCLRaw.lib.e_sqlite3.android 2.1.10 is not safe by construction — it just gets lucky. Its RELRO happens to end at 0x1ac000, which is 16 KB-aligned by coincidence:
LOAD vaddr=0x00000000001a8af8 memsz=0x007628 align=0x4000
GNU_RELRO vaddr=0x00000000001a8af8 memsz=0x003508 -> ends 0x1ac000
Since neither library sets common-page-size, any change to that library's data section size could move the boundary off 16 KB and produce the same crash there. Worth fixing both rather than only the one currently failing.
Affected versions
SQLitePCLRaw.lib.e_sqlcipher.android 2.1.10 and 2.1.11 have byte-identical segment layouts for jni/arm64-v8a/libe_sqlcipher.so, so upgrading 2.1.10 → 2.1.11 changes nothing here. 2.1.11 is the newest published version of that package.
Only 64-bit is affected; the 32-bit ABIs are ELF32 and Android's 16 KB support is 64-bit only.
Reproducing
An API 35 + 16 KB emulator isolates this cleanly. Using an Android 16/17 image conflates page size with API level, which is what led me to initially blame the newer platform. Create an AVD on system-images;android-35;google_apis_playstore_ps16k;arm64-v8a — the app crashes there and works on the identical API 35 image with 4 KB pages, holding everything else constant.
Verification
To confirm the diagnosis I rewrote the PT_GNU_RELRO program header to PT_NULL in a copy of the shipped .so (so the linker applies no RELRO and therefore over-protects nothing), repackaged and re-signed. On the same API 35 + 16 KB emulator the app then launches normally, opens its encrypted database and runs — no SIGSEGV.
That is obviously not a fix worth shipping upstream, since it discards RELRO hardening for the library; it's offered only as evidence that the RELRO boundary is the actual culprit. Relinking with common-page-size=16384 should fix it properly and keep RELRO intact.
Happy to test a build if that's useful.
Related: #621, #632, #648. I realise from #648 that 3.x no longer includes freely distributed encryption builds — the common-page-size point still applies to the libraries that are shipped, and I'm reporting it in case it saves someone else the diagnosis.
Following up on #621, where the conclusion was "we don't yet know what the right fix is". I think I have the root cause, and it explains why the earlier fix attempt (bumping LOAD alignment) didn't resolve it.
Summary
libe_sqlcipher.sois correctly built withmax-page-size=16384— everyLOADsegment reportsalign=0x4000, which is why it passeszipalign -c -P 16and the Android Studio APK analyzer, and why this looks fixed. The problem is one segment further down:GNU_RELROends on a 4 KB boundary but not a 16 KB one.From
SQLitePCLRaw.lib.e_sqlcipher.android2.1.10,jni/arm64-v8a/libe_sqlcipher.so:0x1aa3b0 .. 0x1b9d000x1aa3b0 .. 0x1ae000After relocation the dynamic linker makes the RELRO range read-only, rounding the end up to a page boundary:
0x1aa000 .. 0x1ae0000x1a8000 .. 0x1b0000.data/.bssSo on a 16 KB device, memory that SQLite expects to write becomes read-only, and the first write kills the process.
Crash
sqlite3_initializewrites into that over-protected window:Subtracting the load bias (
0x75c70e0556e0 - 0x156e0 = 0x75c70e040000) puts the faulting address at vaddr0x1ae078— i.e. 0x78 bytes past the RELRO end, squarely inside the 8 KB the linker over-protected.SEGV_ACCERRis a permission violation rather than a bad pointer, which matches.The symptom varies by API level and is easy to misattribute:
SIGSEGVinsqlite3_initializeSystem.DllNotFoundException: e_sqlcipherBoth are this same root cause.
Likely fix
The native build appears to pass
-Wl,-z,max-page-size=16384but not-Wl,-z,common-page-size=16384.max-page-sizegovernsLOADalignment (already correct);common-page-sizeis what makes the linker pad sections so the RELRO boundary lands on a 16 KB boundary too. Setting both should resolve it.Note on
e_sqlite3SQLitePCLRaw.lib.e_sqlite3.android2.1.10 is not safe by construction — it just gets lucky. Its RELRO happens to end at0x1ac000, which is 16 KB-aligned by coincidence:Since neither library sets
common-page-size, any change to that library's data section size could move the boundary off 16 KB and produce the same crash there. Worth fixing both rather than only the one currently failing.Affected versions
SQLitePCLRaw.lib.e_sqlcipher.android2.1.10 and 2.1.11 have byte-identical segment layouts forjni/arm64-v8a/libe_sqlcipher.so, so upgrading 2.1.10 → 2.1.11 changes nothing here. 2.1.11 is the newest published version of that package.Only 64-bit is affected; the 32-bit ABIs are ELF32 and Android's 16 KB support is 64-bit only.
Reproducing
An API 35 + 16 KB emulator isolates this cleanly. Using an Android 16/17 image conflates page size with API level, which is what led me to initially blame the newer platform. Create an AVD on
system-images;android-35;google_apis_playstore_ps16k;arm64-v8a— the app crashes there and works on the identical API 35 image with 4 KB pages, holding everything else constant.Verification
To confirm the diagnosis I rewrote the
PT_GNU_RELROprogram header toPT_NULLin a copy of the shipped.so(so the linker applies no RELRO and therefore over-protects nothing), repackaged and re-signed. On the same API 35 + 16 KB emulator the app then launches normally, opens its encrypted database and runs — no SIGSEGV.That is obviously not a fix worth shipping upstream, since it discards RELRO hardening for the library; it's offered only as evidence that the RELRO boundary is the actual culprit. Relinking with
common-page-size=16384should fix it properly and keep RELRO intact.Happy to test a build if that's useful.
Related: #621, #632, #648. I realise from #648 that 3.x no longer includes freely distributed encryption builds — the
common-page-sizepoint still applies to the libraries that are shipped, and I'm reporting it in case it saves someone else the diagnosis.