From 70e1ffb12c2f0fa8f615c5ce7acc529b55a55954 Mon Sep 17 00:00:00 2001 From: Enginex0 Date: Mon, 16 Mar 2026 22:06:53 +0100 Subject: [PATCH] fix(interception): prevent ghost key responses after software key deletion After deleting a software-generated key, getKeyEntry was falling through to the real keystore2 service which could return a stale hardware key with the same alias. The post-transact live-patch fallback would then resurrect the key with a patched chain, detectors flag this as binder inconsistency. Track deleted software key aliases and return KEY_NOT_FOUND (7) for subsequent getKeyEntry calls. Also always invoke cleanupKeyData on delete to clear stale patchedChains entries for hardware keys. --- .../attestation/AttestationConstants.kt | 2 +- .../interception/keystore/Keystore2Interceptor.kt | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationConstants.kt b/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationConstants.kt index 767e8b3..a899a12 100644 --- a/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationConstants.kt +++ b/app/src/main/java/org/matrix/TEESimulator/attestation/AttestationConstants.kt @@ -89,5 +89,5 @@ object AttestationConstants { // --- Other Constants --- // https://cs.android.com/android/platform/superproject/main/+/main:system/keymaster/km_openssl/attestation_record.cpp - const val CHALLENGE_LENGTH_LIMIT = 128 // kMaximumAttestationChallengeLength + const val CHALLENGE_LENGTH_LIMIT = 128 } diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt index 64145d6..8d25fb3 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/Keystore2Interceptor.kt @@ -10,6 +10,7 @@ import android.system.keystore2.KeyDescriptor import android.system.keystore2.KeyEntryResponse import java.security.SecureRandom import java.security.cert.Certificate +import java.util.concurrent.ConcurrentHashMap import org.matrix.TEESimulator.attestation.AttestationPatcher import org.matrix.TEESimulator.attestation.KeyMintAttestation import org.matrix.TEESimulator.config.ConfigurationManager @@ -54,6 +55,9 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { .associate { field -> (field.get(null) as Int) to field.name.split("_")[1] } } + private const val RESPONSE_KEY_NOT_FOUND = 7 + private val deletedSoftwareKeys: MutableSet = ConcurrentHashMap.newKeySet() + override val serviceName = "android.system.keystore2.IKeystoreService/default" override val processName = "keystore2" override val injectionCommand = "exec ./inject `pidof keystore2` libTEESimulator.so entry" @@ -156,8 +160,10 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { val keyId = KeyIdentifier(callingUid, descriptor.alias) if (code == DELETE_KEY_TRANSACTION) { - if (KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId) != null) { - KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId) + val wasSoftwareKey = KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId) != null + KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId) + if (wasSoftwareKey) { + deletedSoftwareKeys.add(keyId) SystemLogger.info( "[TX_ID: $txId] Deleted cached keypair ${descriptor.alias}, replying with empty response." ) @@ -166,6 +172,11 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { return TransactionResult.ContinueAndSkipPost } + if (keyId in deletedSoftwareKeys) { + SystemLogger.info("[TX_ID: $txId] Returning KEY_NOT_FOUND for deleted key ${descriptor.alias}") + return InterceptorUtils.createErrorReply(RESPONSE_KEY_NOT_FOUND) + } + val response = KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId) ?: return TransactionResult.Continue