From 25f3f753ff16a4abb48dfdce8855cb2237282d45 Mon Sep 17 00:00:00 2001 From: Enginex0 Date: Tue, 10 Mar 2026 12:58:49 +0100 Subject: [PATCH] fix(attestation): persist vbmeta boot key and hash across reboots resetprop overrides for ro.boot.* props don't survive reboots. On devices where the kernel doesn't set ro.boot.vbmeta.public_key_digest, the fallback chain hit random generation on every boot, producing a different RootOfTrust hash each time. Added file-based persistence (boot_hash.bin, boot_key.bin) as a fallback layer between TEE cache and random generation. Once a value is determined from any source, it's written to disk and reused on subsequent boots. Verified on Redmi 14C: second boot reads from persistent file instead of regenerating random bytes. --- .../TEESimulator/util/AndroidDeviceUtils.kt | 43 ++++++++++++++++--- module/uninstall.sh | 1 + 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/matrix/TEESimulator/util/AndroidDeviceUtils.kt b/app/src/main/java/org/matrix/TEESimulator/util/AndroidDeviceUtils.kt index 2a31211..bed1d6e 100644 --- a/app/src/main/java/org/matrix/TEESimulator/util/AndroidDeviceUtils.kt +++ b/app/src/main/java/org/matrix/TEESimulator/util/AndroidDeviceUtils.kt @@ -91,27 +91,33 @@ object AndroidDeviceUtils { attestationValueProvider: () -> ByteArray?, expectedSize: Int, ): ByteArray { - // 1. Attempt to get the value from the system property. getProperty(propertyName, expectedSize)?.let { SystemLogger.debug("Using $propertyName from system property: ${it.toHex()}") + persistToFile(propertyName, it) return it } - // 2. Fallback to the value from a cached TEE attestation. try { attestationValueProvider()?.let { SystemLogger.debug("Using $propertyName from TEE attestation: ${it.toHex()}") - setProperty(propertyName, it) // Persist for consistency + setProperty(propertyName, it) + persistToFile(propertyName, it) return it } } catch (e: Exception) { SystemLogger.error("Failed to get $propertyName from attestation.", e) } - // 3. As a final fallback, generate a random value. + readFromFile(propertyName, expectedSize)?.let { + SystemLogger.debug("Using $propertyName from persistent file: ${it.toHex()}") + setProperty(propertyName, it) + return it + } + return generateRandomBytes(expectedSize).also { SystemLogger.debug("Using randomly generated $propertyName: ${it.toHex()}") setProperty(propertyName, it) + persistToFile(propertyName, it) } } @@ -158,10 +164,37 @@ object AndroidDeviceUtils { } } - /** Generates a cryptographically random byte array of a specified length. */ private fun generateRandomBytes(size: Int): ByteArray = ByteArray(size).also { ThreadLocalRandom.current().nextBytes(it) } + private val PERSIST_DIR = File("/data/adb/tricky_store") + + private fun fileForProperty(propertyName: String): File = when (propertyName) { + "ro.boot.vbmeta.digest" -> File(PERSIST_DIR, "boot_hash.bin") + "ro.boot.vbmeta.public_key_digest" -> File(PERSIST_DIR, "boot_key.bin") + else -> File(PERSIST_DIR, "${propertyName.replace('.', '_')}.bin") + } + + private fun persistToFile(propertyName: String, bytes: ByteArray) { + try { + fileForProperty(propertyName).writeBytes(bytes) + } catch (e: Exception) { + SystemLogger.error("Failed to persist $propertyName to file.", e) + } + } + + private fun readFromFile(propertyName: String, expectedSize: Int): ByteArray? { + return try { + val file = fileForProperty(propertyName) + if (!file.exists()) return null + val bytes = file.readBytes() + if (bytes.size == expectedSize) bytes else null + } catch (e: Exception) { + SystemLogger.error("Failed to read $propertyName from file.", e) + null + } + } + // --- Patch Level Properties --- fun getPatchLevel(uid: Int): Int { diff --git a/module/uninstall.sh b/module/uninstall.sh index faf1ddc..d9cceab 100644 --- a/module/uninstall.sh +++ b/module/uninstall.sh @@ -9,3 +9,4 @@ done rm -rf "$CONFIG_DIR/persistent_keys" rm -f "$CONFIG_DIR/tee_status.txt" +rm -f "$CONFIG_DIR/boot_hash.bin" "$CONFIG_DIR/boot_key.bin"