From 9a7011eb5eb6c14c45deafe07773b429ea5bb2c2 Mon Sep 17 00:00:00 2001 From: Enginex0 Date: Sun, 22 Mar 2026 00:29:46 +0100 Subject: [PATCH] fix(interception): restore key lifecycle tracking and cache invalidation Restores pre-PR157 custom features: - deletedSoftwareKeys tracking in Keystore2Interceptor (returns KEY_NOT_FOUND for getKeyEntry after software key deletion instead of falling through to hardware) - invalidatePatchedChains() for bulk cert chain cache clearing - Per-UID LRU operation pruning (MAX_CONCURRENT_OPS_PER_UID=15) prevents resource exhaustion from concurrent software operations - SoftwareOperation.isFinalized made public for LRU eviction checks --- .../keystore/Keystore2Interceptor.kt | 7 ++++- .../shim/KeyMintSecurityLevelInterceptor.kt | 28 ++++++++++++++----- .../keystore/shim/SoftwareOperation.kt | 15 +++++----- 3 files changed, 35 insertions(+), 15 deletions(-) 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 f3526a2..eb53799 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 @@ -61,8 +61,8 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { .associate { field -> (field.get(null) as Int) to field.name.split("_")[1] } } - // Keys whose certs were updated via updateSubcomponent; skip re-patching on getKeyEntry. private val userUpdatedKeys = ConcurrentHashMap.newKeySet() + private val deletedSoftwareKeys = ConcurrentHashMap.newKeySet() // Backdoor binder for registering new interceptors at runtime. private var backdoorBinder: IBinder? = null @@ -225,6 +225,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { KeyMintSecurityLevelInterceptor.generatedKeys.containsKey(keyId) KeyMintSecurityLevelInterceptor.cleanupKeyData(keyId) if (isSoftwareKey) { + deletedSoftwareKeys.add(keyId) SystemLogger.info( "[TX_ID: $txId] Deleted cached keypair ${keyId.alias}, replying with empty response." ) @@ -239,6 +240,10 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() { } val keyId = KeyIdentifier(callingUid, descriptor.alias) + if (deletedSoftwareKeys.remove(keyId)) { + return InterceptorUtils.createErrorReply(7) // KEY_NOT_FOUND + } + val response = KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId) ?: return TransactionResult.Continue diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt index c01f95e..c25c079 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/KeyMintSecurityLevelInterceptor.kt @@ -51,8 +51,8 @@ class KeyMintSecurityLevelInterceptor( private val securityLevel: Int, ) : BinderInterceptor() { - // --- Data Structures for State Management --- private val recentOps = ConcurrentHashMap>() + private val activeOps = ConcurrentHashMap>() data class GeneratedKeyInfo( val keyPair: KeyPair?, @@ -253,11 +253,17 @@ class KeyMintSecurityLevelInterceptor( timestamps.addLast(System.nanoTime()) } - /** - * Handles the `createOperation` transaction. It checks if the operation is for a key that was - * generated in software. If so, it creates a software-based operation handler. Otherwise, it - * lets the call proceed to the real hardware service. - */ + private fun pruneOpsForUid(callingUid: Int, newOp: SoftwareOperation) { + val ops = activeOps.computeIfAbsent(callingUid) { ConcurrentLinkedDeque() } + ops.removeIf { it.isFinalized } + while (ops.size >= MAX_CONCURRENT_OPS_PER_UID) { + val oldest = ops.pollFirst() ?: break + oldest.abort() + SystemLogger.debug("Pruned oldest op for uid=$callingUid (LRU eviction)") + } + ops.addLast(newOp) + } + private fun handleCreateOperation( txId: Long, callingUid: Int, @@ -402,6 +408,7 @@ class KeyMintSecurityLevelInterceptor( effectiveParams, opLatency, ) + pruneOpsForUid(callingUid, softwareOperation) // Decrement usage counter on finish; delete key when exhausted. if (keyParams.usageCountLimit != null && resolvedKeyId != null) { @@ -956,6 +963,7 @@ class KeyMintSecurityLevelInterceptor( private const val STRONGBOX_MAX_CONCURRENT_OPS = 4 private const val STRONGBOX_OP_WINDOW_NS = 10_000_000_000L private const val MAX_ALIAS_LENGTH = 256 * 1024 + private const val MAX_CONCURRENT_OPS_PER_UID = 15 private fun isStrongBoxCapable(params: KeyMintAttestation): Boolean = when (params.algorithm) { Algorithm.RSA -> params.keySize <= 2048 @@ -1056,7 +1064,13 @@ class KeyMintSecurityLevelInterceptor( } } - // Clears all cached keys. + fun invalidatePatchedChains(reason: String? = null) { + val count = patchedChains.size + if (count == 0) return + patchedChains.clear() + SystemLogger.info("Invalidated $count patched cert chains${reason?.let { " due to $it" } ?: ""}.") + } + fun clearAllGeneratedKeys(reason: String? = null) { val count = generatedKeys.size val reasonMessage = reason?.let { " due to $it" } ?: "" diff --git a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/SoftwareOperation.kt b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/SoftwareOperation.kt index 8bf6845..1eb4275 100644 --- a/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/SoftwareOperation.kt +++ b/app/src/main/java/org/matrix/TEESimulator/interception/keystore/shim/SoftwareOperation.kt @@ -257,7 +257,8 @@ class SoftwareOperation( ) { private val primitive: CryptoPrimitive - @Volatile private var finalized = false + @Volatile var isFinalized = false + private set init { val purpose = params.purpose.firstOrNull() @@ -294,7 +295,7 @@ class SoftwareOperation( } private fun checkActive() { - if (finalized) + if (isFinalized) throw ServiceSpecificException( KeystoreErrorCode.INVALID_OPERATION_HANDLE, "Operation already finalized.", @@ -306,10 +307,10 @@ class SoftwareOperation( try { primitive.updateAad(data) } catch (e: ServiceSpecificException) { - finalized = true + isFinalized = true throw e } catch (e: Exception) { - finalized = true + isFinalized = true SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to updateAad.", e) throw ServiceSpecificException(KeystoreErrorCode.SYSTEM_ERROR, e.message) } @@ -320,10 +321,10 @@ class SoftwareOperation( try { return primitive.update(data) } catch (e: ServiceSpecificException) { - finalized = true + isFinalized = true throw e } catch (e: Exception) { - finalized = true + isFinalized = true SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to update operation.", e) throw mapToServiceSpecificException(e) } @@ -348,7 +349,7 @@ class SoftwareOperation( SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to finish operation.", e) throw mapToServiceSpecificException(e) } finally { - finalized = true + isFinalized = true } }