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
This commit is contained in:
+6
-1
@@ -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<KeyIdentifier>()
|
||||
private val deletedSoftwareKeys = ConcurrentHashMap.newKeySet<KeyIdentifier>()
|
||||
|
||||
// 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
|
||||
|
||||
+21
-7
@@ -51,8 +51,8 @@ class KeyMintSecurityLevelInterceptor(
|
||||
private val securityLevel: Int,
|
||||
) : BinderInterceptor() {
|
||||
|
||||
// --- Data Structures for State Management ---
|
||||
private val recentOps = ConcurrentHashMap<Int, ConcurrentLinkedDeque<Long>>()
|
||||
private val activeOps = ConcurrentHashMap<Int, ConcurrentLinkedDeque<SoftwareOperation>>()
|
||||
|
||||
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" } ?: ""
|
||||
|
||||
+8
-7
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user