fix(cache): encrypted cache crash-loops on launch (UnsatisfiedLinkError, SQLiteConnection.nativeOpen) #210

Closed
opened 2026-07-03 14:29:49 +00:00 by JMR-dev · 0 comments
JMR-dev commented 2026-07-03 14:29:49 +00:00 (Migrated from github.com)

Status: Fixed — PR #208 (in review).

Summary

The opt-in encrypted cache (encryptCache) crash-loops on launch for any user who has enabled it, on the first cold start after enabling — reported after an app upgrade on a Pixel 10 Pro XL.

FATAL EXCEPTION: arch_disk_io
java.lang.UnsatisfiedLinkError: No implementation found for long
  net.zetetic.database.sqlcipher.SQLiteConnection.nativeOpen(...) — is the library loaded, e.g. System.loadLibrary?
    at ...SupportHelper.getWritableDatabase(...)   ← Room opening the encrypted cache

Root cause

System.loadLibrary("sqlcipher") (in DatabaseEncryption.ensureNativeLibraryLoaded()) was only ever called as a side effect of an actual plaintext↔encrypted conversion (DatabaseEncryption.migrate) or the one-time #111 account migration (AccountDataMigrator). On a steady-state start the cache is already encrypted (so ensureEncrypted early-returns) and the migration is already done (so it no-ops), so nothing loads the native library before Room opens the keyed DB via SupportOpenHelperFactory → nativeOpen. The previous process survived only because an earlier conversion had loaded the .so in-memory; the next cold start (an upgrade, or any force-stop + relaunch) crashes.

Not a packaging/ABI problem — lib/arm64-v8a/libsqlcipher.so is present in the APK and loads fine; it was simply never asked to load.

Fix

Load the library explicitly in DatabaseProvisioner whenever it commits to an encrypted open (idempotent; no-ops when already loaded), plus regression assertions in DatabaseProvisionerTest (encrypted path must load it; plaintext path must not).

Verified on a Pixel 10 Pro XL: an in-place update preserving the already-encrypted cache now launches to the mailbox instead of crash-looping.

Fix: #208

**Status:** Fixed — PR #208 (in review). ## Summary The opt-in encrypted cache (`encryptCache`) crash-loops on launch for any user who has enabled it, on the first cold start after enabling — reported after an app upgrade on a Pixel 10 Pro XL. ``` FATAL EXCEPTION: arch_disk_io java.lang.UnsatisfiedLinkError: No implementation found for long net.zetetic.database.sqlcipher.SQLiteConnection.nativeOpen(...) — is the library loaded, e.g. System.loadLibrary? at ...SupportHelper.getWritableDatabase(...) ← Room opening the encrypted cache ``` ## Root cause `System.loadLibrary("sqlcipher")` (in `DatabaseEncryption.ensureNativeLibraryLoaded()`) was only ever called as a **side effect** of an actual plaintext↔encrypted conversion (`DatabaseEncryption.migrate`) or the one-time #111 account migration (`AccountDataMigrator`). On a steady-state start the cache is already encrypted (so `ensureEncrypted` early-returns) and the migration is already done (so it no-ops), so nothing loads the native library before Room opens the keyed DB via `SupportOpenHelperFactory` → `nativeOpen`. The previous process survived only because an earlier conversion had loaded the `.so` in-memory; the next cold start (an upgrade, or any force-stop + relaunch) crashes. Not a packaging/ABI problem — `lib/arm64-v8a/libsqlcipher.so` is present in the APK and loads fine; it was simply never asked to load. ## Fix Load the library explicitly in `DatabaseProvisioner` whenever it commits to an encrypted open (idempotent; no-ops when already loaded), plus regression assertions in `DatabaseProvisionerTest` (encrypted path must load it; plaintext path must not). Verified on a Pixel 10 Pro XL: an in-place update preserving the already-encrypted cache now launches to the mailbox instead of crash-looping. Fix: #208
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: JMR-dev/LibreMail#210