Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
213653e
Fix USER_ID authorization security level to match AOSP keystore2
MhmRdd Mar 18, 2026
c263ab1
Implement updateAad for software-backed crypto operations
MhmRdd Mar 18, 2026
45ebf9a
Patch authorizations alongside certificate chains in PATCH mode
MhmRdd Mar 18, 2026
e756891
Derive certificate signature algorithm from signing key, not subject key
MhmRdd Mar 18, 2026
962cef6
Add operation lifecycle tracking, concurrency guard, and input limits
MhmRdd Mar 18, 2026
b6bc7a1
Patch certificates and authorizations for imported keys
MhmRdd Mar 18, 2026
da452cf
Populate CreateOperationResponse.parameters for GCM operations
MhmRdd Mar 18, 2026
0ebdded
Use ServiceSpecificException with AOSP error codes for operation errors
MhmRdd Mar 18, 2026
fbe5605
Fix default certificate subject to match KeyMint reference
MhmRdd Mar 18, 2026
f33769d
Fix certificate validity defaults and RSA exponent null safety
MhmRdd Mar 18, 2026
8a90ea3
Only include ATTESTATION_APPLICATION_ID when challenge is present
MhmRdd Mar 18, 2026
e4814bf
Fix attestation key override to update nspace and patch authorizations
MhmRdd Mar 18, 2026
086a580
Add .gitattributes to enforce LF line endings for shell scripts
MhmRdd Mar 18, 2026
e767649
Derive KEY_SIZE from EC_CURVE when not explicitly provided
MhmRdd Mar 18, 2026
50cd77f
Validate operation purpose and reject caller-provided CREATION_DATETIME
MhmRdd Mar 18, 2026
60d978d
Fix TOO_MUCH_DATA error code to 21 and add missing error constants
MhmRdd Mar 18, 2026
4bc4713
Parse enforcement tags from key generation parameters
MhmRdd Mar 18, 2026
41abe77
Include enforcement tags in attestation teeEnforced list
MhmRdd Mar 18, 2026
3078ea9
Implement AOSP enforcements.rs authorize_create for software operations
MhmRdd Mar 18, 2026
890ee70
Handle Domain.APP in createOperation for software-generated keys
MhmRdd Mar 18, 2026
2bc46be
Fix UNSUPPORTED_PURPOSE for EC VERIFY/ENCRYPT and USAGE_COUNT_LIMIT
MhmRdd Mar 18, 2026
400df41
Remove verbose AOSP references and test-label comments
MhmRdd Mar 18, 2026
07c98bc
Reject device ID attestation tags, fix operation param merging and us…
MhmRdd Mar 18, 2026
ca3fcbc
Filter intercepted transaction codes at native level to reduce latency
MhmRdd Mar 18, 2026
ed98768
Check READ_PRIVILEGED_PHONE_STATE before rejecting device ID attestation
MhmRdd Mar 18, 2026
9ffeda1
Use hardcoded AndroidSystem AAID for system and root UIDs
MhmRdd Mar 18, 2026
3ec2153
Add android and com.android.shell to default target list
MhmRdd Mar 18, 2026
4b264b8
Compute unique ID when INCLUDE_UNIQUE_ID is present
MhmRdd Mar 18, 2026
fd8799e
Skip cert re-patching for keys updated via updateSubcomponent
MhmRdd Mar 18, 2026
2a30b33
Move enforcement tags to softwareEnforced and fix updateSubcomponent …
MhmRdd Mar 18, 2026
e55d16d
Check SELinux gen_unique_id and REQUEST_UNIQUE_ID_ATTESTATION for uni…
MhmRdd Mar 18, 2026
7c952f7
Return proper TEE error codes on software generation failure
MhmRdd Mar 18, 2026
37ee5f1
Replace static TEE check with async race for AUTO mode
MhmRdd Mar 19, 2026
453950d
Remove legacy tee_status.txt during module installation
MhmRdd Mar 19, 2026
ed134bc
Encode TAG_PADDING as SET OF INTEGER in attestation extension
MhmRdd Mar 19, 2026
509d157
Reject AGREE_KEY for all non-EC algorithms with UNSUPPORTED_PURPOSE
MhmRdd Mar 19, 2026
7952e55
Fix NO_AUTH_REQUIRED and ORIGIN in attestation, add WRAP_KEY rejection
MhmRdd Mar 19, 2026
550bacd
Parse additional attestation tags from key generation parameters
MhmRdd Mar 19, 2026
1cc1be0
Add EARLY_BOOT_ONLY, ALLOW_WHILE_ON_BODY, TRUSTED_USER_PRESENCE_REQUI…
MhmRdd Mar 19, 2026
eec69ab
Include version-guarded tags and remove non-attestation tags
MhmRdd Mar 19, 2026
1acf686
Include BLOCK_MODE as SET OF INTEGER in teeEnforced attestation
MhmRdd Mar 19, 2026
d3bf3c8
Support symmetric key generation (AES, HMAC) in software mode
MhmRdd Mar 19, 2026
25aca9b
Include BLOCK_MODE in KeyMetadata authorizations
MhmRdd Mar 19, 2026
98472d3
Simulate realistic TEE hardware latency for software key generation
MhmRdd Mar 19, 2026
802f7b7
Restore async race for AUTO mode with getKeyEntry caching fix
MhmRdd Mar 19, 2026
a0985f2
Fix deleteKey to forward to hardware for TEE-cached keys
MhmRdd Mar 19, 2026
5c85dd2
Include software key count in getNumberOfEntries
MhmRdd Mar 19, 2026
7486530
Handle deleteKey for KEY_ID domain to prevent stale cache entries
MhmRdd Mar 19, 2026
fa38975
Skip attest key override for imported keys in getKeyEntry
MhmRdd Mar 19, 2026
492d6dc
Align KeyMetadata authorizations and operation semantics with AOSP
MhmRdd Mar 19, 2026
4c8d078
Harden getKeyEntry cache resolution and restore import key skip
MhmRdd Mar 21, 2026
2b3e5de
Verify TEE attestation capability, pass operation params, and add SEC…
MhmRdd Mar 21, 2026
25538da
Handle IV/nonce, OAEP, GCM tags, CTR mode, and ECDH in software opera…
MhmRdd Mar 21, 2026
d839b69
Fix silent error paths, challenge error code, and null handling
MhmRdd Mar 21, 2026
7a7e362
Add dir class to sepolicy and crash safety for binder interceptors
MhmRdd Mar 21, 2026
9218657
Wire DEBUG flag to build variant and add bughunter logcat capture
MhmRdd Mar 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Ensure shell scripts always have LF line endings, even on Windows.
# These get packaged into flashable zips and run on Android devices.
*.sh text eol=lf
module/daemon text eol=lf
14 changes: 13 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ androidComponents {
// Now, copy and process the files from 'module' directory.
val sourceModuleDir = rootProject.projectDir.resolve("module")
from(sourceModuleDir) {
exclude("module.prop") // Exclude the template file.
exclude("module.prop", "service.sh") // Exclude templated files.
}

// Copy and filter the module.prop template separately.
Expand All @@ -136,6 +136,18 @@ androidComponents {
)
}

// Wire DEBUG flag in service.sh to the build variant.
// Cannot use expand() here because shell syntax ${0%/*} conflicts.
// FixCrLfFilter applied last to ensure LF output on Windows.
from(sourceModuleDir) {
include("service.sh")
filter { it.replace("DEBUG=false", "DEBUG=$isDebug") }
filter(
mapOf("eol" to org.apache.tools.ant.filters.FixCrLfFilter.CrLf.newInstance("lf")),
org.apache.tools.ant.filters.FixCrLfFilter::class.java,
)
}

// The destination for all the above 'from' operations.
into(tempModuleDir)
}
Expand Down
80 changes: 68 additions & 12 deletions app/src/main/cpp/binder_interceptor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@ class BinderInterceptor : public BBinder {
struct RegistrationEntry {
wp<IBinder> target;
sp<IBinder> callback_interface;
// Transaction codes to intercept. Empty = intercept all (legacy behavior).
std::vector<uint32_t> filtered_codes;
};

// Reader-Writer lock for the registry to allow concurrent reads (lookups)
Expand All @@ -244,10 +246,15 @@ class BinderInterceptor : public BBinder {
public:
BinderInterceptor() = default;

// Checks if a specific Binder instance is currently registered for interception
bool isBinderIntercepted(const wp<BBinder> &target) const {
// Checks if a specific Binder+code combination should be intercepted.
// Returns true if the binder is registered AND the code is in its filter
// (or the filter is empty, meaning intercept everything).
bool shouldIntercept(const wp<BBinder> &target, uint32_t code) const {
std::shared_lock lock(registry_mutex_);
return registry_.find(target) != registry_.end();
auto it = registry_.find(target);
if (it == registry_.end()) return false;
const auto &codes = it->second.filtered_codes;
return codes.empty() || std::find(codes.begin(), codes.end(), code) != codes.end();
}

// Main entry point for processing the "Man-in-the-Middle" logic
Expand Down Expand Up @@ -307,7 +314,11 @@ class BinderStub : public BBinder {
}

if (!found_context) {
LOGW("BinderStub received transaction but no context found for thread");
LOGW("BinderStub received transaction but no context found for thread (code=%u)", code);
#ifndef NDEBUG
std::lock_guard<std::mutex> dbg_lock(g_thread_context_mutex);
LOGW(" Thread context map has %zu entries", g_thread_context_map.size());
#endif
return UNKNOWN_TRANSACTION;
}

Expand Down Expand Up @@ -386,13 +397,16 @@ void inspectAndRewriteTransaction(binder_transaction_data *txn_data) {
// This is safe because we are holding a strong reference.
wp<BBinder> wp_target = target_binder_ptr;

if (g_interceptor_instance->isBinderIntercepted(wp_target)) {
if (g_interceptor_instance->shouldIntercept(wp_target, txn_data->code)) {
info.transaction_code = txn_data->code;
info.target_binder = wp_target; // Assign the valid weak pointer
hijack = true;
}
// Manually release the temporary strong reference we acquired at the start.
target_binder_ptr->decStrong(nullptr);
} else {
LOGD("[Hook] attemptIncStrong failed for target %p (code=%u, uid=%d) — binder may be dying",
reinterpret_cast<void*>(txn_data->target.ptr), txn_data->code, txn_data->sender_euid);
}
}

Expand All @@ -409,7 +423,13 @@ void inspectAndRewriteTransaction(binder_transaction_data *txn_data) {

// Store context for the stub to retrieve later in its onTransact
std::lock_guard<std::mutex> lock(g_thread_context_mutex);
g_thread_context_map[std::this_thread::get_id()].push(std::move(info));
auto &queue = g_thread_context_map[std::this_thread::get_id()];
queue.push(std::move(info));
#ifndef NDEBUG
if (queue.size() > 8) {
LOGW("[Hook] Thread context queue depth=%zu for thread — possible leak", queue.size());
}
#endif
}
}

Expand Down Expand Up @@ -537,12 +557,26 @@ status_t BinderInterceptor::handleRegister(const Parcel &data) {
return BAD_TYPE;
}

// Read optional transaction code filter. If present: int32 count + count * uint32 codes.
// If absent or count <= 0: intercept all transaction codes (legacy behavior).
std::vector<uint32_t> codes;
int32_t code_count = 0;
if (data.dataAvail() >= sizeof(int32_t) && data.readInt32(&code_count) == OK && code_count > 0) {
codes.reserve(code_count);
for (int32_t i = 0; i < code_count; i++) {
uint32_t c = 0;
if (data.readUint32(&c) == OK) codes.push_back(c);
}
LOGI("Interceptor registered for binder %p with %zu filtered codes", target.get(), codes.size());
} else {
LOGI("Interceptor registered for binder %p (all codes)", target.get());
}

wp<IBinder> weak_target = target;

std::unique_lock lock(registry_mutex_);
registry_[weak_target] = {weak_target, callback};
registry_[weak_target] = {weak_target, callback, std::move(codes)};

LOGI("Interceptor registered for binder %p", target.get());
return OK;
}

Expand Down Expand Up @@ -592,8 +626,24 @@ bool BinderInterceptor::processInterceptedTransaction(uint64_t tx_id, sp<BBinder
Parcel pre_req, pre_resp;
writeTransactionData(pre_req, tx_id, target, code, flags, request);

if (callback->transact(intercept::kPreTransact, pre_req, &pre_resp) != OK) {
LOGW("[TX_ID: %" PRIu64 "] Pre-transaction callback failed. Forwarding original call.", tx_id);
#ifndef NDEBUG
struct timespec ts_start{};
clock_gettime(CLOCK_MONOTONIC, &ts_start);
#endif

status_t pre_cb_status = callback->transact(intercept::kPreTransact, pre_req, &pre_resp);

#ifndef NDEBUG
struct timespec ts_end{};
clock_gettime(CLOCK_MONOTONIC, &ts_end);
double pre_ms = (ts_end.tv_sec - ts_start.tv_sec) * 1000.0 + (ts_end.tv_nsec - ts_start.tv_nsec) / 1e6;
if (pre_ms > 5000.0) {
LOGW("[TX_ID: %" PRIu64 "] Pre-callback took %.0fms (code=%u) — possible hang", tx_id, pre_ms, code);
}
#endif

if (pre_cb_status != OK) {
LOGW("[TX_ID: %" PRIu64 "] Pre-transaction callback failed (status=%d). Forwarding original call.", tx_id, pre_cb_status);
return false; // Callback failed, proceed as if not intercepted
}

Expand Down Expand Up @@ -627,8 +677,10 @@ bool BinderInterceptor::processInterceptedTransaction(uint64_t tx_id, sp<BBinder
if (action == intercept::kActionOverrideData) {
size_t size = pre_resp.readUint64();
final_request.appendFrom(&pre_resp, pre_resp.dataPosition(), size);
} else if (action == intercept::kActionContinue) {
final_request.appendFrom(&request, 0, request.dataSize());
} else {
// Default (kActionContinue): Use original data
LOGW("[TX_ID: %" PRIu64 "] Unknown pre-callback action %d (code=%u). Forwarding original data.", tx_id, action, code);
final_request.appendFrom(&request, 0, request.dataSize());
}

Expand All @@ -647,14 +699,18 @@ bool BinderInterceptor::processInterceptedTransaction(uint64_t tx_id, sp<BBinder
VALIDATE_STATUS(tx_id, post_req.appendFrom(reply, 0, reply_size));
}

if (callback->transact(intercept::kPostTransact, post_req, &post_resp) == OK) {
status_t post_cb_status = callback->transact(intercept::kPostTransact, post_req, &post_resp);
if (post_cb_status == OK) {
int32_t post_action = post_resp.readInt32();
if (post_action == intercept::kActionOverrideReply && reply) {
result = post_resp.readInt32(); // Read new status
size_t new_size = post_resp.readUint64();
reply->setDataSize(0); // Clear original reply
VALIDATE_STATUS(tx_id, reply->appendFrom(&post_resp, post_resp.dataPosition(), new_size));
}
} else {
LOGW("[TX_ID: %" PRIu64 "] Post-transaction callback failed (status=%d, code=%u). Using original reply.",
tx_id, post_cb_status, code);
}

return true; // We handled the flow, even if we just forwarded it
Expand Down
2 changes: 0 additions & 2 deletions app/src/main/java/org/matrix/TEESimulator/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ object App {
// Initialize and start the appropriate keystore interceptors.
initializeInterceptors()

// Load the package configuration.
ConfigurationManager.initialize()
// Set up the device's boot key and hash, which are crucial for attestation.
AndroidDeviceUtils.setupBootKeyAndHash()

// Android ships with a stripped-down Bouncy Castle provider under the name "BC".
Expand Down
Loading