Merge pull request #208 from JMR-dev/fix-sqlcipher-native-lib-load

fix(cache): load SQLCipher native lib before every keyed open
This commit was merged in pull request #208.
This commit is contained in:
Jason Ross
2026-07-03 09:38:35 -05:00
committed by GitHub
2 changed files with 16 additions and 0 deletions
@@ -120,6 +120,13 @@ class DatabaseProvisioner internal constructor(
settings.encryptCache -> {
val passphrase = keyStore.resolvePassphrase(appLock)
DatabaseEncryption.ensureEncrypted(dbFile, passphrase)
// Room is about to open the cache with SQLCipher (SupportOpenHelperFactory), so its
// native library must already be loaded. ensureEncrypted() above loads it only as a
// side effect of an actual plaintext -> encrypted conversion; on a steady-state start
// (cache already encrypted, nothing to convert) that no-ops, so without this explicit
// load the keyed open reaches SQLiteConnection.nativeOpen with no library loaded and
// crashes with UnsatisfiedLinkError on every cold start once encryption is enabled.
DatabaseEncryption.ensureNativeLibraryLoaded()
CacheOpenMode.Encrypted(passphrase)
}
@@ -61,6 +61,7 @@ class DatabaseProvisionerTest {
every { DatabaseEncryption.isEncrypted(any()) } returns false
every { DatabaseEncryption.ensureEncrypted(any(), any()) } just Runs
every { DatabaseEncryption.ensurePlaintext(any(), any()) } just Runs
every { DatabaseEncryption.ensureNativeLibraryLoaded() } just Runs
every { settingsRepository.settings } returns flowOf(AppSettings())
coEvery { keyStore.isClearPending() } returns false
@@ -89,6 +90,11 @@ class DatabaseProvisionerTest {
coVerify(exactly = 1) { keyStore.resolvePassphrase(false) }
verify(exactly = 1) { DatabaseEncryption.ensureEncrypted(any(), PASSPHRASE) }
verify(exactly = 0) { DatabaseEncryption.ensurePlaintext(any(), any()) }
// Regression (crash-on-launch after upgrade): the encrypted open path MUST load SQLCipher's
// native library itself. ensureEncrypted no-ops when the cache is already encrypted, so if the
// load only rode on that conversion, Room's keyed open would hit nativeOpen with no .so loaded
// and throw UnsatisfiedLinkError on every cold start.
verify(exactly = 1) { DatabaseEncryption.ensureNativeLibraryLoaded() }
}
@Test
@@ -143,6 +149,9 @@ class DatabaseProvisionerTest {
coVerify(exactly = 0) { keyStore.resolvePassphrase(any()) }
verify(exactly = 0) { DatabaseEncryption.ensureEncrypted(any(), any()) }
verify(exactly = 0) { DatabaseEncryption.ensurePlaintext(any(), any()) }
// A plaintext cache opens with the framework helper, never SQLCipher, so it must not touch the
// native library — the counterpart to the encrypted path's mandatory load above.
verify(exactly = 0) { DatabaseEncryption.ensureNativeLibraryLoaded() }
}
@Test