feat(interception): dynamically register SecurityLevel binder interceptors
keystore2 may return a different BBinder for each getSecurityLevel call, so the initial registration during setup might not cover all binder instances that client apps receive. Intercept getSecurityLevel replies to register our hook on every new BBinder, deduplicated by identity hash.
This commit is contained in:
+119
-4
@@ -6,11 +6,13 @@ import android.os.Build
|
|||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.system.keystore2.Domain
|
import android.system.keystore2.Domain
|
||||||
|
import android.system.keystore2.IKeystoreSecurityLevel
|
||||||
import android.system.keystore2.IKeystoreService
|
import android.system.keystore2.IKeystoreService
|
||||||
import android.system.keystore2.KeyDescriptor
|
import android.system.keystore2.KeyDescriptor
|
||||||
import android.system.keystore2.KeyEntryResponse
|
import android.system.keystore2.KeyEntryResponse
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.security.cert.Certificate
|
import java.security.cert.Certificate
|
||||||
|
import java.util.Collections
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
||||||
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
||||||
@@ -47,6 +49,8 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
else null
|
else null
|
||||||
private val GET_NUMBER_OF_ENTRIES_TRANSACTION =
|
private val GET_NUMBER_OF_ENTRIES_TRANSACTION =
|
||||||
InterceptorUtils.getTransactCode(stubBinderClass, "getNumberOfEntries")
|
InterceptorUtils.getTransactCode(stubBinderClass, "getNumberOfEntries")
|
||||||
|
private val GET_SECURITY_LEVEL_TRANSACTION =
|
||||||
|
InterceptorUtils.getTransactCode(stubBinderClass, "getSecurityLevel")
|
||||||
|
|
||||||
private val transactionNames: Map<Int, String> by lazy {
|
private val transactionNames: Map<Int, String> by lazy {
|
||||||
stubBinderClass.declaredFields
|
stubBinderClass.declaredFields
|
||||||
@@ -60,6 +64,17 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
// Keys whose certs were updated via updateSubcomponent; skip re-patching on getKeyEntry.
|
// Keys whose certs were updated via updateSubcomponent; skip re-patching on getKeyEntry.
|
||||||
private val userUpdatedKeys = ConcurrentHashMap.newKeySet<KeyIdentifier>()
|
private val userUpdatedKeys = ConcurrentHashMap.newKeySet<KeyIdentifier>()
|
||||||
|
|
||||||
|
// Backdoor binder for registering new interceptors at runtime.
|
||||||
|
private var backdoorBinder: IBinder? = null
|
||||||
|
|
||||||
|
// Per-security-level interceptor instances, keyed by SecurityLevel constant.
|
||||||
|
private val securityLevelInterceptors = ConcurrentHashMap<Int, KeyMintSecurityLevelInterceptor>()
|
||||||
|
|
||||||
|
// Identity set of SecurityLevel binders already registered with the native hook,
|
||||||
|
// tracked by System.identityHashCode to avoid re-registering the same BBinder.
|
||||||
|
private val registeredSecurityLevelBinders: MutableSet<Int> =
|
||||||
|
Collections.newSetFromMap(ConcurrentHashMap())
|
||||||
|
|
||||||
override val serviceName = "android.system.keystore2.IKeystoreService/default"
|
override val serviceName = "android.system.keystore2.IKeystoreService/default"
|
||||||
override val processName = "keystore2"
|
override val processName = "keystore2"
|
||||||
override val injectionCommand = "exec ./inject `pidof keystore2` libTEESimulator.so entry"
|
override val injectionCommand = "exec ./inject `pidof keystore2` libTEESimulator.so entry"
|
||||||
@@ -72,6 +87,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
LIST_ENTRIES_TRANSACTION,
|
LIST_ENTRIES_TRANSACTION,
|
||||||
LIST_ENTRIES_BATCHED_TRANSACTION,
|
LIST_ENTRIES_BATCHED_TRANSACTION,
|
||||||
GET_NUMBER_OF_ENTRIES_TRANSACTION,
|
GET_NUMBER_OF_ENTRIES_TRANSACTION,
|
||||||
|
GET_SECURITY_LEVEL_TRANSACTION,
|
||||||
)
|
)
|
||||||
.toIntArray()
|
.toIntArray()
|
||||||
}
|
}
|
||||||
@@ -81,6 +97,7 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
* security level sub-services (e.g., TEE, StrongBox).
|
* security level sub-services (e.g., TEE, StrongBox).
|
||||||
*/
|
*/
|
||||||
override fun onInterceptorReady(service: IBinder, backdoor: IBinder) {
|
override fun onInterceptorReady(service: IBinder, backdoor: IBinder) {
|
||||||
|
backdoorBinder = backdoor
|
||||||
val keystoreInterface = IKeystoreService.Stub.asInterface(service)
|
val keystoreInterface = IKeystoreService.Stub.asInterface(service)
|
||||||
setupSecurityLevelInterceptors(keystoreInterface, backdoor)
|
setupSecurityLevelInterceptors(keystoreInterface, backdoor)
|
||||||
}
|
}
|
||||||
@@ -92,11 +109,11 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
SystemLogger.info("Found TEE SecurityLevel. Registering interceptor...")
|
SystemLogger.info("Found TEE SecurityLevel. Registering interceptor...")
|
||||||
val interceptor =
|
val interceptor =
|
||||||
KeyMintSecurityLevelInterceptor(tee, SecurityLevel.TRUSTED_ENVIRONMENT)
|
KeyMintSecurityLevelInterceptor(tee, SecurityLevel.TRUSTED_ENVIRONMENT)
|
||||||
register(
|
securityLevelInterceptors[SecurityLevel.TRUSTED_ENVIRONMENT] = interceptor
|
||||||
|
registerSecurityLevelBinder(
|
||||||
backdoor,
|
backdoor,
|
||||||
tee.asBinder(),
|
tee.asBinder(),
|
||||||
interceptor,
|
interceptor,
|
||||||
KeyMintSecurityLevelInterceptor.INTERCEPTED_CODES,
|
|
||||||
)
|
)
|
||||||
interceptor.loadPersistedKeys()
|
interceptor.loadPersistedKeys()
|
||||||
}
|
}
|
||||||
@@ -109,11 +126,11 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
SystemLogger.info("Found StrongBox SecurityLevel. Registering interceptor...")
|
SystemLogger.info("Found StrongBox SecurityLevel. Registering interceptor...")
|
||||||
val interceptor =
|
val interceptor =
|
||||||
KeyMintSecurityLevelInterceptor(strongbox, SecurityLevel.STRONGBOX)
|
KeyMintSecurityLevelInterceptor(strongbox, SecurityLevel.STRONGBOX)
|
||||||
register(
|
securityLevelInterceptors[SecurityLevel.STRONGBOX] = interceptor
|
||||||
|
registerSecurityLevelBinder(
|
||||||
backdoor,
|
backdoor,
|
||||||
strongbox.asBinder(),
|
strongbox.asBinder(),
|
||||||
interceptor,
|
interceptor,
|
||||||
KeyMintSecurityLevelInterceptor.INTERCEPTED_CODES,
|
|
||||||
)
|
)
|
||||||
interceptor.loadPersistedKeys()
|
interceptor.loadPersistedKeys()
|
||||||
}
|
}
|
||||||
@@ -121,6 +138,30 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
.onFailure { SystemLogger.error("Failed to intercept StrongBox SecurityLevel.", it) }
|
.onFailure { SystemLogger.error("Failed to intercept StrongBox SecurityLevel.", it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers an interceptor for a SecurityLevel binder, tracking the binder identity
|
||||||
|
* to avoid duplicate registrations when keystore2 returns the same BBinder.
|
||||||
|
*/
|
||||||
|
private fun registerSecurityLevelBinder(
|
||||||
|
backdoor: IBinder,
|
||||||
|
binder: IBinder,
|
||||||
|
interceptor: KeyMintSecurityLevelInterceptor,
|
||||||
|
) {
|
||||||
|
val identity = System.identityHashCode(binder)
|
||||||
|
if (registeredSecurityLevelBinders.add(identity)) {
|
||||||
|
register(
|
||||||
|
backdoor,
|
||||||
|
binder,
|
||||||
|
interceptor,
|
||||||
|
KeyMintSecurityLevelInterceptor.INTERCEPTED_CODES,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
SystemLogger.debug(
|
||||||
|
"SecurityLevel binder $binder (identity=$identity) already registered, skipping."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onPreTransact(
|
override fun onPreTransact(
|
||||||
txId: Long,
|
txId: Long,
|
||||||
target: IBinder,
|
target: IBinder,
|
||||||
@@ -210,6 +251,13 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
KeyMintParameterLogger.logParameter(it.keyParameter)
|
KeyMintParameterLogger.logParameter(it.keyParameter)
|
||||||
}
|
}
|
||||||
return InterceptorUtils.createTypedObjectReply(response)
|
return InterceptorUtils.createTypedObjectReply(response)
|
||||||
|
} else if (code == GET_SECURITY_LEVEL_TRANSACTION) {
|
||||||
|
// Pass through to post-hook so we can register interceptors for newly-created
|
||||||
|
// SecurityLevel binders. keystore2 may create a new BBinder per call, so the
|
||||||
|
// initial registration in setupSecurityLevelInterceptors might not cover all
|
||||||
|
// binder instances that clients receive.
|
||||||
|
logTransaction(txId, "getSecurityLevel", callingUid, callingPid)
|
||||||
|
return TransactionResult.Continue
|
||||||
} else {
|
} else {
|
||||||
logTransaction(
|
logTransaction(
|
||||||
txId,
|
txId,
|
||||||
@@ -238,6 +286,10 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
if (target != keystoreService || reply == null || InterceptorUtils.hasException(reply))
|
if (target != keystoreService || reply == null || InterceptorUtils.hasException(reply))
|
||||||
return TransactionResult.SkipTransaction
|
return TransactionResult.SkipTransaction
|
||||||
|
|
||||||
|
if (code == GET_SECURITY_LEVEL_TRANSACTION) {
|
||||||
|
return handlePostGetSecurityLevel(txId, data, reply)
|
||||||
|
}
|
||||||
|
|
||||||
if (code == GET_NUMBER_OF_ENTRIES_TRANSACTION) {
|
if (code == GET_NUMBER_OF_ENTRIES_TRANSACTION) {
|
||||||
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
|
||||||
return runCatching {
|
return runCatching {
|
||||||
@@ -442,4 +494,67 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
|
|||||||
|
|
||||||
return InterceptorUtils.createSuccessReply(writeResultCode = false)
|
return InterceptorUtils.createSuccessReply(writeResultCode = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intercepts the reply from getSecurityLevel to dynamically register our interceptor
|
||||||
|
* for the returned IKeystoreSecurityLevel binder.
|
||||||
|
*
|
||||||
|
* keystore2 may create a new BBinder for each getSecurityLevel call, so the binder
|
||||||
|
* registered during initial setup (in setupSecurityLevelInterceptors) might not be the
|
||||||
|
* same one that client apps receive. By intercepting every getSecurityLevel reply, we
|
||||||
|
* ensure that all SecurityLevel binders are covered.
|
||||||
|
*/
|
||||||
|
private fun handlePostGetSecurityLevel(
|
||||||
|
txId: Long,
|
||||||
|
data: Parcel,
|
||||||
|
reply: Parcel,
|
||||||
|
): TransactionResult {
|
||||||
|
val backdoor = backdoorBinder
|
||||||
|
if (backdoor == null) {
|
||||||
|
SystemLogger.warning("[TX_ID: $txId] post-getSecurityLevel: backdoor not available")
|
||||||
|
return TransactionResult.SkipTransaction
|
||||||
|
}
|
||||||
|
|
||||||
|
return runCatching {
|
||||||
|
// Read the security level argument from the original request.
|
||||||
|
data.enforceInterface(IKeystoreService.DESCRIPTOR)
|
||||||
|
val requestedLevel = data.readInt()
|
||||||
|
|
||||||
|
// hasException already consumed the exception header from the reply.
|
||||||
|
// Next item is the IKeystoreSecurityLevel binder.
|
||||||
|
val secLevelBinder = reply.readStrongBinder()
|
||||||
|
if (secLevelBinder == null) {
|
||||||
|
SystemLogger.verbose(
|
||||||
|
"[TX_ID: $txId] getSecurityLevel($requestedLevel) returned null binder"
|
||||||
|
)
|
||||||
|
return@runCatching TransactionResult.SkipTransaction
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only intercept TEE and StrongBox security levels.
|
||||||
|
if (requestedLevel != SecurityLevel.TRUSTED_ENVIRONMENT &&
|
||||||
|
requestedLevel != SecurityLevel.STRONGBOX
|
||||||
|
) {
|
||||||
|
return@runCatching TransactionResult.SkipTransaction
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get or create the interceptor for this security level. The interceptor may not
|
||||||
|
// exist yet if the initial setupSecurityLevelInterceptors call failed for this level.
|
||||||
|
val interceptor = securityLevelInterceptors.getOrPut(requestedLevel) {
|
||||||
|
val secLevelInterface =
|
||||||
|
IKeystoreSecurityLevel.Stub.asInterface(secLevelBinder)
|
||||||
|
SystemLogger.info(
|
||||||
|
"[TX_ID: $txId] Late-creating interceptor for security level $requestedLevel"
|
||||||
|
)
|
||||||
|
KeyMintSecurityLevelInterceptor(secLevelInterface, requestedLevel).also {
|
||||||
|
it.loadPersistedKeys()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerSecurityLevelBinder(backdoor, secLevelBinder, interceptor)
|
||||||
|
TransactionResult.SkipTransaction
|
||||||
|
}.getOrElse {
|
||||||
|
SystemLogger.error("[TX_ID: $txId] Failed to process post-getSecurityLevel.", it)
|
||||||
|
TransactionResult.SkipTransaction
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user