Skip to content

16 KB page crash root cause: libe_sqlcipher.so GNU_RELRO end is not 16 KB aligned (follow-up to #621) #676

Description

@wroolie

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.

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