fix(interception): restore G2 binder overhead mitigations from pre-PR157

Commit b6f9d7b introduced G2-specific fixes (ratio dropped from 5.00x
to 2.10x) that were lost when resetting to upstream PR #157 at 94c8e5b.

Restores: try-catch safety in BinderInterceptor.onTransact, shouldPatch
early-exit in getKeyEntry post-transact, safe parcel reads (!! to ?:)
at 6 sites, teeResponses cache population in generateKey/importKey
post-transact, uncaught exception handler in App.kt, and removes the
pingBinder liveness check that added ~1.8x overhead per pre-transact.
This commit is contained in:
Enginex0
2026-03-21 05:41:56 +01:00
parent 13a1dd7887
commit c7b0af2d29
5 changed files with 48 additions and 28 deletions
+2 -8
View File
@@ -612,14 +612,8 @@ bool BinderInterceptor::processInterceptedTransaction(uint64_t tx_id, sp<BBinder
Parcel pre_req, pre_resp; Parcel pre_req, pre_resp;
writeTransactionData(pre_req, tx_id, target, code, flags, request); writeTransactionData(pre_req, tx_id, target, code, flags, request);
status_t pre_status = callback->transact(intercept::kPreTransact, pre_req, &pre_resp); if (callback->transact(intercept::kPreTransact, pre_req, &pre_resp) != OK) {
if (pre_status != OK) { LOGW("[TX_ID: %" PRIu64 "] Pre-transaction callback failed. Forwarding original call.", tx_id);
if (callback->pingBinder() != OK) {
LOGE("[TX_ID: %" PRIu64 "] Interceptor DEAD. Blocking to prevent attestation leak.", tx_id);
result = DEAD_OBJECT;
return true;
}
LOGW("[TX_ID: %" PRIu64 "] Pre-transaction callback failed (not dead). Forwarding.", tx_id);
return false; return false;
} }
@@ -35,8 +35,11 @@ object App {
fun main(args: Array<String>) { fun main(args: Array<String>) {
SystemLogger.info("Welcome to TEESimulator!") SystemLogger.info("Welcome to TEESimulator!")
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
SystemLogger.error("Uncaught exception on ${thread.name}", throwable)
}
try { try {
// Initialize the Android framework environment
prepareEnvironment() prepareEnvironment()
// Initialize and start the appropriate keystore interceptors. // Initialize and start the appropriate keystore interceptors.
initializeInterceptors() initializeInterceptors()
@@ -109,17 +109,17 @@ abstract class BinderInterceptor : Binder() {
* `handlePostTransact`). * `handlePostTransact`).
*/ */
final override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean { final override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
// The native hook prepends a transaction ID to the data parcel.
val txId = data.readLong() val txId = data.readLong()
val result = val result = try {
when (code) { when (code) {
// These codes are defined in the native layer to distinguish hook types.
PRE_TRANSACT_CODE -> handlePreTransact(txId, data) PRE_TRANSACT_CODE -> handlePreTransact(txId, data)
POST_TRANSACT_CODE -> handlePostTransact(txId, data) POST_TRANSACT_CODE -> handlePostTransact(txId, data)
else -> return super.onTransact(code, data, reply, flags) else -> return super.onTransact(code, data, reply, flags)
} }
} catch (e: Throwable) {
// The reply parcel is guaranteed to be non-null for our custom transactions. SystemLogger.error("[TX_ID: $txId] Interceptor exception, falling through to HAL", e)
TransactionResult.ContinueAndSkipPost
}
writeResultToReply(result, reply!!) writeResultToReply(result, reply!!)
return true return true
} }
@@ -289,6 +289,9 @@ object Keystore2Interceptor : AbstractKeystoreInterceptor() {
callingPid, callingPid,
) )
if (!ConfigurationManager.shouldPatch(callingUid))
return TransactionResult.SkipTransaction
runCatching { runCatching {
val response = reply.readTypedObject(KeyEntryResponse.CREATOR)!! val response = reply.readTypedObject(KeyEntryResponse.CREATOR)!!
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias) val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
@@ -82,7 +82,8 @@ class KeyMintSecurityLevelInterceptor(
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid) logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR) data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!! val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
?: return TransactionResult.ContinueAndSkipPost
SystemLogger.info( SystemLogger.info(
"[TX_ID: $txId] Forward to post-importKey hook for ${keyDescriptor.alias}[${keyDescriptor.nspace}]" "[TX_ID: $txId] Forward to post-importKey hook for ${keyDescriptor.alias}[${keyDescriptor.nspace}]"
) )
@@ -140,6 +141,10 @@ class KeyMintSecurityLevelInterceptor(
metadata.authorizations = metadata.authorizations =
InterceptorUtils.patchAuthorizations(metadata.authorizations, callingUid) InterceptorUtils.patchAuthorizations(metadata.authorizations, callingUid)
patchedChains[keyId] = newChain patchedChains[keyId] = newChain
teeResponses[keyId] = KeyEntryResponse().apply {
this.metadata = metadata
iSecurityLevel = original
}
SystemLogger.debug("Cached patched certificate chain for imported key $keyId.") SystemLogger.debug("Cached patched certificate chain for imported key $keyId.")
return InterceptorUtils.createTypedObjectReply(metadata) return InterceptorUtils.createTypedObjectReply(metadata)
} }
@@ -148,8 +153,10 @@ class KeyMintSecurityLevelInterceptor(
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid) logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR) data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!! val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
val params = data.createTypedArray(KeyParameter.CREATOR)!! ?: return TransactionResult.SkipTransaction
val params = data.createTypedArray(KeyParameter.CREATOR)
?: return TransactionResult.SkipTransaction
val parsedParams = KeyMintAttestation(params) val parsedParams = KeyMintAttestation(params)
val forced = data.readBoolean() val forced = data.readBoolean()
if (forced) if (forced)
@@ -157,7 +164,8 @@ class KeyMintSecurityLevelInterceptor(
"[TX_ID: $txId] Current operation has a very high pruning power." "[TX_ID: $txId] Current operation has a very high pruning power."
) )
val response: CreateOperationResponse = val response: CreateOperationResponse =
reply.readTypedObject(CreateOperationResponse.CREATOR)!! reply.readTypedObject(CreateOperationResponse.CREATOR)
?: return TransactionResult.SkipTransaction
SystemLogger.verbose( SystemLogger.verbose(
"[TX_ID: $txId] CreateOperationResponse: ${response.iOperation} ${response.operationChallenge}" "[TX_ID: $txId] CreateOperationResponse: ${response.iOperation} ${response.operationChallenge}"
) )
@@ -190,9 +198,6 @@ class KeyMintSecurityLevelInterceptor(
val metadata: KeyMetadata = val metadata: KeyMetadata =
reply.readTypedObject(KeyMetadata.CREATOR) reply.readTypedObject(KeyMetadata.CREATOR)
?: return TransactionResult.SkipTransaction ?: return TransactionResult.SkipTransaction
KeyMintAttestation(
metadata.authorizations?.map { it.keyParameter }?.toTypedArray() ?: emptyArray()
)
val originalChain = val originalChain =
CertificateHelper.getCertificateChain(metadata) CertificateHelper.getCertificateChain(metadata)
?: return TransactionResult.SkipTransaction ?: return TransactionResult.SkipTransaction
@@ -201,16 +206,23 @@ class KeyMintSecurityLevelInterceptor(
// Cache the newly patched chain to ensure consistency across subsequent API calls. // Cache the newly patched chain to ensure consistency across subsequent API calls.
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR) data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!! val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
val key = metadata.key!! ?: return TransactionResult.SkipTransaction
val key = metadata.key
?: return TransactionResult.SkipTransaction
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias) val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow() CertificateHelper.updateCertificateChain(metadata, newChain).getOrThrow()
metadata.authorizations = metadata.authorizations =
InterceptorUtils.patchAuthorizations(metadata.authorizations, callingUid) InterceptorUtils.patchAuthorizations(metadata.authorizations, callingUid)
// We must clean up cached generated keys before storing the patched chain
cleanupKeyData(keyId) cleanupKeyData(keyId)
patchedChains[keyId] = newChain patchedChains[keyId] = newChain
teeResponses[keyId] = KeyEntryResponse().apply {
this.metadata = metadata
iSecurityLevel = original
}
SystemLogger.debug( SystemLogger.debug(
"Cached patched certificate chain for $keyId. (${key.alias} [${key.domain}, ${key.nspace}])" "Cached patched certificate chain for $keyId. (${key.alias} [${key.domain}, ${key.nspace}])"
) )
@@ -232,9 +244,9 @@ class KeyMintSecurityLevelInterceptor(
data: Parcel, data: Parcel,
): TransactionResult { ): TransactionResult {
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR) data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!! val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
?: return TransactionResult.ContinueAndSkipPost
// Resolve key descriptor to a generated key via nspace (KEY_ID) or alias (APP).
val resolvedEntry: Map.Entry<KeyIdentifier, GeneratedKeyInfo>? = val resolvedEntry: Map.Entry<KeyIdentifier, GeneratedKeyInfo>? =
when (keyDescriptor.domain) { when (keyDescriptor.domain) {
Domain.KEY_ID -> { Domain.KEY_ID -> {
@@ -414,13 +426,15 @@ class KeyMintSecurityLevelInterceptor(
private fun handleGenerateKey(txId: Long, callingUid: Int, callingPid: Int, data: Parcel): TransactionResult { private fun handleGenerateKey(txId: Long, callingUid: Int, callingPid: Int, data: Parcel): TransactionResult {
return runCatching { return runCatching {
data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR) data.enforceInterface(IKeystoreSecurityLevel.DESCRIPTOR)
val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)!! val keyDescriptor = data.readTypedObject(KeyDescriptor.CREATOR)
?: return@runCatching TransactionResult.ContinueAndSkipPost
val attestationKey = data.readTypedObject(KeyDescriptor.CREATOR) val attestationKey = data.readTypedObject(KeyDescriptor.CREATOR)
SystemLogger.debug( SystemLogger.debug(
"Handling generateKey ${keyDescriptor.alias}, attestKey=${attestationKey?.alias}" "Handling generateKey ${keyDescriptor.alias}, attestKey=${attestationKey?.alias}"
) )
val params = data.createTypedArray(KeyParameter.CREATOR)!! val params = data.createTypedArray(KeyParameter.CREATOR)
?: return@runCatching TransactionResult.ContinueAndSkipPost
val parsedParams = KeyMintAttestation(params) val parsedParams = KeyMintAttestation(params)
val challenge = parsedParams.attestationChallenge val challenge = parsedParams.attestationChallenge
@@ -478,10 +492,16 @@ class KeyMintSecurityLevelInterceptor(
val isAuto = ConfigurationManager.isAutoMode(callingUid) val isAuto = ConfigurationManager.isAutoMode(callingUid)
val isStrongBox = securityLevel == SecurityLevel.STRONGBOX
when { when {
forceGenerate -> doSoftwareGeneration( forceGenerate -> doSoftwareGeneration(
callingUid, keyDescriptor, attestationKey, parsedParams, isAttestKeyRequest callingUid, keyDescriptor, attestationKey, parsedParams, isAttestKeyRequest
) )
// StrongBox before TEE-race: broken StrongBox HALs must never reach raceTeePatch
isAuto && isStrongBox -> doSoftwareGeneration(
callingUid, keyDescriptor, attestationKey, parsedParams, isAttestKeyRequest
)
isAuto && !teeFunctional -> raceTeePatch( isAuto && !teeFunctional -> raceTeePatch(
callingUid, keyDescriptor, attestationKey, params, parsedParams, isAttestKeyRequest callingUid, keyDescriptor, attestationKey, params, parsedParams, isAttestKeyRequest
) )