Compare commits

...
300 Commits
Author SHA1 Message Date
Enginex0 6d241e56d6 chore(release): v6.0.1-307 2026-07-11 18:29:07 +01:00
Enginex0 1f96c8a13e fix(keymint): carry op OAEP MGF digest 2026-07-11 17:48:28 +01:00
Enginex0 000e926693 chore(release): v6.0.1-305 2026-07-11 17:38:22 +01:00
Enginex0 5b7373ad7a fix(attestation): log VINTF fallback source 2026-07-11 17:19:13 +01:00
Enginex0 eb94820192 fix(attestation): source module hash from framework 2026-07-11 17:10:46 +01:00
Enginex0 d7109421ef fix(keymint): execute HMAC operations 2026-07-11 16:52:24 +01:00
Enginex0 c9918952f4 fix(keystore): resolve grant-domain attest key 2026-07-11 16:39:05 +01:00
Enginex0 d2cb950cb6 fix(keymint): apply OAEP digest spec 2026-07-11 16:25:54 +01:00
Enginex0 2cf6526b5c fix(build): floor versionCode above shipped 298
The 2026-07-08 public-release history scrub (0f1143a) rewrote history
and dropped `git rev-list --count` below the build number already
shipped to testers (298), so post-scrub builds (291, 294) read as
downgrades. Add a floor offset so versionCode clears 298 and stays
monotonic across the rewrite: the current count maps to 300, and each
later commit still increments by one.
2026-07-11 13:50:21 +01:00
Enginex0 ab4f41eb2a fix(keymint): enforce operation authorizations
Add checkOperationAuthorizations to the AuthorizeCreate chain so the
interceptor rejects operations whose parameters are incompatible with
the key, matching real KeyMint HAL behavior:

- block mode, padding, digest, and RSA-OAEP MGF digest must each be a
  subset of the key's authorized set;
- AES-GCM rejects a requested MAC length below the key minimum;
- RSA-OAEP requires a digest.

Add the four backing KeyMint error codes (INCOMPATIBLE_BLOCK_MODE,
INCOMPATIBLE_PADDING_MODE, INCOMPATIBLE_DIGEST, INVALID_MAC_LENGTH) to
KeystoreErrorCodes, resolved at runtime with AOSP-correct fallbacks.

The check reads the raw request params (AuthorizeCreate.check is called
with parsedParams), so no op-param construction change is needed, and
execution is unaffected: our SoftwareOperation already runs GCM (128-bit
tag) and OAEP.
2026-07-11 13:23:29 +01:00
Enginex0 422f78ebcf fix(keystore): handle grant subcomponent updates
Extend our grant model to the updateSubcomponent path. A grantee holding
a grant with the UPDATE access-vector bit can now update the cert/chain
subcomponent of the owner's synthetic key; previously a Domain.GRANT
updateSubcomponent fell through to the real keystore2 and failed for
synthetic keys.

Reuse the existing grant machinery (resolveGrant / softwareGrants /
getGeneratedKeyResponse) already backing the getKeyEntry Domain.GRANT
read, gated on KEY_PERMISSION_UPDATE (0x80) instead of GET_INFO (0x4).
Extract a shared updateResponseSubcomponent() helper so the grant and
owner paths share one cert-swap plus re-persist body.
2026-07-11 13:06:22 +01:00
Enginex0 dc2d894647 feat(spoof): add boot_props_mode Oplus carve-out
Add a boot_props_mode control so global ro.boot.* property spoofing
can be tuned per device.

BootStateManager reads /data/adb/tricky_store/boot_props_mode
(auto/force/disable). In auto, Oplus-family devices
(OnePlus/OPPO/realme/Oplus) skip the global ro.boot.* spoof so vendor
TEE services such as ultrasonic fingerprint calibration keep working.
AndroidDeviceUtils routes the bootKey/bootHash resetprop writes
through a setBootProperty() gate honoring the same mode; the forged
value is still persisted and returned, so attestation is unaffected.

Tradeoff: in auto, skipping ro.boot.* leaves direct system-property
boot-state checks truthful on Oplus devices (compatibility over
stealth). Latent, not a reported issue.
2026-07-11 12:50:07 +01:00
Enginex0 a811919d0c fix(attestation): align KeyMint version to VINTF
getAttestVersion/getKeymasterVersion now read the device VINTF manifest
and derive attestationVersion=keymasterVersion=aidl*100 (KeyMint) or the
legacy HIDL Keymaster pair, so the forged attestation certificate's
version matches the device's declared IKeyMintDevice interface. This
removes the version MISMATCH that Duck Detector flags (attested 400 vs
declared 300). Falls back to the existing cache/SDK-map/400 chain when
VINTF is absent or unreadable.

Refs #40
2026-07-11 12:08:00 +01:00
Enginex0 0f1143a445 chore: prepare source tree for public release
- Freeze changelog.md and update.json at v6.0.1-282
- Add refactoring note to changelog top
- Untrack local planning files (CLAUDE.md, bucket/) via gitignore
2026-07-08 13:45:33 +01:00
Enginex0 4429f0ed60 docs(soter): initSigh resultCode is load-bearing
The probe's signSessionOk requires resultCode==0 (SoterCapabilityProbe
.kt:107), so writeInt(SOTER_OK) for initSigh is not optional. Cite the
probe line in the comment so a future cleanup does not drop it on the
false "detector ignores resultCode" belief, true only for the export
path.
2026-06-26 16:57:12 +01:00
Enginex0 5ddd8137df fix(soter): harden on-demand mount recovery
The supervisor mounted the forge on the happy path but could not
re-attempt: mount() returned silently on inject/handshake failure, and a
live-but-uninjected binding never died to trigger a rebind, stranding the
forge for the life of that soterserver process (audit F1).

Route every unmounted outcome through scheduleRetry(): inject failure,
post-inject handshake-null, and register failure now schedule a re-bind
instead of returning. Add onNullBinding (F2) and exponential backoff
capped at 30s, reset on a clean mount (F3). register() now returns
whether the transact succeeded so mount() retries on a false reply (F4);
existing keystore callers ignore the new return.

Audit remediation. compileDebugKotlin clean.
2026-06-26 16:57:12 +01:00
Enginex0 a4875dc200 feat(soter): wire supervisor into App startup
Start SoterProcessSupervisor from App.main() so the Layer-A forge
mounts on the on-demand soterserver process. prepareEnvironment() now
returns the system Context it previously discarded; main() hands it to
start() after keystore init and before Looper.loop(). start() runs on
its own HandlerThread and returns at once, so neither the blocking
keystore loop nor the message loop is affected.

The stub types ActivityThread.getSystemContext() as a bare ContextImpl,
so the Context cast warns CAST_NEVER_SUCCEEDS; the real ContextImpl does
extend Context, so it is runtime-safe and the warning is suppressed.

Checkpoint 10.W. compileDebugKotlin clean.
2026-06-26 16:13:03 +01:00
Enginex0 6b84c76f10 fix(module): detect diag.sh by file not exit code
The release build is meant to nuke any debug NDJSON directory on
install, but the sweep never ran. customize.sh keyed the debug vs
release decision on unzip's exit code:

  if unzip -qqjo "$ZIPFILE" "diag.sh" ...; then ...

Info-ZIP returns 11 when the entry is absent, but the busybox/toybox
unzip in the Magisk/KSU install environment exits 0, so on a release
ZIP (which correctly omits diag.sh) the branch was wrongly taken and
the rm -rf in else never fired.

Detect presence by the extracted file instead: run unzip, then test
[ -f "$MODPATH/diag.sh" ]. Robust to any unzip implementation.
2026-06-26 15:54:19 +01:00
Enginex0 43a2301982 feat(soter): sepolicy grants for ptrace injection
Injection into the soterserver app (platform_app domain, per recon)
needs ptrace under SELinux enforcing. Add the grant mirroring the
keystore one, in the base rule so it applies to both variants:

  allow crash_dump platform_app process *

The per-UID NDJSON write grant is debug-only: appended for debug
builds in build.gradle.kts's isDebug doLast, mirroring the existing
keystore media_rw_data_file grant. Keeping it out of the base rule
stops an external-storage write from leaking into release.

No soter_server SELinux type exists; platform_app is the soterserver
app domain. Runtime policy (KSU/magiskpolicy) grants this past the
compile-time neverallow; on-device avc verification is 10.V.

Checkpoint 10.C.
2026-06-26 15:54:19 +01:00
Enginex0 7de3ef1ba3 feat(soter): supervise on-demand injection
soterserver is Intent-bound and on-demand, so the one-shot pidof +
inject the always-alive keystore path uses never lands. Bind the
service to both poke its start and obtain the ISoterService binder
(the identity the native MITM registry keys on), inject
libTEESimulator.so, confirm the landing via the 0xdeadbeef handshake,
then register the forge.

Re-binds and re-injects on every respawn instead of exiting like the
keystore one-shot. Runs on its own HandlerThread so it never stalls
keystore init or the daemon looper; lifecycle logging is debug-gated.
Not yet wired into App.kt (that is 10.W).

Checkpoint 10.B.
2026-06-26 15:31:11 +01:00
Enginex0 9a8c06bca6 feat(soter): forge ISoterService Layer-A replies
Forge healthy com.tencent.soter.soterserver.ISoterService AIDL replies
from inside the injected soterserver process so the SOTER capability
probe reads available=true / damaged=false on a bootloader-unlocked
device whose SOTER TA can no longer use its factory ATTK.

Hardcodes the 13 obfuscation-stable transaction codes (R8 stripped the
Stub) and fills the 5 parcelable payloads with detector-valid values:
the export blob is a little-endian length-framed SOTER pubkey envelope
that the SDK's retrieveJsonFromExportedData parses to a non-null model.
Every request and forged reply is captured to per-UID NDJSON,
debug-gated.

Checkpoints 10.A (forge) and 10.M (reply marshalling).
2026-06-26 15:31:11 +01:00
Enginex0 4e61f1523d chore(release): sync update.json to v6.0.1-292 2026-06-26 11:40:57 +01:00
Enginex0 ab40677dac fix(attestation): per-security-level RSA/EC probe
c2552ba gated AUTO forge on isRsaAttestable, but the probe minted its
key without setIsStrongBoxBacked, so it measured only the TEE. A device
whose TEE provisions an RSA attestation key while its StrongBox cannot
(OnePlus PJZ110, Android 16) had StrongBox RSA requests PATCHed against
the real keystore, which has no StrongBox attestation key and returns
-74 (ATTESTATION_KEYS_NOT_PROVISIONED).

Probe each (algorithm, security-level) pair independently and have
dispatch consult the verdict matching the request's security level, for
both RSA and EC. StrongBox-incapable requests forge; capable ones keep
the genuine TEE chain via PATCH.

Refs #37
2026-06-26 11:38:39 +01:00
Enginex0 07dde7c756 fix(build): revert NDK to 27.3.13750724
NDK 29's Clang-21 libc++ makes libTEESimulator.so reference
__cxa_init_primary_exception, which the platform libc++ inside
keystore2 does not export. The injected lib resolves its C++ ABI
symbols against the target process at dlopen time, so injection
failed with "cannot locate symbol" on every retry. keystore2 ran
unhooked and every app saw the raw TEE chain (KeyAttestation showed
the real unlocked bootloader; per-UID NDJSON never created).

libc++ began emitting that symbol from std::exception_ptr
construction in Clang 19, so 27.3 (Clang 18) is the last toolchain
that builds a loadable lib. The exception_ptr machinery enters via
the AOSP/binder stub headers, not module code.

Verified on device: lib injects (3 maps in keystore2), KeyAttestation
generateKey -> PATCH with deviceLocked=true, verifiedBootState=Verified.
2026-06-25 17:14:15 +01:00
Enginex0 718c80a68b chore: bump NDK to 29.0.14206865
NDK 27.3.13750724's sysroot is corrupted (missing sys/cdefs.h and the
aarch64 asm headers), breaking the native-certgen build. NDK 29 is
installed and healthy, so move the toolchain to it.
2026-06-25 03:05:39 +01:00
Enginex0 9ffa00332b docs(readme): rewrite in plain language
Define load-bearing terms on first use, cut marketing phrasing, and
remove every em dash. Add an ASCII flow diagram to "How it works".
Trim credits to JingMatrix, ring, fatalcoder524, and huguangares.
Reword the tagline and update the build requirement to NDK 29.
2026-06-25 03:05:39 +01:00
Enginex0 5c36288461 refactor(certgen): drop dead native logging code
Remove orphaned native logging that nothing reached:

- The /sdcard/Download zip dump (NativeCertGen.dump and the dumpLogs
  JNI, dump_logs_inner, dump.rs, pub mod dump), superseded by the
  diag.sh export.
- The verbose-marker helpers (sysfs.rs, pub mod sysfs); the manual
  .verbose toggle still works via mod.rs::init's inline check.

Drop the now-unused direct deps zip and libc and the orphaned jstring
import. cargo ndk build is warning-clean.
2026-06-25 02:31:35 +01:00
Enginex0 d0139003a6 feat(logging): per-UID NDJSON on external storage
Move debug diagnostics off /data/local/tmp/teesim to
/data/media/0/TEESimulator (visible at /sdcard/TEESimulator), so users
can pull them without a root explorer. The logging code runs in the
keystore SELinux domain, so a debug-only media_rw_data_file grant plus
a debug-only diag.sh fragment gate the plane: diag.sh's presence is the
signal service.sh (setup) and action.sh (export) test. customize.sh
extracts diag.sh on debug installs or sweeps the dir on release, since
the release keystore domain cannot remove it itself.

Replace the per-call .bin parcel dumps (a fresh undecodable file per
generateKey) with one NDJSON record per event on the UID's own file,
carrying decoded fields plus the raw parcel as base64 for the offline
parsers. computeIfAbsent makes per-UID writer creation atomic.
2026-06-25 02:31:35 +01:00
Enginex0 28f48f2e01 fix(attestation): harden RSA capability probe
The RSA capability probe cached any first-call failure for the process
lifetime via by-lazy plus a catch-all false, so a transient keystore
hiccup could freeze the device into forging an attestation the real TEE
could serve, silently re-creating the issue #37 regression with no
self-heal.

Memoize only a definitive verdict: a successful probe, or a permanent
KeyStoreException per the framework's own isTransientFailure(). Transient
and non-keystore failures fail open, reporting the device attestable so
dispatch PATCHes the genuine chain, and re-probe on the next read. An
AtomicBoolean guard keeps at most one probe in flight with no lock held
across the keygen. Delete the probe key best-effort in finally.

Refs #37
2026-06-25 02:19:51 +01:00
Enginex0 c2552ba164 fix(dispatch): gate AUTO forge on RSA capability
v282 forged every AUTO attestation that carried a challenge, so a
strict app that validates attestation server-side, such as Kraken,
rejected the software-forged chain where it accepted a patched
real-TEE chain, breaking login. The trigger was algorithm-blind: the
AUTO capability probe only mints an EC key, so it could not tell an
EC-capable TEE from one that cannot provision RSA attestation keys.

Add an isRsaAttestable probe and forge AUTO attestation only for RSA
the real TEE cannot provision. EC and RSA-capable devices keep their
genuine TEE chain via PATCH, restoring the v280 behavior strict apps
depend on while preserving the RSA red fix on incapable devices.

Refs #37
2026-06-25 00:58:13 +01:00
Enginex0 e2dc7aa210 fix(keystore): vendor-gate real-op updateAad
OperationInterceptor rejected non-AEAD updateAad with INVALID_TAG
unconditionally, while SoftwareOperation's VendorQuirks gate returns
success on Samsung and Xiaomi-MTK. On those devices the real-key and
forged-key paths disagreed, and the genuine TEE accepts the call, so
the inconsistency fingerprinted the injection layer through Duck
Detector's operation error-path probe.

Apply the same gate to the real-op path: a void success reply where
nonAeadUpdateAadSucceeds(), else the INVALID_TAG reply. Promote
VendorQuirks to internal so both paths share one decision.

Refs #36
2026-06-25 00:58:00 +01:00
Enginex0 9193b79a6a chore(release): sync update.json to v6.0.1-282 2026-06-19 16:45:01 +01:00
Enginex0 b2d84b4661 fix(dispatch): forge AUTO attestation requests
Plain attestation (Use-attest-key OFF, challenge present) on an AUTO-mode target was routed to PATCH, deferring to the real TEE. The AUTO probe (checkTeeFunctionality) only proves the device can mint one EC key, so devices that cannot attest RSA or device-ID, or whose patched chain fails RSA verify, surfaced as KeyAttestation reds (ATTESTATION_KEYS_NOT_PROVISIONED/-49, BLOCK_TYPE_IS_NOT_01).

Forge these from the keybox instead, gated on isAutoMode + attestationChallenge, matching the attest-key-ON path that already yields a green Google-rooted chain. Non-attestation keys still pass through to real hardware, so KeyDetector hardware-backed checks are unaffected.

Verified offline against real FORGE captures with scripts/keyatt_conformance.py: uid10389/uid10154 chains are GREEN and the root SPKI byte-matches GOOGLE_ROOT_PUBLIC_KEY.
2026-06-19 14:22:56 +01:00
Enginex0 af3c27451d chore(release): sync update.json to v6.0.1-280 2026-06-19 01:46:06 +01:00
Enginex0 0586db18d9 fix(keystore): reorder auths off genmode sentinel
Duck's generate-mode parcel fingerprint reads the reply with a flat
12-byte stride and flags the sentinel tuple the device's native
ALGORITHM-first auth order lands on, at count 12 and 13. Real A16
hardware trips it too, so faithful mirroring stays flagged.

Add InterceptorUtils.normalizeAuthorizationLayout: marshal the auth
array, run Duck's exact predicate, and only when it would match,
reorder by a deterministic minimal move until it clears. Order
carries no keystore semantics and the cert chain is a separate
field, so count, values, security levels, and attestation are all
preserved. Applied on both the patch and forge reply paths; it keys
on the byte condition, never on any package.
2026-06-19 01:44:04 +01:00
Enginex0 ec5df08574 chore(release): sync update.json to v6.0.1-277
The packaging task rewrites update.json to gitCommitCount on every
build; this records the v6.0.1-277 artifacts and supersedes the manual
271 bump made before the build counter was understood.
2026-06-17 20:34:17 +01:00
Enginex0 d65526aaa5 chore(release): bump module to v6.0.1-271 2026-06-17 20:28:28 +01:00
Enginex0 ca226bd7de chore(pki): log attest-sign signer vs leaf algo
At the attest-key signing instant, log signer key algorithm, served
leaf algorithm, chain depth, and issuer (debug, targeted uid) so an EC
attest-key run pins the mismatched edge of the two-root chain.

Bucket a16-ec-attestkey-red, task T01.
2026-06-17 20:28:28 +01:00
Enginex0 e5d24c3907 fix(keystore): drop algo-split key on restore
A persisted record whose private-key algorithm disagrees with its
served leaf public key (EC private under an RSA leaf) makes every
signature fail as DATA_TOO_LARGE_FOR_MODULUS: the A16 EC two-root.
Require the two to match on restore and drop the record otherwise, so
the next generateKey rebirths a coherent key. Awaiting an EC device
capture to confirm the red originates from a restored record.

Bucket a16-ec-attestkey-red, task T01.
2026-06-17 20:28:28 +01:00
Enginex0 a54e8a5315 fix(attestation): cache AOSP attestVersion on A16
The A16 test device's KeyMint HAL reports attestVersion 100 (KeyMint
1.0); the lazy cache stored that and it shadowed the map's BAKLAVA->400
in getAttestVersion. fetchAttestationData now caches
AndroidDeviceUtils.aospAttestVersion (attestVersionMap[SDK_INT]),
falling back to the parsed device value only when the SDK is unmapped,
so the forge presents the AOSP-correct 400. attestVersionMap unchanged.

Bucket a16-ec-attestkey-red, task T02.
2026-06-17 20:28:27 +01:00
Enginex0 0b67700763 fix(keystore): evict stale cached key on regen
keystore2 replaces a key when generateKey reuses an alias. Mirror that:
drop any cached chain for the alias so a later getKeyEntry serves the
current key, not a stale FORGE from a prior generation (an
attest-key-mode leaf cached, then re-generated without an attest key).
2026-06-17 14:54:22 +01:00
Enginex0 fbee59688d feat(logging): log served and verified chains
Log each cert chain the module hands the app so an attestation
verification failure is provable from the per-UID log, not inferred.

- formatChainVerification verifies every edge of a produced chain and
  reports RSA signature-vs-modulus sizes (the DATA_TOO_LARGE condition).
- formatChainKeys and logServedChain record the chain served back on
  each getKeyEntry, keyed by alias, since the app reassembles its chain
  from the leaf alias plus the attest-key alias.

Debug-build only, gated by isUidLogged.
2026-06-17 14:54:22 +01:00
Enginex0 2e5155fb75 fix(keystore): serve getKeyEntry for skipped UIDs
Un-targeted privileged callers (e.g. KeyAttestation via Shizuku) have
their attest-key and device-id generateKey requests force-forged, but
getKeyEntry blanket-skipped those UIDs before the owned-key lookup, so
the framework's attestKeyAlias resolution in
AndroidKeyStoreKeyPairGeneratorSpi.initialize() returned KEY_NOT_FOUND
and surfaced as "Invalid attestKeyAlias".

Let getKeyEntry reach the owned-key lookup for skipped UIDs; a non-owned
key still skips post-processing so an un-targeted app's real key is
never patched.
2026-06-17 11:34:22 +01:00
Enginex0 ce22327147 refactor(keystore): strip unique-id at parse time
Decide the effective generateKey params once via .let when the caller lacks gen_unique_id / REQUEST_UNIQUE_ID_ATTESTATION, instead of mutating var params/parsedParams deep in handleGenerateKey and re-parsing KeyMintAttestation a second time. isAttestKeyRequest now derives from the final parsedParams, closing the staleness flagged in PR #27 review r3308356496.

Behavior is unchanged: no gate between the parse and the old strip site reads INCLUDE_UNIQUE_ID, and the && short-circuits so the permission lookups still run only when the tag is present.
2026-06-17 10:38:32 +01:00
Enginex0 f69fee21a8 chore(debug): co-locate per-UID logs with dumps
Per-UID dossier logs moved from /data/adb/tricky_store/logs to
/data/local/tmp/teesim, beside the .bin dumps, so the whole debug
trail comes off the device in one `adb pull /data/local/tmp/teesim/`
with no root and no /sdcard hop.

The release purge now sweeps .log/.log.1 from the diagnostic dir and
keeps legacy sweeps of both old locations (loose /data/local/tmp and
the module config dir) so upgrading to a release build leaves nothing
behind. Repoint package.sh --clear-logs to the new path.
2026-06-04 21:15:34 +01:00
Enginex0 9561f7d9c0 feat(logging): per-UID forge diagnostics
The attestation dossier only fired on a successfully produced chain, so
the StrongBox/BHIM failures left nothing on the per-UID plane and had to
be reconstructed from marshalled .bin dumps offline. Add three records,
all debug- and target-gated like the existing dossier:

- keybox-pick: which keybox signs the forge (requested algo, exact match
  vs EC fail-safe, signer subject) -- makes an EC-only-keybox RSA
  fallback visible instead of silent.
- forge-fail: emit the failure reason on the per-UID plane when a forge
  throws (e.g. ATTESTATION_KEYS_NOT_PROVISIONED), paired with dispatch.
- auth-shape: the emitted authorization list (count, ordered tags,
  per-auth securityLevel) -- the surface the duck generate-mode parcel
  fingerprint stride-walks, readable without offline decode.
2026-06-04 20:39:49 +01:00
Enginex0 79e4fe905e chore(debug): per-request gen-mode result dumps
The asymmetric and symmetric result dumps wrote a single fixed
filename (teesim-gen-mode-asym.bin / -sym.bin), so each forge
overwrote the previous one and only the final reply survived a
capture -- which is why a tester's zip showed one app's good chain
while the failing chain was already gone.

Tag both dumps with uid and tx, matching the request dumps, so every
forged chain is retained and correlatable with its request.
2026-06-04 20:00:35 +01:00
Enginex0 5bbb0bffe0 fix(pki): root RSA forge on EC-only keybox
The forge keybox selector matched the requested algorithm exactly and
threw -75 ATTESTATION_KEYS_NOT_PROVISIONED on a miss, while the patch
path already falls back to any usable key (EC preferred). An RSA
ATTEST_KEY request on an EC-only keybox therefore never rooted: the
caller's attest-key chain could not reach the Google root and verifiers
reported "unknown certificate".

Fall back to getAnyAttestationKey when no algorithm-matching keybox
exists. An EC attestation key validly ECDSA-signs an RSA-subject leaf,
so the EC keybox roots the RSA forge. No-op when the keybox is dual.
2026-06-04 20:00:08 +01:00
Enginex0 59a2357312 fix(keystore): repair attestation generation gaps
Two gaps in attestation generation surfaced by a tester's Key Attestation
app runs on build 259.

Device-ID attestation (IMEI/serial) via Shizuku arrives as a privileged
UID (shell/system) absent from target.txt, so it was skipped and the real
TEE rejected it with CANNOT_ATTEST_IDS (-66). Stop skipping requests that
carry device-ID tags, and force the forge path for them (the real TEE
cannot attest IDs, so there is no chain to patch). The permission gate
still rejects ordinary apps, mirroring a real device.

'Use attest key' produced WRONG_PUBLIC_KEY_TYPE: a reused persistent attest
key is designated by KEY_ID with a null alias, so the lookup missed and the
leaf was silently re-rooted under the keybox, double-rooting the chain the
caller assembles. Resolve the attest key by KEY_ID as well as alias, and
refuse to emit a leaf rather than fall back to the keybox when a designated
attest key cannot be resolved.
2026-06-04 19:07:42 +01:00
Enginex0 7f33bd737d chore(debug): move diagnostic dumps to subfolder
The debug-only .bin dumps wrote loose into /data/local/tmp, cluttering a
directory shared with every other tool. Route both writers through a
shared DIAGNOSTIC_DIR (/data/local/tmp/teesim) with mkdir-on-write, and
extend the release purge to sweep the new folder plus any loose leftovers
from older debug installs.
2026-06-04 19:05:26 +01:00
Enginex0 b90af0b716 fix(attestation): patch RSA leaf under EC keybox
An EC-only Google keybox could not re-root an RSA-keyed attestation: the keybox lookup asked for an RSA signing key, got null, and threw. patchCertificateChain caught the throw and returned the original chain untouched, leaking the device's real unlocked Root of Trust for RSA keys while EC keys patched correctly.

Fall back to any available keybox key (preferring EC) and sign the patched leaf with the keybox key's own algorithm rather than the original leaf's. A leaf's signature algorithm is independent of its subject key, so the RSA leaf re-signs validly under the EC keybox and the chain still roots to the Google keybox with the forged, locked RoT.

Add KeyBoxManager.getAnyAttestationKey; drop the now-dead sigAlgName param and normalizeSignatureAlgorithm helper.
2026-06-04 17:43:19 +01:00
Enginex0 e5483afc70 feat(logging): UID-keyed attestation dossier
Add a debug-only per-UID diagnostic plane gated on BuildConfig.DEBUG.
For apps in target.txt it records every keystore interaction and the
forged attestation it produces to teesim-uid-<uid>.log: decoded cert
chain (FORGE and PATCH paths), key params, keybox, and prop sources,
with the calling UID threaded through the C++ binder hook and Rust
certgen. Release builds stay silent (R8 strips the write plane and the
runtime gate short-circuits). Adds --clear-logs to package.sh.
2026-06-04 15:53:06 +01:00
Enginex0 f826312fc4 fix(pki): log keybox serial on every fetch
The serial log added previously lived in parseKeysFromXml, which
getAttestationKey runs only on a cache miss -- so it emitted at most
once per boot and scrolled off the buffer before it could be read.
Move it into getAttestationKey so the keybox attestation cert serials
are logged on every fetch, on the live native cert-gen path.

Verified on device: "Using RSA keybox keybox.xml; attestation cert
serials (hex): ..." now prints on each forge.
2026-06-04 13:58:43 +01:00
Enginex0 40519b94ea feat(keystore): vendor-gate non-AEAD updateAad
Mirror Duck Detector's OperationErrorPathProbe: real Samsung and
Xiaomi-MTK TEEs return success for updateAad on a non-AEAD operation,
while other vendors reject it with INVALID_TAG. The shim reads the same
Build identity the probe reads and answers accordingly, in both the
CryptoPrimitive default (sign/verify) and CipherPrimitive paths.

Forward hardening for the #28 detector: the prior unconditional
ServiceSpecificException throw already passes the probe on every vendor,
so this guards against stricter future probes rather than fixing a
current failure.
2026-06-04 13:14:43 +01:00
Enginex0 37e9d007de feat(pki): log keybox attestation cert serials
Emit each loaded keybox's certificate-chain serials (lowercase hex) at
parse time. A revoked or leaked keybox is then visible from logcat
alone, since Google's CRL and Duck Detector's "mass abuse" check both
match by certificate serial.

Diagnostic aid for the revoked-keybox danger in #28; the actual fix is
rotating to a non-revoked keybox.
2026-06-04 13:00:38 +01:00
Enginex0 5c300ff47b style: apply ktfmt formatting pass
Run the project ktfmt kotlinLangStyle formatter over app/ to bring the
tree into canonical form. Formatting only -- no logic change.

Verified semantic-neutral: ktfmt(working tree) is byte-identical to
ktfmt(committed HEAD) across all of app/src, so the prior uncommitted
WIP carried zero behavioral change.
2026-06-04 12:55:07 +01:00
Enginex0 5bd563d8db feat(keystore): probe trail for generateKey
Add a debug-only structured log line per generateKey, emitted at every
outcome (SKIP, the four REJECTs, FORWARD_HAL, FORGE, PATCH, PASSTHROUGH).
Each line carries the resolving package (via the cached
ConfigurationManager.getPackagesForUid), alias, algorithm, StrongBox
flag, the attestation tag set, and the verdict, so triaging "app X
broke" becomes a single logcat grep instead of decoding parcel dumps.

Gated on SystemLogger.isDebugBuild: release builds return before any
string is built, keeping the path silent and artifact-free. Logs to
logcat only, never to files.
2026-05-30 14:54:57 +01:00
Enginex0 254fb0f0a9 fix(keystore): forge device-property attestation
The canAttestDeviceIds gate (3575c74) probed the live TEE to decide
whether to honor device-property/ID attestation. That probe is gated on
isTeeFunctional, which is false on every dead-TEE device the module
serves, so GENERATE mode rejected all such requests with
CANNOT_ATTEST_IDS, including GMS Play Integrity's hardware path, which
broke BHIM and any UPI/Play-Integrity-gated app.

Remove the gate. Device-property attestation (BRAND/MODEL/...) now forges
unconditionally, as genuine devices universally attest it. Device-ID
attestation stays governed by the pre-existing caller-permission check,
the real KeyMint rule: privileged callers get it, ordinary apps do not.
Drop the now-unused DeviceAttestationService.canAttestDeviceIds probe.
2026-05-30 14:51:48 +01:00
Enginex0 8d195010fd chore(release): publish v6.0.1-251
Grant-plane coherence (Android 16 incl.), Google Wallet + fingerprint
compatibility (PR #26/#27), and removal of the in-module PIF/bulletin
resolvers. Frozen at gitCommitCount 251.
2026-05-30 13:45:55 +01:00
Enginex0 d6ddc925ce fix(action): sample getevent in 1s bursts
The piped getevent stream block-buffered on Magisk's BusyBox ash and
missed a single vol-key press before the timeout. Sample getevent in 1s
timeout bursts in a deadline loop instead.
2026-05-30 13:42:31 +01:00
Enginex0 a67efa4102 refactor(app): drop PIF resolvers + dump purge
Remove PatchLevelManager (auto-resolved the security-patch date from an
installed PlayIntegrityFix module into security_patch.txt, with a
FileObserver hot-reload) and BulletinPoller (scheduled bulletin refresh),
and their App.kt init/start calls.

Add purgeDebugDiagnostics(): release builds sweep stale teesim-*.bin
dumps from /data/local/tmp at boot so a prior debug install can't leave a
detection artifact. Stabilize the InterceptorUtils diagnostic dump path
to a single file instead of one per call.
2026-05-30 13:42:31 +01:00
Enginex0 00e8cc36fe chore(release): bump version to v6.0.1 2026-05-30 13:42:31 +01:00
Enginex0 ca857c30ac fix(keystore): grant plane serves patch-mode keys
Domain.GRANT readback only recognized synthetic keys (generatedKeys), so
patch-mode keys (real TEE key whose attestation we patch on read, cached
in teeResponses) fell through to the real keystore2 unpatched. Android 16
made KeyStoreManager.grantKeyAccess a public API, so the owner read
returned our patched chain while the grant read returned the raw real
chain -> duck SELF_/ISOLATED_CHAIN_SPLIT.

Gate grant()/ungrant()/resolveGrant on ownsKeyResponse() (synthetic OR
patch-mode) so every access plane serves the same cached KeyEntryResponse.
Pre-36 still answers PERMISSION_DENIED; no behavior change on Android 15.
2026-05-30 13:42:31 +01:00
Enginex0 a58c4798c3 fix(keystore): mirror TEE device-ID capability
generateKey synthesized device-property attestation unconditionally:
the BRAND/DEVICE/PRODUCT/MANUFACTURER/MODEL tags that
setDevicePropertiesAttestationIncluded emits. A forged key thus
succeeded where real silicon returns CANNOT_ATTEST_IDS. Hardware that
never provisioned device IDs cannot attest them, so forging them is an
over-capability tell: a genuine device of the same class fails the
identical request.

Add DeviceAttestationService.canAttestDeviceIds, a lazy probe that asks
the real TEE to attest device properties once and caches the verdict.
It is gated behind isTeeFunctional, so a silent or dead TEE
short-circuits to "cannot attest" without a second doomed probe.

handleGenerateKey now returns KEYMINT_CANNOT_ATTEST_IDS for any
device-ID or device-property attestation the real TEE cannot satisfy,
uniformly across AUTO, PATCH, and GENERATE. Basic attestation carries
none of these tags and is untouched.

Verified on 23106RN0DA: kknd under GENERATE now WARNs, matching a stock
locked-bootloader device. Principle: forge health, mirror capability.
2026-05-30 13:42:31 +01:00
Enginex0 8cebcf14a8 feat(keystore): mirror lifecycle via maintenance
Hook the keystore2 daemon's android.security.maintenance binder (hosted
by the same process, reached by the already-injected native hook) so
synthetic key state follows real key-lifecycle events:

- clearNamespace(APP) purges synthetic keys for the uid and their grants.
- deleteAllKeys() clears all synthetic keys and grants.
- migrateKeyNamespace() re-keys the synthetic entry, preserving material,
  chain, and grants.

Pure side-effect hook: every handled transaction mutates only our own
synthetic state, then returns ContinueAndSkipPost so the real keystore2
still performs the real operation. Unhandled codes pass through, so real
key lifecycle is never disturbed. Pre-empts delete-then-read and
clearNamespace coherence probes (Phase 9 Change 4).

Adds a minimal IKeystoreMaintenance compile stub for the descriptor;
transaction codes resolve reflectively on-device.

Refs Phase 9 .omc/plans/tee-fingerprint-phase-9-grant-plane-coherence.md
2026-05-30 13:42:31 +01:00
Enginex0 ab58b11e4d fix(keystore): evict stale chains on key mutation
Two synthetic-cache staleness gaps let getKeyEntry replay a pre-mutation
attestation:

- importKey now drops teeResponses and patchedChains for the alias, not
  only generatedKeys. A successful import replaces the real key, so the
  retained patched chain was a tell (duck STALE_GENERATED_AFTER_IMPORT).
- After updateSubcomponent re-keys a patched chain, getKeyEntry on a
  patch-mode key evicts the cached TEE response by KEY_ID or APP so the
  read falls through to the updated real keystore2
  (duck STALE_TEE_RESPONSE_AFTER_KEY_ID_UPDATE).

Refs Phase 9 .omc/plans/tee-fingerprint-phase-9-grant-plane-coherence.md
2026-05-30 13:42:31 +01:00
Enginex0 bed32b7454 fix(keystore): gate synthetic grant to Android 16
KeyStoreManager.grantKeyAccess() became a public app API only in
Android 16 (API 36). Before that grant was a hidden API and SELinux
denied untrusted_app, so a real Android 15 device answers a private-
binder grant() with PERMISSION_DENIED.

The virtualized grant plane (5579b16) issued a synthetic grant on every
SDK, exposing a capability a real Android 15 app does not have. Gate
grant() and ungrant() on SDK_INT >= 36: pre-36 returns PERMISSION_DENIED
for synthetic keys (matching the real device, which Duck then marks
UNAVAILABLE rather than a tell); 36+ keeps the coherent virtualized
grant.

On-device 23106RN0DA (SDK 35): all four grant rows report UNAVAILABLE,
not RED; tamper score unaffected.

Refs Phase 9 .omc/plans/tee-fingerprint-phase-9-grant-plane-coherence.md
2026-05-30 13:42:31 +01:00
Enginex0 43e948efb3 fix(shim): match real gen-mode auth shape
Real keystore2 (captured on-device, MediaTek SDK 35) emits 11 EC
authorizations in the generateKey KeyMetadata: no VENDOR_PATCHLEVEL or
BOOT_PATCHLEVEL, and USER_ID tagged at SecurityLevel.SOFTWARE. The shim
emitted 13 with both patchlevels and USER_ID at KEYSTORE.

Duck-Detector's generate-mode parcel fingerprint stride-walks the reply
and keys on the 13-entry layout, so the two extra entries were the tell.
Drop both patchlevels from the authorization list (they remain in the
attestation extension via AttestationBuilder, so attestation content is
unchanged) and move USER_ID to SOFTWARE to mirror the captured device.

On-device 23106RN0DA: generate-mode fingerprint signal gone (0 local),
TEE tamper score 18 -> 8.

Refs Phase 7 .omc/plans/tee-fingerprint-phase-7-generate-mode-coherence.md
2026-05-30 13:42:31 +01:00
Enginex0 cf4fb127f2 feat(keystore): virtualize grant plane
Duck-Detector's grant-domain probes generate an attested key, then
reach it through a second access plane -- IKeystoreService.grant() then
getKeyEntry(Domain.GRANT, grantId) -- and compare the certificate
chains. We synthesized the owner key but never virtualized the GRANT
plane, so grant reads fell through to the real keystore2, which has no
record of the synthetic key. That single fall-through produced six RED
rows.

Virtualize the plane so every access path returns the same synthesized
KeyEntryResponse:

- SoftwareGrant state model in the shim companion: issue/resolve/
  revoke/purge, caller-bound and access-vector-aware (Change 1).
- grant/ungrant/getKeyEntry(GRANT) handlers in Keystore2Interceptor.
  resolveGrant() enforces caller-binding (non-grantee -> KEY_NOT_FOUND,
  PR #57 probe 4) and the GET_INFO=0x4 access-vector gate (missing ->
  PERMISSION_DENIED, PR #57 probe 3); a valid read returns the owner's
  exact KeyEntryResponse for a coherent chain (Change 2).
- Purge grants on key teardown and clearAll, so grants die with the
  key and re-key orphans them -- matching real keystore2 (Change 3).

The Domain.GRANT read is resolved before the package-scoped
shouldSkipUid filter: isolated grantees (bindIsolatedService) have no
package mapping and would otherwise be dropped to the real keystore2,
leaving three grant rows Unavailable. Caller-binding in resolveGrant()
is the real access gate, mirroring keystore2's grantee+id row keying.

Verified on-device (generate mode, build #237): all four grant rows
clean, TEE tamper score 28 -> 18, zero adjacent regression.

Refs Phase 9 .omc/plans/tee-fingerprint-phase-9-grant-plane-coherence.md
2026-05-30 13:42:31 +01:00
Enginex0andGitHub 8dd644a2e5 Merge pull request #27 from Andrea-lyz/pr/fix-gpay-include-unique-id
fix(interception): strip INCLUDE_UNIQUE_ID instead of rejecting when permission missing
2026-05-30 11:25:44 +01:00
Enginex0andGitHub a0d7418504 Merge pull request #26 from Andrea-lyz/fix/createOperation-key-id-not-found
fix(intercept): use ContinueAndSkipPost for KEY_ID createOperation NOT FOUND
2026-05-30 11:25:40 +01:00
Andrea-lyz ebe2040b2b fix(interception): strip INCLUDE_UNIQUE_ID instead of rejecting on missing permission
The INCLUDE_UNIQUE_ID gate in handleGenerateKey (introduced as part of the
PR157 AOSP-compliance work) returns PERMISSION_DENIED when the caller
holds neither SELinux gen_unique_id nor REQUEST_UNIQUE_ID_ATTESTATION.

This breaks Google Wallet card binding on real devices: Wallet's
generateKey carries INCLUDE_UNIQUE_ID without holding the permission, so
its attestation key request is rejected and Wallet surfaces the failure
as "this phone does not meet the security requirements for Google
Wallet". Symptoms reported by users: clearing GMS data only helps for a
few seconds before the state regresses; no card can be added.

Naive removal of the gate is not safe: AttestationBuilder honours
`includeUniqueId == true` by computing an HMAC-SHA256 unique_id and
embedding it in the attestation extension. With the gate gone, GMS
attestation flows that include the tag end up with a unique_id in the
extension that Play Integrity flags as inconsistent for the caller,
turning all three integrity verdicts red.

The fix here splits the difference: when the permission check fails,
silently strip the INCLUDE_UNIQUE_ID tag from the KeyParameter array
(and re-parse `parsedParams`) instead of rejecting the request. The key
generates normally, AttestationBuilder takes the
`else { ByteArray(0) }` branch, and the resulting attestation simply
omits the unique_id field, matching pre-PR157 behaviour, where the
tag effectively had no effect.

Verified on a device that previously failed Wallet binding on the
PR157 baseline:
  - Play Integrity: BASIC + DEVICE + STRONG all pass.
  - Google Wallet: card binding completes successfully.
  - Calls that DO hold the permission are unaffected (still emit
    unique_id as before).

The rest of the PR157 compliance work (CALLER_NONCE handling,
AuthorizeCreate ordering, USAGE_COUNT_LIMIT counters, effectiveParams
merging) is preserved.
2026-05-27 06:07:18 +02:00
Andrea-lyz a731b33f09 fix(intercept): use ContinueAndSkipPost for KEY_ID createOperation NOT FOUND
When handleCreateOperation receives a Domain.KEY_ID request for a key
not in our generatedKeys cache, it correctly forwards to the real HAL.
However, it previously returned TransactionResult.Continue, which lets
the post-handler run. The post-handler unconditionally registers an
OperationInterceptor on the IKeystoreOperation binder returned by real
keystore2. This interceptor then interferes with the caller's
operation (intercepting finish/abort/updateAad calls).

On devices where vendor daemons (e.g. fingerprint calibration) use
Domain.KEY_ID for their hardware-backed keys, this causes operation
failures, the OperationInterceptor races with the immediate
finish/abort call and may reject updateAad with INVALID_TAG if the
operation is not GCM mode.

Fix: return ContinueAndSkipPost (matching the existing Domain.APP NOT
FOUND path) so the post-handler never runs for operations on keys we
don't own. When the key IS in generatedKeys, we never reach this
return, we proceed to create a SoftwareOperation directly in
pre-transact.

Symptom: OnePlus engineering mode ultrasonic fingerprint calibration
hash retrieval fails with module enabled, works with module disabled.

Signed-off-by: Andrea-lyz <Andrea-lyz@users.noreply.github.com>
2026-05-26 22:53:28 +02:00
Enginex0 67c283b75b chore(release): publish v6.0.0-235 2026-05-20 07:05:02 +01:00
Enginex0 40f652f725 chore: bump versionCode to 235 2026-05-20 06:54:35 +01:00
Enginex0 25fd28a32b chore: bump versionCode to 233 2026-05-20 06:52:11 +01:00
Enginex0 108e027a98 fix: reorder hal-enforced auths to evade duck detector
Duck-Detector's generate-mode parser walks the reply parcel at
12-byte strides and matches (secLevel=256, tag=1, unionTag=32) at
slot[count-1]. Those bytes are actually KEY_SIZE.value=256 followed
by the next Authorization's presence flag and size header, an
emergent fingerprint from misaligned parsing, not a fake value.

Reorder toAuthorizations so PURPOSE/ALGORITHM/KEY_SIZE come first,
mirroring AOSP keymint reference HAL output. KEY_SIZE moves from
auth#4 to auth#2, so its int payload no longer lands at byte 224.
Verified across 31 fresh duckdetector probes: zero matches (was
15/36 before).

Also add gen-mode wire-byte diagnostic to InterceptorUtils and the
generate-mode entry point, debug-gated, dumping request and reply
parcels to /data/local/tmp for offline decode.
2026-05-20 06:52:04 +01:00
Enginex0 be4c418893 feat(service): clear logd size props on boot
Spawn a backgrounded subshell from service.sh that polls
sys.boot_completed once per second and, once set, blanks the
persist.logd.size variants (root, crash, system, main).

Runs alongside the existing supervisor fork so daemon startup is
unaffected. Idempotent across reboots: even if a previous boot
already cleared the props, setting them to empty again is a no-op.
2026-05-20 04:13:57 +01:00
Enginex0 0829bc98ca fix: intercept createOperation under any caller UID
Mirror of the change applied to GENERATE_KEY in 76e0337. Drop the
outer shouldSkipUid gate so handleCreateOperation always runs.

handleCreateOperation already gates on the cache lookup
(KeyMintSecurityLevelInterceptor.kt:298-326): domain=APP looks up
KeyIdentifier(callingUid, alias) in generatedKeys and forwards to
HAL on miss; domain=KEY_ID looks up by nspace filtered by uid and
forwards on miss. The outer UID gate was redundant when the lookup
hits, and harmful when a key was generated under a non-target-list
UID (e.g. Shizuku-routed callers at shell 2000 / root 0 after
76e0337).

Without this change, a BYO key created under Shizuku-routed UID
that the app later attempts to use (signing operation under the
same Shizuku-routed UID) would be forwarded to real HAL, which has
no record of our software key, producing a silent operation
failure instead of the simulator handling the sign internally.

Surfaced by adversarial audit. CREATE_OPERATION no longer needs the
outer gate because handleCreateOperation's own cache-or-forward
logic is the correct gate.
2026-05-20 03:59:10 +01:00
Enginex0 49763cdca7 fix: harden software gen symmetric branch errors
doSoftwareKeyGen's symmetric branch (AES/HMAC/3DES) previously did
two things wrong:

1. It accepted attestationKey != null silently and then ignored the
   reference: the symmetric path never consults attestationKey, so a
   caller asking for BYO on a symmetric key would have received a key
   with no certificate chain and no signal that BYO was dropped.
   Reject early with KEYMINT_INVALID_ARGUMENT matching real KeyMint
   HAL behavior.

2. The unsupported-algorithm path (e.g. 3DES, which has no JCA
   mapping in this branch) threw SECURE_HW_COMMUNICATION_FAILED
   (-49), the same numeric code InterceptorUtils labels as
   Error::Km(UNSUPPORTED_TAG). That confuses diagnosis since -49 is
   exactly the symptom the BYO fix series was just chasing. Use
   KEYMINT_INVALID_ARGUMENT (-38) which is what real KeyMint returns
   for unsupported algorithms in this context.

Surfaced by adversarial audit of f384871's broadened forceGenerate
gate, which now routes any attestationKey != null to software
unconditionally.
2026-05-20 03:58:53 +01:00
Enginex0 937058a7ff fix: return full keybox chain when BYO attest key misses
CertificateGenerator.generateCertificateChain selected
keybox.keyPair as fallback signer when getAttestationKeyInfo returned
null, but the chain assembly at line 115 still keyed on
"attestKeyAlias != null" and returned only listOf(leafCert). The
caller received a depth-1 chain signed by the keybox root with no
parent attached, structurally invalid.

Track whether the BYO lookup actually returned a key. On hit return
the depth-1 chain (caller holds the rest). On miss include
keybox.certificates so the chain is rooted.

Surfaced by adversarial audit of f384871, which broadened the
software-dispatch gate to all attestationKey != null requests.
Without this companion fix the miss path produces a malformed chain
where the previous code would have forwarded to HAL.
2026-05-20 03:58:20 +01:00
Enginex0 76e033700c fix: intercept BYO request under any caller UID
Shizuku-routed key attestation calls reach keystore2 with callingUid
set to shell (2000) or root (0) instead of the originating app's uid.
target.txt has no entry for those uids so shouldSkipUid returned true
in onPreTransact, and handleGenerateKey was never entered. The
transaction reached the real KeyMint HAL, which on older Keymaster 4.x
HALs rejects Tag::ATTEST_KEY with -49 UNSUPPORTED_TAG.

Move the shouldSkipUid gate from onPreTransact into handleGenerateKey
itself, evaluated after attestationKey and isAttestKeyRequest are
parsed. Skip only when the request is neither BYO nor attest-key-
purpose. CREATE_OPERATION keeps its outer-level gate (not part of the
BYO flow).

After this change, Shizuku-routed BYO requests enter dispatch, hit
forceGenerate=true via the prior simplification, and route to
doSoftwareKeyGen. Non-BYO non-attest calls from shell/root uids
still fall through to HAL unchanged.

D3 (option 2B) from /home/rootdev/.claude/plans/breezy-seeking-wozniak.md.
2026-05-20 03:47:16 +01:00
Enginex0 f384871f5a refactor: simplify forceGenerate dispatch gate
Any attest-key request or BYO request goes software unconditionally.
Drops the alias-Elvis lookup and the nspace fallback added by the
17c6312 -> eea6001 -> be04f16 revert/restore churn, both of which
silently missed when callers (Shizuku, post-restart sessions) did
not share an in-memory KeyIdentifier(uid, alias) with the attest
key originally registered in attestationKeys.

The (shouldPatch && isAttestKeyRequest) clause is subsumed by the
broader isAttestKeyRequest clause.

D2 from /home/rootdev/.claude/plans/breezy-seeking-wozniak.md.
2026-05-20 03:46:47 +01:00
Enginex0 1cea3ab8f1 refactor: remove AUTO TEE race dispatch
The race added in b3aa795 forwarded BYO attest-key requests to real
HAL on cache miss, producing -49 UNSUPPORTED_TAG on devices whose
persistent attest key alias survived in keystore2 across daemon
restarts but never re-entered our in-memory attestationKeys set.

AUTO resolution now relies solely on
ConfigurationManager.getPackageModeForUid (config/ConfigurationManager.kt:115),
which uses DeviceAttestationService.isTeeFunctional
(attestation/DeviceAttestationService.kt:67), a Kotlin by-lazy probe
evaluated once per daemon session. Matches upstream JingMatrix and
the v5.0-138 baseline.

Drops the AtomicReference<Boolean?> identity-equality compiler
warnings the race relied on.
2026-05-20 03:46:19 +01:00
Enginex0 a6452c3a26 fix(action): stream getevent for vol on Magisk
Users reported vol+ confirmation not registering on Magisk. The
prior backgrounded `getevent -qlc 1` + `kill -0` poll captured
the first kernel event of any type, then restarted on miss. With
six events per keypress (EV_MSC scan, EV_KEY DOWN, EV_SYN, then
release variants) and a 1s poll cadence, the 10s budget exhausts
before a DOWN sample lands. The chainfire note that piped getevent
breaks BusyBox grep applies under Magisk's ash standalone mode.

Replace with a single streaming `getevent -lq` matched inline
against `KEY_VOLUMEUP DOWN` / `KEY_VOLUMEDOWN DOWN`, wrapped in
`/system/bin/timeout 10`. Full paths bypass BusyBox aliasing.
2026-05-20 02:44:43 +01:00
Enginex0 da9a99bfbf chore(release): publish v6.0.0-224
Bump OTA pointer to v6.0.0-224 and document the 59-commit delta
from v6.0.0-162. Highlights:

- Duck Detector TamperScore-4 cleared on Xiaomi A16
- Self-sufficient spoofing (PatchLevelManager, BulletinPoller,
  PIF FileObserver, vbmeta complement props)
- Persistent symmetric key storage (PR #22)
- Action button hardened: vol+ confirm + 22-language i18n
- Build: JVM 21, gradle auto-rewrites update.json
2026-05-19 19:41:31 +01:00
Enginex0 b9defa70ae feat(action): i18n the clear-keys confirmation
Add action_i18n.sh with 22 language arms (ar, az, bn, de, el, es-ES,
fa, fr, id, it, ja, ko, pl, pt-BR, ru, th, tl, tr, uk, vi, zh-CN,
zh-TW) plus an English default. Detect device locale via
persist.sys.locale with ro.product.locale and ro.system.locale
fallbacks; split Chinese on Hans/Hant.

action.sh sources the helper at top and substitutes every echoed
literal with $(_msg <key>). The confirm() flow stays unchanged.

Wire action_i18n.sh into customize.sh's per-file extraction list so
the helper lands alongside action.sh in /data/adb/modules/tricky_store
at install time.

Pattern mirrors tricky-addon-enhanced/install_i18n.sh.
2026-05-19 18:43:46 +01:00
Enginex0 60fee55a0f feat(action): require vol+ confirm to clear keys
Users reported accidentally triggering the module action button from
the root manager UI, which wiped /data/adb/tricky_store/persistent_keys
and forced every attestation-dependent app to re-enroll.

Gate the destructive operation behind an explicit Vol+ confirmation
with a 10-second timeout. Vol- or timeout aborts and preserves keys.
Pattern mirrors tricky-addon-enhanced/install_func.sh choose_automation
(getevent -qlc 1 polled per second), but defaults to cancel on timeout
because the destructive default of the previous script is the
behavior we are correcting.
2026-05-19 18:31:16 +01:00
Enginex0 182450d3b4 fix(intercept): cache non-attested keys for parity
After PR #22 and the AUTO-mode extension started caching attested
generateKey responses in teeResponses, KEY_ID getKeyEntry lookups for
attested keys returned from memory in ~1ms while non-attested keys
forwarded to real keystore2 took ~1.5ms.
TimingSideChannelProbe measured the 1.55x ratio against its 1.1x
threshold and flagged the asymmetry.

Forward non-attested generateKey to real keystore2 with post-hook
enabled (Continue instead of ContinueAndSkipPost), and extend the
GENERATE_KEY post-hook to cache no-chain responses into teeResponses.
The KEY_ID lookup added in the previous commit now resolves both
paths from memory at matched latency. Cert-chain patching is skipped
for the no-chain branch because there is no attestation extension to
rewrite.
2026-05-19 18:01:06 +01:00
Enginex0 6d11362034 feat(intercept): resolve KEY_ID via teeResponses
PR #22's KEY_ID lookup at Keystore2Interceptor.onPreTransact only
scanned generatedKeys, which is populated exclusively by
doSoftwareKeyGen (GENERATE mode and the attest-key override path).
For AUTO packages on TEE-good devices the real TEE handles
generateKey and the response lands in teeResponses via the existing
GENERATE_KEY_TRANSACTION post-hook, so getKeyEntry(KEY_ID) by
TimingSideChannelProbe missed and the call leaked SSE.

Add findTeeResponseByKeyId companion helper that mirrors
findGeneratedKeyByKeyId's shape but scans teeResponses keyed by
response.metadata.key.nspace. Wire it as a fallback after the
existing PR #22 lookup. Behavior unchanged for GENERATE packages.
2026-05-19 17:45:54 +01:00
Enginex0 20a58f8a58 fix(config): isAutoMode reads raw package mode
getPackageModeForUid collapses AUTO -> PATCH/GENERATE at the call site
based on DeviceAttestationService.isTeeFunctional, so isAutoMode never
observed Mode.AUTO and always returned false. That made the
isAuto-gated dispatch arms in KeyMintSecurityLevelInterceptor and the
entire raceTeePatch path unreachable.

Iterate packageModes directly with the same priority order as
getPackageModeForUid: first non-null mode wins. AUTO returns true,
PATCH and GENERATE return false. Activates raceTeePatch for AUTO
packages on the first generateKey per security level.
2026-05-19 17:45:37 +01:00
Enginex0 5a12970426 Merge PR #22: persist symmetric keys + byte-identical metadata 2026-05-19 17:00:14 +01:00
Enginex0 cfbba4cddb chore(release): bump update.json to v6.0.0-211 2026-05-19 17:00:04 +01:00
Enginex0 c4ea3e0bb2 fix(intercept): normalize passthrough SSE shape
Real keystore2 SSE replies passed through SkipTransaction kept
the daemon's anyhow chain in the parcel string. Run those replies
through the same synthesizer used for module-generated SSEs so
wire shape stays consistent regardless of source.
2026-05-19 17:00:04 +01:00
Enginex0 ea37792653 fix(util): drop StrongBox attest version hardcode
StrongBox was pinned to attest/keymaster v300 regardless of SDK,
which mismatches devices shipping KeyMint v400 on Android 16.
Fall through to the same SDK_INT->version map used by the TEE
path so StrongBox reports the device-correct tier.
2026-05-19 17:00:04 +01:00
Enginex0 80f65b02ac fix(intercept): synthesize canonical SSE messages
writeString(null) on service-specific exception replies left the
message word as 0xFFFFFFFF, which diverges from AOSP keystore2's
anyhow-formatted "Error::Rc(NAME)" / "Error::Km(NAME)" strings.
Map known ResponseCode/KeyMint codes to their canonical names so
the wire shape matches a stock TEE reply.
2026-05-19 17:00:04 +01:00
Enginex0 be04f16a50 fix(shim): restore nspace attest key lookup
Reverts eea6001. The nspace attestation key lookup was part of
the score-4 baseline and closes a real NPE on KEY_ID-domain
references where the alias field is null. Reverted in error
during the score-4-to-14 rollback; restoring to match the
working baseline.
2026-05-19 14:35:19 +01:00
Enginex0 9a7e4d7251 fix(intercept): restore updateAad SSE injection
Reverts 180dc03. The updateAad SSE injection landed earlier as
the F1 quirk fix and was part of the score-4 baseline on mt6768.
It was reverted in error during the score-4-to-14 rollback; the
actual regression driver was the uncommitted DELETE_KEY absorber
which has already been dropped. Restoring to match the working
baseline.
2026-05-19 14:35:12 +01:00
Enginex0 180dc039cd fix(intercept): revert updateAad SSE injection
Reverts 5d33701. Unconditionally injecting SSE(INVALID_TAG) on
non-AEAD updateAad matched the AOSP TA spec but diverged from
real-device behavior on mt6768, which returns silently. A
behavior-fingerprint detector on the Xiaomi probe flagged the
divergence and the Tamper score climbed from 4 to 14, with a
second detector raising key-tamper. Roll back to investigate a
device-conformant approach.
2026-05-19 14:29:39 +01:00
Enginex0 eea60018ca fix(shim): revert nspace attestation key lookup
Reverts 17c6312. The KEY_ID-domain alias-null branch targeted
duck-detector's timing-side-channel WARN, but the WARN persisted
in subsequent testing and the combined fix attempts pushed the
Tamper score from 4 to 14 with a new key-tamper detection on a
second detector. Roll back to the 2e55d56 baseline to investigate
from a clean state.
2026-05-19 14:29:29 +01:00
Enginex0 17c63120f5 fix(shim): resolve attest key by nspace
The probe in duck-detector's TimingSideChannelProbe chain calls
generateSigningKey with a KEY_ID-domain attestation key reference
where alias is null. KMSLI.kt:498 fed that null alias into the
non-null String param of KeyIdentifier, triggering an NPE that
the outer runCatching wraps into a ServiceSpecificException(-49).
Any SSE crossing the binder boundary carries the Parcel.read/
createException(OrNull) stack frames, which the detector's
TeeReportReducer at line 2635-2641 matches verbatim to emit
"Captured private binder exception during timing skip".

Branch on the alias before constructing KeyIdentifier: when
alias is present, retain the existing isAttestationKey lookup;
when null (Domain::KEY_ID), scan attestationKeys for a matching
uid + nspace via generatedKeys. Mirrors AOSP keystore2's own
dispatch in database.rs (Domain::APP by alias, Domain::KEY_ID
by key id).
2026-05-19 13:32:54 +01:00
Enginex0 5d33701601 feat(intercept): inject SSE on non-AEAD updateAad
Real MediaTek mt6768 KeyMint silently returns OK on non-AEAD
updateAad, contradicting AOSP's mandate at
system/keymint/ta/src/operation.rs:430-446 to throw InvalidTag
when aad_allowed is false. Duck-detector flags this divergence
as "updateAad mismatch" in OperationErrorPathProbe.

Wire UPDATE_AAD into OperationInterceptor and inject
ServiceSpecificException(INVALID_TAG) when create params
indicate non-AEAD. AEAD (BlockMode.GCM) passes through to real
KeyMint untouched so AES-GCM round-trips remain valid.
2026-05-19 13:05:36 +01:00
Enginex0 2e55d56426 feat(spoof): add TEE op latency floor
Attested keystore operations finishing faster than non-attested
ones on the same device is a timing inversion that detectors
score against TEE coherence. Floor TEE op latency at 4ms to
preserve the natural ordering.
2026-05-19 08:04:05 +01:00
Enginex0 c69e2d47b8 feat(spoof): fill absent vbmeta complement props
invalidate_on_error, avb_version, hash_alg, and size are sibling
props of vbmeta.device_state. When device_state is present but
its complements are missing, the partial set is itself a
detection signal. Fill with safe defaults if absent; never
overwrite when present.
2026-05-19 08:03:04 +01:00
Enginex0 3b1e908670 feat(spoof): skip absent boot-lock props
Creating a vendor-specific boot prop on a device that never had
one is itself a detection signal. Existence-guard the four
boot-lock targets so absent props stay absent.
2026-05-19 08:01:10 +01:00
Enginex0 259d27c3c7 feat(spoof): include vbmeta.device_state
Live install on the user's Pixel surfaced a fourth bootloader-lock
prop the source plan never enumerated. The Chunqiu-style detector
card listed three indicators on its bootloader-unlock row and
flagged red because ro.boot.vbmeta.device_state remained unlocked
even after ro.boot.verifiedbootstate, ro.boot.flash.locked, and
ro.boot.veritymode were all spoofed.

Add ro.boot.vbmeta.device_state=locked to BootStateManager.targets.
Verified post-reboot on device: all four props now report the
spoofed values.
2026-05-19 06:06:58 +01:00
Enginex0 d15abe3c62 fix(spoof): emit validation_rejected status
The source plan's bulletin-history schema declared a four-value
status enum: success, network_error, parse_error,
validation_rejected. The poller only ever wrote the first three;
when PatchLevelManager.updateTo silently rejected a date for bad
format, floor violation, past/future bounds, or atomicWrite IO
error, the history still recorded status=success and applied=true
because applied was set from isNewer before updateTo ran.

Make updateTo return Boolean. Wire the result through fetchAndParse
so a rejected apply lands as status=validation_rejected with
applied=false and an error string identifying the date that failed.
Closes the only spec gap from fancy-humming-firefly.md uncovered
during the source-plan cross-audit.
2026-05-19 05:55:18 +01:00
Enginex0 99957e18c4 fix(spoof): validate currentPatch against date regex
currentPatch returned the raw system= value unchanged. A
malformed value such as system=tomorrow flowed into the
lexicographic comparison `date > current`, where any well-formed
YYYY-MM-DD from the bulletin sorts before lowercase letters, so
the poller permanently judged its date "not newer" and never
applied an update. Validate the read value against the date
pattern; on mismatch, log a warning and treat as passive.
2026-05-19 05:36:36 +01:00
Enginex0 393ae6073f docs(spoof): explain MAX_FUTURE_DAYS rationale
The 60-day window covers Pixel monthly bulletin cadence plus
pre-announcement slip but rejects far-future hostile inputs. The
prior bare constant left readers wondering whether the value was
arbitrary; the KDoc closes that loop.
2026-05-19 05:35:40 +01:00
Enginex0 fbe819d6d8 fix(spoof): serialize concurrent applyToProps calls
applyToProps reaches Runtime.exec("resetprop", name, value) twice
per call, once for system and once for vendor. Boot-time
initialize, BulletinPoller's handler thread, and the PifObserver
inotify thread can all reach applyToProps independently. Two
concurrent invocations for different dates could interleave such
that system and vendor end up with mismatched values. Synchronize
on the singleton so each apply runs to completion before the
next begins.
2026-05-19 05:35:28 +01:00
Enginex0 ad1a9b8c32 fix(spoof): propagate read errors out of mergedContents
A read failure (partial-write race during user edit, SELinux
denial, or any other IOException) used to fall through the
runCatching and rewrite the file with only the global block,
silently destroying every existing [pkg] override. Drop the
runCatching so the failure bubbles to atomicWrite, where the M3
guard in updateTo logs and returns without applyToProps, leaving
both file and props untouched.
2026-05-19 05:35:13 +01:00
Enginex0 d146ad8234 fix(spoof): require = in global key-assignment check
isGlobalKeyAssignment treated any bare line whose first token
matched system/boot/vendor/all as a global assignment and
stripped it. A user line of literally "all" or "system" written
without a value (malformed config but reachable) thus got eaten
on the next atomicWrite. Require '=' in the trimmed line before
treating it as a key=value assignment.
2026-05-19 05:34:59 +01:00
Enginex0 e737453e02 fix(util): skip day synthesis for YYYY-MM input
parsePatchLevelValue silently synthesized day=01 when input was
6 chars (YYYY-MM) and caller wanted 8-digit YYYYMMDD. If
ro.vendor.build.security_patch ever returns YYYY-MM on a target
device (older Samsung firmware does), the synthesized day
disagrees with the real bulletin day -- a detection fingerprint.
Return null so getRealDevicePatchLevelInt falls through to
Build.VERSION.SECURITY_PATCH, which is always YYYY-MM-DD.
2026-05-19 05:17:40 +01:00
Enginex0 429e033b7f fix(interception): emit KEY_SIZE for EC keys
Revert 59dfb2e. AOSP 15 KeyMint reference TA at
system/keymint/common/src/tag/info.rs:61-89 lists both Tag::EcCurve
and Tag::KeySize in KEYMINT_ENFORCED_CHARACTERISTICS, and
check_ec_params at common/src/tag.rs:632 says "Key size is not
needed, but if present should match the curve" -- the TA passes
through whatever the caller supplies and keystore2 supplies both
for EC keys per KeyMintBenchmark.cpp:234,259. Omitting KEY_SIZE
made the simulator's characteristics list shorter than real
hardware, a detection fingerprint.
2026-05-19 05:15:51 +01:00
Enginex0 f706ffdf64 feat(spoof): hot-reload PIF via FileObserver
PatchLevelManager.initialize ran once at boot; PIF edits required
a reboot to take effect. Add a FileObserver on
/data/adb/modules/playintegrityfix for CLOSE_WRITE, MOVED_TO, and
DELETE on the four known PIF filenames, re-resolving and applying
the new date when any of them changes. Skip the watch when the
PIF dir is absent so the daemon does not start a stale inotify
node before the module is even installed.
2026-05-19 05:14:32 +01:00
Enginex0 bbeab27f11 fix(spoof): skip empty PIF source files
resolvePifPatch selected the last existing file regardless of
size. A 0-byte file picked up by lastOrNull caused JSONObject("")
to throw, the catch silently fell back to SystemProperties, and a
preceding non-empty PIF was ignored. Filter out zero-length files
so the lookup walks past them to the next valid candidate.
2026-05-19 05:10:59 +01:00
Enginex0 864c8841c1 fix(spoof): guard atomicWrite errors in updateTo
writeText and Files.move can throw IOException, SecurityException,
or AtomicMoveNotSupportedException. The exception previously
propagated through updateTo into BulletinPoller.fetchAndParse's
broad catch, which mislabelled it as "network_error" in the
history. Wrap atomicWrite, log the real failure, and return
before resetprop so the on-disk file and live props stay
consistent on failure.
2026-05-19 05:10:34 +01:00
Enginex0 4e55ba4e77 fix(spoof): preserve [pkg] sections in atomicWrite
atomicWrite previously overwrote the entire security_patch.txt
with only the three global lines, destroying the per-package
[pkg] overrides supported by ConfigurationManager. Read the
existing file, strip only global system/boot/vendor/all key
assignments, prepend the refreshed global block, and append
everything else (comments, blanks, all [pkg] sections) verbatim.
2026-05-19 05:09:39 +01:00
Enginex0 c511cc48e9 fix(spoof): respect system=prop passive default
PatchLevelManager.initialize previously called updateTo, which
overwrote security_patch.txt with explicit dates and destroyed
the Phase 1 default of system=prop. Split prop application into
a new private applyToProps so initialize only resetprops; never
writes the file. BulletinPoller now treats currentPatch() == null
(the signal for system=prop or missing/blank) as passive and
skips updateTo. The file becomes user-owned config; props track
PIF or the device default.
2026-05-19 05:06:59 +01:00
Enginex0 37179bbbfc fix(spoof): order spoofers before keystore hook
BootStateManager.apply and PatchLevelManager.initialize ran after
initializeInterceptors, so keystore2 cached ro.boot.* and
ro.build.version.security_patch from the un-spoofed values during
hook init. Move both before the interceptor so the hook sees the
spoofed snapshot. ConfigurationManager stays between them since
it only loads files and is independent of prop state.
2026-05-19 05:04:36 +01:00
Enginex0 a0e7fcf400 fix(spoof): isolate BulletinPoller.start failure
BulletinPoller.start ran inside App.main's outer try{...} catch
that rethrows, so any HandlerThread or Looper init failure killed
the daemon including keystore interception. Wrap the start call in
its own try so a poller failure logs and falls through, leaving
the rest of the pipeline alive.
2026-05-19 05:03:27 +01:00
Enginex0 c4e0a6ee48 fix(spoof): wrap pollOnce in umbrella try/catch
fetchAndParse and appendHistory each catch their own exceptions,
but scheduleNext can throw IllegalStateException if the Looper is
torn down or any helper raises an unanticipated error. Without an
outer catch, the reschedule chain broke and the poller stayed dead
until reboot. Wrap the entire body so a thrown exception still
attempts to schedule the next poll.
2026-05-19 05:03:19 +01:00
Enginex0 a79d7e3637 fix(spoof): allow UDP egress for DNS resolution
HttpsURLConnection resolves bulletin.source via getaddrinfo, which
uses UDP/53 first. Without UDP socket rules the resolver fails
before TCP even attempts, killing BulletinPoller silently on
enforcing SELinux kernels. Mirror the existing TCP rules onto UDP
for ksu and magisk.
2026-05-19 05:03:12 +01:00
Enginex0 8cb8616068 fix(spoof): bound future patch dates in updateTo
PatchLevelManager only rejected dates more than ~1 year in the
past. A MITM serving <td>2099-12-31</td> from a spoofed bulletin
response slipped through validation and got written to
security_patch.txt plus resetprop'd. Add a 60-day upper bound past
today using LocalDate.plusDays so month boundaries are handled
correctly. The existing past bound stays.
2026-05-19 05:01:51 +01:00
Enginex0 4b4b7ec626 chore(scripts): make package.sh find user-local cargo
Gradle's buildRustCertgen task uses commandLine("cargo", ...), which
ProcessBuilder resolves against the daemon's inherited PATH rather
than the env injected via Exec.environment(). Non-login shells (CI,
IDE-spawned terminals, fresh tmux panes) don't source the profile.d
hook that prepends ~/.cargo/bin, so the daemon dies with
"A problem occurred starting process 'command 'cargo''" even when
rustup is installed. Prepending ~/.cargo/bin at script entry makes
the script self-contained regardless of how the shell was launched.
2026-05-19 04:11:46 +01:00
Enginex0 439a9d8254 feat(spoof): periodic bulletin refresh via BulletinPoller
BulletinPoller fetches the Pixel security bulletin index page on
its own HandlerThread with 5s/30s/2m/10m/30m bootstrap backoff,
then 24h steady cadence. The first <td>YYYY-MM-DD</td> match is
the latest published patch; newer-than-current dates flow through
PatchLevelManager.updateTo for validation + atomic write + resetprop.

Persists the last 10 attempts to last_bulletin_fetch.json (atomic
rename) with status, http_code, parsed_date, applied, and error
fields so operators can audit history without logcat.

Sepolicy rule appends TCP-socket allow rules for both ksu and
magisk source domains so HttpsURLConnection survives SELinux
enforcement on either root provider. Uninstall.sh cleans the
three new artifacts.
2026-05-19 04:01:30 +01:00
Enginex0 128783dfd4 feat(spoof): PatchLevelManager with PIF resolution
PatchLevelManager resolves the active security patch from
PlayIntegrityFix via the same six-path override chain as
Tricky-Addon's get_extra.sh (pif.json/pif.prop/custom.pif.*,
later entries override earlier ones). Falls back to live
ro.build.version.security_patch when no PIF source is present.

updateTo() validates YYYY-MM-DD format, rejects dates below
2020-01-01 or more than one year older than today, then atomically
stages security_patch.txt with explicit system/boot/vendor dates
and resetprops ro.build.version.security_patch plus
ro.vendor.build.security_patch. Cert tags 706/718/719 then encode
consistent dates via AndroidDeviceUtils.parsePatchLevelValue
(YYYYMM for OS, YYYYMMDD for VENDOR/BOOT per AOSP Tag.aidl).

Wired from App.main after BootStateManager.apply().
2026-05-19 03:59:28 +01:00
Enginex0 da9ade723d feat(spoof): resetprop bootloader lock at boot
BootStateManager.apply() runs from App.main after ConfigurationManager
init and sets ro.boot.verifiedbootstate=green, ro.boot.flash.locked=1,
ro.boot.veritymode=enforcing via resetprop so the attestation
extension's hardcoded verifiedBootState=Verified agrees with what
detectors observe via getprop.

Adds an internal AndroidDeviceUtils.setProperty(name, value: String)
overload so the existing private ByteArray variant stays exclusive
to vbmeta digest persistence while config-package callers can set
plain string props without hex encoding.

Closes documented vulnerability D44 (countermeasure-matrix.md).
2026-05-19 03:57:23 +01:00
Enginex0 5a6d336fa8 feat(install): drop default security_patch.txt at install
Out-of-box install seeds /data/adb/tricky_store/security_patch.txt
with system=prop so TEESimulator passively mirrors live device props.
ConfigurationManager auto-forces boot=prop+vendor=prop when system=prop
(ConfigurationManager.kt:253-256), giving full coverage with one line.

Eliminates Chunqiu code 26 (Tampered Attestation Key) on out-of-box
installs without requiring the Tricky-Addon-Update-Target-List
companion module.
2026-05-19 03:53:53 +01:00
Enginex0 813ff814ea build(gradle): auto-rewrite update.json on packaging
Previously module/update.json had to be hand-bumped to keep
versionCode and zipUrl in lockstep with module.prop's expanded
$gitCommitCount. Wire a refreshUpdateJson task to the
prepareModuleFiles${variant} pipeline so every zipDebug/zipRelease
regenerates the file from current verName and gitCommitCount.
2026-05-19 03:25:58 +01:00
Enginex0 ee6770bcc7 build(gradle): expose cargo bin path to rust task
Gradle's exec environment does not inherit the user's interactive
shell PATH, so cargo-ndk could not find cargo even when it lived in
~/.cargo/bin. Prepend ~/.cargo/bin to PATH for buildRustCertgen so
the Rust toolchain resolves reliably from any shell.
2026-05-19 03:24:11 +01:00
Enginex0 bf29946fdc build(gradle): set kotlin jvmTarget to JVM_21
Java sourceCompatibility/targetCompatibility were already 21, but
the Kotlin compiler defaulted to JVM 17 bytecode, producing a
toolchain skew warning on every build. Align the Kotlin target to
match the Java target.
2026-05-19 03:23:55 +01:00
Enginex0 59dfb2eb85 fix(interception): omit KEY_SIZE for EC keys with ecCurve
AOSP keystore2 attestation lists KEY_SIZE only when there is no
authoritative key-shape tag. For EC keys the curve already pins the
key size, so emitting both KEY_SIZE and EC_CURVE is a forgery
fingerprint. Guard the createAuth call accordingly.
2026-05-19 03:23:10 +01:00
Enginex0 ee7e5ba698 fix(interception): drop delete marker on key regen
Regenerated keys were being filtered as deleted because the
deletion marker in Keystore2Interceptor.deletedSoftwareKeys
survived past the regen call. Clear the marker at both software
and TEE generation paths so the next getKeyEntry returns the
fresh key instead of NOT_FOUND.
2026-05-19 03:22:42 +01:00
Enginex0 04c310e8d3 wip(keystore): add F1 Phase A diagnostic logs in updateAad path
Instrumentation-only. Logs entry (primitive class, callingUid, input size)
and throwable propagation (class, SSE error code, message, stack top) in
both SoftwareOperation.updateAad and SoftwareOperationBinder.updateAad.
Intended to distinguish F1 hypotheses H1 (binder swallows SSE) vs H3
(AIDL signature drift) once duck's probe key is routed through our
simulator instead of the real TEE.
2026-05-19 00:27:38 +01:00
Yunzhe LiaoandGitHub f4d72a641e Merge branch 'Enginex0:main' into fix/persistence-and-keystore-issues 2026-05-18 15:01:17 +02:00
Andrea-lyz 5803039309 review: clean error codes, defensive symmetric fallback, v3 doc
Address Copilot/CodeRabbit review feedback on the persistence PR.

1. SoftwareOperation: replace requireNotNull(keyPair) in SIGN/VERIFY/AGREE_KEY
   branches with ServiceSpecificException(invalidArgument). The original
   requireNotNull throws IllegalArgumentException, which the binder layer
   wraps as KEYMINT_UNKNOWN_ERROR, defeating the goal of surfacing a
   clean keystore-style error. Aligns with how ENCRYPT/DECRYPT already
   handle missing key material in the same when block.

2. loadPersistedKeys: when a symmetric record has empty metadataBytes (e.g.
   a save where Parcel.marshall() was empty for any reason), rebuild a
   minimal KeyMetadata from PersistedKeyData primitive fields instead of
   skipping the record. Skipping silently dropped the AES key, which is
   the same 'logged out after reboot' behavior the PR is trying to fix.
   The rebuilt metadata is structurally minimal but preserves the secret
   material, which is the dominant correctness concern.

3. Comment fix: rebuildResponseFromRecord docs referred to 'v2 metadata
   snapshot', the format in this PR is v3.
2026-05-16 19:10:55 +02:00
Andrea-lyz 8b0acb649d fix: persist symmetric keys + byte-identical metadata; stop wiping keys on keybox edits
Five issues that together caused keystore-pinned apps to be silently
logged out across reboots and config changes. All flow from the same
root cause: GeneratedKeyPersistence loses information on save -> reload.

1. Symmetric keys (AES, HMAC, 3DES) were never persisted at all
   - GeneratedKeyPersistence.save only accepted KeyPair, ignoring SecretKey
   - AndroidX security MasterKey (AES-GCM-256) regenerated on every
     reboot, making EncryptedSharedPreferences undecryptable
   - Apps that wrap session tokens in EncryptedSharedPreferences
     interpret this as session expiry and force a relogin

2. Restored KeyMetadata authorizations differed from generation-time bytes
   - loadPersistedKeys rebuilt KeyMintAttestation with mostly null/empty
     fields, so toAuthorizations emitted a different tag set after
     reboot vs. at generateKey time
   - Apps that fingerprint metadata across keystore calls saw a
     "changed key"

3. certificate / certificateChain split could shift after restore
   - buildKeyEntryResponse called updateCertificateChain on the rebuilt
     metadata, which is allowed to repartition leaf vs. chain bytes
   - Apps with strict leaf fingerprint checks saw a "changed cert"

4. Touching ANY .xml under /data/adb/tricky_store wiped every cached key
   - ConfigObserver called clearAllGeneratedKeys() which also calls
     GeneratedKeyPersistence.deleteAll()
   - Editing keybox.xml (or any unrelated .xml) thus deleted every
     persisted key on disk
   - Even the keybox-cache argument does not justify wiping per-app keys:
     patched chains alone are stale, raw keypairs are not

5. SoftwareOperation NPE when restored keyParams missed PURPOSE tag
   - Init dereferenced keyPair!! before checking purpose, so a
     half-restored record crashed instead of producing a clean error

Single on-disk format (FORMAT_VERSION = 3) covers everything: PKCS8
private key bytes for asymmetric, raw secret bytes for symmetric, plus
the byte-identical KeyMetadata parcel snapshot so authorizations
restore exactly. Earlier dev-only formats are silently skipped by the
loader; the next generateKey for those aliases re-creates them in v3.

ConfigObserver now calls invalidatePatchedChains() instead of
clearAllGeneratedKeys() on .xml edits - only the chain cache is
stale, not the underlying keypairs.

Tested on OnePlus 13 (Android 16, KSU 3.2.4):
- Apps survive force-stop + cold reboot without losing keystore state
- Apps survive keybox.xml edits / replacements (touch, sed, cp -mv)
- Tamper score still 4 (CONSISTENT) on Duck Detector
- KeyAttestation chain output unchanged
2026-05-16 18:43:09 +02:00
Enginex0andGitHub 15a1c0ca40 Merge pull request #21 from Andrea-lyz/fix/duck-detector-generate-fingerprint
fix: defeat Duck Detector "generate-mode fingerprint" probe
2026-05-15 16:47:44 +01:00
Andrea-lyz 4ecbfc7259 fix: use SecurityLevel.KEYSTORE in createSwAuth to match real hardware
Duck Detector's 'TEE Simulator generate-mode fingerprint' probe scans the
generateKey reply parcel for a 16-byte marker where the securityLevel byte
is 0x00 (SOFTWARE). Real KeyMint HAL uses 0x64 (KEYSTORE=100) for
keystore-enforced metadata (creation time, user ID, etc.).

This single-line change aligns with real hardware behavior and defeats
the probe. Tested on OnePlus 13 (Android 16, KSU 3.2.4):
- Before: 'TEE Simulator generate-mode fingerprint: Matched' (score 50)
- After:  'No TEE Simulator generate-mode fingerprint observed' (score 4)

Reference: https://github.com/eltavine/Duck-Detector-Refactoring/commit/e368038
2026-05-15 16:23:05 +02:00
github-actions[bot] db882ec326 chore(release): bump update.json to v6.0.0-162 [skip ci] 2026-03-31 18:47:49 +00:00
Enginex0 37454e6262 ci(release): upload versioned assets only, auto-update zipUrl 2026-03-31 19:41:12 +01:00
github-actions[bot] feee04b95d chore(release): bump versionCode to 160 [skip ci] 2026-03-31 18:32:18 +00:00
github-actions[bot] 161be9fc36 chore(release): bump versionCode to 159 [skip ci] 2026-03-31 18:18:59 +00:00
Enginex0 23b5497f57 ci(release): auto-bump versionCode and add stable asset names
The release job now uploads assets with stable names
(TEESimulator-RS-Release.zip) alongside versioned ones, so the
/latest/download/ URL in update.json always resolves. versionCode
in update.json is bumped to the commit count automatically after
each release, committed with [skip ci] to prevent loops.
2026-03-31 19:12:46 +01:00
Enginex0 0081e93eb0 docs(changelog): add AUTO mode banking app fix to v6.0.0 notes 2026-03-31 18:59:28 +01:00
Enginex0 6ebc6d0bb1 fix(config): restore AUTO mode resolution for bare target entries
v6.0 changed bare target.txt entries from AUTO to GENERATE, breaking
apps like BHIM that need TEE-backed attestation keys. Restore AUTO as
default and resolve it at config level (PATCH if TEE works, GENERATE
if not) to bypass the non-deterministic raceTeePatch path.
2026-03-31 18:53:18 +01:00
Enginex0 0f749dded9 docs(readme): rewrite for TEESimulator-RS v6.0.0
Fix all links from old TEESimulator repo, strip emoji clutter,
add v6.0.0 changelog, update update.json to point at new repo.
2026-03-26 12:34:36 +01:00
Enginex0 23d40b8975 chore(version): bump to v6.0.0 2026-03-26 12:28:23 +01:00
Enginex0 80387b9516 fix(certgen): self-signed certs for no-challenge keys per AOSP spec
AOSP ta/src/keys.rs:451-478 requires self-signed leaf (depth 1) when
no attestation challenge is provided. Both Kotlin and Rust paths now
return subject==issuer, signed by generated key, no attestation
extension. Adds cert chain trace logging in debug builds.
2026-03-26 12:28:17 +01:00
Enginex0 7d470cf830 fix(operation): pass operation-time params through to CipherPrimitive
createOperation was building effectiveParams from key-generation params
but dropping operation-time fields (nonce, blockMode, padding,
minMacLength). This caused GCM decrypt to fail with
"IV must be specified in GCM mode" since the nonce from the begin call
never reached CipherPrimitive.

Also adds nonce field to KeyMintAttestation and handles GCM/CBC/CTR IV
initialization in CipherPrimitive.
2026-03-26 05:03:01 +01:00
Enginex0 76461ad39a fix(certgen): omit attestation extension when no challenge provided
AOSP KeyMint only includes the attestation extension (OID
1.3.6.1.4.1.11129.2.1.17) when ATTESTATION_CHALLENGE is present.
Without a challenge, generateKey produces a plain self-signed cert.
Our code unconditionally added the extension, which behavioral
probes detect by generating a key without a challenge and checking
for the OID.

Fixes both the Rust native-certgen and BouncyCastle paths.
Also skips AAID computation when no challenge is provided,
matching keystore2 security_level.rs:457 behavior.
2026-03-26 04:22:22 +01:00
Enginex0 08e8c769ab fix(interception): patch authorizations on import-overwrite path
The retained cert chain was applied to response metadata but the
authorizations array was left unpatched, allowing a detector to compare
metadata patch levels against cert attestation values and spot the
divergence. Refs upstream JingMatrix #164.
2026-03-26 02:49:01 +01:00
Enginex0 45d54f9369 feat(config): default bare target entries to GENERATE mode
PATCH and AUTO modes inherit the real TEE's attestation quirks (epoch 0
cert dates, version mismatch, missing USAGE_COUNT_LIMIT) which can't be
fixed in post-patch. GENERATE mode builds attestation from scratch with
full control over every field. Users who want real TEE key generation
can still use the ? suffix for explicit PATCH mode.
2026-03-26 02:32:14 +01:00
Enginex0 75acfb9235 perf(logging): add rate limiter and lazy formatting to SystemLogger
Under binder stress, debug builds hammered logd with 6-7 syscalls per
keygen, causing thread contention that spiked ping latency past G10b's
threshold. Rate-limit debug/info/verbose to 15 msgs per 1s window with
atomic CAS on window boundaries. Warnings and errors always pass.

Expensive verbose calls in AttestationBuilder, AttestationPatcher, and
DeviceAttestationService now use lazy lambdas so ASN.1 formatting only
runs when the message will actually be emitted.
2026-03-26 02:11:17 +01:00
Enginex0 1959f0a780 perf(interception): optimize ioctl hook hot path for ping latency
Strip LOGV from the buffer parse loop and add a fast pre-check that
peeks at the first binder command before entering the full parser.
Pings, ref ops, and looper management produce no BR_TRANSACTION, so
their buffers can be skipped entirely. Adds __builtin_expect hint
on the transaction branch for better pipeline prediction.

Drops G2 binder ping ratio from 3.95x to 1.17x in debug builds.
2026-03-26 01:59:32 +01:00
Enginex0 7a98b35666 fix(interception): route oversized transactions to software gen
The 256KB native size guard skipped interception entirely for oversized
transactions, causing them to reach the real TEE which returns different
attestation values. This inconsistency is exactly what G10 detects.

Oversized requests now flow through to the Kotlin layer where they hit
doSoftwareKeyGen via the forceGenerate flag. Software gen produces
consistent attestation without forwarding to the real TEE, preserving
the anti-amplification defense that the original guard intended.
2026-03-26 01:30:07 +01:00
Enginex0 0b8985d8bd fix(interception): resolve B3, C2, F1 and harden AUTO mode
B3: AttestationPatcher now accepts optional notBefore/notAfter overrides
so the PATCH path honors CERTIFICATE_NOT_BEFORE instead of inheriting
the real TEE's epoch 0.

C2: getKeymasterVersion delegates to getAttestVersion directly, ensuring
attestationVersion == keymasterVersion regardless of cache source.

F1: Remove incorrect EC+DECRYPT guard in AuthorizeCreate that returned
UNSUPPORTED_PURPOSE instead of INCOMPATIBLE_PURPOSE.

AUTO mode: Replace volatile teeFunctional boolean with AtomicReference
tri-state (null/true/false) so the first race winner locks the path for
all subsequent requests, preventing mixed attestation under concurrency.
2026-03-26 01:27:43 +01:00
Enginex0 b3aa7950c5 feat(interception): add AUTO mode TEE race for G10 attestation consistency
AUTO mode now races TEE hardware against software generation via
CompletableFuture. If TEE succeeds, the cert chain is patched and
cached in teeResponses before returning, making attestation
stress-resilient. If TEE fails, software fallback is used.

ConfigurationManager no longer resolves AUTO at config time; it
passes Mode.AUTO through to KeyMintSecurityLevelInterceptor for
runtime dispatch. shouldPatch() returns true for both PATCH and
AUTO modes. TEE status file persistence removed entirely.

Aligns handleGenerateKey with upstream PR #157 three-way dispatch:
forceGenerate, raceTeePatch, or hardware forwarding with post-patch.

Hardware keygen rate limiting removed (replaced by raceTeePatch for
AUTO, plain Continue for PATCH). Attest key override in
Keystore2Interceptor now patches authorizations and uses null-safe
nspace assignment.
2026-03-20 04:44:52 +01:00
Enginex0 4c89acced3 fix(interception): harden daemon against binder stress crashes
BinderInterceptor.onTransact now catches Throwable, preventing any
exception on a binder thread from killing the daemon. Adds a global
uncaught exception handler as defense in depth.

Replace Thread.sleep with TeeLatencySimulator (LockSupport.parkNanos +
statistical delay model) for keygen latency, reducing binder thread
blocking. Move GeneratedKeyPersistence.save to a background executor
to avoid disk I/O on binder threads.

Convert force-unwrap parcel reads to safe calls with early returns in
onPreTransact/onPostTransact hot paths. Add -DNDEBUG to native release
builds to compile out verbose logging from the ioctl hook.

Targets G2 (ping overhead) and G10 (stress attestation consistency).
2026-03-19 17:38:29 +01:00
Enginex0 da75e08d58 feat(certgen): add enforcement tags to native DER encoder and teeResponses cache
Extend Rust native cert gen with software-enforced attestation tags
(CALLER_NONCE, ACTIVE_DATETIME, ORIGINATION_EXPIRE_DATETIME,
USAGE_EXPIRE_DATETIME, USAGE_COUNT_LIMIT, UNLOCKED_DEVICE_REQUIRED)
and make NO_AUTH_REQUIRED conditional in teeEnforced. Fixes F5/F6
test failures where these tags were missing from NativeCertGen path.

Add teeResponses cache so PATCH mode keys patched in onPostTransact
return consistent attestation via getKeyEntry. Without this, getKeyEntry
fell through to real keystore2, returning unpatched metadata.

Remove dead Rust enums (KeyPurpose, SecurityLevel, VerifiedBootState)
that were never referenced by the DER encoder.
2026-03-19 15:43:17 +01:00
Enginex0 4d5e94f835 fix(interception): make NO_AUTH_REQUIRED conditional in KeyMetadata authorizations
Upstream removed the unconditional NO_AUTH_REQUIRED from toAuthorizations.
A key generated with auth requirements would incorrectly report
NO_AUTH_REQUIRED in metadata, creating a detectable inconsistency
with the attestation extension.
2026-03-19 09:48:08 +01:00
Enginex0 fef17c07ec feat(interception): close remaining PR #157 compliance gaps
Full diff analysis against upstream's 50 commits revealed 8 functional
gaps after v5.0. These are detectable by conformance tests or detector
apps inspecting KeyMetadata authorizations and operation semantics.

KeyMetadata authorizations:
- Add 9 TEE-enforced tags (CALLER_NONCE, MIN_MAC_LENGTH, ROLLBACK_RESISTANCE,
  EARLY_BOOT_ONLY, ALLOW_WHILE_ON_BODY, TRUSTED_USER_PRESENCE_REQUIRED,
  TRUSTED_CONFIRMATION_REQUIRED, MAX_USES_PER_BOOT, MAX_BOOT_LEVEL)
- Fix CREATION_DATETIME to SOFTWARE security level via createSwAuth
- Add SOFTWARE-enforced date enforcement, USAGE_COUNT_LIMIT, UNLOCKED_DEVICE_REQUIRED

Symmetric key support:
- Generate AES/HMAC keys in software via javax.crypto.KeyGenerator
- GeneratedKeyInfo expanded with nullable keyPair + secretKey fields
- CipherPrimitive accepts java.security.Key for symmetric operations
- SoftwareOperation routes ENCRYPT/DECRYPT to secretKey when available

Operation compliance:
- beginParameters property replaces manual IV wrapping for GCM
- KeyAgreementPrimitive for ECDH AGREE_KEY operations
- handleCreateOperation wrapped in runCatching (crash prevention)
- SECURE_HW_COMMUNICATION_FAILED on software gen failure

Certificate patching:
- Import key cert chain + authorization patching in onPostTransact
- patchAuthorizations added to post-generateKey PATCH mode path
2026-03-19 09:40:48 +01:00
Enginex0 9f03b84364 feat(interception): add AOSP authorize_create enforcement and wire format fixes
Integrate upstream AOSP compliance checks that failed post-v5.0 testing:

- INCLUDE_UNIQUE_ID: SELinux gen_unique_id + Android permission gate
- Forced operation rejection with PERMISSION_DENIED
- Null purpose guard returning INVALID_ARGUMENT
- Wire format: use createServiceSpecificErrorReply for authorize_create
- USAGE_COUNT_LIMIT with AtomicInteger counters and onFinishCallback
- effectiveParams merging key digest with operation purpose
- AuthorizeCreate rewrite: algorithm-purpose before purpose-list (AOSP HAL order)
- CALLER_NONCE in attestation teeEnforced list

Based on upstream commits e55d16d, 3078ea9, 2bc46be, 07c98bc, 41abe77.
2026-03-19 09:21:52 +01:00
Enginex0 3acf73210d docs(readme): credit MhmRdd for upstream AOSP compliance work 2026-03-19 07:37:53 +01:00
Enginex0 c6587c5447 chore(version): bump to v5.0 with changelog for AOSP compliance overhaul 2026-03-19 07:36:20 +01:00
Enginex0 91070d7ede docs: credit upstream PR #157 contributors 2026-03-19 07:33:57 +01:00
Enginex0 77462cb42a feat(config): add SELinux permission checks, latency simulation, and hbk seed
ConfigurationManager gains checkSELinuxPermission (reads /proc/pid/attr)
and hasPermissionForUid (delegates to IPackageManager.checkPermission)
for AOSP-compliant access control. TeeLatencySimulator provides log-normal
distribution matching real QTEE/Trustonic hardware timing profiles.

Module customize.sh now generates a device-unique hardware-bound key seed
(32 bytes from /dev/random) and clears stale tee_status.txt on install.
2026-03-19 07:33:47 +01:00
Enginex0 c80aaef7ae feat(operation): add AOSP-compliant error handling, authorize_create, and GCM IV
SoftwareOperation now throws ServiceSpecificException for all error paths
instead of raw Java exceptions, matching AIDL wire format. updateAad on
non-AEAD operations returns INVALID_TAG (-76) per AOSP operation.rs.
SoftwareOperationBinder methods are @Synchronized to match AOSP Mutex
semantics. GCM encrypt operations return the generated IV in
CreateOperationResponse.parameters.

AuthorizeCreate enforces PURPOSE validation, algorithm-purpose
compatibility (EC rejects ENCRYPT/DECRYPT, RSA rejects AGREE_KEY),
temporal constraints (ACTIVE_DATETIME, ORIGINATION_EXPIRE, USAGE_EXPIRE),
and CALLER_NONCE prohibition. GeneratedKeyInfo carries keyParams for
authorize_create enforcement on software createOperation.
2026-03-19 07:33:30 +01:00
Enginex0 27eeaec384 feat(interception): add binder tx code filtering and keystore2 service compliance
Native binder_interceptor now accepts a filtered_codes vector per
registration, skipping JNI round-trip for non-intercepted transaction
codes. Keystore2Interceptor adds getNumberOfEntries software key counting,
deleteKey KEY_ID domain resolution, patchAuthorizations for OS/VENDOR/BOOT
patch levels, importedKeys tracking to prevent stale attest-key overrides,
and nspace consistency fix in the attest-key override path.

InterceptorUtils gains createServiceSpecificErrorReply for AIDL-compliant
error serialization and patchAuthorizations for authorization array patching.
2026-03-19 07:33:11 +01:00
Enginex0 468b6f5121 feat(attestation): align attestation extension and cert generation with AOSP
KeyMintAttestation now carries all 17 enforcement tags that AOSP's
authorize_create and buildKeyDescription paths expect. AttestationBuilder
populates BLOCK_MODE as SET OF INTEGER, gates version-guarded tags
(RSA_OAEP_MGF_DIGEST >=100, ROLLBACK_RESISTANCE >=3, EARLY_BOOT_ONLY >=4),
computes INCLUDE_UNIQUE_ID via HMAC-SHA256 per KeyMint HAL spec, and
gates AAID on challenge presence.

CertificateGenerator uses AOSP cert validity defaults (epoch notBefore,
9999-12-31 notAfter), returns ServiceSpecificException(-75) for missing
keybox, and adds RSA exponent null safety.
2026-03-19 07:32:53 +01:00
Enginex0 4a103c9231 docs(release): add v4.8.1 changelog for StrongBox op rejection fix 2026-03-18 03:24:58 +01:00
Enginex0 8c27e43ab0 fix(interception): enforce StrongBox op limit for software-generated keys
trackAndEnforceOpLimit was only called in the Domain.KEY_ID not-found
path, so software-generated keys (found via Domain.APP) bypassed the
STRONGBOX_MAX_CONCURRENT_OPS=4 limit entirely. DuckDetector's concurrent
signing handles test created 24+ operations that all succeeded via LRU
pruning instead of being rejected with TOO_MANY_OPERATIONS (-29).
2026-03-18 03:21:34 +01:00
Enginex0 444afadc3b docs(release): add v4.8 changelog for StrongBox hardening and LRU pruning 2026-03-17 19:56:45 +01:00
Enginex0 5b8ee6d278 chore(version): bump to v4.8 2026-03-17 19:48:48 +01:00
Enginex0 69a6648d92 feat(interception): add StrongBox hardening and LRU operation pruning
DuckDetector flags several behavioral anomalies that real TEE/StrongBox
hardware exhibits but our software interceptor did not:

- LRU pruning: cap concurrent ops at 15 (TEE) / 4 (StrongBox) per UID,
  aborting oldest when exceeded, matches AOSP keystore2 malus scoring
- StrongBox param guard: forward unsupported params (RSA>2048, non-P256)
  to real HAL for proper rejection instead of generating in software
- StrongBox latency floors: 250ms keygen, 80ms sign to match real SE
  timing characteristics
- Sliding-window op limit for hardware-generated StrongBox keys that
  bypass the software pruning path
- Domain.APP lookup path for createOperation to find software-generated
  keys that never reach keystore2's database
2026-03-17 19:48:40 +01:00
Enginex0 981cefb506 feat(operation): add LRU pruning support and latency floor to SoftwareOperation
Expose finalized state for pruning, add latency floor parameter for
StrongBox timing simulation, and add trace logging for 32KB test
diagnosis.
2026-03-17 19:48:29 +01:00
Enginex0 6856d4cb47 fix(certgen): accept ECDSA as EC algorithm alias in JCA key type matching
Some Android 10 devices (e.g. Sony H8296) report EC private key
algorithm as "ECDSA" instead of "EC", causing IllegalArgumentException
in certificate signing and a SIGSEGV crash in the keystore process.

Closes #4
2026-03-17 19:48:19 +01:00
Enginex0 ea7e770a6c fix(operation): correct TOO_MUCH_DATA fallback to match AOSP ResponseCode
AOSP ResponseCode.TOO_MUCH_DATA = 21, not 29.
2026-03-17 14:16:00 +01:00
Enginex0 e74dd8318d fix(keygen): forward symmetric algorithms to HAL and add missing JCA mappings
Symmetric keys (AES/HMAC/3DES) don't have KeyPairs or attestation
certs, routing them through doSoftwareKeyGen crashes with
"Unsupported algorithm: 32". Skip the software path entirely and
let the real HAL handle them.

Also adds CTR block mode, RSA_PKCS1_1_5_SIGN cipher padding, and
RSA_PSS signature padding to JcaAlgorithmMapper.
2026-03-17 13:44:25 +01:00
Enginex0andGitHub 745b7d2f8f fix(interception): add permission checks for device ID attestation tags
fix(interception): Add permission checks for KeyMintSecurityLevelInterceptor and fix some regression
2026-03-17 13:05:36 +01:00
fatalcoder524 5defc0d832 fix(interception): Add permission checks for KeyMintSecurityLevelInterceptor and fix some regression
1. Add permission checks for KeyMintSecurityLevelInterceptor to ensure that only authorized users can access sensitive information about the security level of the key mint.
2. Fix regression where device id attestation was allowed for all users by adding appropriate permission checks.
3. Update .gitignore to exclude build artifacts and generated files to keep the repository clean and prevent accidental commits of unnecessary files.
2026-03-17 11:49:54 +00:00
Enginex0 2b000b738d docs(release): bump to v4.7 with operation and attestation fixes changelog 2026-03-17 07:12:30 +01:00
Enginex0 6fc3269229 fix(operation): match AOSP error-path semantics for software operations
KeyDetector's OperationErrorPathChecker (flag 0x400000) probes three
error-path behaviors that real keystore2 operations expose. Our
SoftwareOperationBinder was missing all three, plus had no updateAad
implementation which caused AbstractMethodError on Android 16 where
the runtime Stub declares it abstract.

SoftwareOperation changes:
- Add finalized state tracking; post-abort calls now throw
  INVALID_OPERATION_HANDLE (-28) matching AOSP operation.rs
- Add input length guard (0x8000) throwing TOO_MUCH_DATA (29)
  matching AOSP operation.rs MAX_RECEIVE_DATA
- Add updateAad to CryptoPrimitive interface and SoftwareOperationBinder
- Add KeystoreErrorCodes with runtime reflection + AOSP fallback values

KeyMintSecurityLevelInterceptor changes:
- Infer algorithm from stored key pair when operation params omit
  ALGORITHM tag, matching AOSP behavior where createOperation uses
  the key's stored algorithm rather than requiring it in op params

Stub addition:
- ServiceSpecificException compile stub (framework-internal class
  resolved at runtime on device)

Tested on OnePlus Android 16 (SDK 36), KeyDetector passes all three
probes: updateAad succeeds, TOO_MUCH_DATA returns code=21,
INVALID_OPERATION_HANDLE returns after abort.
2026-03-17 07:04:59 +01:00
Enginex0 b83769ff80 fix(attestation): encode PADDING as SET OF INTEGER per AOSP schema
PADDING (tag 6) is ENUM_REP in Tag.aidl, meaning SET OF INTEGER in the
attestation extension ASN.1, same as PURPOSE and DIGEST. Commit 1fa6f5a
added it as individual [6] INTEGER entries, causing parsers to fail with
CertificateParsingException on any RSA key attestation.
2026-03-17 04:36:49 +01:00
Enginex0 5d270ce1ad ci(release): fetch full history for accurate commit count 2026-03-17 03:43:16 +01:00
Enginex0 46ebde8d4f chore(brand): rebrand to TEESimulator-RS with simplified versioning
Fork identity: rename across module metadata, CI pipeline, and build
scripts. Version scheme changed from v4.5-115-f388529 to v4.6-117
format, commit count auto-increments, git hash dropped from filenames.
2026-03-17 03:35:32 +01:00
Enginex0 0dbeeca15b perf(keygen): replace Gaussian RTT normalization with 15ms floor fence
The old Gaussian sleep (mean=55ms, stddev=12ms) triggered detection on
Chunqiu Native Check 2.8. A flat 15ms floor satisfies the minimum RTT
threshold without creating a detectable delay pattern, both attested
and non-attested paths get identical treatment, keeping the D50 ratio
at ~1.0 while staying above the >=15ms requirement.
2026-03-17 03:35:20 +01:00
Enginex0 f388529bda fix(certgen): derive signing algorithm from attestation key and allow device ID tags
signerAlgorithm was derived from params.algorithm (the generated key)
instead of the signing key, causing BouncyCastle to throw when signing
RSA keys with an EC attestation key. Now reads signingKeyPair.private.algorithm.

Device ID tags (serial/imei/meid/secondImei) were blanket-rejected
instead of flowing through to software cert gen like AOSP does.
Narrowed rejection to DEVICE_UNIQUE_ATTESTATION only.
2026-03-17 00:05:56 +01:00
Enginex0 1fa6f5a12a fix(attestation): align authorization list and cert extension with AOSP keystore2 semantics
toAuthorizations() was missing OS_VERSION, OS_PATCHLEVEL, VENDOR_PATCHLEVEL,
BOOT_PATCHLEVEL, CREATION_DATETIME, USER_ID, PADDING, and RSA_PUBLIC_EXPONENT
tags that real TEE-generated KeyMetadata always includes. EC_CURVE was also
hardcoded unconditionally, producing invalid authorizations for RSA keys.

Additionally, live-patched certificate chains in getKeyEntry weren't cached,
causing re-patching on every call with potentially different signatures.

Ports upstream JingMatrix/TEESimulator#148 and #150.
2026-03-16 23:01:28 +01:00
Enginex0 93c6761990 fix(interception): check generatedKeys before deletedSoftwareKeys on getKeyEntry
The deletion guard must not shadow re-generated keys. If an app
deletes a key then re-creates it, getKeyEntry was still returning
KEY_NOT_FOUND because deletedSoftwareKeys was checked first.
2026-03-16 22:36:02 +01:00
Enginex0 806ec26a03 docs(release): bump to v4.5 with detection hardening changelog 2026-03-16 22:07:56 +01:00
Enginex0 70e1ffb12c fix(interception): prevent ghost key responses after software key deletion
After deleting a software-generated key, getKeyEntry was falling
through to the real keystore2 service which could return a stale
hardware key with the same alias. The post-transact live-patch
fallback would then resurrect the key with a patched chain, 
detectors flag this as binder inconsistency.

Track deleted software key aliases and return KEY_NOT_FOUND (7) for
subsequent getKeyEntry calls. Also always invoke cleanupKeyData on
delete to clear stale patchedChains entries for hardware keys.
2026-03-16 22:06:53 +01:00
Enginex0 3749dec58b perf(keygen): normalize software generateKey RTT to match TEE latency
Software-generated keys complete in ~4ms, real TEE averages 55-65ms
with a floor around 15ms. Detectors measure this RTT to distinguish
software from hardware paths. Gaussian delay sampling (mean=55ms,
σ=12ms, floor=15ms) brings total RTT into the expected range.
2026-03-16 22:06:38 +01:00
Enginex0 2a9ce5e0c8 docs(release): bump to v4.4 with AOSP conformance changelog 2026-03-16 13:26:34 +01:00
Enginex0 1475c0be02 fix(interception): absorb upstream correctness fixes and patch error reply format
Cherry-pick three upstream fixes: Parcel position reset in hasException()
so the method doesn't consume reply data (bab7093), list_past_alias
enumeration filter inversion (71f75de), and KeyMetadata alignment with
AOSP semantics, modificationTimeMs, Tag.ORIGIN, KeyDescriptor
normalization (4e3dcc5).

Additionally, createErrorReply() was missing the empty remote stack
trace header int between the exception message and error code, per
AOSP Status.cpp:196. Binder readers expecting the standard
EX_SERVICE_SPECIFIC wire format would misparse our error replies.
2026-03-16 13:23:36 +01:00
Enginex0 4b9c5fe1d0 docs(release): bump to v4.3 with changelog and update metadata 2026-03-11 13:12:03 +01:00
Enginex0 256bb91a6c ci(build): fix pipeline trigger and release job gating
paths-ignore for .github/** was preventing workflow-only pushes
from triggering the pipeline at all. Release job was gated to
push events only, so workflow_dispatch never published. Simplify
paths-ignore to just **.md and allow both push and dispatch to
trigger the release job.
2026-03-11 13:03:35 +01:00
Enginex0 93ec464d02 ci(build): use mv instead of cp to avoid duplicate artifacts
cp left the original zip alongside the renamed copy, so the glob
matched both, doubling artifact size. mv removes the original.
2026-03-11 12:51:02 +01:00
Enginex0 ff7539f158 perf(daemon): add restart backoff, process priority, and map eviction
Supervisor had zero-delay restart on crash loops, pins CPU core at
100% if daemon keeps dying. Add exponential backoff (500ms to 30s cap,
resets after 30s stable). Set nice=10 on daemon child to yield CPU
to foreground apps. Evict stale entries from fileLocks and rate limiter
ConcurrentHashMaps that grew unbounded. Upload pre-built flashable zips
in CI instead of unpacking and re-compressing loose files.
2026-03-11 12:41:57 +01:00
Enginex0 3e47a3a9b3 perf(logging): gate debug-level logs behind isDebugBuild
debug() was hitting Log.d() unconditionally in release builds, 
every intercepted binder transaction triggered string formatting
and logcat syscalls. verbose() already had the guard; debug() was
just missing it. Also remove dead SERVICE_SLEEP_MS constant.
2026-03-11 12:41:46 +01:00
Enginex0 19b87e64b8 ci(build): add release job with changelog and both ZIPs
The workflow only uploaded unzipped contents as CI artifacts, 
no GitHub release was ever created from CI. Restructured into
build + release jobs: build produces both debug and release ZIPs
(renamed to clean `TEESimulator-vX.Y-{Variant}.zip` format),
release extracts changelog from module/changelog.md and publishes
a GitHub release with both ZIPs attached.
2026-03-11 04:06:20 +01:00
Enginex0 88177ab59e build(gradle): keep debug symbols in debug variant
Debug ZIPs now ship unstripped native libs sourced from
merged_native_libs instead of stripped_native_libs. Gives
meaningful stack traces for crash debugging on-device.
2026-03-11 00:45:00 +01:00
Enginex0 58afe2e0ef docs(readme): add build badge and building-from-source section 2026-03-11 00:28:28 +01:00
Enginex0 dc2f31ff74 ci(build): add Rust toolchain and cargo-ndk for native-certgen
Gradle's buildRustCertgen task requires cargo-ndk and Android NDK
targets to cross-compile libcertgen.so. Without these, CI fails on
any commit after 6aba82e which wired the Rust crate into the pipeline.
2026-03-11 00:12:02 +01:00
Enginex0 938d414ebf docs(release): bump to v4.2 with changelog and update metadata 2026-03-10 16:55:39 +01:00
Enginex0 8876f5bc5f chore(module): bump versionCode to 95 2026-03-10 16:37:58 +01:00
Enginex0 1f076468db perf(binder): skip interception for system transaction codes
AIDL methods use codes 1..0x00ffffff. System transactions like
PING_TRANSACTION (0x5f4e4750) fall above that range. Intercepting
pings forces a full JNI round-trip to Java and back, adding enough
latency for timing detectors to flag the ratio (3.85x vs 3.0x
threshold). Early-return for codes above LAST_CALL_TRANSACTION
eliminates this overhead while preserving all AIDL interception.
2026-03-10 16:37:50 +01:00
Enginex0 4a96491e63 fix(attestation): correct leaf CN casing and enforce keystore2 parameter policy
Leaf cert Subject CN used "KeyStore" (capital S) but AOSP
KeyGenParameterSpec uses "Keystore" (lowercase s). Fixed in both
the Rust native certgen and BouncyCastle paths.

Replicate keystore2's security_level.rs parameter validation for
software-generated keys: reject CREATION_DATETIME (output-only tag,
ResponseCode 20) and device ID attestation tags (CANNOT_ATTEST_IDS
-66) that real keystore2 blocks before they reach the HAL.

Also fix createErrorReply parcel write order, AIDL protocol expects
exception_code, message, error_code but we had message and error_code
swapped, causing malformed replies for positive error codes.
2026-03-10 14:23:33 +01:00
Enginex0 9807f89b71 docs(release): bump to v4.1 with changelog and update metadata
versionCode=94 matches post-commit count.
2026-03-10 13:00:57 +01:00
Enginex0 25f3f753ff fix(attestation): persist vbmeta boot key and hash across reboots
resetprop overrides for ro.boot.* props don't survive reboots. On
devices where the kernel doesn't set ro.boot.vbmeta.public_key_digest,
the fallback chain hit random generation on every boot, producing a
different RootOfTrust hash each time.

Added file-based persistence (boot_hash.bin, boot_key.bin) as a
fallback layer between TEE cache and random generation. Once a value
is determined from any source, it's written to disk and reused on
subsequent boots.

Verified on Redmi 14C: second boot reads from persistent file instead
of regenerating random bytes.
2026-03-10 12:58:49 +01:00
Enginex0 1bfe628317 fix(module): align update.json versionCode with release ZIP
Release ZIP was built at 89 commits (versionCode=89) but update.json
had versionCode=90, causing an infinite update loop in KSU Manager.
2026-03-10 04:40:53 +01:00
Enginex0 3ecc72bcb7 docs(release): write v4.0 changelog and update module metadata
Native Rust cert gen release. Points update.json to fork URLs.
2026-03-09 22:48:20 +01:00
Enginex0 5964eb7e45 docs(readme): rewrite README for personal fork
Matches ZeroMount style, badges, feature checklists, compatibility
tables, config docs. Clarifies this is a fork of JingMatrix/TEESimulator.
2026-03-09 22:17:38 +01:00
Enginex0 e02bae6f43 fix(attestation): reject oversized challenges and rewrite cert DER encoding
DuckDetector flagged two issues:
1. Oversized challenge accepted, 256-byte attestation challenge should
   return INVALID_INPUT_LENGTH (-21) like real KeyMint. Added early check
   in handleGenerateKey before any path decision.
2. Issuer/subject chain mismatch, rcgen's HashMap loses DN attribute
   ordering and converts PrintableString to UTF8String, producing
   different DER bytes. Replaced rcgen with manual DER assembly that
   injects raw keybox issuer_dn_der bytes directly.

Verified on device: TX_ID 315 rejects 256-byte challenge, TX_ID 501
generates valid 4-cert chain with correct issuer linkage.
2026-03-09 21:52:06 +01:00
Enginex0 71c30e68d0 chore(build): bump to v4.0, update module metadata and add package script
Native certgen integration milestone. Adds action.sh/uninstall.sh to
customize.sh extraction loop, points update.json to fork, includes
build/deploy helper script.
2026-03-09 21:51:48 +01:00
Enginex0 e5fb27c8f9 fix(attestation): null out all-zero verifiedBootHash from TEE cache
Matches the existing verifiedBootKey null-zero guard. When the TEE
returns a zeroed hash, fall through to the system property or random
fallback instead of embedding a detectable all-zero value.
2026-03-09 20:00:14 +01:00
Enginex0 5bace3ad30 feat(interception): override pre-existing attest keys, skip GMS list hooking
Two changes to Keystore2Interceptor:

1. Hardware attest keys created before TEESimulator loads now get
   detected in the getKeyEntry post-hook via isAttestKey(). A software
   replacement keypair is generated, cached, and persisted so the
   unpatched hardware chain is never served.

2. GMS calls listEntries frequently. Skip the post-hook injection
   for com.google.android.gms to reduce log flooding and unnecessary
   key merging work.

Also adds null-alias guard in onPreTransact to avoid NPE on keys
looked up by domain/nspace without an alias.
2026-03-09 19:59:51 +01:00
Enginex0 20603572f3 feat(attestation): add origin field and isAttestKey/isImportKey helpers
Parse ORIGIN tag from KeyParameter array into KeyMintAttestation
data class. Add isAttestKey() and isImportKey() convenience methods
to consolidate purpose/origin checks scattered across interceptors.
2026-03-09 19:59:38 +01:00
Enginex0 a781b29e0d fix(attestation): correct module_hash to match AOSP Keystore2
BouncyCastle DERSet() sorts by full encoded sequence, but AOSP
keystore2 maintenance.rs sorts by encoded name only. Replace
PackageManager-based APEX enumeration with filesystem scan of
/apex/ directories using a minimal protobuf parser for
apex_manifest.pb. Encode the DER SET tag manually to preserve
the name-only sort order.
2026-03-09 19:59:30 +01:00
Enginex0 f781f61f44 fix(native-certgen): address production audit findings
Make logging init idempotent (swallow SetGlobalDefaultError on repeat
call), remove unused dumpLogs JNI params that violated the API contract,
and strip dead public_key_spki field + build_ec_spki() that were
computed on every keygen but never consumed by the cert builder.
2026-03-09 18:16:50 +01:00
Enginex0 6aba82edb1 build(native-certgen): wire Rust crate into Gradle pipeline
cargo-ndk builds libcertgen.so for arm64-v8a during prepareModuleFiles.
AGP mergeJniLibFolders picks up jniLibs/ and routes through
stripped_native_libs into the module ZIP. customize.sh extracts the .so
on device install. ProGuard keeps NativeCertGen JNI class and
CertGenConfig fields for runtime JNI field access.
2026-03-09 16:46:32 +01:00
Enginex0 7f3d72ba20 fix(pki): align JNI signatures between Kotlin and Rust
initLogging now takes logDir param matching Rust entry point.
dumpLogs takes logDir+baseDir params matching Rust. Removed unused
generateSoftwareKeyPair declaration. Added buffer bounds checks in
parseNativeResult to prevent OOM on malformed native output.
2026-03-09 16:37:52 +01:00
Enginex0 e82abe1ce9 feat(pki): integrate native cert gen with BouncyCastle fallback
NativeCertGen.kt provides CertGenConfig data class and JNI bridge
to libcertgen.so. KeyMintSecurityLevelInterceptor.doSoftwareKeyGen()
tries native path first, falls back to BouncyCastle on failure or
when library unavailable. App.kt loads libcertgen.so at daemon start.
2026-03-09 16:23:09 +01:00
Enginex0 95724116b7 feat(native-certgen): implement JNI bridge with panic-safe entry points
Three JNI exports: generateAttestedKeyPair (orchestrates keygen,
attestation, certbuilder, returns length-prefixed binary),
initLogging (multi-output tracing setup), dumpLogs (diagnostic ZIP).
CertGenConfig extraction via typed JNI field accessors. catch_unwind
on all FFI boundaries.
2026-03-09 16:13:41 +01:00
Enginex0 d335809682 fix(native-certgen): address Phase 3 validation findings
Remove ENCRYPT/VERIFY from KeyUsage mapping to match Kotlin behavior.
Document BasicConstraints and SKI suppression via rcgen NoCa default.
Fix rotating log off-by-one that kept one extra backup file. Handle
BMPString (UTF-16BE) and VisibleString in X.500 DN parser.
2026-03-09 16:08:07 +01:00
Enginex0 0b680c578e feat(native-certgen): implement X.509 certificate chain builder
Builds v3 leaf certificate with attestation extension and KeyUsage,
signs with keybox private key via rcgen 0.13.2. Assembles full chain
(leaf + keybox intermediates + root). Supports EC and RSA keybox
signing keys. Uses rcgen's signed_by() with a synthesized issuer
Certificate, no manual DER fallback needed.
2026-03-09 15:55:15 +01:00
Enginex0 98f7e0f08b feat(native-certgen): implement logging subsystem
Multi-output logging via tracing: /dev/kmsg for logcat, rotating file
appender (512KB, 3 files), stderr for debug. Diagnostic ZIP dump with
log files and TEE status snapshots. Verbose toggle via JNI flag or
.verbose marker file.
2026-03-09 15:41:46 +01:00
Enginex0 e8672459e0 feat(native-certgen): implement ASN.1 attestation extension encoder
DER encoder for Android KeyMint attestation extension (OID
1.3.6.1.4.1.11129.2.1.17). SecurityLevel and VerifiedBootState as
ENUMERATED, EXPLICIT context-specific tagging with long-form for
tags >= 31, sorted AuthorizationList fields, RootOfTrust with
BOOLEAN TRUE=0xFF, SET OF INTEGER with DER sort, DO_NOT_REPORT
sentinel omission.
2026-03-09 15:32:31 +01:00
Enginex0 9dc8ec1530 fix(native-certgen): address Phase 0-1 validation findings
EC keygen now returns proper SPKI DER instead of raw point bytes.
RSA keygen uses caller-supplied exponent via new_with_exp() and
validates key size to 2048/3072/4096. Keybox parser extracts leaf
subject DN (not issuer). Added AttestKey=7 to KeyPurpose. Realigned
error variants with spec.
2026-03-09 15:21:33 +01:00
Enginex0 5fcd4ab7b6 feat(native-certgen): implement keybox DER certificate chain parser
Splits concatenated DER cert chains into individual certificates,
extracts leaf issuer DN and notAfter via x509-cert crate. Handles
multi-byte DER length encoding (0x81-0x84).
2026-03-09 15:09:29 +01:00
Enginex0 30d5188c69 feat(native-certgen): scaffold Rust crate with foundation types and keygen
Cargo.toml with 16 dependencies per build spec, error types with
From impls for all upstream error types, CertGenParams mapping the
full JNI config contract, EC/RSA key generation via ring and rsa crates.

Compiles clean for aarch64-linux-android via cargo-ndk.
2026-03-09 15:05:25 +01:00
Enginex0 770a4f0134 Derive boot and vendor patch levels from system prop when system=prop
TrickyAddon fetches Pixel bulletin dates for boot/vendor but system=prop
resolves to the real device prop, creating a cross-component date mismatch
on non-Pixel devices. Force all three through the same prop resolution path.
2026-02-07 00:47:53 +01:00
Enginex0 bb28a8d30c Rate-limit per-UID hardware keygen and harden importKey eviction
Sliding window limits each UID to 2 hardware generateKey calls per
30s burst window with max 2 concurrent. Overflow falls back to
software cert generation.

importKey post-hook retains patched chains instead of full eviction,
preventing detectors from using generate-then-import to bypass
attestation patching. getKeyEntry serves retained chains for imported
keys that overwrote attested aliases.
2026-02-07 00:47:47 +01:00
Enginex0 3e2aaa2ff0 Cap interceptable binder payload size at 256KB
Prevents thread starvation from flood attacks targeting the
binder interceptor with oversized payloads.
2026-02-07 00:47:42 +01:00
Enginex0 ff88543c98 Add file-level locking to prevent race conditions in key persistence
Per-key ReentrantLock prevents concurrent writes to same key file
2026-02-07 00:47:36 +01:00
Enginex0 c99a2ab302 Reject oversized aliases to prevent binder buffer exhaustion
MAX_ALIAS_LENGTH (256KB) with 4x safety margin for transaction overhead
2026-02-07 00:47:31 +01:00
Enginex0 b33e1ae3ac fix(pki): strip HTML comments from PEM blocks before parsing
Some upstream keybox sources inject HTML comments inside PEM
certificate blocks. BouncyCastle's PEMParser chokes on these
non-base64 lines, silently failing to load the keybox.

Filter lines starting with <!-- in trimLines() before the content
reaches the PEM parser.
2026-02-07 00:47:26 +01:00
Enginex0 fc64789dc3 fix(config): prevent FileObserver NPE on config file deletion
When a config file is deleted, the event handler sets file=null but
then force-unwraps it with file!! in the when block, crashing the
FileObserver thread. All subsequent config change notifications are
silently lost.

Replace force-unwrap with safe call, log a warning on deletion.
2026-02-07 00:47:20 +01:00
Enginex0 945f3cac79 Integrate key persistence with interceptors
Save keys on generation, restore on daemon startup, delete on cleanup.
Re-persist when cert chain updates via updateSubcomponents.
2026-02-07 00:47:16 +01:00
Enginex0 4e50a70366 Add generated key persistence layer
Persist GENERATE-mode keys to disk so they survive daemon restarts.
Binary format with version header, atomic write via tmp+rename.
2026-02-07 00:47:10 +01:00
Enginex0 8b00d7985f feat(module): add supervisor daemon with leak-safe restart and lifecycle scripts
Fork-based supervisor ensures the interceptor process survives crashes.
pingBinder() liveness check on pre-transact returns DEAD_OBJECT to
callers when interceptor is down, preventing real TEE state from leaking
during the restart window.

action.sh clears persistent key storage via KSU Action button.
uninstall.sh kills daemon processes and removes module artifacts while
preserving target.txt and keybox configuration.
2026-02-07 00:47:04 +01:00
Enginex0andGitHub e7444bb62a Set correct certificate KeyUsage based on KeyPurpose (#119)
The previous implementation hardcoded the X.509 KeyUsage extension to `keyCertSign` for all generated certificates. This was only correct for keys with the `ATTEST_KEY` purpose and violated the Android HAL specification for keys intended for other uses. For instance, a key created for signing (`KeyPurpose::SIGN`) requires the `digitalSignature` bit to be set, not `keyCertSign`.

This commit corrects the logic by dynamically constructing the `KeyUsage` bitmask from the key's specified purposes, adhering to the mapping defined in `KeyCreationResult.aidl`. This ensures that generated certificates now have the correct KeyUsage bits, accurately reflecting the key's intended function (e.g., signing, decryption, key wrapping) and making them compliant with the specification.
2026-02-04 09:07:21 +01:00
JingMatrixandGitHub 6fdf5c766b Resolve reference leak and warnings in binder interception (#122)
This merge addresses a critical strong reference leak in the ioctl hook that occurred during binder transaction interception. The leak was caused by a double increment of the reference count, once manually and once by a smart pointer's constructor, with only a single corresponding decrement. The fix ensures a balanced increment and decrement, preventing the leak and subsequent crashes.

Additionally, this change:
-   Reverts a now-unnecessary compatibility layer for the Android 11 RefBase ABI.
-   Implements `getInterfaceDescriptor` in the `BinderStub` to silence framework warnings that appeared after the primary leak was fixed.
2026-02-04 09:03:51 +01:00
JingMatrix 33397f244c Release TEESimulator 3.1 2026-01-31 22:49:29 +01:00
JingMatrix 23bef3f88e Correct misunderstanding of takeIf execution order
The previous code incorrectly assumed `takeIf` prevents the execution of the receiver statement. Since `takeIf` is an extension function, the receiver, `InterceptorUtils.getTransactCode`, was evaluated eagerly *before* the version check predicate could run.

This commit replaces the `takeIf` chain with a standard `if/else` block to ensure the reflection call is only executed when the API level supports it.

Additionally, repeated `IKeystoreService.Stub::class.java` references were refactored into a `stubBinderClass` property.
2026-01-31 12:51:09 +01:00
129cec06bf Support key enumeration via listEntries interception (#84)
Previously, generated keys were functional but invisible to enumeration APIs like `KeyStore.aliases()`. Because these keys reside solely in the simulator's memory, the standard database query performed by the system Keystore does not return them.

This commit intercepts `listEntries` and `listEntriesBatched` to inject these generated keys into the results.

Key implementation details:
- ListEntriesHandler: Encapsulates the logic to merge hardware-backed keys with software-backed keys.
- Ordering: Uses a `TreeMap` to ensure merged results are lexicographically sorted, mimicking AOSP behavior.
- Binder Safety: Implements `estimateSafeAmountToReturn` to calculate the response size. The handler truncates the result list if it exceeds the binder transaction limit (~350KB) as done in AOSP.
- Pagination: Respects the `startPastAlias` parameter to support batched listing.

Co-authored-by: JingMatrix <jingmatrix@gmail.com>
2026-01-31 11:37:25 +01:00
JingMatrix 54f68b99b1 Move attestation challenge check to certificate generation
Relocate the `attestationChallenge` length validation from `generateSoftwareKeyPair` to `generateCertificateChain`.

The challenge is only utilized during the construction of the certificate chain (via `AttestationBuilder.buildKeyDescription`). Placing the check in the key pair generation stage caused the logic to miss the `attestKey` transaction hook in `KeystoreInterceptor`.

This fixes a bug introduced in d77508d0c1 which missed the detection bypass for Android 10 and 11 devices.
2026-01-30 21:13:02 +01:00
JingMatrix 8649b8b928 Handle swapped attestation lists on certain Android 11 devices (#108)
Observed an abnormal Keymaster attestation structure on certain Android 11 devices where the `softwareEnforced` and `teeEnforced` authorization lists were swapped in order. This is a deviation from the documented specification and the behavior seen on most devices.

This non-compliance caused parsing failures, as the code expected the `teeEnforced` list to be at a fixed index (7). On the affected devices, this index contained the `softwareEnforced` list, which critically lacks the `TAG_ROOT_OF_TRUST` needed for successful validation and patching.

This commit introduces a defensive normalization step to handle this device-specific anomaly gracefully:

1.  Before parsing, the code now inspects the ASN.1 sequence at the expected `softwareEnforced` index (6).
2.  It checks for the presence of the `TAG_ROOT_OF_TRUST`, which can only exist in the TEE-enforced list.
3.  If the tag is found, the code concludes the lists are swapped and corrects the `allFields` array in-place by swapping the elements at indices 6 and 7.

By normalizing the data structure at the beginning, the rest of the parsing and patching logic can proceed without modification, ensuring correct operation on both compliant and non-compliant devices.
2026-01-30 21:10:41 +01:00
JingMatrix 68af5ac680 Correct alias parsing in KeystoreInterceptor (#106)
The `extractAlias` utility was failing to strip `USRCERT_` and `CACERT_` prefixes, causing a cache miss during certificate chain patching. The function is now updated to correctly handle these prefixes.

Moreover, more logs are added to help debugging in the future.
2026-01-29 19:23:57 +01:00
JingMatrixandGitHub 10d673b606 Add SELinux rules for libTEESimulator.so loading (#104)
Allow `keystore` to access the `file` class for `adb_data_file` and `shell_data_file` contexts.

The target contexts correspond to the following locations:
- `adb_data_file`: The library path `/data/adb/modules/tricky_store/libTEESimulator.so`, used for FD transfer.
- `shell_data_file`: The fallback mechanism for loading the library by staging it in `/data/local/tmp`.

Note: The rule for the `dir` class (directory search) has been removed because the supporting audit logs were lost. The remaining file access logs were observed on a MEIZU 21 Note.
2026-01-29 15:15:56 +01:00
JingMatrixandGitHub b5251c0418 Fix multiple crashes and race conditions on Android 12 (#99)
This resolves several critical stability issues observed on Android 12 devices, including race conditions and API compatibility problems.

Key changes include:

-   Resolves Race Condition in TEE Check:
    Fixes a NullPointerException that occurred when the TEE functionality check was executed before the PackageManagerService was ready. The code now explicitly waits for the package manager to become available, preventing the crash on startup.

-   Fixes IllegalStateException on Initialization:
    Eliminates a crash caused by `setTelephonyServiceManager called twice`. This was due to a redundant call to `initializeMainlineModules()` in the DeviceAttestationService, which is now correctly handled a single time during application startup.

-   Fixes NoSuchAlgorithmException in Attestation:
    Adds a normalization function to handle signature algorithm names reported in all-caps by older Android versions (e.g., "SHA256WITHECDSA"). This ensures compatibility with Bouncy Castle, which expects a specific casing (e.g., "SHA256withECDSA").
2026-01-29 15:00:09 +01:00
JingMatrix 1d2c60c510 Prevent recursion when configured to intercept system UID (#100)
When the TEESimulator is configured to intercept UID 1000, accessing the `lazy` `bootKey` property causes a StackOverflowError.

The property's initializer sends a key generation request (UID 0) to probe real hardware. Previously, the C++ layer hijacked this request and spoofed it to UID 1000. This sent the request back to the Kotlin interceptor (if configured so), which attempted to access `bootKey` again to build the response, creating an infinite loop.

This change spoofs UID 0 requests to 1000 (to pass Keystore permissions) but explicitly bypasses hijacking, ensuring the probe request hits the real hardware.
2026-01-28 22:15:27 +01:00
JingMatrix b997304f02 Remove SELinux context manipulations during injection (#87)
After few tests in various devices, it seems that SELinux context modifications are unnecessary for the injection to work.

We thus remove all related manipulations. Further (partial) reverting of the commit must be justified with SELinux logs:

> adb shell su -c 'cat /proc/kmsg | grep avc'
2026-01-28 22:14:08 +01:00
JingMatrix 5e394f1b72 Fix ARM ptrace compatibility and improve remote call safety (#94)
- Implement fallbacks to `PTRACE_GETREGS` and `PTRACE_SETREGS` for 32-bit ARM (`__arm__`). Some kernels return `EIO` or `EINVAL` when attempting to access `NT_PRSTATUS` via `PTRACE_GETREGSET`/`PTRACE_SETREGSET`.

- Update `transfer_fd_to_remote` to use `libc_return_addr` instead of `0` as the return address during the `recvmsg` split-call. This ensures the remote process stops predictably at a known non-executable location rather than relying on a potentially unsafe jump to `0x0`.

- Clarify comments regarding i386 argument passing in `utils.cpp`. Correctly note that a linear `write_proc` starting at the new SP matches the `cdecl` Right-to-Left memory layout (since stacks grow downwards while memory writes move upwards), removing the suggestion that arguments needed reversing.
2026-01-28 14:08:28 +01:00
JingMatrixandGitHub b1f3b5d28d Fix cache consistency on key overwrite (#97)
Android allows applications to generate a new key using an existing alias without explicitly calling `deleteKey` first. In this scenario, the new key effectively replaces the old one. As a simulator, we must strictly follow this logic to prevent returning stale data.

Previously, `KeyMintSecurityLevelInterceptor` did not enforce mutual exclusion between the software key cache (`generatedKeys`) and the hardware chain cache (`patchedChains`). This led to state desynchronization where a stale software key could shadow a newly patched hardware chain if the alias was reused.

This change ensures `cleanupKeyData` is invoked immediately before caching a new key / chain in both the software (`handleGenerateKey`) and hardware (`onPostTransact`) paths, ensuring the simulator returns the correct key for the most recent generation request.
2026-01-28 13:50:05 +01:00
JingMatrix e9d7321b5f Fix crash by avoiding hardcoded index for moduleHash
The previous implementation attempted to retrieve `moduleHash` from the `softwareEnforced` sequence using a hardcoded index (index 2).

However, fields in the Key Attestation `AuthorizationList` are optional. In observed crashes, index 2 actually corresponded to `keySize` (Tag 3, ASN1Integer) rather than `moduleHash`, causing an `IllegalArgumentException` when the code attempted to parse it as an `ASN1OctetString`.

This commit replaces the index-based access with a dynamic lookup for Tag 724.
2026-01-26 23:26:52 +01:00
JingMatrix 4097ffde6a Fix x86_64 injection: Red Zone adjustment and fallback logic (#91)
- Strictly adhere to the System V AMD64 ABI by skipping the 128-byte "Red Zone" before modifying the stack, see page 23 of https://gitlab.com/x86-psABIs/x86-64-ABI/-/jobs/artifacts/master/raw/x86-64-ABI/abi.pdf?job=build for details.

- Added `inject_via_staging` as a fallback strategy:
  1. Copies the payload to `/data/local/tmp`.
  2. Sets permissions/context (`u:object_r:system_file:s0`).
  3. Loads via standard `dlopen`.
  4. Immediately unlinks the file for stealth.

- Introduced `RegisterRestorer` RAII class to guarantee original registers are restored even if the injection logic returns early due to error.
2026-01-26 23:15:09 +01:00
JingMatrixandGitHub dcb961d084 Fix support for Android 10 (#92)
Users report that the method `waitForService` doesn't exist on Android 10.
Close #90 as completed.
2026-01-26 16:54:59 +01:00
JingMatrixandGitHub 151410c75e Fix Android 11 Keystore execution: Init framework and spoof UID 1000 (#85)
This commit resolves `KeyStore` API failures on Android 11 when running as a standalone CLI executable (UID 0), addressing both environment initialization and permission denial issues.

1. Initialize Android Framework Environment:
   Android 11 Keystore APIs expect a fully initialized application context and a Main Looper, which are missing in a raw root process. This patch:
   - Manually bootstraps `ActivityThread` via `systemMain()`.
   - Initializes `Looper.prepareMainLooper()`.
   - Injects a dummy `Application` object attached to the system context to satisfy `KeyStore.getApplicationContext()` checks.
   - Updates framework stubs to allow compilation of these hidden APIs.

2. Bypass Keystore Permission Checks via UID Spoofing:
   `KeyStoreService::generateKey` enforces the `P_INSERT` permission. Analysis of `permissions.cpp` reveals that UID 0 (Root) is explicitly denied this permission (granted only `P_GET`), whereas UID 1000 (System) holds all permissions (`~0`).
   
   To bypass this restriction, the binder interceptor now detects transactions originating from UID 0 and rewrites the `sender_euid` to 1000. This fools `KeyStoreService` into granting the request.
 
3. Refactor Execution Loop:
   Replaces the previous `Thread.sleep()` maintenance loop with `Looper.loop()`.
2026-01-26 14:07:06 +01:00
205fda43ba Intercept updateSubcomponent to fix software key state inconsistency (#82)
Apps attempting to update the certificate chain of a simulated software-based key (e.g., via KeyStore.setKeyEntry) currently trigger a KEY_NOT_FOUND error. This happens because the request is passed to the hardware Keystore daemon, which has no knowledge of keys existing only in the simulator's memory.

To fix detecting points exploiting this inconsistency, we intercept the UPDATE_SUBCOMPONENT_TRANSACTION. If the target is a recognized virtual key, the simulator now:
1. Updates the in-memory certificate/chain metadata.
2. Returns NO_ERROR immediately to the caller.
3. Prevents the transaction from reaching the real hardware service.

Co-authored-by: JingMatrix <jingmatrix@gmail.com>
2026-01-23 17:53:34 +01:00
d77508d0c1 Enforce attestation challenge length limit (#70)
Throws IllegalArgumentException if the challenge exceeds 128 bytes, per Android specs. Also fixes a duplicate assignment typo in KeystoreInterceptor.

Reference: https://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.Builder#setAttestationChallenge(byte[])

Co-authored-by: JingMatrix <jingmatrix@gmail.com>
2026-01-20 19:00:48 +01:00
dependabot[bot]andJingMatrix aff7cf9c32 Update dependencies 2026-01-11 16:24:34 +01:00
JingMatrixandGitHub fa2956ce1b Implement multi-purpose simulation for crypto operations (#59)
This commit introduces a comprehensive simulation engine for Keystore's `createOperation`, enabling the simulator to correctly handle multiple cryptographic purposes (SIGN, VERIFY, ENCRYPT, DECRYPT) for software-generated keys.

The implementation correctly mimics the AOSP framework's internal key identification mechanism. Instead of relying on an alias, a unique `keyId` is generated and embedded in the `nspace` field of the KeyDescriptor during `generateKey`. The `createOperation` hook then uses this `keyId` to dispatch requests: if the ID matches a known software key, the operation is simulated; otherwise, it is forwarded to the hardware service.

To support this, the `SoftwareOperation` engine was architected using a Strategy Pattern. A `CryptoPrimitive` interface defines common actions, with concrete implementations for `Signer`, `Verifier`, and `CipherPrimitive`. The main `SoftwareOperation` class acts as a controller, instantiating the correct primitive based on the `KeyPurpose` tag from the incoming operation parameters. A `JcaAlgorithmMapper` was added to centralize the logic for converting KeyMint constants into JCA algorithm strings.

For operations on real hardware-backed keys, a lightweight `OperationInterceptor` is now used for observation. It attaches to the genuine `iOperation` binder for logging and properly unregisters itself upon completion to prevent resource leaks. This is supported by new binder unregistration capabilities in the core `BinderInterceptor`.

This change also includes necessary stub files and minor regression fixes to make the simulation more robust and accurate.

See AOSP source for key identification logic:
https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java
2025-12-08 19:59:32 +01:00
JingMatrix 19ad610cec Ensure mocked replies use native OK status (#60)
Corrects a bug where the native binder `status_t` was being set to application-level error codes (e.g., `KeyStore.NO_ERROR` which is 1).

Moreover, we call method `InterceptorUtils.createTypedObjectReply` to keep the code style consistent.
2025-12-08 04:30:01 +01:00
JingMatrix 1a1334e3f3 Release TEESimulator 3.0 2025-12-06 16:59:28 +01:00
JingMatrixandGitHub 17ab5b0e2c Correct crypto provider handling and signing logic (#53)
Resolves crashes during certificate operations caused by cryptographic provider conflicts and incorrect algorithm selection.

The Bouncy Castle (BC) provider is now initialized globally at app startup to ensure it is the default. To eliminate ambiguity, all content signers are also now explicitly set to use the BC provider.

The attestation patcher is fixed to correctly use the certificate's signature algorithm (sigAlgName), not the subject's public key algorithm, to select the appropriate signing key from the KeyBoxManager. A normalization function was added to support this.

Moreover, we also modify the XML parser in `KeyBoxManager` to no longer trust the `algorithm` attribute from the XML tag. The parser now determines the key's true algorithm (RSA or EC) by inspecting the type of the parsed private key object. This derived algorithm is used as the key for the cache, preventing cache corruption from malformed files where the tag does not match the key data.
2025-12-06 16:12:12 +01:00
JingMatrixandGitHub ccd4ae7f5f Handle invalid verified boot key (#55)
Treat the `verifiedBootKey` as null if it consists entirely of zero bytes, as some devices return this invalid value.

Additionally, this commit adds missing KDoc comments to the `AttestationData` class for better documentation.
2025-12-06 11:49:46 +01:00
JingMatrixandGitHub b0206c10ec Add dynamic dates and TEE-based patch defaults (#52)
Implements dynamic date keywords ('today') and templates ('YYYY-MM-DD') in the security_patch.txt configuration. This allows for auto-updating patch levels.

The `device_default` keyword is now significantly more accurate. It prioritizes reading real patch levels directly from a cached TEE attestation before falling back to system properties.

The README has been updated to document these new features.
2025-12-06 07:27:06 +01:00
JingMatrixandGitHub bfc15cba62 Implement per-package security patch configuration (#49)
This commit introduces a hierarchical configuration system for the security patch levels reported in attestations, allowing for both global defaults and per-package overrides.

The `security_patch.txt` file is enhanced to support this new syntax. Settings at the top of the file act as a global default, which can be overridden for specific applications by defining settings under a `[package.name]` section.
2025-12-04 23:14:51 +01:00
小潼andGitHub f76a4dd977 Correctly handle deleteKey for software keys (#42)
This resolves an issue introduced in 6193da0 where a `deleteKey` transaction for a software-generated key was incorrectly passed through to the hardware keystore. Since the hardware is unaware of such keys, this results in inconsistent state management.

The success reply is formatted correctly without a result code, per the AIDL interface specification.

Reference: https://cs.android.com/android/platform/superproject/main/+/main:out/soong/.intermediates/system/hardware/interfaces/keystore2/aidl/android.system.keystore2-V6-java-source/gen/android/system/keystore2/IKeystoreSecurityLevel.java;l=406
2025-12-04 20:01:16 +01:00
JingMatrix a0ac3f71bb Bypass KeyMint hooks for certain UIDs
Adds a check using `ConfigurationManager.shouldSkipUid` at the start of the `onPreTransact` handlers for key generation and import.

If a UID is configured to be skipped, the transaction is forwarded directly to the hardware, and the post-transaction hook is bypassed. This prevents certificate patching and other modifications for trusted or problematic apps, improving compatibility.
2025-12-04 02:02:28 +01:00
JingMatrix a30459628f Set correct attestation version for StrongBox
We observe that attestations generated with a security level of
`StrongBox` (value 2) must have an `attestationVersion` of 300. The
previous implementation determined this version based only on the
Android SDK version, which could lead to invalid attestations.

This commit refactors the version retrieval logic to be dependent on the
security level:

- In `AndroidDeviceUtils`, the `attestVersion` and `keymasterVersion`
  properties have been converted into `getAttestVersion(securityLevel)`
  and `getKeymasterVersion(securityLevel)` functions.
- `getAttestVersion` now correctly returns `300` when the security level
  is `StrongBox`.
- `AttestationBuilder` is updated to call these new functions, passing
  the appropriate security level to ensure the generated attestation is
  compliant with official documentation.
2025-12-04 01:57:59 +01:00
JingMatrixandGitHub cd3970869d Prevent detection via inconsistent certificate signatures (#45)
Fixes a detection vector where the simulator could be identified by comparing certificate signatures from different API calls.

Previously, the simulator would re-patch and re-sign a certificate on-the-fly for both `generateKey` and `getKeyEntry` calls. Due to the non-deterministic nature of ECDSA signing, this resulted in different signatures for the same certificate, which is a detectable anomaly not present in a real TEE.

This is resolved by caching the patched certificate chain after its initial creation in `KeyMintSecurityLevelInterceptor`. The `getKeyEntry` hook in `Keystore2Interceptor` now retrieves the chain from this cache, guaranteeing that subsequent calls return a byte-for-byte identical certificate.

Cache cleanup logic was also integrated into key deletion and clearing functions to maintain state consistency.
2025-12-04 00:59:59 +01:00
31a0906c02 Reduce logging in the release build (#44)
Verbose logging are now disabled in the release build.
With this change, we reinterpret the last argument passed to `logTransaction` as `skipPost`, and classify logs satisfying `skipPost` or `shouldSkipUid` as verbose.

Co-authored-by: JingMatrix <jingmatrix@gmail.com>
2025-12-03 23:50:12 +01:00
JingMatrix eeefdc48eb Implement software key generation for legacy IKeystoreService (#34)
This commit introduces a complete, software-based simulation of the key generation and attestation flow for the legacy IKeystoreService API, as used on Android 11. It refactors the KeystoreInterceptor to handle the entire multi-step transaction sequence (`generateKey`, `getKeyCharacteristics`, `exportKey`, `attestKey`) in software.

A new `LegacyKeygenParameters` data class is introduced to decouple the legacy interception logic from modern data structures. This class parses arguments from the old `KeymasterArguments`, stores the state across the multi-step generation process, and acts as an adapter to the generic `CertificateGenerator` by converting the parameters to the modern `KeyMintAttestation` format.

The `CertificateGenerator` has been refactored to better model the behavior of the legacy Keystore API. Key pair generation (`generateSoftwareKeyPair`) and certificate chain creation (`generateCertificateChain`) are now separate functions. This allows the interceptor to correctly create a key pair during the `handleExportKey` step and then generate a certificate for that pre-existing key pair during the `handleAttestKey` step.

Finally, the implementation correctly extracts and applies the `attestationChallenge` provided during the `attestKey` transaction, ensuring the generated certificate chain contains the appropriate attestation.
2025-12-03 19:29:21 +01:00
JingMatrix 4d9787367c Increase Gradle JVM memory in build workflow
The CI build was failing with a "JVM garbage collector is thrashing" error due to insufficient memory. This commit increases the Gradle max heap size to 2GB in the GitHub Actions workflow to resolve the build failure.
2025-12-03 19:22:39 +01:00
JingMatrixandGitHub 987c7ba35c Fix value and location of moduleHash (#35)
`moduleHash` should be in the software enforced list.
However, the manual calculation of the KeyMint `moduleHash` has
failed to produce a value matching the hardware-generated attestation.

The official documentation specifies the following structure:
  Modules ::= SET OF Module
  Module ::= SEQUENCE {
      packageName       OCTET_STRING,
      version                    INTEGER,
  }
The critical requirement is that the `SET OF` elements must be sorted
lexicographically based on their full DER-encoded byte value. Despite
implementing this using Bouncy Castle's `DERSet`, the resulting hash
is still incorrect.

This commit changes the strategy to favor stability:
1.  The `DeviceAttestationService` now extracts the real `moduleHash`
    from the `softwareEnforced` list of a genuine attestation certificate
    and caches it.
2.  The `moduleHash` property now returns this cached value if available.
3.  The manual calculation remains as a fallback and is marked with a
    `TODO` to indicate the issue is unresolved.

Additionally, `ConfigurationManager` initialization is moved earlier.
2025-11-30 00:13:14 +01:00
JingMatrix 2446461aab Properly source and use verifiedBootKey
The previous implementation used a randomly generated value for the `verifiedBootKey` within the simulated attestation's Root of Trust. This is a significant discrepancy from a genuine attestation and represents a clear detection vector for any verification service that inspects the full certificate chain.

This commit introduces a robust, multi-layered approach to source and manage both the `verifiedBootKey` and the `verifiedBootHash`, ensuring the simulated attestation is as authentic as possible.
2025-11-29 19:58:02 +01:00
JingMatrix 0275eb7ad2 Preserve extension order and prevent duplicates
This commit refactors the attestation patching logic to improve stealth and ensure correctness by addressing potential detection vectors related to the ASN.1 structure of the certificate extension.

1. Preserve Extension Order: The original implementation rebuilt the entire certificate, which could alter the order of X.509 extensions. Some verification systems may be sensitive to this order. The logic is now updated to replace the attestation extension in-place, preserving the original order of all other extensions.

2. Avoid Duplicate Properties: The previous logic used an `ASN1EncodableVector` to assemble TEE-enforced properties. This could lead to duplicate entries if a property (e.g., `OS_VERSION`) was present in the original certificate and also added by the simulator. The code now uses a `MutableMap` keyed by the ASN.1 tag number. This ensures that any simulated properties overwrite the original ones, preventing duplicates and potential parsing errors.

3. Add Detailed Logging: A recursive ASN.1 formatting function has been added to provide clear and readable logs of the certificate data both before and after patching. This significantly improves debuggability.

By ensuring the patched certificate is structurally as close as possible to the original, these changes reduce the chances of the simulator being detected by attestation validation services.
2025-11-29 19:58:02 +01:00
JingMatrix f5d4ab1f44 Patch certificate chain in generateKey reply
When an application generates a key with an attestation request, the `generateKey` method returns a `KeyMetadata` object which contains the full, unpatched certificate chain.

This leaves a potential detection vector open. A sophisticated application could inspect the returned data in its own process memory and discover the original, hardware-backed certificates before they are used for attestation, thus detecting the hooking framework.

This commit introduces a post-transaction hook for the `generateKey` transaction. After the genuine KeyStore service has executed the request, this hook intercepts the reply parcel. It extracts the certificate chain from the `KeyMetadata`, applies the patching routine, and then reconstructs the reply with the modified (patched) certificate chain.
2025-11-29 19:58:02 +01:00
JingMatrixandGitHub 79145e3bff Add support for Android 11 RefBase ABI (#29)
Implements a compatibility layer to allow the binary to run on
Android 11 (API 30) and older, which lack the `incStrongRequireStrong`
symbol in their `libutils.so`.

This is achieved by creating a runtime wrapper that checks the device's
SDK version.
- On Android 12 (API 31) and newer, it dynamically loads and calls the
  `incStrongRequireStrong` function using `dlsym`.
- On older versions, it safely falls back to the universally available
  `incStrong` method.

This resolves the fatal `dlopen` error "cannot locate symbol" when
injecting the library into processes on older Android versions.

See AOSP change
https://android-review.googlesource.com/c/platform/system/core/+/1660499
2025-11-29 19:29:48 +01:00
JingMatrixandGitHub ecc1dbdaeb Fix software enforced list for certificates generation (#28)
Properly implement the `ATTESTATION_APPLICATION_ID` tag into key description.

Moreover, we add the `ATTESTATION_ID_SERIAL` tag to the TEE enforced list, and re-order all tags to remain consistent with the object `AttestationConstants`.
2025-11-29 14:20:58 +01:00
JingMatrix 9b57e14752 Release TEESimulator v2.1 2025-11-28 20:00:07 +01:00
JingMatrixandGitHub 6a58f804d7 Fix date format of vendor patch level (#24)
This was a mistake during the refactoring of TrickyStoreOSS.
After correcting it, we can obtain STRONG integrity (instead of DEVICE) with a valid keybox.

The correct format can be easily found using the `Key Attestation` app.
2025-11-28 19:45:53 +01:00
JingMatrixandGitHub 78e80391d3 Set boot digest via resetprop (#22)
The stub method `SystemProperties.set` has wrong signature and is unable to set read-only system properties.
2025-11-28 13:11:54 +01:00
QingandJingMatrix 0afcaeeb53 Clear generated key cache on keybox updates for Android 12+ (#16)
Ensures that the cache of generated keys is invalidated and cleared whenever a keybox file is updated. This prevents the system from using stale certificates after a keybox change.

Co-authored-by: JingMatrix <jingmatrix@gmail.com>
2025-11-27 23:29:43 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1e644d71e9 Bump org.bouncycastle:bcpkix-jdk18on from 1.82 to 1.83 (#13)
Bumps [org.bouncycastle:bcpkix-jdk18on](https://github.com/bcgit/bc-java) from 1.82 to 1.83.
- [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html)
- [Commits](https://github.com/bcgit/bc-java/commits)

---
updated-dependencies:
- dependency-name: org.bouncycastle:bcpkix-jdk18on
  dependency-version: '1.83'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-27 22:40:33 +01:00
JingMatrixandGitHub 3351a1c932 Clean up cached keys on successful import (#18)
Generated and attestation keys are cached, and if a key is imported with the same name, the cached key would be returned instead of the newly imported one.

This change invalidates the cached key when a key is successfully imported with the same alias.
Close #17 as fixed.

The logging has also been improved to be more consistent across the different interceptors.
2025-11-27 15:43:56 +01:00
JingMatrix ee02216534 Prepare to release TEESimulator 2.0
The following two bugs are fixed:
1. `zygisk.json` is renamed to `update.json`, which is indicated in `module.prop`.
2. To avoid over optimization of R8, we must keep certains packages, which are found after many experiments.
2025-11-26 18:34:08 +01:00
JingMatrixandGitHub 6193da0e66 Support key generation with attestation keys (#15)
This commit enhances the interception logic to correctly handle key
generation requests that specify an `attestationKey` (via
`setAttestKeyAlias`).

When an attestation key is used, the system signs the newly generated
key with it. A simple leaf certificate patch after the fact is
insufficient, as it breaks this cryptographic chain. To create a valid,
verifiable chain, we must now intercept these `generateKey` operations
and perform a full software-based key and certificate generation, even
when in patch mode.

This ensures that keys attested by other simulated keys are correctly
signed and chained together, bypassing more sophisticated detection
methods.

Fixes:
- Correctly use the `android.hardware.security.keymint.Tag` constants for
  building authorization lists, resolving a bug where internal ASN.1
  sequence indices were being used improperly.
2025-11-26 16:50:30 +01:00
JingMatrixandGitHub ee9863009a Improve logging to understand detection methods (#14)
Via extensive and detailed logging, we can inspect various detection techniques of target packages.
2025-11-26 11:43:53 +01:00
JingMatrixandGitHub fa64f941b3 Bypass detection by skipping imported keys (#12)
In patch mode, a key's origin provides a robust way to avoid modifying
user-imported keys, which is a well-known detection vector. This commit
implements a new strategy to check the `KeyOrigin` tag from the key's
metadata. If a key is marked as `IMPORTED` or `SECURELY_IMPORTED`, the
patching process is now skipped entirely.

This new origin-based check is more reliable and cleaner than the
previous fingerprinting implementation, which has been removed.

Additionally, this commit acknowledges a remaining detection vector in
patch mode: when an `attestationKey` is used, a key must be generated.
Purely software-generated keys are detectable. To address this in the
future, the full software "generate mode" must be implemented even for
devices without a broken TEE. The old key generation logic has been
stubbed with a TODO in preparation for this redesign.
2025-11-26 02:54:43 +01:00
JingMatrix 8aa93f1427 Add GitHub CI build config 2025-11-26 00:19:05 +01:00
JingMatrix 0894b44166 Add module template files
Current AOSP keybox can be found at:
https://cs.android.com/android/platform/superproject/main/+/main:device/generic/trusty/keymaster_soft_wrapped_attestation_keys.xml

However, the support of parsing private keys in iecs format is not implemented yet.
2025-11-26 00:19:05 +01:00
JingMatrix c017c9b0ab Restructure and overhaul entire Kotlin codebase
This commit introduces a complete architectural refactoring of the
Kotlin-based interception logic, based on the source of
1. https://github.com/5ec1cff/TrickyStore
2. https://github.com/beakthoven/TrickyStoreOSS

The primary purpose of this code is to intercept binder transactions to
the Android Keystore and KeyMint services. The overall workflow operates
in conjunction with a native library (injected via ptrace). The native
library hooks the binder's `transact` function and forwards pre- and
post-transaction events to the Kotlin side. This Kotlin code contains
all the high-level logic for parsing parameters, patching certificates,
and generating simulated keys.

The codebase is now organized into a clear, package-based architecture:

- attestation: Manages the creation and patching of ASN.1 attestation
  data structures.
- config: Handles loading and observing configuration files from disk.
- interception: Contains the core binder interception framework and its
  specific implementations for legacy Keystore (Android Q/R) and modern
  KeyMint/Keystore2 (Android S+).
- logging: Provides a centralized and consistent logging utility.
- pki: Manages Public Key Infrastructure, including certificate
  generation, parsing of key store XML files, and cryptographic helpers.
- util: Contains Android-specific utility functions for device properties.

This refactoring focuses on establishing a robust and extensible
architecture. The fine-tuning of the interception logic itself,
especially for corner cases in key generation and patching, is currently
under redesign and will be further refined in subsequent commits.
2025-11-26 00:19:01 +01:00
JingMatrix 9bd75d15f7 Add binder transaction interception framework
This commit introduces a comprehensive framework for intercepting and manipulating binder transactions on Android at the `ioctl` level. It provides a man-in-the-middle layer between the binder driver and user-space `libbinder`, enabling detailed analysis and control over IPC.

The core mechanism works by hooking the `ioctl` system call within the context of a target process. It specifically intercepts the `BINDER_WRITE_READ` command's return buffer from the kernel.

Key components of the framework:

- IOCTL Hook: Intercepts `BR_TRANSACTION` commands delivered by the binder driver to the process.
- Transaction Rewriting: If a transaction is intended for a monitored service, its destination is rewritten in-memory to a local `BinderStub`. The original transaction details are saved in a thread-local context.
- BinderStub: A fake binder service that receives the hijacked transaction. It retrieves the original context and delegates processing to the `BinderInterceptor`.
- BinderInterceptor: The central management class. It maintains a registry of monitored binders and their associated callback interfaces. It orchestrates the pre-transact and post-transact hooks.
- Callback Protocol: Defines a clear protocol for a remote tool to:
    - Register and unregister binders for interception.
    - Receive pre-transaction notifications and choose to: continue, modify data, skip the transaction, or provide an immediate fake reply.
    - Receive post-transaction notifications with the final result and modify the reply.
2025-11-25 19:21:05 +01:00
JingMatrix cc52307ca8 Add stub for AOSP Binder and utility components
The primary function of these stubs is to provide necessary interface definitions and that can be utilized by `binder_interceptor.cpp` during compilation (and runtime).

Crucially, `libTEESimulator.so` (which encapsulates these stubs) is dynamically loaded into the target process via `ptrace` after the system's official libraries, such as `/system/lib64/libbinder.so` and `/system/lib64/libutils.so`, have already been loaded and their symbols resolved by the dynamic linker.

Consequently, the dynamic linker will have already established bindings to the robust, canonical implementations within the system libraries for existing code paths. The dynamic linker does not automatically re-resolve or update these established symbol bindings when a new library with conflicting definitions is loaded later.

The AOSP files are downloaded via links:
1. https://android.googlesource.com/platform/frameworks/native/+/refs/heads/main/libs/binder/include/binder
2. https://android.googlesource.com/platform/system/core/+/refs/heads/main/libutils/binder/include/utils

The link for binder header in Android kernel is:
https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/include/uapi/linux/android/binder.h
2025-11-25 19:21:05 +01:00
JingMatrix ffb27915e2 Implement shared library injection via ptrace
There are still many functions in the header `utils.hpp` not implemented yet, which are however not needed for our purpose.
2025-11-25 19:20:59 +01:00
JingMatrix 9fe8919696 Feat: Add 'app' subproject and integrate LSPlt submodule
This commit introduces the main application subproject, 'app', and sets up the necessary infrastructure for the TEESimulator.

Key changes:
*   'app' Subproject Setup: Added the new :app module with its initial structure, including build files, manifest, and Kotlin main entry point.
*   LSPlt Integration: Added the LSPlt hooking framework as a Git submodule in app/src/main/cpp/external/ and configured its use in CMake.
*   Native Build Configuration: Configured the C++ build to use LSPlt statically and compile two essential native libraries: libinject.so (for injection) and libTEESimulator.so (for interception/logic).
*   Module Packaging: Implemented complex Gradle logic within app/build.gradle.kts to automate the creation of a flashable zip module (supporting Magisk, Ksu, and Apatch) with versioning based on Git information.
*   Initial Module Files: Added the template files (module.prop, update-binary, updater-script) for the flashable module structure.
2025-11-22 16:22:27 +01:00
174 changed files with 33679 additions and 58 deletions
+6
View File
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "gradle"
directory: "/"
schedule:
interval: "daily"
+169
View File
@@ -0,0 +1,169 @@
name: Build
on:
push:
branches: [ "main" ]
paths-ignore: [ '**.md' ]
pull_request:
branches: [ "main" ]
paths-ignore: [ '**.md' ]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: "recursive"
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
cache: 'gradle'
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-linux-android,armv7-linux-androideabi,i686-linux-android,x86_64-linux-android
- name: Cache Rust artifacts
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
~/.cargo/bin/cargo-ndk
native-certgen/target
key: rust-${{ runner.os }}-${{ hashFiles('native-certgen/Cargo.lock') }}
restore-keys: rust-${{ runner.os }}-
- name: Install cargo-ndk
run: command -v cargo-ndk || cargo install cargo-ndk
- name: Set up ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: ccache-${{ runner.os }}-${{ github.ref_name }}
restore-keys: |
ccache-${{ runner.os }}-${{ github.ref_name }}
ccache-${{ runner.os }}-
ccache-
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Build with Gradle
run: |
chmod +x ./gradlew
./gradlew zipRelease zipDebug -Porg.gradle.parallel=true -Porg.gradle.vfs.watch=true -Dorg.gradle.jvmargs=-Xmx2048m
- name: Read version
id: ver
run: |
ver=$(grep 'val verName' app/build.gradle.kts | sed 's/.*"\(.*\)".*/\1/')
count=$(git rev-list HEAD --count)
echo "version=${ver}-${count}" >> "$GITHUB_OUTPUT"
- name: List build artifacts
run: |
echo "Release: $(ls out/*Release*.zip | head -1) ($(du -h out/*Release*.zip | head -1 | cut -f1))"
echo "Debug: $(ls out/*Debug*.zip | head -1) ($(du -h out/*Debug*.zip | head -1 | cut -f1))"
- uses: actions/upload-artifact@v4
with:
name: TEESimulator-RS-release-zip
path: out/TEESimulator-RS-*-Release.zip
retention-days: 30
compression-level: 0
- uses: actions/upload-artifact@v4
with:
name: TEESimulator-RS-debug-zip
path: out/TEESimulator-RS-*-Debug.zip
retention-days: 7
compression-level: 0
- uses: actions/upload-artifact@v4
with:
name: release-mappings
path: app/build/outputs/mapping/release
retention-days: 30
compression-level: 9
release:
needs: build
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Read version
id: ver
run: |
ver=$(grep 'val verName' app/build.gradle.kts | sed 's/.*"\(.*\)".*/\1/')
count=$(git rev-list HEAD --count)
echo "version=${ver}-${count}" >> "$GITHUB_OUTPUT"
- uses: actions/download-artifact@v4
with:
name: TEESimulator-RS-release-zip
path: zips
- uses: actions/download-artifact@v4
with:
name: TEESimulator-RS-debug-zip
path: zips
- name: Extract changelog
run: |
ver="${VER#v}"
awk "/^## TEESimulator-RS v${ver%%-*}/{flag=1; next} /^## TEESimulator-RS v/{if(flag) exit} flag" module/changelog.md > /tmp/notes.md
cat /tmp/notes.md
env:
VER: ${{ steps.ver.outputs.version }}
- name: Create release
run: |
gh release delete "$VER" --yes 2>/dev/null || true
RELEASE=$(ls zips/*Release*.zip | head -1)
DEBUG=$(ls zips/*Debug*.zip | head -1)
gh release create "$VER" \
--title "$VER" \
--latest \
--notes-file /tmp/notes.md \
"$RELEASE" \
"$DEBUG"
env:
VER: ${{ steps.ver.outputs.version }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Bump update.json
run: |
COUNT=$(git rev-list HEAD --count)
RELEASE_NAME=$(basename zips/*Release*.zip)
ZIP_URL="https://github.com/${{ github.repository }}/releases/download/${VER}/${RELEASE_NAME}"
jq ".versionCode = $COUNT | .zipUrl = \"$ZIP_URL\"" module/update.json > /tmp/update.json
mv /tmp/update.json module/update.json
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add module/update.json
git diff --cached --quiet || {
git commit -m "chore(release): bump update.json to $VER [skip ci]"
git push origin HEAD:main
}
env:
VER: ${{ steps.ver.outputs.version }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+3
View File
@@ -0,0 +1,3 @@
[submodule "app/src/main/cpp/external/LSPlt"]
path = app/src/main/cpp/external/LSPlt
url = https://github.com/JingMatrix/LSPlt
+128 -55
View File
@@ -1,87 +1,160 @@
# TEESimulator A Full TEE Emulation Framework
<p align="center">
<h1 align="center">TEESimulator-RS</h1>
<p align="center"><b>Pass hardware security checks on a rooted Android phone</b></p>
<p align="center">
<a href="https://github.com/Enginex0/TEESimulator-RS/actions/workflows/build.yml"><img src="https://github.com/Enginex0/TEESimulator-RS/actions/workflows/build.yml/badge.svg" alt="Build"></a>
<img src="https://img.shields.io/badge/Android-10%2B-green?logo=android" alt="Android 10+">
<a href="https://t.me/superpowers9"><img src="https://img.shields.io/badge/Telegram-community-blue?logo=telegram" alt="Telegram"></a>
</p>
</p>
**TEESimulator** is a system module designed to create a complete, software-based simulation of a hardware-backed Trusted Execution Environment ([TEE](https://source.android.com/docs/security/features/trusty)) for [Key Attestation](https://developer.android.com/privacy-and-security/security-key-attestation).
---
The project's goal is to move beyond simple certificate patching and build a robust framework that can create and manage virtual, self-consistent cryptographic keys.
> [!NOTE]
> This is a fork of [JingMatrix/TEESimulator](https://github.com/JingMatrix/TEESimulator). It adds certificate generation written in Rust, generated keys that survive reboots, and attestation behavior that matches stock Android. See the upstream repo for the original project.
## ✨ Core Principles
## What it does
* **Bypass Hardware-Backed Attestation:** The primary goal of this project is to defeat Key Attestation, a security mechanism that allows apps to verify that they are running on a secure, unmodified device. This module provides the tools to bypass these checks on rooted or modified devices.
* **Stateful Emulation:** Instead of patching responses from the real TEE, the ultimate goal is to create and manage virtual keys entirely in a simulated software environment. Any request concerning a virtual key will be handled by the simulator, ensuring perfect consistency without ever touching the real hardware.
* **Architectural Interception:** By hooking low-level Binder IPC calls to the Keystore, the framework can transparently redirect requests for virtual keys to the software-based simulator, while allowing requests for real keys to pass through to the hardware TEE.
* **100% FOSS:** Licensed under GPLv3, ensuring it stays free, auditable, and compliant with open-source laws.
Some Android apps refuse to run on a rooted phone. They ask the phone to prove it still has a genuine security chip, a check called hardware attestation. A rooted phone normally fails that check.
## 📱 Requirements
- Android 10 or above
TEESimulator makes it pass. Android runs a system process named `keystore2` that answers these proof requests. TEESimulator sits in front of `keystore2`, watches for the requests apps make to create keys and read their certificates, and builds the proof itself: a full chain of certificates signed by your `keybox.xml`. To the app, the phone looks genuine.
## 📦 Installation & Configuration
It replaces TrickyStore and its forks completely. It reads config from the same files, so you can switch without moving anything, but the internals are rewritten: certificates are generated in Rust, keys are saved across reboots, and each app gets its own limit on how fast it can request hardware-backed keys.
1. Flash this module via (Magisk / KernelSU / APatch) and reboot. It will replace [TrickyStore](https://github.com/5ec1cff/TrickyStore), [TrickyStoreOSS](https://github.com/beakthoven/TrickyStoreOSS) and their forks.
2. (Optional) Place a hardware-backed `keybox.xml` at `/data/adb/tricky_store/keybox.xml`. This provides the cryptographic "root of trust" for the simulator.
3. (Optional) Customize target packages in `/data/adb/tricky_store/target.txt`.
4. (Optional) Customize the simulated security patch level in `/data/adb/tricky_store/security_patch.txt`.
5. Enjoy!
## Requirements
**All configuration files are monitored and will take effect immediately upon saving.**
> [!IMPORTANT]
> You need a valid `keybox.xml`. This is the file used to sign the proof. Without it, TEESimulator can only produce software-only certificates, which strict apps reject.
### The `keybox.xml` Root of Trust
1. Android 10 or newer
2. A root manager: KernelSU, Magisk, or APatch
3. A `keybox.xml` file at `/data/adb/tricky_store/keybox.xml`
This file provides the master cryptographic identity for the simulator. It contains a private key and a valid, hardware-backed certificate chain from a real device. The simulator uses this to sign the virtual certificates it generates, making them appear legitimate to verifiers.
## Quick start
```xml
<?xml version="1.0"?>
<AndroidAttestation>
<Keybox DeviceID="...">
<Key algorithm="ecdsa|rsa">
<PrivateKey format="pem">...</PrivateKey>
<CertificateChain>...</CertificateChain>
</Key>
</Keybox>
</AndroidAttestation>
1. Download the latest ZIP from [Releases](https://github.com/Enginex0/TEESimulator-RS/releases).
2. Install it with your root manager, then reboot.
3. Put your `keybox.xml` at `/data/adb/tricky_store/keybox.xml`.
4. List the apps you want to cover in `/data/adb/tricky_store/target.txt`.
5. Check that it works with Play Integrity or the Key Attestation Demo app.
## How it works
```
App
| asks the phone to prove it has real security hardware
v
+----------------------------------------------------+
| keystore2 (the Android process that answers) |
| |
| ioctl <- TEESimulator hooks the call here |
| | |
| v |
| builds a certificate chain and signs it |
| with your keybox.xml |
+----------------------------------------------------+
| the signed chain goes back to the app
v
App -> sees a genuine, hardware-backed device
```
### Mode and Keybox Configuration (`target.txt`)
**Certificate generation in Rust.** A native library, `libcertgen.so`, builds the X.509 certificate chains in Rust with the `ring` crypto library, encoding the bytes by hand in DER, the standard certificate format. Three key types fall outside `ring`'s support (the P-224, P-521, and Curve25519 curves); for those it falls back to Java's BouncyCastle.
TEESimulator currently operates in two primary modes as it transitions towards full emulation.
You can control the simulation mode and the specific keybox.xml file used on a per-package basis.
**Hooking keystore2.** Inside the `keystore2` process, TEESimulator redirects `ioctl`, the low-level system call Android uses to pass messages between processes. It does this with `lsplt`, a hooking library. From there it can read and answer three kinds of request: creating a key, importing a key, and fetching a key's certificate.
#### Mode Suffixes
**Matching stock Android.** The output matches what a real device produces. Keys that are not attested get self-signed certificates. The fields inside the attestation record keep the same order. Fields that only exist on certain Android versions appear only on those versions. The same usage checks run before a key is used.
* **`!` → Force Generation Mode:** Creates a complete, software-based virtual key. This is the foundation of the full TEE simulation.
* **`?` → Force Leaf Hacking Mode:** A legacy mode where a real TEE key is generated, but its attestation certificate is intercepted and modified.
* **No symbol → Automatic Mode:** The module selects the most appropriate mode for the device.
**Keys that survive reboots.** Generated keys are written to disk and stay valid after a restart. File locking stops two writers from corrupting the store.
#### Multi-Keybox Configuration
**Per-app rate limit.** Each app may request at most 2 hardware-backed keys per 30 seconds, and only 2 at a time. Past that, it receives a software-only certificate.
You can specify different keybox files for different groups of applications. This is done by adding a line with the filename in square brackets (e.g., [demo_keybox.xml]).
## Configuration
All applications listed after this line will use the specified keybox file, until a new keybox is declared. Applications listed before any custom keybox declaration will use the default `keybox.xml`.
All config files live in `/data/adb/tricky_store/`. TEESimulator reloads them the moment you save, so a reboot is not needed.
### target.txt
Lists the apps TEESimulator handles, one package name per line. A suffix sets how each app is handled.
| Suffix | What it does |
|--------|--------------|
| `!` | Always make a software key |
| `?` | Keep the real hardware key, patch only its certificate |
| none | Decide automatically |
To use more than one keybox, add a `[filename.xml]` header above the apps that should use that file:
For example:
```
# These two apps will use the default /data/adb/tricky_store/keybox.xml
com.google.android.gms!
io.github.vvb2060.keyattestation?
# Switch to a different keybox for the following apps.
# The file must be located at /data/adb/tricky_store/aosp_keybox.xml
[aosp_keybox.xml]
com.google.android.gsf
# Switch again to another keybox.
# The file must be located at /data/adb/tricky_store/demo_keybox.xml
[demo_keybox.xml]
org.matrix.demo
```
### Security Patch Level (`security_patch.txt`)
### security_patch.txt
This allows you to configure the security patch level that the simulator will report in its forged attestation certificates.
Sets the security patch dates reported in the attestation certificates. Global defaults go at the top. Override them for one app with a `[package.name]` header.
| Key | What it sets |
|-----|--------------|
| `system` | OS patch level |
| `vendor` | Vendor patch level |
| `boot` | Boot and kernel patch level |
| `all` | All three at once |
Accepted values: `today`, a `YYYY-MM-DD` template, `no` to omit the field, `device_default`, or `prop` to read the value from a system property.
```
# Advanced Configuration
system=2025-11
boot=no # Do not report a boot patch level
vendor=20251101 # Report a specific vendor patch level
system=YYYY-MM-05
vendor=device_default
boot=no
[com.google.android.gms]
system=2025-10-01
```
**Note:** This only affects the Key Attestation data generated by the simulator. It does not change system properties.
### boot_props_mode
Controls global `ro.boot.*` property spoofing. Values: `auto` (default), `force`, or `disable`.
In `auto`, Oplus-family devices (OnePlus/OPPO/realme/Oplus) skip boot-state prop spoofing to avoid conflicts with vendor TEE services such as ultrasonic fingerprint calibration. Create `/data/adb/tricky_store/boot_props_mode` with `force` to restore the old behavior, or `disable` to turn it off on any device.
## Building from source
You need JDK 21, the Android SDK and NDK 29, Rust (stable) with the `aarch64-linux-android` target, and `cargo-ndk`.
```bash
git clone --recursive https://github.com/Enginex0/TEESimulator-RS.git
cd TEESimulator-RS
./gradlew zipRelease zipDebug
```
The ZIPs land in `out/`. Gradle runs `cargo ndk` for you to cross-compile `libcertgen.so`. To build on CI instead, push to `main` or run Actions > Build > Run workflow.
## Compatibility
| Root manager | Status |
|---|---|
| KernelSU | Tested, including the Action button and lifecycle scripts |
| Magisk | Supported |
| APatch | Supported |
## Community
<p align="center">
<a href="https://t.me/superpowers9">
<img src="https://img.shields.io/badge/SuperPowers_Telegram-Join-blue?style=for-the-badge&logo=telegram" alt="Telegram">
</a>
</p>
## Credits
- [JingMatrix](https://github.com/JingMatrix/TEESimulator) for the original TEESimulator and its interception design
- [ring](https://github.com/briansmith/ring) for the Rust cryptography
- [fatalcoder524](https://github.com/fatalcoder524) for contributions and collaboration
- [huguangares](https://github.com/huguangares) for collaboration and testing
## License
[GNU General Public License v3.0](LICENSE)
+300
View File
@@ -0,0 +1,300 @@
import com.android.build.api.artifact.SingleArtifact
import java.io.ByteArrayOutputStream
import javax.inject.Inject
import org.gradle.process.ExecOperations
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.ktfmt)
}
ktfmt { kotlinLangStyle() }
// Helper class to get access to the ExecOperations service
abstract class GitExecutor @Inject constructor(private val execOperations: ExecOperations) {
fun execute(command: String, currentWorkingDir: File): String {
val byteOut = ByteArrayOutputStream()
execOperations.exec {
workingDir = currentWorkingDir
commandLine = command.split("\\s".toRegex())
standardOutput = byteOut
}
return String(byteOut.toByteArray()).trim()
}
}
// Instantiate the helper class using Gradle's object factory
val gitExecutor = objects.newInstance(GitExecutor::class.java)
// versionCode = git commit count + floor offset. The 2026-07-08 public-release
// history scrub (0f1143a) rewrote history and dropped the raw commit count below
// the build number already shipped to testers (298), so post-scrub counts read as
// downgrades. The floor offset lifts versionCode back above that peak and keeps it
// monotonic across the rewrite; each later commit still bumps it by one.
val versionCodeFloorOffset = 5
val gitCommitCount =
gitExecutor.execute("git rev-list HEAD --count", rootDir).toInt() + versionCodeFloorOffset
val gitCommitHash = gitExecutor.execute("git rev-parse --verify --short HEAD", rootDir)
val verName = "v6.0.1"
android {
namespace = "org.matrix.TEESimulator"
compileSdk = 36
ndkVersion = "27.3.13750724"
buildToolsVersion = "36.0.0"
defaultConfig {
applicationId = "org.matrix.TEESimulator"
minSdk = 29
targetSdk = 36
versionCode = gitCommitCount
versionName = verName
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles("proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
buildFeatures { buildConfig = true }
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
buildStagingDirectory = layout.buildDirectory.get().asFile
}
}
}
kotlin { compilerOptions { jvmTarget.set(JvmTarget.JVM_21) } }
dependencies {
compileOnly(project(":stub"))
compileOnly(libs.annotation)
implementation(libs.bcpkix)
}
// --- Rust native cert gen build task ---
val buildRustCertgen by
tasks.registering(Exec::class) {
group = "TEESimulator-RS Native Build"
description = "Builds libcertgen.so via cargo-ndk for arm64-v8a."
workingDir = rootProject.projectDir.resolve("native-certgen")
commandLine(
"cargo",
"ndk",
"-t",
"arm64-v8a",
"-o",
rootProject.projectDir.resolve("app/src/main/jniLibs").absolutePath,
"build",
"--release",
)
inputs.dir(rootProject.projectDir.resolve("native-certgen/src"))
inputs.file(rootProject.projectDir.resolve("native-certgen/Cargo.toml"))
inputs.file(rootProject.projectDir.resolve("native-certgen/Cargo.lock"))
outputs.dir(rootProject.projectDir.resolve("app/src/main/jniLibs"))
environment("ANDROID_NDK_HOME", android.ndkDirectory.absolutePath)
environment(
"PATH",
"${System.getProperty("user.home")}/.cargo/bin:${System.getenv("PATH") ?: ""}",
)
}
// AGP auto-detects jniLibs/ as an input to mergeJniLibFolders — wire the dependency
tasks.configureEach {
if (name.endsWith("JniLibFolders") && name.startsWith("merge")) {
dependsOn(buildRustCertgen)
}
}
// Auto-rewrite module/update.json on every packaging build so versionCode and
// zipUrl track gitCommitCount automatically, matching module.prop.
val refreshUpdateJson by
tasks.registering {
group = "TEESimulator-RS Module Packaging"
description = "Rewrite module/update.json to match current verName and gitCommitCount."
val updateJsonFile = rootProject.projectDir.resolve("module/update.json")
val capturedVerName = verName
val capturedCount = gitCommitCount
inputs.property("verName", capturedVerName)
inputs.property("gitCommitCount", capturedCount)
outputs.file(updateJsonFile)
doLast {
val fullVer = "$capturedVerName-$capturedCount"
updateJsonFile.writeText(
"""{
"version": "$fullVer",
"versionCode": $capturedCount,
"zipUrl": "https://github.com/Enginex0/TEESimulator-RS/releases/download/$fullVer/TEESimulator-RS-$fullVer-Release.zip",
"changelog": "https://raw.githubusercontent.com/Enginex0/TEESimulator-RS/main/module/changelog.md"
}
"""
)
}
}
androidComponents {
onVariants(selector().all()) { variant ->
val capitalized = variant.name.replaceFirstChar { it.uppercase() }
val isDebug = variant.buildType == "debug"
// --- Define output locations and file names ---
// Stage all files in a temporary directory inside 'build' before zipping
val tempModuleDir = project.layout.buildDirectory.dir("module/${variant.name}")
val zipFileName = "TEESimulator-RS-$verName-$gitCommitCount-$capitalized.zip"
// Task 1: Prepare all module files in the temporary build directory.
// Using Sync ensures that stale files from previous runs are removed.
val prepareModuleFilesTask =
tasks.register<Sync>("prepareModuleFiles${capitalized}") {
group = "TEESimulator-RS Module Packaging"
description = "Prepares all files for the ${variant.name} module zip."
if (isDebug) {
dependsOn("package${capitalized}")
} else {
dependsOn("minify${capitalized}WithR8")
dependsOn("strip${capitalized}DebugSymbols")
}
dependsOn(buildRustCertgen)
dependsOn(refreshUpdateJson)
if (isDebug) {
from(variant.artifacts.get(SingleArtifact.APK)) {
include("*.apk")
rename { "service.apk" }
}
} else {
from(
project.layout.buildDirectory.dir(
"intermediates/dex/${variant.name}/minify${capitalized}WithR8"
)
) {
include("classes.dex")
}
}
val nativeLibsDir =
if (isDebug) {
"intermediates/merged_native_libs/${variant.name}/merge${capitalized}NativeLibs/out/lib"
} else {
"intermediates/stripped_native_libs/${variant.name}/strip${capitalized}DebugSymbols/out/lib"
}
from(project.layout.buildDirectory.dir(nativeLibsDir)) {
into("lib")
include(
"**/libinject.so",
"**/libTEESimulator.so",
"**/libsupervisor.so",
"**/libcertgen.so",
)
}
// 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("diag.sh") // Debug-only diagnostic plane; included for debug below.
}
// Copy and filter the module.prop template separately.
from(sourceModuleDir) {
include("module.prop")
// Use expand() for simple key-value replacement.
expand(
"REPLACEMEVERCODE" to gitCommitCount.toString(),
"REPLACEMEVER" to "$verName-$gitCommitCount",
)
}
if (isDebug) {
from(sourceModuleDir) { include("diag.sh") }
}
// The destination for all the above 'from' operations.
into(tempModuleDir)
if (isDebug) {
doLast {
// Debug-only: grant the keystore + soterserver (platform_app) domains
// external-storage access for the per-UID NDJSON sink. diag.sh (shipped
// only in debug) carries the shell side of the diagnostic plane.
tempModuleDir.get().asFile.resolve("sepolicy.rule")
.appendText(
"\nallow keystore media_rw_data_file { dir file } *" +
"\nallow platform_app media_rw_data_file { dir file } *\n",
)
}
}
}
// Task 2: Zip the prepared files from the temporary directory.
val zipTask =
tasks.register<Zip>("zip${capitalized}") {
group = "TEESimulator-RS Module Packaging"
description = "Creates the flashable zip for the ${variant.name} module."
dependsOn(prepareModuleFilesTask)
archiveFileName.set(zipFileName)
destinationDirectory.set(project.rootDir.resolve("out"))
from(tempModuleDir) // Zip the entire contents of the staging directory.
}
// Task 3: A helper function to create installation tasks for different root providers.
fun createInstallTasks(rootProvider: String, installCli: String) {
val pushTask =
tasks.register<Exec>("push${rootProvider}Module${capitalized}") {
group = "TEESimulator-RS Module Installation"
description =
"Pushes the ${variant.name} module to the device for $rootProvider."
dependsOn(zipTask)
commandLine(
"adb",
"push",
zipTask.get().archiveFile.get().asFile,
"/data/local/tmp",
)
}
val installTask =
tasks.register<Exec>("install${rootProvider}${capitalized}") {
group = "TEESimulator-RS Module Installation"
description = "Installs the ${variant.name} module via $rootProvider."
dependsOn(pushTask)
commandLine(
"adb",
"shell",
"su",
"-c",
"$installCli /data/local/tmp/$zipFileName",
)
}
tasks.register<Exec>("install${rootProvider}AndReboot${capitalized}") {
group = "TEESimulator-RS Module Installation"
description = "Installs the ${variant.name} module via $rootProvider and reboots."
dependsOn(installTask)
commandLine("adb", "reboot")
}
}
createInstallTasks("Magisk", "magisk --install-module")
createInstallTasks("Ksu", "ksud module install")
createInstallTasks("Apatch", "/data/adb/apd module install")
}
}
+15
View File
@@ -0,0 +1,15 @@
-keep class org.matrix.TEESimulator.interception.keystore.** { *; }
-keep class org.bouncycastle.jcajce.provider.** { *; }
-keep class org.bouncycastle.jce.provider.** { *; }
-dontwarn javax.naming.**
-keepclasseswithmembers class org.matrix.TEESimulator.App {
public static void main(java.lang.String[]);
}
-keepclasseswithmembers class org.matrix.TEESimulator.pki.NativeCertGen {
native <methods>;
*;
}
-keep class org.matrix.TEESimulator.pki.CertGenConfig { *; }
+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />
+32
View File
@@ -0,0 +1,32 @@
cmake_minimum_required(VERSION 3.10)
project(TEESimulator)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG")
# LSPlt configuration
OPTION(LSPLT_BUILD_SHARED OFF)
add_subdirectory(external/LSPlt/lsplt/src/main/jni)
add_compile_definitions(BINDER_DISABLE_NATIVE_HANDLE)
add_library(utils SHARED stub/stub_utils.cpp)
target_include_directories(utils PUBLIC external/AOSP/include)
add_library(binder SHARED stub/stub_binder.cpp)
target_include_directories(binder PUBLIC external/AOSP/include)
target_link_libraries(binder PRIVATE utils)
add_executable(libinject.so inject/main.cpp inject/utils.cpp)
target_include_directories(libinject.so PUBLIC include)
target_link_libraries(libinject.so PRIVATE lsplt_static)
add_executable(libsupervisor.so supervisor.cpp)
target_link_libraries(libsupervisor.so PRIVATE log)
add_library(${CMAKE_PROJECT_NAME} SHARED binder_interceptor.cpp)
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC external/linux-kernel/include include)
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE binder lsplt_static utils)
+724
View File
@@ -0,0 +1,724 @@
#include <android/binder.h>
#include <binder/Binder.h>
#include <binder/Common.h>
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/Parcel.h>
#include <sys/ioctl.h>
#include <utils/StrongPointer.h>
#include <atomic>
#include <cinttypes>
#include <map>
#include <mutex>
#include <queue>
#include <shared_mutex>
#include <string_view>
#include <thread>
#include <utility>
#include "logging.hpp"
#include "lsplt.hpp"
/**
* =========================================================================================
* BINDER INTERCEPTION LOGIC
* =========================================================================================
*
* [ Application / libbinder.so ] [ Android System / Service ]
* | ^
* | (1. Calls ioctl(BINDER_WRITE_READ) to wait for work) |
* v |
* [ Kernel Driver ] <------------------------------------------------------+
* |
* | (2. Kernel has an incoming transaction for this process,
* | prepares a BR_TRANSACTION command in the read_buffer)
* |
* v
* [ return from ioctl() is HOOKED ]
* |
* +---(3. Hook inspects the read_buffer from the Kernel)
* |
* +--- If a BR_TRANSACTION targets a monitored Binder:
* | (4) Rewrites the transaction's target to our BinderStub
* |
* v
* [ libbinder.so ]
* |
* | (5. libbinder processes the (modified) buffer and
* | dispatches the command to the BinderStub)
* |
* v
* [ BinderStub::onTransact ]
* |
* v
* [ BinderInterceptor ]
* |
* +---(6. Pre-Process / Modify / Log)
* |
* +---(7. Forward to Real Target) ----> [ Real Target BBinder ]
* |
* +---(8. Post-Process Reply)
* |
* v
* [ (9) Return Result to libbinder ]
*
* --- Explanation of the Flow ---
*
* This diagram illustrates a "man-in-the-middle" attack on the Binder framework, achieved
* by hooking the ioctl system call within the application's process.
*
* 1. Waiting for Work:
* An application's binder thread calls `ioctl()` with the `BINDER_WRITE_READ` command.
* This call typically blocks in the kernel, waiting for incoming transactions or other commands.
*
* 2. Kernel Prepares Command:
* When an external process sends a transaction to a service hosted in this application,
* the kernel driver prepares a `BR_TRANSACTION` command and places it in the `read_buffer`
* associated with the waiting `ioctl` call.
*
* 3. Interception on Return:
* The `ioctl()` call returns to userspace.
* Our hook intercepts this return. It now has access to the `read_buffer`
* populated by the kernel *before* `libbinder` gets to see it.
*
* 4. Hijacking:
* The hook parses the `read_buffer`. If it finds a `BR_TRANSACTION` command destined
* for a service that is registered with our `BinderInterceptor`, it rewrites the transaction data in-place.
* Specifically, it changes the target binder handle to that of our `BinderStub`
* and saves the original transaction details in a thread-local map.
*
* 5. Dispatch to Stub:
* The hook then returns control to the original caller, `libbinder`.
* `libbinder` proceeds to parse the now-modified buffer.
* Seeing a transaction for `BinderStub`, it invokes its `onTransact` method.
*
* 6. Pre-Processing:
* The `BinderStub` retrieves the original, unmodified transaction details from the thread-local map.
* It then passes control to the `BinderInterceptor`, which can log, modify,
* or block the transaction before it reaches its real destination.
*
* 7. Forwarding:
* The `BinderInterceptor` forwards the (potentially modified) transaction to the original,
* intended `BBinder` service.
*
* 8. Post-Processing:
* After the real service processes the transaction and generates a reply,
* the reply is returned to the `BinderInterceptor`,
* which gets a final chance to inspect or modify the result.
*
* 9. Return Result:
* The final result is returned up the call stack to `libbinder`,
* which sends the reply back to the kernel driver to be delivered to the original caller.
*
*
* =========================================================================================
**/
using namespace android;
// =============================================================================================
// Constants and Protocols
// =============================================================================================
namespace {
namespace intercept {
// Interceptor protocol codes (User space agreement between App and Interceptor Service)
constexpr uint32_t kRegisterInterceptor = 1;
constexpr uint32_t kUnregisterInterceptor = 2;
constexpr uint32_t kPreTransact = 1;
constexpr uint32_t kPostTransact = 2;
constexpr uint32_t kActionSkipTransaction = 1;
constexpr uint32_t kActionContinue = 2;
constexpr uint32_t kActionOverrideReply = 3;
constexpr uint32_t kActionOverrideData = 4;
constexpr uint32_t kActionContinueAndSkipPost = 5;
constexpr uint32_t kBackdoorCode = 0xdeadbeef;
// Strings for LibBinder hooks
constexpr std::string_view kBinderLibName = "/libbinder.so";
constexpr std::string_view kIoctlSymbol = "ioctl";
} // namespace intercept
// =============================================================================================
// Binder Driver Protocol Definitions (Ref: Android Kernel Header)
// =============================================================================================
// Use an X-Macro to define a list of all binder return protocols. This allows us
// to generate a string conversion function without a massive, hard-to-maintain switch statement.
#define BINDER_RETURN_COMMAND_LIST(X) \
X(BR_ERROR) \
X(BR_OK) \
X(BR_TRANSACTION_SEC_CTX) \
X(BR_TRANSACTION) \
X(BR_REPLY) \
X(BR_ACQUIRE_RESULT) \
X(BR_DEAD_REPLY) \
X(BR_TRANSACTION_COMPLETE) \
X(BR_INCREFS) \
X(BR_ACQUIRE) \
X(BR_RELEASE) \
X(BR_DECREFS) \
X(BR_ATTEMPT_ACQUIRE) \
X(BR_NOOP) \
X(BR_SPAWN_LOOPER) \
X(BR_FINISHED) \
X(BR_DEAD_BINDER) \
X(BR_CLEAR_DEATH_NOTIFICATION_DONE) \
X(BR_FAILED_REPLY) \
X(BR_FROZEN_REPLY) \
X(BR_ONEWAY_SPAM_SUSPECT) \
X(BR_TRANSACTION_PENDING_FROZEN) \
X(BR_FROZEN_BINDER) \
X(BR_CLEAR_FREEZE_NOTIFICATION_DONE)
// Helper macro to generate a 'case CMD: return "CMD";' line.
#define GENERATE_CASE_STRING(CMD) \
case CMD: \
return #CMD;
/**
* @brief Converts a binder driver return command code into its string representation.
* @param cmd The command code (e.g., BR_TRANSACTION).
* @return A string literal of the command name or "UNKNOWN_BR_COMMAND".
*/
const char *getBinderReturnCommandName(uint32_t cmd) {
switch (cmd) {
BINDER_RETURN_COMMAND_LIST(GENERATE_CASE_STRING)
default:
return "UNKNOWN_BR_COMMAND";
}
}
} // namespace
// =============================================================================================
// Global State & Forward Declarations
// =============================================================================================
// Original ioctl function pointer
int (*g_original_ioctl)(int fd, int request, ...) = nullptr;
// Unique ID generator for transactions
static std::atomic<uint64_t> g_transaction_id_counter = 0;
// Context info to pass from the ioctl hook (processBinderWriteRead) to the BinderStub.
struct ThreadTransactionInfo {
uint64_t transaction_id;
uint32_t transaction_code;
wp<BBinder> target_binder;
// Default constructor
ThreadTransactionInfo() : transaction_id(0), transaction_code(0) {}
ThreadTransactionInfo(uint64_t id, uint32_t code, wp<BBinder> target)
: transaction_id(id), transaction_code(code), target_binder(std::move(target)) {}
};
// A map keyed by thread ID. When ioctl intercepts a transaction intended for us,
// it pushes the info here. When the runtime calls our Stub, it pops the info.
static std::mutex g_thread_context_mutex;
static std::map<std::thread::id, std::queue<ThreadTransactionInfo>> g_thread_context_map;
// =============================================================================================
// Class: BinderInterceptor
// Logic: Manages the registry of intercepted Binders and handles the protocol (Pre/Post calls).
// =============================================================================================
class BinderInterceptor : public BBinder {
struct RegistrationEntry {
wp<IBinder> target;
sp<IBinder> callback_interface;
std::vector<uint32_t> filtered_codes;
};
mutable std::shared_mutex registry_mutex_;
std::map<wp<IBinder>, RegistrationEntry> registry_;
public:
BinderInterceptor() = default;
bool shouldIntercept(const wp<BBinder> &target, uint32_t code) const {
std::shared_lock lock(registry_mutex_);
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
bool processInterceptedTransaction(uint64_t tx_id, sp<BBinder> target, uint32_t code, const Parcel &data,
Parcel *reply, uint32_t flags, status_t &result);
protected:
// Handle configuration commands sent to the Interceptor itself
status_t onTransact(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) override;
private:
status_t handleRegister(const Parcel &data);
status_t handleUnregister(const Parcel &data);
// Helpers to serialize data for the remote callback interface
status_t writeTransactionData(Parcel &out, uint64_t tx_id, sp<BBinder> target, uint32_t code, uint32_t flags,
const Parcel &in_data) const;
};
static sp<BinderInterceptor> g_interceptor_instance = nullptr;
// =============================================================================================
// Class: BinderStub
// Logic: The "Dummy" binder that acts as the destination for intercepted calls.
// It retrieves context from the global map and delegates to BinderInterceptor.
// =============================================================================================
class BinderStub : public BBinder {
public:
const String16& getInterfaceDescriptor() const override {
static const String16 kDescriptor("org.matrix.TEESimulator.BinderStub");
return kDescriptor;
}
protected:
status_t onTransact(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) override {
if (code != intercept::kBackdoorCode) {
LOGE("BinderStub received an unexpected direct call with code %u! This is a bug or misuse.", code);
return UNKNOWN_TRANSACTION;
}
ThreadTransactionInfo info;
bool found_context = false;
// 1. Retrieve the context for this thread (set previously by inspectAndRewriteTransaction)
{
std::lock_guard<std::mutex> lock(g_thread_context_mutex);
auto it = g_thread_context_map.find(std::this_thread::get_id());
if (it != g_thread_context_map.end() && !it->second.empty()) {
info = std::move(it->second.front());
it->second.pop();
if (it->second.empty()) {
g_thread_context_map.erase(it); // Cleanup to prevent memory leak
}
found_context = true;
}
}
if (!found_context) {
LOGW("BinderStub received transaction but no context found for thread");
return UNKNOWN_TRANSACTION;
}
// 2. Handle special "Backdoor" to get the Interceptor reference
if (info.transaction_code == intercept::kBackdoorCode && info.target_binder == nullptr && reply) {
LOGD("Backdoor handshake received.");
reply->writeStrongBinder(g_interceptor_instance);
return OK;
}
// 3. Promote the weak reference to the real target
sp<BBinder> real_target = info.target_binder.promote();
if (!real_target) {
LOGE("[TX_ID: %" PRIu64 "] Target binder is dead.", info.transaction_id);
return DEAD_OBJECT;
}
// 4. Delegate to the Interceptor logic
status_t status = OK;
bool interceptorManagedFlow = g_interceptor_instance->processInterceptedTransaction(
info.transaction_id, real_target, info.transaction_code, data, reply, flags, status);
// 5. If Interceptor logic says "Forward it", we call the original binder
if (!interceptorManagedFlow) {
LOGV("[TX_ID: %" PRIu64 "] Forwarding to original implementation.", info.transaction_id);
status = real_target->transact(info.transaction_code, data, reply, flags);
}
return status;
}
};
static sp<BinderStub> g_stub_instance = nullptr;
// =============================================================================================
// Hook Logic: IOCTL & Buffer Parsing
// =============================================================================================
namespace {
void inspectAndRewriteTransaction(binder_transaction_data *txn_data) {
if (!txn_data || txn_data->target.ptr == 0)
return;
// AIDL methods use codes in [FIRST_CALL_TRANSACTION, LAST_CALL_TRANSACTION] (1..0x00ffffff).
// System transactions (PING, INTERFACE, DUMP, SHELL_COMMAND) use codes above that range.
// Skip those — intercepting a ping adds measurable latency that timing detectors flag.
if (txn_data->code > 0x00ffffffu && txn_data->code != intercept::kBackdoorCode)
return;
bool hijack = false;
ThreadTransactionInfo info;
// Check 1: Root user backdoor for retrieving the interceptor service binder
if (txn_data->code == intercept::kBackdoorCode && txn_data->sender_euid == 0) {
info.transaction_code = intercept::kBackdoorCode;
info.target_binder = nullptr;
hijack = true;
// Check 2: Spoof uid of KeyStore requests from the daemon to bypass permission check
} else if (txn_data->sender_euid == 0) {
// The kernel driver fills sender_euid.
// libbinder.so trusts this value to populate IPCThreadState.
txn_data->sender_euid = 1000;
LOGV("[Hook] Spoofing UID for transaction: 0 -> %d", txn_data->sender_euid);
hijack = false; // Never hijack to avoid recursion
// Check 3: Normal interception based on registry of monitored binders
} else {
// Safe casting based on Binder driver ABI
RefBase::weakref_type *weak_ref = reinterpret_cast<RefBase::weakref_type *>(txn_data->target.ptr);
// Try to acquire a temporary strong reference to check the object safely
if (weak_ref && weak_ref->attemptIncStrong(nullptr)) {
// The raw pointer to the binder object itself is stored in the cookie
BBinder *target_binder_ptr = reinterpret_cast<BBinder *>(txn_data->cookie);
// Create a weak pointer for the lookup and to store in our context map.
// This is safe because we are holding a strong reference.
wp<BBinder> wp_target = target_binder_ptr;
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);
}
}
if (hijack) {
uint64_t tx_id = ++g_transaction_id_counter;
info.transaction_id = tx_id;
// tx_id is the same counter handed to the Kotlin interceptor, and sender_euid is the
// calling app; together they correlate this native hijack with that UID's per-UID file.
LOGV("[Hook] Hijacking Transaction %" PRIu64 " (Code: %u, uid=%u)", tx_id, txn_data->code,
txn_data->sender_euid);
// Rewrite the destination to our Stub
txn_data->target.ptr = reinterpret_cast<uintptr_t>(g_stub_instance->getWeakRefs());
txn_data->cookie = reinterpret_cast<uintptr_t>(g_stub_instance.get());
txn_data->code = intercept::kBackdoorCode;
// 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));
}
}
/**
* @brief Parses the read buffer from a BINDER_WRITE_READ ioctl call, which contains
* commands sent from the kernel driver to userspace.
* @param bwr The binder_write_read struct containing buffer pointers and sizes.
*/
void processBinderReadBuffer(const binder_write_read &bwr) {
if (bwr.read_size == 0 || bwr.read_consumed == 0 || bwr.read_buffer == 0)
return;
uintptr_t ptr = bwr.read_buffer;
uintptr_t end = ptr + bwr.read_consumed;
while (ptr < end) {
if (end - ptr < sizeof(uint32_t))
break;
uint32_t cmd = *reinterpret_cast<const uint32_t *>(ptr);
ptr += sizeof(uint32_t);
size_t cmd_size = _IOC_SIZE(cmd);
if (ptr + cmd_size > end) {
LOGE("[Hook] Buffer overrun parsing command 0x%x", cmd);
break;
}
if (__builtin_expect(cmd == BR_TRANSACTION || cmd == BR_TRANSACTION_SEC_CTX, 0)) {
binder_transaction_data *txn;
if (cmd == BR_TRANSACTION_SEC_CTX) {
txn = &reinterpret_cast<binder_transaction_data_secctx *>(ptr)->transaction_data;
} else {
txn = reinterpret_cast<binder_transaction_data *>(ptr);
}
inspectAndRewriteTransaction(txn);
}
ptr += cmd_size;
}
}
} // namespace
// =============================================================================================
// The Actual Hook Function
// =============================================================================================
int intercepted_ioctl(int fd, int request, ...) {
va_list ap;
va_start(ap, request);
void *arg = va_arg(ap, void *);
va_end(ap);
// 1. Call original kernel ioctl to let the driver do its work
int result = g_original_ioctl(fd, request, arg);
if (result >= 0 && request == BINDER_WRITE_READ && arg != nullptr) {
const auto *bwr = static_cast<const binder_write_read *>(arg);
// Fast reject: only enter the parser if the buffer could contain a BR_TRANSACTION.
// Pings, ref ops, and looper management never produce BR_TRANSACTION, so scanning
// their buffers is pure overhead (~2-5us per ioctl in debug builds).
if (bwr->read_consumed >= sizeof(uint32_t)) {
uint32_t first_cmd = *reinterpret_cast<const uint32_t *>(bwr->read_buffer);
if (first_cmd == BR_TRANSACTION || first_cmd == BR_TRANSACTION_SEC_CTX
|| bwr->read_consumed > sizeof(uint32_t) + _IOC_SIZE(first_cmd)) {
processBinderReadBuffer(*bwr);
}
}
}
return result;
}
// =============================================================================================
// BinderInterceptor Implementation
// =============================================================================================
// Placed at the top of the .cpp file, inside the BinderInterceptor implementation section.
#define VALIDATE_STATUS(tx_id, expr) \
do { \
status_t __result = (expr); \
if (__result != OK) { \
LOGE("[TX_ID: %" PRIu64 "] Parcel operation failed in %s: '%s' returned %d", (tx_id), __func__, #expr, \
__result); \
return __result; \
} \
} while (0)
status_t BinderInterceptor::onTransact(uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) {
switch (code) {
case intercept::kRegisterInterceptor:
return handleRegister(data);
case intercept::kUnregisterInterceptor:
return handleUnregister(data);
default:
return BBinder::onTransact(code, data, reply, flags);
}
}
status_t BinderInterceptor::handleRegister(const Parcel &data) {
sp<IBinder> target;
sp<IBinder> callback;
if (data.readStrongBinder(&target) != OK || !target)
return BAD_VALUE;
if (data.readStrongBinder(&callback) != OK || !callback)
return BAD_VALUE;
if (target->localBinder() == nullptr) {
LOGE("Cannot intercept remote binder proxies.");
return BAD_TYPE;
}
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, std::move(codes)};
return OK;
}
status_t BinderInterceptor::handleUnregister(const Parcel &data) {
sp<IBinder> target;
if (data.readStrongBinder(&target) != OK || !target)
return BAD_VALUE;
wp<IBinder> weak_target = target;
std::unique_lock lock(registry_mutex_);
if (registry_.erase(weak_target) > 0) {
LOGI("Interceptor unregistered for binder %p", target.get());
return OK;
}
LOGW("Attempted to unregister a non-existent interceptor for binder %p", target.get());
return NAME_NOT_FOUND;
}
status_t BinderInterceptor::writeTransactionData(Parcel &out, uint64_t tx_id, sp<BBinder> target, uint32_t code,
uint32_t flags, const Parcel &in_data) const {
// This is the data contract for communicating with the remote analysis/control tool
VALIDATE_STATUS(tx_id, out.writeInt64(tx_id));
VALIDATE_STATUS(tx_id, out.writeStrongBinder(target));
VALIDATE_STATUS(tx_id, out.writeUint32(code));
VALIDATE_STATUS(tx_id, out.writeUint32(flags));
VALIDATE_STATUS(tx_id, out.writeInt32(IPCThreadState::self()->getCallingUid()));
VALIDATE_STATUS(tx_id, out.writeInt32(IPCThreadState::self()->getCallingPid()));
VALIDATE_STATUS(tx_id, out.writeUint64(in_data.dataSize()));
VALIDATE_STATUS(tx_id, out.appendFrom(&in_data, 0, in_data.dataSize()));
return OK;
}
bool BinderInterceptor::processInterceptedTransaction(uint64_t tx_id, sp<BBinder> target, uint32_t code,
const Parcel &request, Parcel *reply, uint32_t flags,
status_t &result) {
sp<IBinder> callback;
{
std::shared_lock lock(registry_mutex_);
auto it = registry_.find(target);
if (it == registry_.end())
return false; // Should not happen given logic in hook, but safe
callback = it->second.callback_interface;
}
// --- Phase 1: Pre-Transaction Callback ---
Parcel pre_req, pre_resp;
writeTransactionData(pre_req, tx_id, target, code, flags, request);
status_t pre_status = callback->transact(intercept::kPreTransact, pre_req, &pre_resp);
if (pre_status != OK) {
// Block when interceptor is dead to prevent privacy leak to third-party apps
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;
}
int32_t action = pre_resp.readInt32();
// ACTION: Override Reply immediately and skip the real transaction
if (action == intercept::kActionOverrideReply) {
if (reply) {
result = pre_resp.readInt32(); // Read status code from response
size_t size = pre_resp.readUint64();
reply->setDataSize(0);
reply->appendFrom(&pre_resp, pre_resp.dataPosition(), size);
}
return true; // Handled
}
// ACTION: Silently skip/drop the transaction
if (action == intercept::kActionSkipTransaction) {
result = OK; // Return OK to caller, but do nothing
return true; // Handled
}
// ACTION: Skip the post-transaction hook
if (action == intercept::kActionContinueAndSkipPost) {
result = OK; // Return OK to caller, but do nothing
return false; // Forward it
}
// ACTION: Modify the transaction's request data before forwarding
Parcel final_request;
if (action == intercept::kActionOverrideData) {
size_t size = pre_resp.readUint64();
final_request.appendFrom(&pre_resp, pre_resp.dataPosition(), size);
} else {
// Default (kActionContinue): Use original data
final_request.appendFrom(&request, 0, request.dataSize());
}
// --- Phase 2: Execute Original Transaction ---
result = target->transact(code, final_request, reply, flags);
// --- Phase 3: Post-Transaction Callback ---
Parcel post_req, post_resp;
writeTransactionData(post_req, tx_id, target, code, flags, final_request);
// Append the result of the execution for the callback to see
VALIDATE_STATUS(tx_id, post_req.writeInt32(result));
size_t reply_size = (reply) ? reply->dataSize() : 0;
VALIDATE_STATUS(tx_id, post_req.writeUint64(reply_size));
if (reply && reply_size > 0) {
VALIDATE_STATUS(tx_id, post_req.appendFrom(reply, 0, reply_size));
}
status_t post_status = callback->transact(intercept::kPostTransact, post_req, &post_resp);
if (post_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));
}
}
return true; // We handled the flow, even if we just forwarded it
}
// =============================================================================================
// Initialization and Entry Point
// =============================================================================================
bool initialize_hooks() {
auto maps = lsplt::MapInfo::Scan();
dev_t binder_dev = 0;
ino_t binder_ino = 0;
bool found = false;
for (const auto &map : maps) {
if (map.path.ends_with(intercept::kBinderLibName)) {
binder_dev = map.dev;
binder_ino = map.inode;
found = true;
LOGD("Found libbinder at: %s", map.path.c_str());
break;
}
}
if (!found) {
LOGE("Could not find libbinder.so in memory maps.");
return false;
}
// Instantiate Singleton components
g_interceptor_instance = sp<BinderInterceptor>::make();
g_stub_instance = sp<BinderStub>::make();
// Register the ioctl hook with LSPLT
lsplt::RegisterHook(binder_dev, binder_ino, intercept::kIoctlSymbol.data(),
reinterpret_cast<void *>(intercepted_ioctl), reinterpret_cast<void **>(&g_original_ioctl));
if (!lsplt::CommitHook()) {
LOGE("lsplt::CommitHook failed.");
return false;
}
LOGI("Binder interception initialized successfully.");
return true;
}
extern "C" [[gnu::visibility("default")]] [[gnu::used]]
bool entry(void *handle) {
LOGI("Binder Interceptor library loaded (handle: %p)", handle);
return initialize_hooks();
}
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,322 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
// DO NOT INCLUDE OTHER LIBBASE HEADERS HERE!
// This file gets used in libbinder, and libbinder is used everywhere.
// Including other headers from libbase frequently results in inclusion of
// android-base/macros.h, which causes macro collisions.
#if defined(__BIONIC__)
#include <android/fdsan.h>
#endif
#if !defined(_WIN32) && !defined(__TRUSTY__)
#include <sys/socket.h>
#endif
namespace android {
namespace base {
// Container for a file descriptor that automatically closes the descriptor as
// it goes out of scope.
//
// unique_fd ufd(open("/some/path", "r"));
// if (ufd.get() == -1) return error;
//
// // Do something useful, possibly including 'return'.
//
// return 0; // Descriptor is closed for you.
//
// See also the Pipe()/Socketpair()/Fdopen()/Fdopendir() functions in this file
// that provide interoperability with the libc functions with the same (but
// lowercase) names.
//
// unique_fd is also known as ScopedFd/ScopedFD/scoped_fd; mentioned here to help
// you find this class if you're searching for one of those names.
//
// unique_fd itself is a specialization of unique_fd_impl with a default closer.
template <typename Closer>
class unique_fd_impl final {
public:
unique_fd_impl() {}
explicit unique_fd_impl(int fd) { reset(fd); }
~unique_fd_impl() { reset(); }
unique_fd_impl(const unique_fd_impl&) = delete;
void operator=(const unique_fd_impl&) = delete;
unique_fd_impl(unique_fd_impl&& other) noexcept { reset(other.release()); }
unique_fd_impl& operator=(unique_fd_impl&& s) noexcept {
int fd = s.fd_;
s.fd_ = -1;
reset(fd, &s);
return *this;
}
[[clang::reinitializes]] void reset(int new_value = -1) { reset(new_value, nullptr); }
int get() const { return fd_; }
#if !defined(ANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION)
// unique_fd's operator int is dangerous, but we have way too much code that
// depends on it, so make this opt-in at first.
operator int() const { return get(); } // NOLINT
#endif
bool operator>=(int rhs) const { return get() >= rhs; }
bool operator<(int rhs) const { return get() < rhs; }
bool operator==(int rhs) const { return get() == rhs; }
bool operator!=(int rhs) const { return get() != rhs; }
bool operator==(const unique_fd_impl& rhs) const { return get() == rhs.get(); }
bool operator!=(const unique_fd_impl& rhs) const { return get() != rhs.get(); }
// Catch bogus error checks (i.e.: "!fd" instead of "fd != -1").
bool operator!() const = delete;
bool ok() const { return get() >= 0; }
int release() __attribute__((warn_unused_result)) {
tag(fd_, this, nullptr);
int ret = fd_;
fd_ = -1;
return ret;
}
private:
void reset(int new_value, void* previous_tag) {
int previous_errno = errno;
if (fd_ != -1) {
close(fd_, this);
}
fd_ = new_value;
if (new_value != -1) {
tag(new_value, previous_tag, this);
}
errno = previous_errno;
}
int fd_ = -1;
// Template magic to use Closer::Tag if available, and do nothing if not.
// If Closer::Tag exists, this implementation is preferred, because int is a better match.
// If not, this implementation is SFINAEd away, and the no-op below is the only one that exists.
template <typename T = Closer>
static auto tag(int fd, void* old_tag, void* new_tag)
-> decltype(T::Tag(fd, old_tag, new_tag), void()) {
T::Tag(fd, old_tag, new_tag);
}
template <typename T = Closer>
static void tag(long, void*, void*) {
// No-op.
}
// Same as above, to select between Closer::Close(int) and Closer::Close(int, void*).
template <typename T = Closer>
static auto close(int fd, void* tag_value) -> decltype(T::Close(fd, tag_value), void()) {
T::Close(fd, tag_value);
}
template <typename T = Closer>
static auto close(int fd, void*) -> decltype(T::Close(fd), void()) {
T::Close(fd);
}
};
// The actual details of closing are factored out to support unusual cases.
// Almost everyone will want this DefaultCloser, which handles fdsan on bionic.
struct DefaultCloser {
#if defined(__BIONIC__)
static void Tag(int fd, void* old_addr, void* new_addr) {
if (android_fdsan_exchange_owner_tag) {
uint64_t old_tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_UNIQUE_FD,
reinterpret_cast<uint64_t>(old_addr));
uint64_t new_tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_UNIQUE_FD,
reinterpret_cast<uint64_t>(new_addr));
android_fdsan_exchange_owner_tag(fd, old_tag, new_tag);
}
}
static void Close(int fd, void* addr) {
if (android_fdsan_close_with_tag) {
uint64_t tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_UNIQUE_FD,
reinterpret_cast<uint64_t>(addr));
android_fdsan_close_with_tag(fd, tag);
} else {
close(fd);
}
}
#else
static void Close(int fd) {
// Even if close(2) fails with EINTR, the fd will have been closed.
// Using TEMP_FAILURE_RETRY will either lead to EBADF or closing someone
// else's fd.
// http://lkml.indiana.edu/hypermail/linux/kernel/0509.1/0877.html
::close(fd);
}
#endif
};
using unique_fd = unique_fd_impl<DefaultCloser>;
#if !defined(_WIN32) && !defined(__TRUSTY__)
// Inline functions, so that they can be used header-only.
// See pipe(2).
// This helper hides the details of converting to unique_fd, and also hides the
// fact that macOS doesn't support O_CLOEXEC or O_NONBLOCK directly.
template <typename Closer>
inline bool Pipe(unique_fd_impl<Closer>* read, unique_fd_impl<Closer>* write,
int flags = O_CLOEXEC) {
int pipefd[2];
#if defined(__linux__)
if (pipe2(pipefd, flags) != 0) {
return false;
}
#else // defined(__APPLE__)
if (flags & ~(O_CLOEXEC | O_NONBLOCK)) {
return false;
}
if (pipe(pipefd) != 0) {
return false;
}
if (flags & O_CLOEXEC) {
if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) != 0 || fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) != 0) {
close(pipefd[0]);
close(pipefd[1]);
return false;
}
}
if (flags & O_NONBLOCK) {
if (fcntl(pipefd[0], F_SETFL, O_NONBLOCK) != 0 || fcntl(pipefd[1], F_SETFL, O_NONBLOCK) != 0) {
close(pipefd[0]);
close(pipefd[1]);
return false;
}
}
#endif
read->reset(pipefd[0]);
write->reset(pipefd[1]);
return true;
}
// See socketpair(2).
// This helper hides the details of converting to unique_fd.
template <typename Closer>
inline bool Socketpair(int domain, int type, int protocol, unique_fd_impl<Closer>* left,
unique_fd_impl<Closer>* right) {
int sockfd[2];
if (socketpair(domain, type, protocol, sockfd) != 0) {
return false;
}
left->reset(sockfd[0]);
right->reset(sockfd[1]);
return true;
}
// See socketpair(2).
// This helper hides the details of converting to unique_fd.
template <typename Closer>
inline bool Socketpair(int type, unique_fd_impl<Closer>* left, unique_fd_impl<Closer>* right) {
return Socketpair(AF_UNIX, type, 0, left, right);
}
// See fdopen(3).
// Using fdopen with unique_fd correctly is more annoying than it should be,
// because fdopen doesn't close the file descriptor received upon failure.
inline FILE* Fdopen(unique_fd&& ufd, const char* mode) {
int fd = ufd.release();
FILE* file = fdopen(fd, mode);
if (!file) {
close(fd);
}
return file;
}
// See fdopendir(3).
// Using fdopendir with unique_fd correctly is more annoying than it should be,
// because fdopen doesn't close the file descriptor received upon failure.
inline DIR* Fdopendir(unique_fd&& ufd) {
int fd = ufd.release();
DIR* dir = fdopendir(fd);
if (dir == nullptr) {
close(fd);
}
return dir;
}
#endif // !defined(_WIN32) && !defined(__TRUSTY__)
// A wrapper type that can be implicitly constructed from either int or
// unique_fd. This supports cases where you don't actually own the file
// descriptor, and can't take ownership, but are temporarily acting as if
// you're the owner.
//
// One example would be a function that needs to also allow
// STDERR_FILENO, not just a newly-opened fd. Another example would be JNI code
// that's using a file descriptor that's actually owned by a
// ParcelFileDescriptor or whatever on the Java side, but where the JNI code
// would like to enforce this weaker sense of "temporary ownership".
//
// If you think of unique_fd as being like std::string in that represents
// ownership, borrowed_fd is like std::string_view (and int is like const
// char*).
struct borrowed_fd {
/* implicit */ borrowed_fd(int fd) : fd_(fd) {} // NOLINT
template <typename T>
/* implicit */ borrowed_fd(const unique_fd_impl<T>& ufd) : fd_(ufd.get()) {} // NOLINT
int get() const { return fd_; }
bool operator>=(int rhs) const { return get() >= rhs; }
bool operator<(int rhs) const { return get() < rhs; }
bool operator==(int rhs) const { return get() == rhs; }
bool operator!=(int rhs) const { return get() != rhs; }
private:
int fd_ = -1;
};
} // namespace base
} // namespace android
template <typename T>
int close(const android::base::unique_fd_impl<T>&)
__attribute__((__unavailable__("close called on unique_fd")));
template <typename T>
FILE* fdopen(const android::base::unique_fd_impl<T>&, const char* mode)
__attribute__((__unavailable__("fdopen takes ownership of the fd passed in; either dup the "
"unique_fd, or use android::base::Fdopen to pass ownership")));
template <typename T>
DIR* fdopendir(const android::base::unique_fd_impl<T>&) __attribute__((
__unavailable__("fdopendir takes ownership of the fd passed in; either dup the "
"unique_fd, or use android::base::Fdopendir to pass ownership")));
+165
View File
@@ -0,0 +1,165 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <atomic>
#include <stdint.h>
#include <binder/Common.h>
#include <binder/IBinder.h>
// ---------------------------------------------------------------------------
namespace android {
namespace internal {
class Stability;
}
class BBinder : public IBinder {
public:
LIBBINDER_EXPORTED BBinder();
LIBBINDER_EXPORTED virtual const String16& getInterfaceDescriptor() const;
LIBBINDER_EXPORTED virtual bool isBinderAlive() const;
LIBBINDER_EXPORTED virtual status_t pingBinder();
LIBBINDER_EXPORTED virtual status_t dump(int fd, const Vector<String16>& args);
// NOLINTNEXTLINE(google-default-arguments)
LIBBINDER_EXPORTED virtual status_t transact(uint32_t code, const Parcel& data, Parcel* reply,
uint32_t flags = 0) final;
// NOLINTNEXTLINE(google-default-arguments)
LIBBINDER_EXPORTED virtual status_t linkToDeath(const sp<DeathRecipient>& recipient,
void* cookie = nullptr, uint32_t flags = 0);
// NOLINTNEXTLINE(google-default-arguments)
LIBBINDER_EXPORTED virtual status_t unlinkToDeath(const wp<DeathRecipient>& recipient,
void* cookie = nullptr, uint32_t flags = 0,
wp<DeathRecipient>* outRecipient = nullptr);
LIBBINDER_EXPORTED virtual void* attachObject(const void* objectID, void* object,
void* cleanupCookie,
object_cleanup_func func) final;
LIBBINDER_EXPORTED virtual void* findObject(const void* objectID) const final;
LIBBINDER_EXPORTED virtual void* detachObject(const void* objectID) final;
LIBBINDER_EXPORTED void withLock(const std::function<void()>& doWithLock);
LIBBINDER_EXPORTED sp<IBinder> lookupOrCreateWeak(const void* objectID,
IBinder::object_make_func make,
const void* makeArgs);
LIBBINDER_EXPORTED virtual BBinder* localBinder();
LIBBINDER_EXPORTED bool isRequestingSid();
// This must be called before the object is sent to another process. Not thread safe.
LIBBINDER_EXPORTED void setRequestingSid(bool requestSid);
LIBBINDER_EXPORTED sp<IBinder> getExtension();
// This must be called before the object is sent to another process. Not thread safe.
LIBBINDER_EXPORTED void setExtension(const sp<IBinder>& extension);
// This must be called before the object is sent to another process. Not thread safe.
//
// This function will abort if improper parameters are set. This is like
// sched_setscheduler. However, it sets the minimum scheduling policy
// only for the duration that this specific binder object is handling the
// call in a threadpool. By default, this API is set to SCHED_NORMAL/0. In
// this case, the scheduling priority will not actually be modified from
// binder defaults. See also IPCThreadState::disableBackgroundScheduling.
//
// Appropriate values are:
// SCHED_NORMAL: -20 <= priority <= 19
// SCHED_RR/SCHED_FIFO: 1 <= priority <= 99
LIBBINDER_EXPORTED void setMinSchedulerPolicy(int policy, int priority);
LIBBINDER_EXPORTED int getMinSchedulerPolicy();
LIBBINDER_EXPORTED int getMinSchedulerPriority();
// Whether realtime scheduling policies are inherited.
LIBBINDER_EXPORTED bool isInheritRt();
// This must be called before the object is sent to another process. Not thread safe.
LIBBINDER_EXPORTED void setInheritRt(bool inheritRt);
LIBBINDER_EXPORTED pid_t getDebugPid();
// Whether this binder has been sent to another process.
LIBBINDER_EXPORTED bool wasParceled();
// Consider this binder as parceled (setup/init-related calls should no
// longer by called. This is automatically set by when this binder is sent
// to another process.
LIBBINDER_EXPORTED void setParceled();
[[nodiscard]] LIBBINDER_EXPORTED status_t setRpcClientDebug(binder::unique_fd clientFd,
const sp<IBinder>& keepAliveBinder);
protected:
LIBBINDER_EXPORTED virtual ~BBinder();
// NOLINTNEXTLINE(google-default-arguments)
LIBBINDER_EXPORTED virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply,
uint32_t flags = 0);
private:
BBinder(const BBinder& o);
BBinder& operator=(const BBinder& o);
class RpcServerLink;
class Extras;
Extras* getOrCreateExtras();
[[nodiscard]] status_t setRpcClientDebug(const Parcel& data);
void removeRpcServerLink(const sp<RpcServerLink>& link);
[[nodiscard]] status_t startRecordingTransactions(const Parcel& data);
[[nodiscard]] status_t stopRecordingTransactions();
std::atomic<Extras*> mExtras;
friend ::android::internal::Stability;
int16_t mStability;
bool mParceled;
bool mRecordingOn;
#ifdef __LP64__
int32_t mReserved1;
#endif
};
// ---------------------------------------------------------------------------
class BpRefBase : public virtual RefBase {
protected:
LIBBINDER_EXPORTED explicit BpRefBase(const sp<IBinder>& o);
LIBBINDER_EXPORTED virtual ~BpRefBase();
LIBBINDER_EXPORTED virtual void onFirstRef();
LIBBINDER_EXPORTED virtual void onLastStrongRef(const void* id);
LIBBINDER_EXPORTED virtual bool onIncStrongAttempted(uint32_t flags, const void* id);
LIBBINDER_EXPORTED inline IBinder* remote() const { return mRemote; }
LIBBINDER_EXPORTED inline sp<IBinder> remoteStrong() const {
return sp<IBinder>::fromExisting(mRemote);
}
private:
BpRefBase(const BpRefBase& o);
BpRefBase& operator=(const BpRefBase& o);
IBinder* const mRemote;
RefBase::weakref_type* mRefs;
std::atomic<int32_t> mState;
};
} // namespace android
// ---------------------------------------------------------------------------
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
#include <utils/Errors.h>
#include <utils/String16.h>
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
// WARNING: deprecated - DO NOT USE - prefer to setup service directly.
//
// This class embellishes a class with a few static methods which can be used in
// limited circumstances (when one service needs to be registered and
// published). However, this is an anti-pattern:
// - these methods are aliases of existing methods, and as such, represent an
// incremental amount of information required to understand the system but
// which does not actually save in terms of lines of code. For instance, users
// of this class should be surprised to know that this will start up to 16
// threads in the binder threadpool.
// - the template instantiation costs need to be paid, even though everything
// done here is generic.
// - the getServiceName API here is undocumented and non-local (for instance,
// this unnecessarily assumes a single service type will only be instantiated
// once with no arguments).
//
// So, DO NOT USE.
// ---------------------------------------------------------------------------
namespace android {
template<typename SERVICE>
class BinderService
{
public:
static status_t publish(bool allowIsolated = false,
int dumpFlags = IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT) {
sp<IServiceManager> sm(defaultServiceManager());
return sm->addService(String16(SERVICE::getServiceName()), new SERVICE(), allowIsolated,
dumpFlags);
}
static void publishAndJoinThreadPool(
bool allowIsolated = false,
int dumpFlags = IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT) {
publish(allowIsolated, dumpFlags);
joinThreadPool();
}
static void instantiate() { publish(); }
static status_t shutdown() { return NO_ERROR; }
private:
static void joinThreadPool() {
sp<ProcessState> ps(ProcessState::self());
ps->startThreadPool();
ps->giveThreadPoolName();
IPCThreadState::self()->joinThreadPool();
}
};
} // namespace android
// ---------------------------------------------------------------------------
+247
View File
@@ -0,0 +1,247 @@
/*
* Copyright (C) 2005 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/IBinder.h>
#include <binder/RpcThreads.h>
#include <binder/unique_fd.h>
#include <map>
#include <optional>
#include <unordered_map>
#include <variant>
// ---------------------------------------------------------------------------
namespace android {
class IPCThreadState;
class RpcSession;
class RpcState;
namespace internal {
class Stability;
}
class ProcessState;
using binder_proxy_limit_callback = std::function<void(int)>;
using binder_proxy_warning_callback = std::function<void(int)>;
class BpBinder : public IBinder {
public:
/**
* Return value:
* true - this is associated with a socket RpcSession
* false - (usual) binder over e.g. /dev/binder
*/
LIBBINDER_EXPORTED bool isRpcBinder() const;
LIBBINDER_EXPORTED virtual const String16& getInterfaceDescriptor() const;
LIBBINDER_EXPORTED virtual bool isBinderAlive() const;
LIBBINDER_EXPORTED virtual status_t pingBinder();
LIBBINDER_EXPORTED virtual status_t dump(int fd, const Vector<String16>& args);
// NOLINTNEXTLINE(google-default-arguments)
LIBBINDER_EXPORTED virtual status_t transact(uint32_t code, const Parcel& data, Parcel* reply,
uint32_t flags = 0) final;
// NOLINTNEXTLINE(google-default-arguments)
LIBBINDER_EXPORTED virtual status_t linkToDeath(const sp<DeathRecipient>& recipient,
void* cookie = nullptr, uint32_t flags = 0);
// NOLINTNEXTLINE(google-default-arguments)
LIBBINDER_EXPORTED virtual status_t unlinkToDeath(const wp<DeathRecipient>& recipient,
void* cookie = nullptr, uint32_t flags = 0,
wp<DeathRecipient>* outRecipient = nullptr);
[[nodiscard]] status_t addFrozenStateChangeCallback(
const wp<FrozenStateChangeCallback>& recipient);
[[nodiscard]] status_t removeFrozenStateChangeCallback(
const wp<FrozenStateChangeCallback>& recipient);
LIBBINDER_EXPORTED virtual void* attachObject(const void* objectID, void* object,
void* cleanupCookie,
object_cleanup_func func) final;
LIBBINDER_EXPORTED virtual void* findObject(const void* objectID) const final;
LIBBINDER_EXPORTED virtual void* detachObject(const void* objectID) final;
LIBBINDER_EXPORTED void withLock(const std::function<void()>& doWithLock);
LIBBINDER_EXPORTED sp<IBinder> lookupOrCreateWeak(const void* objectID,
IBinder::object_make_func make,
const void* makeArgs);
LIBBINDER_EXPORTED virtual BpBinder* remoteBinder();
LIBBINDER_EXPORTED void sendObituary();
LIBBINDER_EXPORTED static uint32_t getBinderProxyCount(uint32_t uid);
LIBBINDER_EXPORTED static void getCountByUid(Vector<uint32_t>& uids, Vector<uint32_t>& counts);
LIBBINDER_EXPORTED static void enableCountByUid();
LIBBINDER_EXPORTED static void disableCountByUid();
LIBBINDER_EXPORTED static void setCountByUidEnabled(bool enable);
LIBBINDER_EXPORTED static void setBinderProxyCountEventCallback(
binder_proxy_limit_callback cbl, binder_proxy_warning_callback cbw);
LIBBINDER_EXPORTED static void setBinderProxyCountWatermarks(int high, int low, int warning);
LIBBINDER_EXPORTED static uint32_t getBinderProxyCount();
LIBBINDER_EXPORTED std::optional<int32_t> getDebugBinderHandle() const;
// Start recording transactions to the unique_fd.
// See RecordedTransaction.h for more details.
LIBBINDER_EXPORTED status_t startRecordingBinder(const binder::unique_fd& fd);
// Stop the current recording.
LIBBINDER_EXPORTED status_t stopRecordingBinder();
// Note: This class is not thread safe so protect uses of it when necessary
class ObjectManager {
public:
ObjectManager();
~ObjectManager();
void* attach(const void* objectID, void* object, void* cleanupCookie,
IBinder::object_cleanup_func func);
void* find(const void* objectID) const;
void* detach(const void* objectID);
sp<IBinder> lookupOrCreateWeak(const void* objectID, IBinder::object_make_func make,
const void* makeArgs);
private:
ObjectManager(const ObjectManager&);
ObjectManager& operator=(const ObjectManager&);
struct entry_t {
void* object = nullptr;
void* cleanupCookie = nullptr;
IBinder::object_cleanup_func func = nullptr;
};
std::map<const void*, entry_t> mObjects;
};
class PrivateAccessor {
private:
friend class BpBinder;
friend class ::android::Parcel;
friend class ::android::ProcessState;
friend class ::android::RpcSession;
friend class ::android::RpcState;
friend class ::android::IPCThreadState;
explicit PrivateAccessor(const BpBinder* binder)
: mBinder(binder), mMutableBinder(nullptr) {}
explicit PrivateAccessor(BpBinder* binder) : mBinder(binder), mMutableBinder(binder) {}
static sp<BpBinder> create(int32_t handle, std::function<void()>* postTask) {
return BpBinder::create(handle, postTask);
}
static sp<BpBinder> create(const sp<RpcSession>& session, uint64_t address) {
return BpBinder::create(session, address);
}
// valid if !isRpcBinder
int32_t binderHandle() const { return mBinder->binderHandle(); }
// valid if isRpcBinder
uint64_t rpcAddress() const { return mBinder->rpcAddress(); }
const sp<RpcSession>& rpcSession() const { return mBinder->rpcSession(); }
void onFrozenStateChanged(bool isFrozen) { mMutableBinder->onFrozenStateChanged(isFrozen); }
const BpBinder* mBinder;
BpBinder* mMutableBinder;
};
LIBBINDER_EXPORTED const PrivateAccessor getPrivateAccessor() const {
return PrivateAccessor(this);
}
PrivateAccessor getPrivateAccessor() { return PrivateAccessor(this); }
private:
friend PrivateAccessor;
friend class sp<BpBinder>;
static sp<BpBinder> create(int32_t handle, std::function<void()>* postTask);
static sp<BpBinder> create(const sp<RpcSession>& session, uint64_t address);
struct BinderHandle {
int32_t handle;
};
struct RpcHandle {
sp<RpcSession> session;
uint64_t address;
};
using Handle = std::variant<BinderHandle, RpcHandle>;
int32_t binderHandle() const;
uint64_t rpcAddress() const;
const sp<RpcSession>& rpcSession() const;
explicit BpBinder(Handle&& handle);
BpBinder(BinderHandle&& handle, int32_t trackedUid);
explicit BpBinder(RpcHandle&& handle);
virtual ~BpBinder();
virtual void onFirstRef();
virtual void onLastStrongRef(const void* id);
virtual bool onIncStrongAttempted(uint32_t flags, const void* id);
friend ::android::internal::Stability;
int32_t mStability;
Handle mHandle;
struct Obituary {
wp<DeathRecipient> recipient;
void* cookie;
uint32_t flags;
};
void onFrozenStateChanged(bool isFrozen);
struct FrozenStateChange {
bool isFrozen = false;
Vector<wp<FrozenStateChangeCallback>> callbacks;
bool initialStateReceived = false;
};
void reportOneDeath(const Obituary& obit);
bool isDescriptorCached() const;
mutable RpcMutex mLock;
volatile int32_t mAlive;
volatile int32_t mObitsSent;
Vector<Obituary>* mObituaries;
std::unique_ptr<FrozenStateChange> mFrozen;
ObjectManager mObjectMgr;
mutable String16 mDescriptorCache;
int32_t mTrackedUid;
static RpcMutex sTrackingLock;
static std::unordered_map<int32_t, uint32_t> sTrackingMap;
static int sNumTrackedUids;
static std::atomic_bool sCountByUidEnabled;
static binder_proxy_limit_callback sLimitCallback;
static uint32_t sBinderProxyCountHighWatermark;
static uint32_t sBinderProxyCountLowWatermark;
static bool sBinderProxyThrottleCreate;
static std::unordered_map<int32_t, uint32_t> sLastLimitCallbackMap;
static std::atomic<uint32_t> sBinderProxyCount;
static std::atomic<uint32_t> sBinderProxyCountWarned;
static binder_proxy_warning_callback sWarningCallback;
static uint32_t sBinderProxyCountWarningWatermark;
};
} // namespace android
// ---------------------------------------------------------------------------
+54
View File
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
// libbinder is built with symbol hidden by default. To add a new symbol to the
// ABI, you must annotate it with this LIBBINDER_EXPORTED macro. When not
// building libbinder (e.g. when another binary includes a libbinder header),
// this macro is a no-op.
//
// Examples:
//
// // Export a function.
// LIBBINDER_EXPORTED void someFunction();
//
// // Export a subset of the symbols for a class.
// class SomeClassA {
// public:
// LIBBINDER_EXPORTED SomeClassA();
//
// LIBBINDER_EXPORTED SomeMethod();
// }
//
// // Export all the symbols for a class, even private symbols.
// class LIBBINDER_EXPORTED SomeClassB {};
//
// For a more detailed explanation of this strategy, see
// https://www.gnu.org/software/gnulib/manual/html_node/Exported-Symbols-of-Shared-Libraries.html
#if BUILDING_LIBBINDER
#define LIBBINDER_EXPORTED __attribute__((__visibility__("default")))
#else
#define LIBBINDER_EXPORTED
#endif
// For stuff that is exported but probably shouldn't be. It behaves the exact
// same way as LIBBINDER_EXPORTED, only exists to help track what we want
// eventually remove.
//
// Needed, at least in part, because the test binaries are using internal
// headers and accessing these symbols directly.
#define LIBBINDER_INTERNAL_EXPORTED LIBBINDER_EXPORTED
@@ -0,0 +1,99 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/IBinder.h>
#if !defined(__BIONIC__) && defined(BINDER_ENABLE_LIBLOG_ASSERT)
#include <log/log.h>
#define __assert(file, line, message) LOG_ALWAYS_FATAL(file ":" #line ": " message)
#endif
#ifndef __BIONIC__
#ifndef __assert
// defined differently by liblog
#pragma push_macro("LOG_PRI")
#ifdef LOG_PRI
#undef LOG_PRI
#endif
#include <syslog.h>
#pragma pop_macro("LOG_PRI")
#define __assert(a, b, c) \
do { \
syslog(LOG_ERR, a ": " c); \
abort(); \
} while (false)
#endif // __assert
#endif // __BIONIC__
namespace android {
/*
* Used to manage AIDL's *Delegator types.
* This is used to:
* - create a new *Delegator object that delegates to the binder argument.
* - or return an existing *Delegator object that already delegates to the
* binder argument.
* - or return the underlying delegate binder if the binder argument is a
* *Delegator itself.
*
* @param binder - the binder to delegate to or unwrap
*
* @return pointer to the *Delegator object or the unwrapped binder object
*/
template <typename T>
sp<T> delegate(const sp<T>& binder) {
const void* isDelegatorId = &T::descriptor;
const void* hasDelegatorId = &T::descriptor + 1;
// is binder itself a delegator?
if (T::asBinder(binder)->findObject(isDelegatorId)) {
if (T::asBinder(binder)->findObject(hasDelegatorId)) {
__assert(__FILE__, __LINE__,
"This binder has a delegator and is also delegator itself! This is "
"likely an unintended mixing of binders.");
return nullptr;
}
// unwrap the delegator
return static_cast<typename T::DefaultDelegator*>(binder.get())->getImpl();
}
struct MakeArgs {
const sp<T>* binder;
const void* id;
} makeArgs;
makeArgs.binder = &binder;
makeArgs.id = isDelegatorId;
// the binder is not a delegator, so construct one
sp<IBinder> newDelegator = T::asBinder(binder)->lookupOrCreateWeak(
hasDelegatorId,
[](const void* args) -> sp<IBinder> {
auto delegator = sp<typename T::DefaultDelegator>::make(
*static_cast<const MakeArgs*>(args)->binder);
// make sure we know this binder is a delegator by attaching a unique ID
(void)delegator->attachObject(static_cast<const MakeArgs*>(args)->id,
reinterpret_cast<void*>(0x1), nullptr, nullptr);
return delegator;
},
static_cast<const void*>(&makeArgs));
return sp<typename T::DefaultDelegator>::cast(newDelegator);
}
} // namespace android
+42
View File
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <iterator>
#include <type_traits>
namespace android {
namespace internal {
// Never instantiated. Used as a placeholder for template variables.
template <typename T>
struct invalid_type;
// AIDL generates specializations of this for enums.
template <typename EnumType, typename = std::enable_if_t<std::is_enum<EnumType>::value>>
constexpr invalid_type<EnumType> enum_values;
} // namespace internal
// Usage: for (const auto v : enum_range<EnumType>() ) { ... }
template <typename EnumType, typename = std::enable_if_t<std::is_enum<EnumType>::value>>
struct enum_range {
constexpr auto begin() const { return std::begin(internal::enum_values<EnumType>); }
constexpr auto end() const { return std::end(internal::enum_values<EnumType>); }
};
} // namespace android
@@ -0,0 +1,71 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <functional>
#include <optional>
namespace android::binder::impl {
template <typename F>
class scope_guard;
template <typename F>
scope_guard<F> make_scope_guard(F f);
template <typename F>
class scope_guard {
public:
inline ~scope_guard() {
if (f_.has_value()) std::move(f_.value())();
}
inline void release() { f_.reset(); }
private:
friend scope_guard<F> android::binder::impl::make_scope_guard<>(F);
inline scope_guard(F&& f) : f_(std::move(f)) {}
std::optional<F> f_;
};
template <typename F>
inline scope_guard<F> make_scope_guard(F f) {
return scope_guard<F>(std::move(f));
}
template <typename F>
constexpr void assert_small_callable() {
// While this buffer (std::function::__func::__buf_) is an implementation detail generally not
// accessible to users, it's a good bet to assume its size to be around 3 pointers.
constexpr size_t kFunctionBufferSize = 3 * sizeof(void*);
static_assert(sizeof(F) <= kFunctionBufferSize,
"Supplied callable is larger than std::function optimization buffer. "
"Try using std::ref, but make sure lambda lives long enough to be called.");
}
template <typename T>
class SmallFunction : public std::function<T> {
public:
template <typename F>
SmallFunction(F&& f) : std::function<T>(f) {
assert_small_callable<F>();
}
};
} // namespace android::binder::impl
+356
View File
@@ -0,0 +1,356 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/unique_fd.h>
#include <utils/Errors.h>
#include <utils/RefBase.h>
#include <utils/String16.h>
#include <utils/Vector.h>
#include <functional>
// linux/binder.h defines this, but we don't want to include it here in order to
// avoid exporting the kernel headers
#ifndef B_PACK_CHARS
#define B_PACK_CHARS(c1, c2, c3, c4) \
((((c1)<<24)) | (((c2)<<16)) | (((c3)<<8)) | (c4))
#endif // B_PACK_CHARS
// ---------------------------------------------------------------------------
namespace android {
class BBinder;
class BpBinder;
class IInterface;
class Parcel;
class IResultReceiver;
class IShellCallback;
/**
* Base class and low-level protocol for a remotable object.
* You can derive from this class to create an object for which other
* processes can hold references to it. Communication between processes
* (method calls, property get and set) is down through a low-level
* protocol implemented on top of the transact() API.
*/
class [[clang::lto_visibility_public]] LIBBINDER_EXPORTED IBinder : public virtual RefBase {
public:
enum {
FIRST_CALL_TRANSACTION = 0x00000001,
LAST_CALL_TRANSACTION = 0x00ffffff,
PING_TRANSACTION = B_PACK_CHARS('_', 'P', 'N', 'G'),
START_RECORDING_TRANSACTION = B_PACK_CHARS('_', 'S', 'R', 'D'),
STOP_RECORDING_TRANSACTION = B_PACK_CHARS('_', 'E', 'R', 'D'),
DUMP_TRANSACTION = B_PACK_CHARS('_', 'D', 'M', 'P'),
SHELL_COMMAND_TRANSACTION = B_PACK_CHARS('_', 'C', 'M', 'D'),
INTERFACE_TRANSACTION = B_PACK_CHARS('_', 'N', 'T', 'F'),
SYSPROPS_TRANSACTION = B_PACK_CHARS('_', 'S', 'P', 'R'),
EXTENSION_TRANSACTION = B_PACK_CHARS('_', 'E', 'X', 'T'),
DEBUG_PID_TRANSACTION = B_PACK_CHARS('_', 'P', 'I', 'D'),
SET_RPC_CLIENT_TRANSACTION = B_PACK_CHARS('_', 'R', 'P', 'C'),
// See android.os.IBinder.TWEET_TRANSACTION
// Most importantly, messages can be anything not exceeding 130 UTF-8
// characters, and callees should exclaim "jolly good message old boy!"
TWEET_TRANSACTION = B_PACK_CHARS('_', 'T', 'W', 'T'),
// See android.os.IBinder.LIKE_TRANSACTION
// Improve binder self-esteem.
LIKE_TRANSACTION = B_PACK_CHARS('_', 'L', 'I', 'K'),
// Corresponds to TF_ONE_WAY -- an asynchronous call.
FLAG_ONEWAY = 0x00000001,
// Corresponds to TF_CLEAR_BUF -- clear transaction buffers after call
// is made
FLAG_CLEAR_BUF = 0x00000020,
// Private userspace flag for transaction which is being requested from
// a vendor context.
FLAG_PRIVATE_VENDOR = 0x10000000,
};
IBinder();
/**
* Check if this IBinder implements the interface named by
* @a descriptor. If it does, the base pointer to it is returned,
* which you can safely static_cast<> to the concrete C++ interface.
*/
virtual sp<IInterface> queryLocalInterface(const String16& descriptor);
/**
* Return the canonical name of the interface provided by this IBinder
* object.
*/
virtual const String16& getInterfaceDescriptor() const = 0;
/**
* Last known alive status, from last call. May be arbitrarily stale.
* May be incorrect if a service returns an incorrect status code.
*/
virtual bool isBinderAlive() const = 0;
virtual status_t pingBinder() = 0;
virtual status_t dump(int fd, const Vector<String16>& args) = 0;
static status_t shellCommand(const sp<IBinder>& target, int in, int out, int err,
Vector<String16>& args, const sp<IShellCallback>& callback,
const sp<IResultReceiver>& resultReceiver);
/**
* This allows someone to add their own additions to an interface without
* having to modify the original interface.
*
* For instance, imagine if we have this interface:
* interface IFoo { void doFoo(); }
*
* If an unrelated owner (perhaps in a downstream codebase) wants to make a
* change to the interface, they have two options:
*
* A). Historical option that has proven to be BAD! Only the original
* author of an interface should change an interface. If someone
* downstream wants additional functionality, they should not ever
* change the interface or use this method.
*
* BAD TO DO: interface IFoo { BAD TO DO
* BAD TO DO: void doFoo(); BAD TO DO
* BAD TO DO: + void doBar(); // adding a method BAD TO DO
* BAD TO DO: } BAD TO DO
*
* B). Option that this method enables!
* Leave the original interface unchanged (do not change IFoo!).
* Instead, create a new interface in a downstream package:
*
* package com.<name>; // new functionality in a new package
* interface IBar { void doBar(); }
*
* When registering the interface, add:
* sp<MyFoo> foo = new MyFoo; // class in AOSP codebase
* sp<MyBar> bar = new MyBar; // custom extension class
* foo->setExtension(bar); // use method in BBinder
*
* Then, clients of IFoo can get this extension:
* sp<IBinder> binder = ...;
* sp<IFoo> foo = interface_cast<IFoo>(binder); // handle if null
* sp<IBinder> barBinder;
* ... handle error ... = binder->getExtension(&barBinder);
* sp<IBar> bar = interface_cast<IBar>(barBinder);
* // if bar is null, then there is no extension or a different
* // type of extension
*/
status_t getExtension(sp<IBinder>* out);
/**
* Dump PID for a binder, for debugging.
*/
status_t getDebugPid(pid_t* outPid);
/**
* Set the RPC client fd to this binder service, for debugging. This is only available on
* debuggable builds.
*
* When this is called on a binder service, the service:
* 1. sets up RPC server
* 2. spawns 1 new thread that calls RpcServer::join()
* - join() spawns some number of threads that accept() connections; see RpcServer
*
* setRpcClientDebug() may be called multiple times. Each call will add a new RpcServer
* and opens up a TCP port.
*
* Note: A thread is spawned for each accept()'ed fd, which may call into functions of the
* interface freely. See RpcServer::join(). To avoid such race conditions, implement the service
* functions with multithreading support.
*
* On death of @a keepAliveBinder, the RpcServer shuts down.
*/
[[nodiscard]] status_t setRpcClientDebug(binder::unique_fd socketFd,
const sp<IBinder>& keepAliveBinder);
// NOLINTNEXTLINE(google-default-arguments)
virtual status_t transact( uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0) = 0;
// DeathRecipient is pure abstract, there is no virtual method
// implementation to put in a translation unit in order to silence the
// weak vtables warning.
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wweak-vtables"
#endif
class DeathRecipient : public virtual RefBase
{
public:
virtual void binderDied(const wp<IBinder>& who) = 0;
};
class FrozenStateChangeCallback : public virtual RefBase {
public:
enum class State {
FROZEN,
UNFROZEN,
};
virtual void onStateChanged(const wp<IBinder>& who, State state) = 0;
};
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
/**
* Register the @a recipient for a notification if this binder
* goes away. If this binder object unexpectedly goes away
* (typically because its hosting process has been killed),
* then DeathRecipient::binderDied() will be called with a reference
* to this.
*
* The @a cookie is optional -- if non-NULL, it should be a
* memory address that you own (that is, you know it is unique).
*
* @note When all references to the binder being linked to are dropped, the
* recipient is automatically unlinked. So, you must hold onto a binder in
* order to receive death notifications about it.
*
* @note You will only receive death notifications for remote binders,
* as local binders by definition can't die without you dying as well.
* Trying to use this function on a local binder will result in an
* INVALID_OPERATION code being returned and nothing happening.
*
* @note This link always holds a weak reference to its recipient.
*
* @note You will only receive a weak reference to the dead
* binder. You should not try to promote this to a strong reference.
* (Nor should you need to, as there is nothing useful you can
* directly do with it now that it has passed on.)
*/
// NOLINTNEXTLINE(google-default-arguments)
virtual status_t linkToDeath(const sp<DeathRecipient>& recipient,
void* cookie = nullptr,
uint32_t flags = 0) = 0;
/**
* Remove a previously registered death notification.
* The @a recipient will no longer be called if this object
* dies. The @a cookie is optional. If non-NULL, you can
* supply a NULL @a recipient, and the recipient previously
* added with that cookie will be unlinked.
*
* If the binder is dead, this will return DEAD_OBJECT. Deleting
* the object will also unlink all death recipients.
*/
// NOLINTNEXTLINE(google-default-arguments)
virtual status_t unlinkToDeath( const wp<DeathRecipient>& recipient,
void* cookie = nullptr,
uint32_t flags = 0,
wp<DeathRecipient>* outRecipient = nullptr) = 0;
/**
* addFrozenStateChangeCallback provides a callback mechanism to notify
* about process frozen/unfrozen events. Upon registration and any
* subsequent state changes, the callback is invoked with the latest process
* frozen state.
*
* If the listener process (the one using this API) is itself frozen, state
* change events might be combined into a single one with the latest state.
* (meaning 'frozen, unfrozen' might just be 'unfrozen'). This single event
* would then be delivered when the listener process becomes unfrozen.
* Similarly, if an event happens before the previous event is consumed,
* they might be combined. This means the callback might not be called for
* every single state change, so don't rely on this API to count how many
* times the state has changed.
*
* @note When all references to the binder are dropped, the callback is
* automatically removed. So, you must hold onto a binder in order to
* receive notifications about it.
*
* @note You will only receive freeze notifications for remote binders, as
* local binders by definition can't be frozen without you being frozen as
* well. Trying to use this function on a local binder will result in an
* INVALID_OPERATION code being returned and nothing happening.
*
* @note This binder always holds a weak reference to the callback.
*
* @note You will only receive a weak reference to the binder object. You
* should not try to promote this to a strong reference. (Nor should you
* need to, as there is nothing useful you can directly do with it now that
* it has passed on.)
*/
[[nodiscard]] status_t addFrozenStateChangeCallback(
const wp<FrozenStateChangeCallback>& callback);
/**
* Remove a previously registered freeze callback.
* The @a callback will no longer be called if this object
* changes its frozen state.
*/
[[nodiscard]] status_t removeFrozenStateChangeCallback(
const wp<FrozenStateChangeCallback>& callback);
virtual bool checkSubclass(const void* subclassID) const;
typedef void (*object_cleanup_func)(const void* id, void* obj, void* cleanupCookie);
/**
* This object is attached for the lifetime of this binder object. When
* this binder object is destructed, the cleanup function of all attached
* objects are invoked with their respective objectID, object, and
* cleanupCookie. Access to these APIs can be made from multiple threads,
* but calls from different threads are allowed to be interleaved.
*
* This returns the object which is already attached. If this returns a
* non-null value, it means that attachObject failed (a given objectID can
* only be used once).
*/
[[nodiscard]] virtual void* attachObject(const void* objectID, void* object,
void* cleanupCookie, object_cleanup_func func) = 0;
/**
* Returns object attached with attachObject.
*/
[[nodiscard]] virtual void* findObject(const void* objectID) const = 0;
/**
* Returns object attached with attachObject, and detaches it. This does not
* delete the object.
*/
[[nodiscard]] virtual void* detachObject(const void* objectID) = 0;
/**
* Use the lock that this binder contains internally. For instance, this can
* be used to modify an attached object without needing to add an additional
* lock (though, that attached object must be retrieved before calling this
* method). Calling (most) IBinder methods inside this will deadlock.
*/
void withLock(const std::function<void()>& doWithLock);
virtual BBinder* localBinder();
virtual BpBinder* remoteBinder();
typedef sp<IBinder> (*object_make_func)(const void* makeArgs);
sp<IBinder> lookupOrCreateWeak(const void* objectID, object_make_func make,
const void* makeArgs);
protected:
virtual ~IBinder();
private:
};
} // namespace android
// ---------------------------------------------------------------------------
@@ -0,0 +1,299 @@
/*
* Copyright (C) 2005 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Binder.h>
#include <binder/Common.h>
#include <assert.h>
namespace android {
// ----------------------------------------------------------------------
class LIBBINDER_EXPORTED IInterface : public virtual RefBase {
public:
IInterface();
static sp<IBinder> asBinder(const IInterface*);
static sp<IBinder> asBinder(const sp<IInterface>&);
protected:
virtual ~IInterface();
virtual IBinder* onAsBinder() = 0;
};
// ----------------------------------------------------------------------
/**
* If this is a local object and the descriptor matches, this will return the
* actual local object which is implementing the interface. Otherwise, this will
* return a proxy to the interface without checking the interface descriptor.
* This means that subsequent calls may fail with BAD_TYPE.
*/
template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
return INTERFACE::asInterface(obj);
}
/**
* This is the same as interface_cast, except that it always checks to make sure
* the descriptor matches, and if it doesn't match, it will return nullptr.
*/
template<typename INTERFACE>
inline sp<INTERFACE> checked_interface_cast(const sp<IBinder>& obj)
{
if (obj->getInterfaceDescriptor() != INTERFACE::descriptor) {
return nullptr;
}
return interface_cast<INTERFACE>(obj);
}
// ----------------------------------------------------------------------
template <typename INTERFACE>
class LIBBINDER_EXPORTED BnInterface : public INTERFACE, public BBinder {
public:
virtual sp<IInterface> queryLocalInterface(const String16& _descriptor);
virtual const String16& getInterfaceDescriptor() const;
typedef INTERFACE BaseInterface;
protected:
virtual IBinder* onAsBinder();
};
// ----------------------------------------------------------------------
template <typename INTERFACE>
class LIBBINDER_EXPORTED BpInterface : public INTERFACE, public BpRefBase {
public:
explicit BpInterface(const sp<IBinder>& remote);
typedef INTERFACE BaseInterface;
protected:
virtual IBinder* onAsBinder();
};
// ----------------------------------------------------------------------
#define DECLARE_META_INTERFACE(INTERFACE) \
public: \
static const ::android::String16 descriptor; \
static ::android::sp<I##INTERFACE> asInterface(const ::android::sp<::android::IBinder>& obj); \
virtual const ::android::String16& getInterfaceDescriptor() const; \
I##INTERFACE(); \
virtual ~I##INTERFACE(); \
static bool setDefaultImpl(::android::sp<I##INTERFACE> impl); \
static const ::android::sp<I##INTERFACE>& getDefaultImpl(); \
\
private: \
static ::android::sp<I##INTERFACE> default_impl; \
\
public:
#define __IINTF_CONCAT(x, y) (x ## y)
#ifndef DO_NOT_CHECK_MANUAL_BINDER_INTERFACES
#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME) \
static_assert(internal::allowedManualInterface(NAME), \
"b/64223827: Manually written binder interfaces are " \
"considered error prone and frequently have bugs. " \
"The preferred way to add interfaces is to define " \
"an .aidl file to auto-generate the interface. If " \
"an interface must be manually written, add its " \
"name to the allowlist."); \
DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE(INTERFACE, NAME)
#else
#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME) \
DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE(INTERFACE, NAME) \
#endif
// Macro to be used by both IMPLEMENT_META_INTERFACE and IMPLEMENT_META_NESTED_INTERFACE
#define DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE0(ITYPE, INAME, BPTYPE) \
const ::android::String16& ITYPE::getInterfaceDescriptor() const { return ITYPE::descriptor; } \
::android::sp<ITYPE> ITYPE::asInterface(const ::android::sp<::android::IBinder>& obj) { \
::android::sp<ITYPE> intr; \
if (obj != nullptr) { \
intr = ::android::sp<ITYPE>::cast(obj->queryLocalInterface(ITYPE::descriptor)); \
if (intr == nullptr) { \
intr = ::android::sp<BPTYPE>::make(obj); \
} \
} \
return intr; \
} \
::android::sp<ITYPE> ITYPE::default_impl; \
bool ITYPE::setDefaultImpl(::android::sp<ITYPE> impl) { \
/* Only one user of this interface can use this function */ \
/* at a time. This is a heuristic to detect if two different */ \
/* users in the same process use this function. */ \
assert(!ITYPE::default_impl); \
if (impl) { \
ITYPE::default_impl = std::move(impl); \
return true; \
} \
return false; \
} \
const ::android::sp<ITYPE>& ITYPE::getDefaultImpl() { return ITYPE::default_impl; } \
ITYPE::INAME() {} \
ITYPE::~INAME() {}
// Macro for an interface type.
#define DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE(INTERFACE, NAME) \
const ::android::StaticString16 I##INTERFACE##_descriptor_static_str16( \
__IINTF_CONCAT(u, NAME)); \
const ::android::String16 I##INTERFACE::descriptor(I##INTERFACE##_descriptor_static_str16); \
DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE0(I##INTERFACE, I##INTERFACE, Bp##INTERFACE)
// Macro for "nested" interface type.
// For example,
// class Parent .. { class INested .. { }; };
// DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_NESTED_INTERFACE(Parent, Nested, "Parent.INested")
#define DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_NESTED_INTERFACE(PARENT, INTERFACE, NAME) \
const ::android::String16 PARENT::I##INTERFACE::descriptor(NAME); \
DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE0(PARENT::I##INTERFACE, I##INTERFACE, \
PARENT::Bp##INTERFACE)
#define CHECK_INTERFACE(interface, data, reply) \
do { \
if (!(data).checkInterface(this)) { return PERMISSION_DENIED; } \
} while (false) \
// ----------------------------------------------------------------------
// No user-serviceable parts after this...
template<typename INTERFACE>
inline sp<IInterface> BnInterface<INTERFACE>::queryLocalInterface(
const String16& _descriptor)
{
if (_descriptor == INTERFACE::descriptor) return sp<IInterface>::fromExisting(this);
return nullptr;
}
template<typename INTERFACE>
inline const String16& BnInterface<INTERFACE>::getInterfaceDescriptor() const
{
return INTERFACE::getInterfaceDescriptor();
}
template<typename INTERFACE>
IBinder* BnInterface<INTERFACE>::onAsBinder()
{
return this;
}
template<typename INTERFACE>
inline BpInterface<INTERFACE>::BpInterface(const sp<IBinder>& remote)
: BpRefBase(remote)
{
}
template<typename INTERFACE>
inline IBinder* BpInterface<INTERFACE>::onAsBinder()
{
return remote();
}
// ----------------------------------------------------------------------
namespace internal {
constexpr const char* const kManualInterfaces[] = {
"android.app.IActivityManager",
"android.app.IUidObserver",
"android.gfx.tests.ICallback",
"android.gfx.tests.IIPCTest",
"android.gfx.tests.ISafeInterfaceTest",
"android.graphicsenv.IGpuService",
"android.gui.IConsumerListener",
"android.gui.IGraphicBufferConsumer",
"android.gui.ITransactionComposerListener",
"android.gui.SensorEventConnection",
"android.gui.SensorServer",
"android.hardware.ICamera",
"android.hardware.ICameraClient",
"android.hardware.ICameraRecordingProxy",
"android.hardware.ICameraRecordingProxyListener",
"android.hardware.IOMXObserver",
"android.hardware.IStreamListener",
"android.hardware.IStreamSource",
"android.media.IAudioService",
"android.media.IDataSource",
"android.media.IMediaCodecList",
"android.media.IMediaExtractor",
"android.media.IMediaHTTPConnection",
"android.media.IMediaHTTPService",
"android.media.IMediaLogService",
"android.media.IMediaMetadataRetriever",
"android.media.IMediaPlayer",
"android.media.IMediaPlayerClient",
"android.media.IMediaPlayerService",
"android.media.IMediaRecorder",
"android.media.IMediaRecorderClient",
"android.media.IMediaResourceMonitor",
"android.media.IMediaSource",
"android.media.IRemoteDisplay",
"android.media.IRemoteDisplayClient",
"android.os.IPermissionController",
"android.os.IProcessInfoService",
"android.os.ISchedulingPolicyService",
"android.os.storage.IObbActionListener",
"android.os.storage.IStorageEventListener",
"android.os.storage.IStorageManager",
"android.os.storage.IStorageShutdownObserver",
"android.ui.ISurfaceComposer",
"android.utils.IMemory",
"android.utils.IMemoryHeap",
"com.android.car.procfsinspector.IProcfsInspector",
"com.android.internal.app.IAppOpsCallback",
"com.android.internal.app.IAppOpsService",
"com.android.internal.app.IBatteryStats",
"com.android.internal.os.IResultReceiver",
"com.android.internal.os.IShellCallback",
"drm.IDrmManagerService",
"drm.IDrmServiceListener",
nullptr,
};
constexpr const char* const kDownstreamManualInterfaces[] = {
// Add downstream interfaces here.
nullptr,
};
constexpr bool equals(const char* a, const char* b) {
if (*a != *b) return false;
if (*a == '\0') return true;
return equals(a + 1, b + 1);
}
constexpr bool inList(const char* a, const char* const* allowlist) {
if (*allowlist == nullptr) return false;
if (equals(a, *allowlist)) return true;
return inList(a, allowlist + 1);
}
constexpr bool allowedManualInterface(const char* name) {
return inList(name, kManualInterfaces) ||
inList(name, kDownstreamManualInterfaces);
}
} // namespace internal
} // namespace android
+122
View File
@@ -0,0 +1,122 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <utils/RefBase.h>
#include <utils/Errors.h>
#include <binder/Common.h>
#include <binder/IInterface.h>
namespace android {
// ----------------------------------------------------------------------------
class LIBBINDER_EXPORTED IMemoryHeap : public IInterface {
public:
DECLARE_META_INTERFACE(MemoryHeap)
// flags returned by getFlags()
enum {
READ_ONLY = 0x00000001
};
virtual int getHeapID() const = 0;
virtual void* getBase() const = 0;
virtual size_t getSize() const = 0;
virtual uint32_t getFlags() const = 0;
virtual off_t getOffset() const = 0;
// these are there just for backward source compatibility
int32_t heapID() const { return getHeapID(); }
void* base() const { return getBase(); }
size_t virtualSize() const { return getSize(); }
};
class LIBBINDER_EXPORTED BnMemoryHeap : public BnInterface<IMemoryHeap> {
public:
// NOLINTNEXTLINE(google-default-arguments)
virtual status_t onTransact(
uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0);
BnMemoryHeap();
protected:
virtual ~BnMemoryHeap();
};
// ----------------------------------------------------------------------------
class LIBBINDER_EXPORTED IMemory : public IInterface {
public:
DECLARE_META_INTERFACE(Memory)
// NOLINTNEXTLINE(google-default-arguments)
virtual sp<IMemoryHeap> getMemory(ssize_t* offset=nullptr, size_t* size=nullptr) const = 0;
// helpers
// Accessing the underlying pointer must be done with caution, as there are
// some inherent security risks associated with it. When receiving an
// IMemory from an untrusted process, there is currently no way to guarantee
// that this process would't change the content after the fact. This may
// lead to TOC/TOU class of security bugs. In most cases, when performance
// is not an issue, the recommended practice is to immediately copy the
// buffer upon reception, then work with the copy, e.g.:
//
// std::string private_copy(mem.size(), '\0');
// memcpy(private_copy.data(), mem.unsecurePointer(), mem.size());
//
// In cases where performance is an issue, this matter must be addressed on
// an ad-hoc basis.
void* unsecurePointer() const;
size_t size() const;
ssize_t offset() const;
private:
// These are now deprecated and are left here for backward-compatibility
// with prebuilts that may reference these symbol at runtime.
// Instead, new code should use unsecurePointer()/unsecureFastPointer(),
// which do the same thing, but make it more obvious that there are some
// security-related pitfalls associated with them.
void* pointer() const;
void* fastPointer(const sp<IBinder>& heap, ssize_t offset) const;
};
class LIBBINDER_EXPORTED BnMemory : public BnInterface<IMemory> {
public:
// NOLINTNEXTLINE(google-default-arguments)
virtual status_t onTransact(
uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0);
BnMemory();
protected:
virtual ~BnMemory();
};
// ----------------------------------------------------------------------------
} // namespace android
@@ -0,0 +1,261 @@
/*
* Copyright (C) 2005 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/Parcel.h>
#include <binder/ProcessState.h>
#include <utils/Errors.h>
#include <utils/Vector.h>
#if defined(_WIN32)
typedef int uid_t;
#endif
// ---------------------------------------------------------------------------
namespace android {
/**
* Kernel binder thread state. All operations here refer to kernel binder. This
* object is allocated per-thread.
*/
class IPCThreadState {
public:
using CallRestriction = ProcessState::CallRestriction;
LIBBINDER_EXPORTED static IPCThreadState* self();
LIBBINDER_EXPORTED static IPCThreadState* selfOrNull(); // self(), but won't instantiate
// Freeze or unfreeze the binder interface to a specific process. When freezing, this method
// will block up to timeout_ms to process pending transactions directed to pid. Unfreeze
// is immediate. Transactions to processes frozen via this method won't be delivered and the
// driver will return BR_FROZEN_REPLY to the client sending them. After unfreeze,
// transactions will be delivered normally.
//
// pid: id for the process for which the binder interface is to be frozen
// enable: freeze (true) or unfreeze (false)
// timeout_ms: maximum time this function is allowed to block the caller waiting for pending
// binder transactions to be processed.
//
// returns: 0 in case of success, a value < 0 in case of error
LIBBINDER_EXPORTED static status_t freeze(pid_t pid, bool enabled, uint32_t timeout_ms);
// Provide information about the state of a frozen process
LIBBINDER_EXPORTED static status_t getProcessFreezeInfo(pid_t pid, uint32_t* sync_received,
uint32_t* async_received);
LIBBINDER_EXPORTED status_t clearLastError();
/**
* Returns the PID of the process which has made the current binder
* call. If not in a binder call, this will return getpid.
*
* Warning: oneway transactions do not receive PID. Even if you expect
* a transaction to be synchronous, a misbehaving client could send it
* as an asynchronous call and result in a 0 PID here. Additionally, if
* there is a race and the calling process dies, the PID may still be
* 0 for a synchronous call.
*/
[[nodiscard]] LIBBINDER_EXPORTED pid_t getCallingPid() const;
/**
* Returns the SELinux security identifier of the process which has
* made the current binder call. If not in a binder call this will
* return nullptr. If this isn't requested with
* Binder::setRequestingSid, it will also return nullptr.
*
* This can't be restored once it's cleared, and it does not return the
* context of the current process when not in a binder call.
*/
[[nodiscard]] LIBBINDER_EXPORTED const char* getCallingSid() const;
/**
* Returns the UID of the process which has made the current binder
* call. If not in a binder call, this will return 0.
*/
[[nodiscard]] LIBBINDER_EXPORTED uid_t getCallingUid() const;
/**
* Make it an abort to rely on getCalling* for a section of
* execution.
*
* Usage:
* IPCThreadState::SpGuard guard {
* .address = __builtin_frame_address(0),
* .context = "...",
* };
* const auto* orig = pushGetCallingSpGuard(&guard);
* {
* // will abort if you call getCalling*, unless you are
* // serving a nested binder transaction
* }
* restoreCallingSpGuard(orig);
*/
struct SpGuard {
const void* address;
const char* context;
};
LIBBINDER_EXPORTED const SpGuard* pushGetCallingSpGuard(const SpGuard* guard);
LIBBINDER_EXPORTED void restoreGetCallingSpGuard(const SpGuard* guard);
/**
* Used internally by getCalling*. Can also be used to assert that
* you are in a binder context (getCalling* is valid). This is
* intentionally not exposed as a boolean API since code should be
* written to know its environment.
*/
LIBBINDER_EXPORTED void checkContextIsBinderForUse(const char* use) const;
LIBBINDER_EXPORTED void setStrictModePolicy(int32_t policy);
LIBBINDER_EXPORTED int32_t getStrictModePolicy() const;
// See Binder#setCallingWorkSourceUid in Binder.java.
LIBBINDER_EXPORTED int64_t setCallingWorkSourceUid(uid_t uid);
// Internal only. Use setCallingWorkSourceUid(uid) instead.
LIBBINDER_EXPORTED int64_t setCallingWorkSourceUidWithoutPropagation(uid_t uid);
// See Binder#getCallingWorkSourceUid in Binder.java.
LIBBINDER_EXPORTED uid_t getCallingWorkSourceUid() const;
// See Binder#clearCallingWorkSource in Binder.java.
LIBBINDER_EXPORTED int64_t clearCallingWorkSource();
// See Binder#restoreCallingWorkSource in Binder.java.
LIBBINDER_EXPORTED void restoreCallingWorkSource(int64_t token);
LIBBINDER_EXPORTED void clearPropagateWorkSource();
LIBBINDER_EXPORTED bool shouldPropagateWorkSource() const;
LIBBINDER_EXPORTED void setLastTransactionBinderFlags(int32_t flags);
LIBBINDER_EXPORTED int32_t getLastTransactionBinderFlags() const;
LIBBINDER_EXPORTED void setCallRestriction(CallRestriction restriction);
LIBBINDER_EXPORTED CallRestriction getCallRestriction() const;
LIBBINDER_EXPORTED int64_t clearCallingIdentity();
// Restores PID/UID (not SID)
LIBBINDER_EXPORTED void restoreCallingIdentity(int64_t token);
LIBBINDER_EXPORTED bool hasExplicitIdentity();
// For main functions - dangerous for libraries to use
LIBBINDER_EXPORTED status_t setupPolling(int* fd);
LIBBINDER_EXPORTED status_t handlePolledCommands();
LIBBINDER_EXPORTED void flushCommands();
LIBBINDER_EXPORTED bool flushIfNeeded();
// Adds the current thread into the binder threadpool.
//
// This is in addition to any threads which are started
// with startThreadPool. Libraries should not call this
// function, as they may be loaded into processes which
// try to configure the threadpool differently.
LIBBINDER_EXPORTED void joinThreadPool(bool isMain = true);
// Stop the local process.
LIBBINDER_EXPORTED void stopProcess(bool immediate = true);
LIBBINDER_EXPORTED status_t transact(int32_t handle, uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags);
LIBBINDER_EXPORTED void incStrongHandle(int32_t handle, BpBinder* proxy);
LIBBINDER_EXPORTED void decStrongHandle(int32_t handle);
LIBBINDER_EXPORTED void incWeakHandle(int32_t handle, BpBinder* proxy);
LIBBINDER_EXPORTED void decWeakHandle(int32_t handle);
LIBBINDER_EXPORTED status_t attemptIncStrongHandle(int32_t handle);
LIBBINDER_EXPORTED static void expungeHandle(int32_t handle, IBinder* binder);
LIBBINDER_EXPORTED status_t requestDeathNotification(int32_t handle, BpBinder* proxy);
LIBBINDER_EXPORTED status_t clearDeathNotification(int32_t handle, BpBinder* proxy);
[[nodiscard]] status_t addFrozenStateChangeCallback(int32_t handle, BpBinder* proxy);
[[nodiscard]] status_t removeFrozenStateChangeCallback(int32_t handle, BpBinder* proxy);
LIBBINDER_EXPORTED static void shutdown();
// Call this to disable switching threads to background scheduling when
// receiving incoming IPC calls. This is specifically here for the
// Android system process, since it expects to have background apps calling
// in to it but doesn't want to acquire locks in its services while in
// the background.
LIBBINDER_EXPORTED static void disableBackgroundScheduling(bool disable);
LIBBINDER_EXPORTED bool backgroundSchedulingDisabled();
// Call blocks until the number of executing binder threads is less than
// the maximum number of binder threads threads allowed for this process.
LIBBINDER_EXPORTED void blockUntilThreadAvailable();
// Service manager registration
LIBBINDER_EXPORTED void setTheContextObject(const sp<BBinder>& obj);
// WARNING: DO NOT USE THIS API
//
// Returns a pointer to the stack from the last time a transaction
// was initiated by the kernel. Used to compare when making nested
// calls between multiple different transports.
LIBBINDER_EXPORTED const void* getServingStackPointer() const;
// The work source represents the UID of the process we should attribute the transaction
// to. We use -1 to specify that the work source was not set using #setWorkSource.
//
// This constant needs to be kept in sync with Binder.UNSET_WORKSOURCE from the Java
// side.
LIBBINDER_EXPORTED static const int32_t kUnsetWorkSource = -1;
private:
IPCThreadState();
~IPCThreadState();
[[nodiscard]] status_t sendReply(const Parcel& reply, uint32_t flags);
[[nodiscard]] status_t waitForResponse(Parcel* reply, status_t* acquireResult = nullptr);
[[nodiscard]] status_t talkWithDriver(bool doReceive = true);
[[nodiscard]] status_t writeTransactionData(int32_t cmd, uint32_t binderFlags, int32_t handle,
uint32_t code, const Parcel& data,
status_t* statusBuffer);
[[nodiscard]] status_t getAndExecuteCommand();
[[nodiscard]] status_t executeCommand(int32_t command);
void processPendingDerefs();
void processPostWriteDerefs();
void clearCaller();
static void threadDestructor(void *st);
static void freeBuffer(const uint8_t* data, size_t dataSize, const binder_size_t* objects,
size_t objectsSize);
static void logExtendedError();
const sp<ProcessState> mProcess;
Vector<BBinder*> mPendingStrongDerefs;
Vector<RefBase::weakref_type*> mPendingWeakDerefs;
Vector<RefBase*> mPostWriteStrongDerefs;
Vector<RefBase::weakref_type*> mPostWriteWeakDerefs;
Parcel mIn;
Parcel mOut;
status_t mLastError;
const void* mServingStackPointer;
const SpGuard* mServingStackPointerGuard;
pid_t mCallingPid;
const char* mCallingSid;
uid_t mCallingUid;
// The UID of the process who is responsible for this transaction.
// This is used for resource attribution.
int32_t mWorkSource;
// Whether the work source should be propagated.
bool mPropagateWorkSource;
bool mIsLooper;
bool mIsFlushing;
bool mHasExplicitIdentity;
int32_t mStrictModePolicy;
int32_t mLastTransactionBinderFlags;
CallRestriction mCallRestriction;
};
} // namespace android
// ---------------------------------------------------------------------------
@@ -0,0 +1,69 @@
/*
* Copyright (C) 2005 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#ifndef __ANDROID_VNDK__
#include <binder/Common.h>
#include <binder/IInterface.h>
#include <stdlib.h>
namespace android {
// ----------------------------------------------------------------------
class LIBBINDER_EXPORTED IPermissionController : public IInterface {
public:
DECLARE_META_INTERFACE(PermissionController)
virtual bool checkPermission(const String16& permission, int32_t pid, int32_t uid) = 0;
virtual int32_t noteOp(const String16& op, int32_t uid, const String16& packageName) = 0;
virtual void getPackagesForUid(const uid_t uid, Vector<String16> &packages) = 0;
virtual bool isRuntimePermission(const String16& permission) = 0;
virtual int getPackageUid(const String16& package, int flags) = 0;
enum {
CHECK_PERMISSION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
NOTE_OP_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION + 1,
GET_PACKAGES_FOR_UID_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION + 2,
IS_RUNTIME_PERMISSION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION + 3,
GET_PACKAGE_UID_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION + 4
};
};
// ----------------------------------------------------------------------
class LIBBINDER_EXPORTED BnPermissionController : public BnInterface<IPermissionController> {
public:
// NOLINTNEXTLINE(google-default-arguments)
virtual status_t onTransact( uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0);
};
// ----------------------------------------------------------------------
} // namespace android
#else // __ANDROID_VNDK__
#error "This header is not visible to vendors"
#endif // __ANDROID_VNDK__
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/IInterface.h>
namespace android {
// ----------------------------------------------------------------------
class LIBBINDER_EXPORTED IResultReceiver : public IInterface {
public:
DECLARE_META_INTERFACE(ResultReceiver)
virtual void send(int32_t resultCode) = 0;
enum {
OP_SEND = IBinder::FIRST_CALL_TRANSACTION
};
};
// ----------------------------------------------------------------------
class LIBBINDER_EXPORTED BnResultReceiver : public BnInterface<IResultReceiver> {
public:
// NOLINTNEXTLINE(google-default-arguments)
virtual status_t onTransact( uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0);
};
// ----------------------------------------------------------------------
} // namespace android
@@ -0,0 +1,354 @@
/*
* Copyright (C) 2005 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/IInterface.h>
// Trusty has its own definition of socket APIs from trusty_ipc.h
#ifndef __TRUSTY__
#include <sys/socket.h>
#endif // __TRUSTY__
#include <utils/String16.h>
#include <utils/Vector.h>
#include <optional>
#include <set>
namespace android {
/**
* Service manager for C++ services.
*
* IInterface is only for legacy ABI compatibility
*/
class LIBBINDER_EXPORTED IServiceManager : public IInterface {
public:
// for ABI compatibility
virtual const String16& getInterfaceDescriptor() const;
IServiceManager();
virtual ~IServiceManager();
/**
* Must match values in IServiceManager.aidl
*/
/* Allows services to dump sections according to priorities. */
static const int DUMP_FLAG_PRIORITY_CRITICAL = 1 << 0;
static const int DUMP_FLAG_PRIORITY_HIGH = 1 << 1;
static const int DUMP_FLAG_PRIORITY_NORMAL = 1 << 2;
/**
* Services are by default registered with a DEFAULT dump priority. DEFAULT priority has the
* same priority as NORMAL priority but the services are not called with dump priority
* arguments.
*/
static const int DUMP_FLAG_PRIORITY_DEFAULT = 1 << 3;
static const int DUMP_FLAG_PRIORITY_ALL = DUMP_FLAG_PRIORITY_CRITICAL |
DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PRIORITY_DEFAULT;
static const int DUMP_FLAG_PROTO = 1 << 4;
/**
* Retrieve an existing service, blocking for a few seconds if it doesn't yet exist. This
* does polling. A more efficient way to make sure you unblock as soon as the service is
* available is to use waitForService or to use service notifications.
*
* Warning: when using this API, typically, you should call it in a loop. It's dangerous to
* assume that nullptr could mean that the service is not available. The service could just
* be starting. Generally, whether a service exists, this information should be declared
* externally (for instance, an Android feature might imply the existence of a service,
* a system property, or in the case of services in the VINTF manifest, it can be checked
* with isDeclared).
*/
[[deprecated("this polls for 5s, prefer waitForService or checkService")]]
virtual sp<IBinder> getService(const String16& name) const = 0;
/**
* Retrieve an existing service, non-blocking.
*/
virtual sp<IBinder> checkService( const String16& name) const = 0;
/**
* Register a service.
*
* Note:
* This status_t return value may be an exception code from an underlying
* Status type that doesn't have a representive error code in
* utils/Errors.h.
* One example of this is a return value of -7
* (Status::Exception::EX_UNSUPPORTED_OPERATION) when the service manager
* process is not installed on the device when addService is called.
*/
// NOLINTNEXTLINE(google-default-arguments)
virtual status_t addService(const String16& name, const sp<IBinder>& service,
bool allowIsolated = false,
int dumpsysFlags = DUMP_FLAG_PRIORITY_DEFAULT) = 0;
/**
* Return list of all existing services.
*/
// NOLINTNEXTLINE(google-default-arguments)
virtual Vector<String16> listServices(int dumpsysFlags = DUMP_FLAG_PRIORITY_ALL) = 0;
/**
* Efficiently wait for a service.
*
* Returns nullptr only for permission problem or fatal error.
*/
virtual sp<IBinder> waitForService(const String16& name) = 0;
/**
* Check if a service is declared (e.g. VINTF manifest).
*
* If this returns true, waitForService should always be able to return the
* service.
*/
virtual bool isDeclared(const String16& name) = 0;
/**
* Get all instances of a service as declared in the VINTF manifest
*/
virtual Vector<String16> getDeclaredInstances(const String16& interface) = 0;
/**
* If this instance is updatable via an APEX, returns the APEX with which
* this can be updated.
*/
virtual std::optional<String16> updatableViaApex(const String16& name) = 0;
/**
* Returns all instances which are updatable via the APEX. Instance names are fully qualified
* like `pack.age.IFoo/default`.
*/
virtual Vector<String16> getUpdatableNames(const String16& apexName) = 0;
/**
* If this instance has declared remote connection information, returns
* the ConnectionInfo.
*/
struct ConnectionInfo {
std::string ipAddress;
unsigned int port;
};
virtual std::optional<ConnectionInfo> getConnectionInfo(const String16& name) = 0;
struct LocalRegistrationCallback : public virtual RefBase {
virtual void onServiceRegistration(const String16& instance, const sp<IBinder>& binder) = 0;
virtual ~LocalRegistrationCallback() {}
};
virtual status_t registerForNotifications(const String16& name,
const sp<LocalRegistrationCallback>& callback) = 0;
virtual status_t unregisterForNotifications(const String16& name,
const sp<LocalRegistrationCallback>& callback) = 0;
struct ServiceDebugInfo {
std::string name;
int pid;
};
virtual std::vector<ServiceDebugInfo> getServiceDebugInfo() = 0;
/**
* Directly enable or disable caching binder during addService calls.
* Only used for testing. This is enabled by default.
*/
virtual void enableAddServiceCache(bool value) = 0;
};
LIBBINDER_EXPORTED sp<IServiceManager> defaultServiceManager();
/**
* Directly set the default service manager. Only used for testing.
* Note that the caller is responsible for caling this method
* *before* any call to defaultServiceManager(); if the latter is
* called first, setDefaultServiceManager() will abort.
*/
LIBBINDER_EXPORTED void setDefaultServiceManager(const sp<IServiceManager>& sm);
template<typename INTERFACE>
sp<INTERFACE> waitForService(const String16& name) {
const sp<IServiceManager> sm = defaultServiceManager();
return interface_cast<INTERFACE>(sm->waitForService(name));
}
template<typename INTERFACE>
sp<INTERFACE> waitForDeclaredService(const String16& name) {
const sp<IServiceManager> sm = defaultServiceManager();
if (!sm->isDeclared(name)) return nullptr;
return interface_cast<INTERFACE>(sm->waitForService(name));
}
template <typename INTERFACE>
sp<INTERFACE> checkDeclaredService(const String16& name) {
const sp<IServiceManager> sm = defaultServiceManager();
if (!sm->isDeclared(name)) return nullptr;
return interface_cast<INTERFACE>(sm->checkService(name));
}
template<typename INTERFACE>
sp<INTERFACE> waitForVintfService(
const String16& instance = String16("default")) {
return waitForDeclaredService<INTERFACE>(
INTERFACE::descriptor + String16("/") + instance);
}
template<typename INTERFACE>
sp<INTERFACE> checkVintfService(
const String16& instance = String16("default")) {
return checkDeclaredService<INTERFACE>(
INTERFACE::descriptor + String16("/") + instance);
}
template<typename INTERFACE>
status_t getService(const String16& name, sp<INTERFACE>* outService)
{
const sp<IServiceManager> sm = defaultServiceManager();
if (sm != nullptr) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
*outService = interface_cast<INTERFACE>(sm->getService(name));
#pragma clang diagnostic pop // getService deprecation
if ((*outService) != nullptr) return NO_ERROR;
}
return NAME_NOT_FOUND;
}
LIBBINDER_EXPORTED void* openDeclaredPassthroughHal(const String16& interface,
const String16& instance, int flag);
LIBBINDER_EXPORTED bool checkCallingPermission(const String16& permission);
LIBBINDER_EXPORTED bool checkCallingPermission(const String16& permission, int32_t* outPid,
int32_t* outUid);
LIBBINDER_EXPORTED bool checkPermission(const String16& permission, pid_t pid, uid_t uid,
bool logPermissionFailure = true);
// ----------------------------------------------------------------------
// Trusty's definition of the socket APIs does not include sockaddr types
#ifndef __TRUSTY__
typedef std::function<status_t(const String16& name, sockaddr* outAddr, socklen_t addrSize)>
RpcSocketAddressProvider;
/**
* This callback provides a way for clients to get access to remote services by
* providing an Accessor object from libbinder that can connect to the remote
* service over sockets.
*
* \param instance name of the service that the callback will provide an
* Accessor for. The provided accessor will be used to set up a client
* RPC connection in libbinder in order to return a binder for the
* associated remote service.
*
* \return IBinder of the Accessor object that libbinder implements.
* nullptr if the provider callback doesn't know how to reach the
* service or doesn't want to provide access for any other reason.
*/
typedef std::function<sp<IBinder>(const String16& instance)> RpcAccessorProvider;
class AccessorProvider;
/**
* Register a RpcAccessorProvider for the service manager APIs.
*
* \param instances that the RpcAccessorProvider knows about and can provide an
* Accessor for.
* \param provider callback that generates Accessors.
*
* \return A pointer used as a recept for the successful addition of the
* AccessorProvider. This is needed to unregister it later.
*/
[[nodiscard]] LIBBINDER_EXPORTED std::weak_ptr<AccessorProvider> addAccessorProvider(
std::set<std::string>&& instances, RpcAccessorProvider&& providerCallback);
/**
* Remove an accessor provider using the pointer provided by addAccessorProvider
* along with the cookie pointer that was used.
*
* \param provider cookie that was returned by addAccessorProvider to keep track
* of this instance.
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t
removeAccessorProvider(std::weak_ptr<AccessorProvider> provider);
/**
* Create an Accessor associated with a service that can create a socket connection based
* on the connection info from the supplied RpcSocketAddressProvider.
*
* \param instance name of the service that this Accessor is associated with
* \param connectionInfoProvider a callback that returns connection info for
* connecting to the service.
* \return the binder of the IAccessor implementation from libbinder
*/
LIBBINDER_EXPORTED sp<IBinder> createAccessor(const String16& instance,
RpcSocketAddressProvider&& connectionInfoProvider);
/**
* Check to make sure this binder is the expected binder that is an IAccessor
* associated with a specific instance.
*
* This helper function exists to avoid adding the IAccessor type to
* libbinder_ndk.
*
* \param instance name of the service that this Accessor should be associated with
* \param binder to validate
*
* \return OK if the binder is an IAccessor for `instance`
*/
LIBBINDER_EXPORTED status_t validateAccessor(const String16& instance, const sp<IBinder>& binder);
/**
* Have libbinder wrap this IAccessor binder in an IAccessorDelegator and return
* it.
*
* This is required only in very specific situations when the process that has
* permissions to connect the to RPC service's socket and create the FD for it
* is in a separate process from this process that wants to service the Accessor
* binder and the communication between these two processes is binder RPC. This
* is needed because the binder passed over the binder RPC connection can not be
* used as a kernel binder, and needs to be wrapped by a kernel binder that can
* then be registered with service manager.
*
* \param instance name of the Accessor.
* \param binder to wrap in a Delegator and register with service manager.
* \param outDelegator the wrapped kernel binder for IAccessorDelegator
*
* \return OK if the binder is an IAccessor for `instance` and the delegator was
* successfully created.
*/
LIBBINDER_EXPORTED status_t delegateAccessor(const String16& name, const sp<IBinder>& accessor,
sp<IBinder>* delegator);
#endif // __TRUSTY__
#ifndef __ANDROID__
// Create an IServiceManager that delegates the service manager on the device via adb.
// This is can be set as the default service manager at program start, so that
// defaultServiceManager() returns it:
// int main() {
// setDefaultServiceManager(createRpcDelegateServiceManager());
// auto sm = defaultServiceManager();
// // ...
// }
// Resources are cleaned up when the object is destroyed.
//
// For each returned binder object, at most |maxOutgoingConnections| outgoing connections are
// instantiated, depending on how many the service on the device is configured with.
// Hence, only |maxOutgoingConnections| calls can be made simultaneously.
// See also RpcSession::setMaxOutgoingConnections.
struct RpcDelegateServiceManagerOptions {
std::optional<size_t> maxOutgoingConnections;
};
LIBBINDER_EXPORTED sp<IServiceManager> createRpcDelegateServiceManager(
const RpcDelegateServiceManagerOptions& options);
#endif
} // namespace android
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <android/os/IServiceManager.h>
namespace android::impl {
LIBBINDER_EXPORTED sp<android::os::IServiceManager>
getJavaServicemanagerImplPrivateDoNotUseExceptInTheOnePlaceItIsUsed();
} // namespace android::impl
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <android/os/IServiceManager.h>
#include "IServiceManager.h"
namespace android {
/**
* Encapsulate an AidlServiceManager in a CppBackendShim. Only used for testing.
*/
LIBBINDER_EXPORTED sp<IServiceManager> getServiceManagerShimFromAidlServiceManagerForTests(
const sp<os::IServiceManager>& sm);
} // namespace android
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/IInterface.h>
namespace android {
// ----------------------------------------------------------------------
class LIBBINDER_EXPORTED IShellCallback : public IInterface {
public:
DECLARE_META_INTERFACE(ShellCallback)
virtual int openFile(const String16& path, const String16& seLinuxContext,
const String16& mode) = 0;
enum {
OP_OPEN_OUTPUT_FILE = IBinder::FIRST_CALL_TRANSACTION
};
};
// ----------------------------------------------------------------------
class LIBBINDER_EXPORTED BnShellCallback : public BnInterface<IShellCallback> {
public:
// NOLINTNEXTLINE(google-default-arguments)
virtual status_t onTransact( uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0);
};
// ----------------------------------------------------------------------
} // namespace android
@@ -0,0 +1,114 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <functional>
#include <binder/Common.h>
#include <binder/IServiceManager.h>
#include <binder/Status.h>
#include <utils/StrongPointer.h>
namespace android {
namespace binder {
namespace internal {
class ClientCounterCallback;
} // namespace internal
/**
* Exits when all services registered through this object have 0 clients
*
* In order to use this class, it's expected that your service:
* - registers all services in the process with this API
* - configures services as oneshot in init .rc files
* - configures services as disabled in init.rc files, unless a client is
* guaranteed early in boot, in which case, forcePersist should also be used
* to avoid races.
* - uses 'interface' declarations in init .rc files
*
* For more information on init .rc configuration, see system/core/init/README.md
**/
class LazyServiceRegistrar {
public:
LIBBINDER_EXPORTED static LazyServiceRegistrar& getInstance();
LIBBINDER_EXPORTED status_t
registerService(const sp<IBinder>& service, const std::string& name = "default",
bool allowIsolated = false,
int dumpFlags = IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT);
/**
* Force the service to persist, even when it has 0 clients.
* If setting this flag from the server side, make sure to do so before calling
* registerService, or there may be a race with the default dynamic shutdown.
*
* This should only be used if it is every eventually set to false. If a
* service needs to persist but doesn't need to dynamically shut down,
* prefer to control it with another mechanism such as ctl.start.
*/
LIBBINDER_EXPORTED void forcePersist(bool persist);
/**
* Set a callback that is invoked when the active service count (i.e. services with clients)
* registered with this process drops to zero (or becomes nonzero).
* The callback takes a boolean argument, which is 'true' if there is
* at least one service with clients.
*
* Callback return value:
* - false: Default behavior for lazy services (shut down the process if there
* are no clients).
* - true: Don't shut down the process even if there are no clients.
*
* This callback gives a chance to:
* 1 - Perform some additional operations before exiting;
* 2 - Prevent the process from exiting by returning "true" from the
* callback.
*
* This method should be called before 'registerService' to avoid races.
*/
LIBBINDER_EXPORTED void setActiveServicesCallback(
const std::function<bool(bool)>& activeServicesCallback);
/**
* Try to unregister all services previously registered with 'registerService'.
* Returns 'true' if successful. This should only be called within the callback registered by
* setActiveServicesCallback.
*/
LIBBINDER_EXPORTED bool tryUnregister();
/**
* Re-register services that were unregistered by 'tryUnregister'.
* This method should be called in the case 'tryUnregister' fails
* (and should be called on the same thread).
*/
LIBBINDER_EXPORTED void reRegister();
/**
* Create a second instance of lazy service registrar.
*
* WARNING: dangerous! DO NOT USE THIS - LazyServiceRegistrar
* should be single-instanced, so that the service will only
* shut down when all services are unused. A separate instance
* is only used to test race conditions.
*/
LIBBINDER_EXPORTED static LazyServiceRegistrar createExtraTestInstance();
private:
std::shared_ptr<internal::ClientCounterCallback> mClientCC;
LazyServiceRegistrar();
};
} // namespace binder
} // namespace android
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <binder/Common.h>
#include <binder/IMemory.h>
namespace android {
// ---------------------------------------------------------------------------
class LIBBINDER_EXPORTED MemoryBase : public BnMemory {
public:
MemoryBase(const sp<IMemoryHeap>& heap, ssize_t offset, size_t size);
virtual ~MemoryBase();
virtual sp<IMemoryHeap> getMemory(ssize_t* offset, size_t* size) const;
protected:
size_t getSize() const { return mSize; }
ssize_t getOffset() const { return mOffset; }
const sp<IMemoryHeap>& getHeap() const { return mHeap; }
private:
size_t mSize;
ssize_t mOffset;
sp<IMemoryHeap> mHeap;
};
// ---------------------------------------------------------------------------
} // namespace android
@@ -0,0 +1,61 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
#include <sys/types.h>
#include <binder/Common.h>
#include <binder/IMemory.h>
#include <binder/MemoryHeapBase.h>
namespace android {
// ----------------------------------------------------------------------------
class SimpleBestFitAllocator;
// ----------------------------------------------------------------------------
class MemoryDealer : public RefBase {
public:
LIBBINDER_EXPORTED explicit MemoryDealer(
size_t size, const char* name = nullptr,
uint32_t flags = 0 /* or bits such as MemoryHeapBase::READ_ONLY */);
LIBBINDER_EXPORTED virtual sp<IMemory> allocate(size_t size);
LIBBINDER_EXPORTED virtual void dump(const char* what) const;
// allocations are aligned to some value. return that value so clients can account for it.
LIBBINDER_EXPORTED static size_t getAllocationAlignment();
sp<IMemoryHeap> getMemoryHeap() const { return heap(); }
protected:
LIBBINDER_EXPORTED virtual ~MemoryDealer();
private:
friend class Allocation;
virtual void deallocate(size_t offset);
LIBBINDER_EXPORTED const sp<IMemoryHeap>& heap() const;
SimpleBestFitAllocator* allocator() const;
sp<IMemoryHeap> mHeap;
SimpleBestFitAllocator* mAllocator;
};
// ----------------------------------------------------------------------------
} // namespace android
@@ -0,0 +1,111 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <binder/Common.h>
#include <binder/IMemory.h>
namespace android {
// ---------------------------------------------------------------------------
class MemoryHeapBase : public BnMemoryHeap {
public:
static constexpr auto MEMFD_ALLOW_SEALING_FLAG = 0x00000800;
enum {
READ_ONLY = IMemoryHeap::READ_ONLY,
// memory won't be mapped locally, but will be mapped in the remote
// process.
DONT_MAP_LOCALLY = 0x00000100,
NO_CACHING = 0x00000200,
// Bypass ashmem-libcutils to create a memfd shared region.
// Ashmem-libcutils will eventually migrate to memfd.
// Memfd has security benefits and supports file sealing.
// Calling process will need to modify selinux permissions to
// open access to tmpfs files. See audioserver for examples.
// This is only valid for size constructor.
// For host compilation targets, memfd is stubbed in favor of /tmp
// files so sealing is not enforced.
FORCE_MEMFD = 0x00000400,
// Default opt-out of sealing behavior in memfd to avoid potential DOS.
// Clients of shared files can seal at anytime via syscall, leading to
// TOC/TOU issues if additional seals prevent access from the creating
// process. Alternatively, seccomp fcntl().
MEMFD_ALLOW_SEALING = FORCE_MEMFD | MEMFD_ALLOW_SEALING_FLAG
};
/*
* maps the memory referenced by fd. but DOESN'T take ownership
* of the filedescriptor (it makes a copy with dup()
*/
LIBBINDER_EXPORTED MemoryHeapBase(int fd, size_t size, uint32_t flags = 0, off_t offset = 0);
/*
* maps memory from the given device
*/
LIBBINDER_EXPORTED explicit MemoryHeapBase(const char* device, size_t size = 0,
uint32_t flags = 0);
/*
* maps memory from ashmem, with the given name for debugging
* if the READ_ONLY flag is set, the memory will be writeable by the calling process,
* but not by others. this is NOT the case with the other ctors.
*/
LIBBINDER_EXPORTED explicit MemoryHeapBase(size_t size, uint32_t flags = 0,
char const* name = nullptr);
LIBBINDER_EXPORTED virtual ~MemoryHeapBase();
/* implement IMemoryHeap interface */
LIBBINDER_EXPORTED int getHeapID() const override;
/* virtual address of the heap. returns MAP_FAILED in case of error */
LIBBINDER_EXPORTED void* getBase() const override;
LIBBINDER_EXPORTED size_t getSize() const override;
LIBBINDER_EXPORTED uint32_t getFlags() const override;
LIBBINDER_EXPORTED off_t getOffset() const override;
LIBBINDER_EXPORTED const char* getDevice() const;
/* this closes this heap -- use carefully */
LIBBINDER_EXPORTED void dispose();
protected:
LIBBINDER_EXPORTED MemoryHeapBase();
// init() takes ownership of fd
LIBBINDER_EXPORTED status_t init(int fd, void* base, size_t size, int flags = 0,
const char* device = nullptr);
private:
status_t mapfd(int fd, bool writeableByCaller, size_t size, off_t offset = 0);
int mFD;
size_t mSize;
void* mBase;
uint32_t mFlags;
const char* mDevice;
bool mNeedUnmap;
off_t mOffset;
};
// ---------------------------------------------------------------------------
} // namespace android
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,70 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/Parcel.h>
#include <binder/Parcelable.h>
#include <binder/unique_fd.h>
namespace android {
namespace os {
/*
* C++ implementation of the Java class android.os.ParcelFileDescriptor
*/
class LIBBINDER_EXPORTED ParcelFileDescriptor : public android::Parcelable {
public:
ParcelFileDescriptor();
explicit ParcelFileDescriptor(binder::unique_fd fd);
ParcelFileDescriptor(ParcelFileDescriptor&& other) noexcept : mFd(std::move(other.mFd)) { }
ParcelFileDescriptor& operator=(ParcelFileDescriptor&& other) noexcept = default;
~ParcelFileDescriptor() override;
int get() const { return mFd.get(); }
binder::unique_fd release() { return std::move(mFd); }
void reset(binder::unique_fd fd = binder::unique_fd()) { mFd = std::move(fd); }
// android::Parcelable override:
android::status_t writeToParcel(android::Parcel* parcel) const override;
android::status_t readFromParcel(const android::Parcel* parcel) override;
inline std::string toString() const { return "ParcelFileDescriptor:" + std::to_string(get()); }
inline bool operator!=(const ParcelFileDescriptor& rhs) const {
return mFd.get() != rhs.mFd.get();
}
inline bool operator<(const ParcelFileDescriptor& rhs) const {
return mFd.get() < rhs.mFd.get();
}
inline bool operator<=(const ParcelFileDescriptor& rhs) const {
return mFd.get() <= rhs.mFd.get();
}
inline bool operator==(const ParcelFileDescriptor& rhs) const {
return mFd.get() == rhs.mFd.get();
}
inline bool operator>(const ParcelFileDescriptor& rhs) const {
return mFd.get() > rhs.mFd.get();
}
inline bool operator>=(const ParcelFileDescriptor& rhs) const {
return mFd.get() >= rhs.mFd.get();
}
private:
binder::unique_fd mFd;
};
} // namespace os
} // namespace android
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <vector>
#include <utils/Errors.h>
#include <utils/String16.h>
#include <binder/Common.h>
namespace android {
class Parcel;
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wweak-vtables"
#endif
// Abstract interface of all parcelables.
class LIBBINDER_EXPORTED Parcelable {
public:
virtual ~Parcelable() = default;
Parcelable() = default;
Parcelable(const Parcelable&) = default;
// Write |this| parcelable to the given |parcel|. Keep in mind that
// implementations of writeToParcel must be manually kept in sync
// with readFromParcel and the Java equivalent versions of these methods.
//
// Returns android::OK on success and an appropriate error otherwise.
virtual status_t writeToParcel(Parcel* parcel) const = 0;
// Read data from the given |parcel| into |this|. After readFromParcel
// completes, |this| should have equivalent state to the object that
// wrote itself to the parcel.
//
// Returns android::OK on success and an appropriate error otherwise.
virtual status_t readFromParcel(const Parcel* parcel) = 0;
// WARNING: for use by auto-generated code only (AIDL). Should not be used
// manually, or there is a risk of breaking CTS, GTS, VTS, or CTS-on-GSI
// tests.
enum class Stability : int32_t {
STABILITY_LOCAL,
STABILITY_VINTF, // corresponds to @VintfStability
};
// 'Stable' means this parcelable is guaranteed to be stable for multiple
// years.
// It must be guaranteed by setting stability field in aidl_interface.
// WARNING: getStability() is only expected to be overridden by auto-generated
// code. Returns true if this parcelable is stable.
virtual Stability getStability() const { return Stability::STABILITY_LOCAL; }
}; // class Parcelable
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
} // namespace android
@@ -0,0 +1,146 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/Parcel.h>
#include <binder/Parcelable.h>
#include <utils/String16.h>
#include <mutex>
#include <optional>
#include <tuple>
namespace android {
namespace os {
/*
* C++ implementation of the Java class android.os.ParcelableHolder
*/
class LIBBINDER_EXPORTED ParcelableHolder : public android::Parcelable {
public:
ParcelableHolder() = delete;
explicit ParcelableHolder(Stability stability) : mStability(stability){}
virtual ~ParcelableHolder() = default;
ParcelableHolder(const ParcelableHolder& other) {
mParcelable = other.mParcelable;
mParcelableName = other.mParcelableName;
if (other.mParcelPtr) {
mParcelPtr = std::make_unique<Parcel>();
mParcelPtr->appendFrom(other.mParcelPtr.get(), 0, other.mParcelPtr->dataSize());
}
mStability = other.mStability;
}
ParcelableHolder(ParcelableHolder&& other) = default;
status_t writeToParcel(Parcel* parcel) const override;
status_t readFromParcel(const Parcel* parcel) override;
void reset() {
this->mParcelable = nullptr;
this->mParcelableName = std::nullopt;
this->mParcelPtr = nullptr;
}
template <typename T>
status_t setParcelable(T&& p) {
using Tt = typename std::decay<T>::type;
return setParcelable<Tt>(std::make_shared<Tt>(std::forward<T>(p)));
}
template <typename T>
status_t setParcelable(std::shared_ptr<T> p) {
static_assert(std::is_base_of<Parcelable, T>::value, "T must be derived from Parcelable");
if (p && this->getStability() > p->getStability()) {
return android::BAD_VALUE;
}
this->mParcelable = p;
this->mParcelableName = T::getParcelableDescriptor();
this->mParcelPtr = nullptr;
return android::OK;
}
template <typename T>
status_t getParcelable(std::shared_ptr<T>* ret) const {
static_assert(std::is_base_of<Parcelable, T>::value, "T must be derived from Parcelable");
const String16& parcelableDesc = T::getParcelableDescriptor();
if (!this->mParcelPtr) {
if (!this->mParcelable || !this->mParcelableName) {
ALOGD("empty ParcelableHolder");
*ret = nullptr;
return android::OK;
} else if (parcelableDesc != *mParcelableName) {
ALOGD("extension class name mismatch expected:%s actual:%s",
String8(*mParcelableName).c_str(), String8(parcelableDesc).c_str());
*ret = nullptr;
return android::BAD_VALUE;
}
*ret = std::static_pointer_cast<T>(mParcelable);
return android::OK;
}
this->mParcelPtr->setDataPosition(0);
status_t status = this->mParcelPtr->readString16(&this->mParcelableName);
if (status != android::OK || parcelableDesc != this->mParcelableName) {
this->mParcelableName = std::nullopt;
*ret = nullptr;
return status;
}
this->mParcelable = std::make_shared<T>();
status = mParcelable.get()->readFromParcel(this->mParcelPtr.get());
if (status != android::OK) {
this->mParcelableName = std::nullopt;
this->mParcelable = nullptr;
*ret = nullptr;
return status;
}
this->mParcelPtr = nullptr;
*ret = std::static_pointer_cast<T>(mParcelable);
return android::OK;
}
Stability getStability() const override { return mStability; }
inline std::string toString() const {
return "ParcelableHolder:" +
(mParcelableName ? std::string(String8(mParcelableName.value()).c_str())
: "<parceled>");
}
inline bool operator!=(const ParcelableHolder& rhs) const {
return this != &rhs;
}
inline bool operator<(const ParcelableHolder& rhs) const {
return this < &rhs;
}
inline bool operator<=(const ParcelableHolder& rhs) const {
return this <= &rhs;
}
inline bool operator==(const ParcelableHolder& rhs) const {
return this == &rhs;
}
inline bool operator>(const ParcelableHolder& rhs) const {
return this > &rhs;
}
inline bool operator>=(const ParcelableHolder& rhs) const {
return this >= &rhs;
}
private:
mutable std::shared_ptr<Parcelable> mParcelable;
mutable std::optional<String16> mParcelableName;
mutable std::unique_ptr<Parcel> mParcelPtr;
Stability mStability;
};
} // namespace os
} // namespace android
@@ -0,0 +1,86 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#ifndef __ANDROID_VNDK__
#include <stdint.h>
#include <unistd.h>
#include <utils/String16.h>
#include <utils/Singleton.h>
#include <utils/SortedVector.h>
#include <binder/Common.h>
namespace android {
// ---------------------------------------------------------------------------
/*
* PermissionCache caches permission checks for a given uid.
*
* Currently the cache is not updated when there is a permission change,
* for instance when an application is uninstalled.
*
* IMPORTANT: for the reason stated above, only system permissions are safe
* to cache. This restriction may be lifted at a later time.
*
*/
class PermissionCache : Singleton<PermissionCache> {
struct Entry {
String16 name;
uid_t uid;
bool granted;
inline bool operator < (const Entry& e) const {
return (uid == e.uid) ? (name < e.name) : (uid < e.uid);
}
};
mutable Mutex mLock;
// we pool all the permission names we see, as many permissions checks
// will have identical names
SortedVector< String16 > mPermissionNamesPool;
// this is our cache per say. it stores pooled names.
SortedVector< Entry > mCache;
// free the whole cache, but keep the permission name pool
void purge();
status_t check(bool* granted,
const String16& permission, uid_t uid) const;
void cache(const String16& permission, uid_t uid, bool granted);
public:
LIBBINDER_EXPORTED PermissionCache();
LIBBINDER_EXPORTED static bool checkCallingPermission(const String16& permission);
LIBBINDER_EXPORTED static bool checkCallingPermission(const String16& permission,
int32_t* outPid, int32_t* outUid);
LIBBINDER_EXPORTED static bool checkPermission(const String16& permission, pid_t pid,
uid_t uid);
LIBBINDER_EXPORTED static void purgeCache();
};
// ---------------------------------------------------------------------------
} // namespace android
#else // __ANDROID_VNDK__
#error "This header is not visible to vendors"
#endif // __ANDROID_VNDK__
@@ -0,0 +1,65 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#ifndef __ANDROID_VNDK__
#include <binder/Common.h>
#include <binder/IPermissionController.h>
#include <utils/Mutex.h>
// ---------------------------------------------------------------------------
namespace android {
class PermissionController {
public:
enum {
MATCH_SYSTEM_ONLY = 1<<16,
MATCH_UNINSTALLED_PACKAGES = 1<<13,
MATCH_FACTORY_ONLY = 1<<21,
MATCH_INSTANT = 1<<23
};
enum {
MODE_ALLOWED = 0,
MODE_IGNORED = 1,
MODE_ERRORED = 2,
MODE_DEFAULT = 3,
};
LIBBINDER_EXPORTED PermissionController();
LIBBINDER_EXPORTED bool checkPermission(const String16& permission, int32_t pid, int32_t uid);
LIBBINDER_EXPORTED int32_t noteOp(const String16& op, int32_t uid, const String16& packageName);
LIBBINDER_EXPORTED void getPackagesForUid(const uid_t uid, Vector<String16>& packages);
LIBBINDER_EXPORTED bool isRuntimePermission(const String16& permission);
LIBBINDER_EXPORTED int getPackageUid(const String16& package, int flags);
private:
Mutex mLock;
sp<IPermissionController> mService;
sp<IPermissionController> getService();
};
} // namespace android
// ---------------------------------------------------------------------------
#else // __ANDROID_VNDK__
#error "This header is not visible to vendors"
#endif // __ANDROID_VNDK__
@@ -0,0 +1,130 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <map>
#include <set>
#include <vector>
#include <binder/Common.h>
#include <binder/Parcelable.h>
#include <utils/String16.h>
#include <utils/StrongPointer.h>
namespace android {
namespace os {
/*
* C++ implementation of PersistableBundle, a mapping from String values to
* various types that can be saved to persistent and later restored.
*/
class LIBBINDER_EXPORTED PersistableBundle : public Parcelable {
public:
PersistableBundle() = default;
virtual ~PersistableBundle() = default;
PersistableBundle(const PersistableBundle& bundle) = default;
status_t writeToParcel(Parcel* parcel) const override;
status_t readFromParcel(const Parcel* parcel) override;
bool empty() const;
size_t size() const;
size_t erase(const String16& key);
/*
* Setters for PersistableBundle. Adds a a key-value pair instantiated with
* |key| and |value| into the member map appropriate for the type of |value|.
* If there is already an existing value for |key|, |value| will replace it.
*/
void putBoolean(const String16& key, bool value);
void putInt(const String16& key, int32_t value);
void putLong(const String16& key, int64_t value);
void putDouble(const String16& key, double value);
void putString(const String16& key, const String16& value);
void putBooleanVector(const String16& key, const std::vector<bool>& value);
void putIntVector(const String16& key, const std::vector<int32_t>& value);
void putLongVector(const String16& key, const std::vector<int64_t>& value);
void putDoubleVector(const String16& key, const std::vector<double>& value);
void putStringVector(const String16& key, const std::vector<String16>& value);
void putPersistableBundle(const String16& key, const PersistableBundle& value);
/*
* Getters for PersistableBundle. If |key| exists, these methods write the
* value associated with |key| into |out|, and return true. Otherwise, these
* methods return false.
*/
bool getBoolean(const String16& key, bool* out) const;
bool getInt(const String16& key, int32_t* out) const;
bool getLong(const String16& key, int64_t* out) const;
bool getDouble(const String16& key, double* out) const;
bool getString(const String16& key, String16* out) const;
bool getBooleanVector(const String16& key, std::vector<bool>* out) const;
bool getIntVector(const String16& key, std::vector<int32_t>* out) const;
bool getLongVector(const String16& key, std::vector<int64_t>* out) const;
bool getDoubleVector(const String16& key, std::vector<double>* out) const;
bool getStringVector(const String16& key, std::vector<String16>* out) const;
bool getPersistableBundle(const String16& key, PersistableBundle* out) const;
/* Getters for all keys for each value type */
std::set<String16> getBooleanKeys() const;
std::set<String16> getIntKeys() const;
std::set<String16> getLongKeys() const;
std::set<String16> getDoubleKeys() const;
std::set<String16> getStringKeys() const;
std::set<String16> getBooleanVectorKeys() const;
std::set<String16> getIntVectorKeys() const;
std::set<String16> getLongVectorKeys() const;
std::set<String16> getDoubleVectorKeys() const;
std::set<String16> getStringVectorKeys() const;
std::set<String16> getPersistableBundleKeys() const;
friend bool operator==(const PersistableBundle& lhs, const PersistableBundle& rhs) {
return (lhs.mBoolMap == rhs.mBoolMap && lhs.mIntMap == rhs.mIntMap &&
lhs.mLongMap == rhs.mLongMap && lhs.mDoubleMap == rhs.mDoubleMap &&
lhs.mStringMap == rhs.mStringMap && lhs.mBoolVectorMap == rhs.mBoolVectorMap &&
lhs.mIntVectorMap == rhs.mIntVectorMap &&
lhs.mLongVectorMap == rhs.mLongVectorMap &&
lhs.mDoubleVectorMap == rhs.mDoubleVectorMap &&
lhs.mStringVectorMap == rhs.mStringVectorMap &&
lhs.mPersistableBundleMap == rhs.mPersistableBundleMap);
}
friend bool operator!=(const PersistableBundle& lhs, const PersistableBundle& rhs) {
return !(lhs == rhs);
}
private:
status_t writeToParcelInner(Parcel* parcel) const;
status_t readFromParcelInner(const Parcel* parcel, size_t length);
std::map<String16, bool> mBoolMap;
std::map<String16, int32_t> mIntMap;
std::map<String16, int64_t> mLongMap;
std::map<String16, double> mDoubleMap;
std::map<String16, String16> mStringMap;
std::map<String16, std::vector<bool>> mBoolVectorMap;
std::map<String16, std::vector<int32_t>> mIntVectorMap;
std::map<String16, std::vector<int64_t>> mLongVectorMap;
std::map<String16, std::vector<double>> mDoubleVectorMap;
std::map<String16, std::vector<String16>> mStringVectorMap;
std::map<String16, PersistableBundle> mPersistableBundleMap;
};
} // namespace os
} // namespace android
@@ -0,0 +1,202 @@
/*
* Copyright (C) 2005 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/IBinder.h>
#include <utils/String16.h>
#include <utils/String8.h>
#include <pthread.h>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <mutex>
// ---------------------------------------------------------------------------
namespace android {
class IPCThreadState;
/**
* Kernel binder process state. All operations here refer to kernel binder. This
* object is allocated per process.
*/
class ProcessState : public virtual RefBase {
public:
LIBBINDER_EXPORTED static sp<ProcessState> self();
LIBBINDER_EXPORTED static sp<ProcessState> selfOrNull();
LIBBINDER_EXPORTED static bool isVndservicemanagerEnabled();
/* initWithDriver() can be used to configure libbinder to use
* a different binder driver dev node. It must be called *before*
* any call to ProcessState::self(). The default is /dev/vndbinder
* for processes built with the VNDK and /dev/binder for those
* which are not.
*
* If this is called with nullptr, the behavior is the same as selfOrNull.
*/
LIBBINDER_EXPORTED static sp<ProcessState> initWithDriver(const char* driver);
LIBBINDER_EXPORTED sp<IBinder> getContextObject(const sp<IBinder>& caller);
// This should be called before startThreadPool at the beginning
// of a program, and libraries should never call it because programs
// should configure their own threadpools. The threadpool size can
// never be decreased.
//
// The 'maxThreads' value refers to the total number of threads
// that will be started by the kernel. This is in addition to any
// threads started by 'startThreadPool' or 'joinRpcThreadpool'.
LIBBINDER_EXPORTED status_t setThreadPoolMaxThreadCount(size_t maxThreads);
// Libraries should not call this, as processes should configure
// threadpools themselves. Should be called in the main function
// directly before any code executes or joins the threadpool.
//
// Starts one thread, PLUS those requested in setThreadPoolMaxThreadCount,
// PLUS those manually requested in joinThreadPool.
//
// For instance, if setThreadPoolMaxCount(3) is called and
// startThreadpPool (+1 thread) and joinThreadPool (+1 thread)
// are all called, then up to 5 threads can be started.
LIBBINDER_EXPORTED void startThreadPool();
[[nodiscard]] LIBBINDER_EXPORTED bool becomeContextManager();
LIBBINDER_EXPORTED sp<IBinder> getStrongProxyForHandle(int32_t handle);
LIBBINDER_EXPORTED void expungeHandle(int32_t handle, IBinder* binder);
// TODO: deprecate.
LIBBINDER_EXPORTED void spawnPooledThread(bool isMain);
LIBBINDER_EXPORTED status_t enableOnewaySpamDetection(bool enable);
// Set the name of the current thread to look like a threadpool
// thread. Typically this is called before joinThreadPool.
//
// TODO: remove this API, and automatically set it intelligently.
LIBBINDER_EXPORTED void giveThreadPoolName();
LIBBINDER_EXPORTED String8 getDriverName();
LIBBINDER_EXPORTED ssize_t getKernelReferences(size_t count, uintptr_t* buf);
// Only usable by the context manager.
// This refcount includes:
// 1. Strong references to the node by this and other processes
// 2. Temporary strong references held by the kernel during a
// transaction on the node.
// It does NOT include local strong references to the node
LIBBINDER_EXPORTED ssize_t getStrongRefCountForNode(const sp<BpBinder>& binder);
enum class CallRestriction {
// all calls okay
NONE,
// log when calls are blocking
ERROR_IF_NOT_ONEWAY,
// abort process on blocking calls
FATAL_IF_NOT_ONEWAY,
};
// Sets calling restrictions for all transactions in this process. This must be called
// before any threads are spawned.
LIBBINDER_EXPORTED void setCallRestriction(CallRestriction restriction);
/**
* Get the max number of threads that have joined the thread pool.
* This includes kernel started threads, user joined threads and polling
* threads if used.
*/
LIBBINDER_EXPORTED size_t getThreadPoolMaxTotalThreadCount() const;
/**
* Check to see if the thread pool has started.
*/
LIBBINDER_EXPORTED bool isThreadPoolStarted() const;
enum class DriverFeature {
ONEWAY_SPAM_DETECTION,
EXTENDED_ERROR,
FREEZE_NOTIFICATION,
};
// Determine whether a feature is supported by the binder driver.
LIBBINDER_EXPORTED static bool isDriverFeatureEnabled(const DriverFeature feature);
private:
static sp<ProcessState> init(const char* defaultDriver, bool requireDefault);
void checkExpectingThreadPoolStart() const;
static void onFork();
static void parentPostFork();
static void childPostFork();
friend class IPCThreadState;
friend class sp<ProcessState>;
explicit ProcessState(const char* driver);
~ProcessState();
ProcessState(const ProcessState& o);
ProcessState& operator=(const ProcessState& o);
String8 makeBinderThreadName();
struct handle_entry {
IBinder* binder;
RefBase::weakref_type* refs;
};
handle_entry* lookupHandleLocked(int32_t handle);
String8 mDriverName;
int mDriverFD;
void* mVMStart;
mutable std::mutex mOnThreadAvailableLock;
std::condition_variable mOnThreadAvailableCondVar;
// Number of threads waiting on `mOnThreadAvailableCondVar`.
std::atomic_int64_t mOnThreadAvailableWaiting = 0;
// Number of binder threads current executing a command.
std::atomic_size_t mExecutingThreadsCount;
// Maximum number of lazy threads to be started in the threadpool by the kernel.
std::atomic_size_t mMaxThreads;
// Current number of threads inside the thread pool.
std::atomic_size_t mCurrentThreads;
// Current number of pooled threads inside the thread pool.
std::atomic_size_t mKernelStartedThreads;
// Time when thread pool was emptied
std::atomic<std::chrono::steady_clock::time_point> mStarvationStartTime;
static constexpr auto never = &std::chrono::steady_clock::time_point::min;
mutable std::mutex mLock; // protects everything below.
Vector<handle_entry> mHandleToObject;
bool mForked;
std::atomic_bool mThreadPoolStarted;
std::atomic_int32_t mThreadPoolSeq;
CallRestriction mCallRestriction;
};
} // namespace android
// ---------------------------------------------------------------------------
@@ -0,0 +1,89 @@
/*
* Copyright (C) 2022, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/Parcel.h>
#include <binder/unique_fd.h>
#include <mutex>
namespace android {
namespace binder::debug {
// Warning: Transactions are sequentially recorded to the file descriptor in a
// non-stable format. A detailed description of the recording format can be found in
// RecordedTransaction.cpp.
class RecordedTransaction {
public:
// Filled with the first transaction from fd.
LIBBINDER_EXPORTED static std::optional<RecordedTransaction> fromFile(
const binder::unique_fd& fd);
// Filled with the arguments.
LIBBINDER_EXPORTED static std::optional<RecordedTransaction> fromDetails(
const String16& interfaceName, uint32_t code, uint32_t flags, timespec timestamp,
const Parcel& data, const Parcel& reply, status_t err);
LIBBINDER_EXPORTED RecordedTransaction(RecordedTransaction&& t) noexcept;
[[nodiscard]] LIBBINDER_EXPORTED status_t dumpToFile(const binder::unique_fd& fd) const;
LIBBINDER_EXPORTED const std::string& getInterfaceName() const;
LIBBINDER_EXPORTED uint32_t getCode() const;
LIBBINDER_EXPORTED uint32_t getFlags() const;
LIBBINDER_EXPORTED int32_t getReturnedStatus() const;
LIBBINDER_EXPORTED timespec getTimestamp() const;
LIBBINDER_EXPORTED uint32_t getVersion() const;
LIBBINDER_EXPORTED const Parcel& getDataParcel() const;
LIBBINDER_EXPORTED const Parcel& getReplyParcel() const;
LIBBINDER_EXPORTED const std::vector<uint64_t>& getObjectOffsets() const;
private:
RecordedTransaction() = default;
android::status_t writeChunk(const binder::borrowed_fd, uint32_t chunkType, size_t byteCount,
const uint8_t* data) const;
#pragma clang diagnostic push
#pragma clang diagnostic error "-Wpadded"
struct TransactionHeader {
uint32_t code = 0;
uint32_t flags = 0;
int32_t statusReturned = 0;
uint32_t version = 0; // !0 iff Rpc
int64_t timestampSeconds = 0;
int32_t timestampNanoseconds = 0;
int32_t reserved = 0;
};
#pragma clang diagnostic pop
static_assert(sizeof(TransactionHeader) == 32);
static_assert(sizeof(TransactionHeader) % 8 == 0);
struct MovableData { // movable
TransactionHeader mHeader;
std::string mInterfaceName;
std::vector<uint64_t> mSentObjectData; /* Object Offsets */
};
MovableData mData;
Parcel mSentDataOnly;
Parcel mReplyDataOnly;
};
} // namespace binder::debug
} // namespace android
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Formats for serializing TLS certificate.
#pragma once
#include <string>
namespace android {
enum class RpcCertificateFormat {
PEM,
DER,
};
static inline std::string PrintToString(RpcCertificateFormat format) {
switch (format) {
case RpcCertificateFormat::PEM:
return "PEM";
case RpcCertificateFormat::DER:
return "DER";
default:
return "<unknown>";
}
}
} // namespace android
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Formats for serializing TLS private keys.
#pragma once
#include <string>
namespace android {
enum class RpcKeyFormat {
PEM,
DER,
};
static inline std::string PrintToString(RpcKeyFormat format) {
switch (format) {
case RpcKeyFormat::PEM:
return "PEM";
case RpcKeyFormat::DER:
return "DER";
default:
return "<unknown>";
}
}
} // namespace android
@@ -0,0 +1,297 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/IBinder.h>
#include <binder/RpcSession.h>
#include <binder/RpcThreads.h>
#include <binder/RpcTransport.h>
#include <binder/unique_fd.h>
#include <utils/Errors.h>
#include <utils/RefBase.h>
#include <bitset>
#include <mutex>
#include <thread>
namespace android {
class FdTrigger;
class RpcServerTrusty;
class RpcSocketAddress;
/**
* This represents a server of an interface, which may be connected to by any
* number of clients over sockets.
*
* Usage:
* auto server = RpcServer::make();
* // only supports one now
* if (!server->setup*Server(...)) {
* :(
* }
* server->join();
*/
class RpcServer final : public virtual RefBase, private RpcSession::EventListener {
public:
LIBBINDER_EXPORTED static sp<RpcServer> make(
std::unique_ptr<RpcTransportCtxFactory> rpcTransportCtxFactory = nullptr);
/**
* Creates an RPC server that bootstraps sessions using an existing
* Unix domain socket pair.
*
* Callers should create a pair of SOCK_STREAM Unix domain sockets, pass
* one to RpcServer::setupUnixDomainSocketBootstrapServer and the other
* to RpcSession::setupUnixDomainSocketBootstrapClient. Multiple client
* session can be created from the client end of the pair.
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t
setupUnixDomainSocketBootstrapServer(binder::unique_fd serverFd);
/**
* This represents a session for responses, e.g.:
*
* process A serves binder a
* process B opens a session to process A
* process B makes binder b and sends it to A
* A uses this 'back session' to send things back to B
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t setupUnixDomainServer(const char* path);
/**
* Sets up an RPC server with a raw socket file descriptor.
* The socket should be created and bound to a socket address already, e.g.
* the socket can be created in init.rc.
*
* This method is used in the libbinder_rpc_unstable API
* RunInitUnixDomainRpcServer().
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t setupRawSocketServer(binder::unique_fd socket_fd);
/**
* Creates an RPC server binding to the given CID at the given port.
*
* Set |port| to VMADDR_PORT_ANY to pick an ephemeral port. In this case, |assignedPort|
* will be set to the picked port number, if it is not null.
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t setupVsockServer(unsigned bindCid, unsigned port,
unsigned* assignedPort = nullptr);
/**
* Creates an RPC server at the current port using IPv4.
*
* TODO(b/182914638): IPv6 support
*
* Set |port| to 0 to pick an ephemeral port; see discussion of
* /proc/sys/net/ipv4/ip_local_port_range in ip(7). In this case, |assignedPort|
* will be set to the picked port number, if it is not null.
*
* Set the IPv4 address for the socket to be listening on.
* "127.0.0.1" allows for local connections from the same device.
* "0.0.0.0" allows for connections on any IP address that the device may
* have
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t setupInetServer(const char* address,
unsigned int port,
unsigned int* assignedPort = nullptr);
/**
* If setup*Server has been successful, return true. Otherwise return false.
*/
[[nodiscard]] LIBBINDER_EXPORTED bool hasServer();
/**
* If hasServer(), return the server FD. Otherwise return invalid FD.
*/
[[nodiscard]] LIBBINDER_EXPORTED binder::unique_fd releaseServer();
/**
* Set up server using an external FD previously set up by releaseServer().
* Return false if there's already a server.
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t setupExternalServer(binder::unique_fd serverFd);
/**
* This must be called before adding a client session. This corresponds
* to the number of incoming connections to RpcSession objects in the
* server, which will correspond to the number of outgoing connections
* in client RpcSession objects.
*
* If this is not specified, this will be a single-threaded server.
*
* TODO(b/167966510): these are currently created per client, but these
* should be shared.
*/
LIBBINDER_EXPORTED void setMaxThreads(size_t threads);
LIBBINDER_EXPORTED size_t getMaxThreads();
/**
* By default, the latest protocol version which is supported by a client is
* used. However, this can be used in order to prevent newer protocol
* versions from ever being used. This is expected to be useful for testing.
*/
[[nodiscard]] LIBBINDER_EXPORTED bool setProtocolVersion(uint32_t version);
/**
* Set the supported transports for sending and receiving file descriptors.
*
* Clients will propose a mode when connecting. If the mode is not in the
* provided list, the connection will be rejected.
*/
LIBBINDER_EXPORTED void setSupportedFileDescriptorTransportModes(
const std::vector<RpcSession::FileDescriptorTransportMode>& modes);
/**
* The root object can be retrieved by any client, without any
* authentication. TODO(b/183988761)
*
* Holds a strong reference to the root object.
*/
LIBBINDER_EXPORTED void setRootObject(const sp<IBinder>& binder);
/**
* Holds a weak reference to the root object.
*/
LIBBINDER_EXPORTED void setRootObjectWeak(const wp<IBinder>& binder);
/**
* Allows a root object to be created for each session.
*
* Takes one argument: a callable that is invoked once per new session.
* The callable takes three arguments:
* - a weak pointer to the session. If you want to hold onto this in the root object, then
* you should keep a weak pointer, and promote it when needed. For instance, if you refer
* to this from the root object, then you could get ahold of transport-specific information.
* - a type-erased pointer to an OS- and transport-specific address structure, e.g.,
* sockaddr_vm for vsock
* - an integer representing the size in bytes of that structure. The callable should
* validate the size, then cast the type-erased pointer to a pointer to the actual type of the
* address, e.g., const void* to const sockaddr_vm*.
*/
LIBBINDER_EXPORTED void setPerSessionRootObject(
std::function<sp<IBinder>(wp<RpcSession> session, const void*, size_t)>&& object);
LIBBINDER_EXPORTED sp<IBinder> getRootObject();
/**
* Set optional filter of incoming connections based on the peer's address.
*
* Takes one argument: a callable that is invoked on each accept()-ed
* connection and returns false if the connection should be dropped.
* See the description of setPerSessionRootObject() for details about
* the callable's arguments.
*/
LIBBINDER_EXPORTED void setConnectionFilter(std::function<bool(const void*, size_t)>&& filter);
/**
* Set optional modifier of each newly created server socket.
*
* The only argument is a successfully created file descriptor, not bound to an address yet.
*/
LIBBINDER_EXPORTED void setServerSocketModifier(
std::function<void(binder::borrowed_fd)>&& modifier);
/**
* See RpcTransportCtx::getCertificate
*/
LIBBINDER_EXPORTED std::vector<uint8_t> getCertificate(RpcCertificateFormat);
/**
* Runs join() in a background thread. Immediately returns.
*/
LIBBINDER_EXPORTED void start();
/**
* You must have at least one client session before calling this.
*
* If a client needs to actively terminate join, call shutdown() in a separate thread.
*
* At any given point, there can only be one thread calling join().
*
* Warning: if shutdown is called, this will return while the shutdown is
* still occurring. To ensure that the service is fully shutdown, you might
* want to call shutdown after 'join' returns.
*/
LIBBINDER_EXPORTED void join();
/**
* Shut down any existing join(). Return true if successfully shut down, false otherwise
* (e.g. no join() is running). Will wait for the server to be fully
* shutdown.
*
* Warning: this will hang if it is called from its own thread.
*/
[[nodiscard]] LIBBINDER_EXPORTED bool shutdown();
/**
* For debugging!
*/
LIBBINDER_EXPORTED std::vector<sp<RpcSession>> listSessions();
LIBBINDER_EXPORTED size_t numUninitializedSessions();
/**
* Whether any requests are currently being processed.
*/
LIBBINDER_EXPORTED bool hasActiveRequests();
LIBBINDER_EXPORTED ~RpcServer();
private:
friend RpcServerTrusty;
friend sp<RpcServer>;
explicit RpcServer(std::unique_ptr<RpcTransportCtx> ctx);
void onSessionAllIncomingThreadsEnded(const sp<RpcSession>& session) override;
void onSessionIncomingThreadEnded() override;
status_t setupExternalServer(
binder::unique_fd serverFd,
std::function<status_t(const RpcServer&, RpcTransportFd*)>&& acceptFn);
static constexpr size_t kRpcAddressSize = 128;
static void establishConnection(
sp<RpcServer>&& server, RpcTransportFd clientFd,
std::array<uint8_t, kRpcAddressSize> addr, size_t addrLen,
std::function<void(sp<RpcSession>&&, RpcSession::PreJoinSetupResult&&)>&& joinFn);
static status_t acceptSocketConnection(const RpcServer& server, RpcTransportFd* out);
static status_t recvmsgSocketConnection(const RpcServer& server, RpcTransportFd* out);
[[nodiscard]] status_t setupSocketServer(const RpcSocketAddress& address);
const std::unique_ptr<RpcTransportCtx> mCtx;
size_t mMaxThreads = 1;
std::optional<uint32_t> mProtocolVersion;
// A mode is supported if the N'th bit is on, where N is the mode enum's value.
std::bitset<8> mSupportedFileDescriptorTransportModes = std::bitset<8>().set(
static_cast<size_t>(RpcSession::FileDescriptorTransportMode::NONE));
RpcTransportFd mServer; // socket we are accepting sessions on
RpcMutex mLock; // for below
std::unique_ptr<RpcMaybeThread> mJoinThread;
bool mJoinThreadRunning = false;
std::map<RpcMaybeThread::id, RpcMaybeThread> mConnectingThreads;
sp<IBinder> mRootObject;
wp<IBinder> mRootObjectWeak;
std::function<sp<IBinder>(wp<RpcSession>, const void*, size_t)> mRootObjectFactory;
std::function<bool(const void*, size_t)> mConnectionFilter;
std::function<void(binder::borrowed_fd)> mServerSocketModifier;
std::map<std::vector<uint8_t>, sp<RpcSession>> mSessions;
std::unique_ptr<FdTrigger> mShutdownTrigger;
RpcConditionVariable mShutdownCv;
std::function<status_t(const RpcServer& server, RpcTransportFd* out)> mAcceptFn;
};
} // namespace android
@@ -0,0 +1,413 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/IBinder.h>
#include <binder/RpcThreads.h>
#include <binder/RpcTransport.h>
#include <binder/unique_fd.h>
#include <utils/Errors.h>
#include <utils/RefBase.h>
#include <map>
#include <optional>
#include <type_traits>
#include <vector>
namespace android {
class Parcel;
class RpcServer;
class RpcServerTrusty;
class RpcSocketAddress;
class RpcState;
class RpcTransport;
class FdTrigger;
constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_NEXT = 2;
constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL = 0xF0000000;
constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION = 1;
// Starting with this version:
//
// * RpcWireReply is larger (4 bytes -> 20).
// * RpcWireTransaction and RpcWireReplyV1 include the parcel data size.
constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_RPC_HEADER_FEATURE_EXPLICIT_PARCEL_SIZE = 1;
/**
* This represents a session (group of connections) between a client
* and a server. Multiple connections are needed for multiple parallel "binder"
* calls which may also have nested calls.
*
* Once a binder exists in the session, if all references to all binders are dropped,
* the session shuts down.
*/
class RpcSession final : public virtual RefBase {
public:
// Create an RpcSession with default configuration (raw sockets).
LIBBINDER_EXPORTED static sp<RpcSession> make();
// Create an RpcSession with the given configuration. |serverRpcCertificateFormat| and
// |serverCertificate| must have values or be nullopt simultaneously. If they have values, set
// server certificate.
LIBBINDER_EXPORTED static sp<RpcSession> make(
std::unique_ptr<RpcTransportCtxFactory> rpcTransportCtxFactory);
/**
* Set the maximum number of incoming threads allowed to be made (for things like callbacks).
* By default, this is 0. This must be called before setting up this connection as a client.
* Server sessions will inherits this value from RpcServer. Each thread will serve a
* connection to the remote RpcSession.
*
* If this is called, 'shutdown' on this session must also be called.
* Otherwise, a threadpool will leak.
*
* TODO(b/189955605): start these lazily - currently all are started
*/
LIBBINDER_EXPORTED void setMaxIncomingThreads(size_t threads);
LIBBINDER_EXPORTED size_t getMaxIncomingThreads();
/**
* Set the maximum number of outgoing connections allowed to be made.
* By default, this is |kDefaultMaxOutgoingConnections|. This must be called before setting up
* this connection as a client.
*
* For an RpcSession client, if you are connecting to a server which starts N threads,
* then this must be set to >= N. If you set the maximum number of outgoing connections
* to 1, but the server requests 10, then it would be considered an error. If you set a
* maximum number of connections to 10, and the server requests 1, then only 1 will be
* created. This API is used to limit the amount of resources a server can request you
* create.
*/
LIBBINDER_EXPORTED void setMaxOutgoingConnections(size_t connections);
LIBBINDER_EXPORTED size_t getMaxOutgoingThreads();
/**
* By default, the minimum of the supported versions of the client and the
* server will be used. Usually, this API should only be used for debugging.
*/
[[nodiscard]] LIBBINDER_EXPORTED bool setProtocolVersion(uint32_t version);
LIBBINDER_EXPORTED std::optional<uint32_t> getProtocolVersion();
enum class FileDescriptorTransportMode : uint8_t {
NONE = 0,
// Send file descriptors via unix domain socket ancillary data.
UNIX = 1,
// Send file descriptors as Trusty IPC handles.
TRUSTY = 2,
};
/**
* Set the transport for sending and receiving file descriptors.
*/
LIBBINDER_EXPORTED void setFileDescriptorTransportMode(FileDescriptorTransportMode mode);
LIBBINDER_EXPORTED FileDescriptorTransportMode getFileDescriptorTransportMode();
/**
* This should be called once per thread, matching 'join' in the remote
* process.
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t setupUnixDomainClient(const char* path);
/**
* Connects to an RPC server over a nameless Unix domain socket pair.
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t
setupUnixDomainSocketBootstrapClient(binder::unique_fd bootstrap);
/**
* Connects to an RPC server at the CID & port.
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t setupVsockClient(unsigned int cid, unsigned int port);
/**
* Connects to an RPC server at the given address and port.
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t setupInetClient(const char* addr, unsigned int port);
/**
* Starts talking to an RPC server which has already been connected to. This
* is expected to be used when another process has permission to connect to
* a binder RPC service, but this process only has permission to talk to
* that service.
*
* For convenience, if 'fd' is -1, 'request' will be called.
*
* For future compatibility, 'request' should not reference any stack data.
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t
setupPreconnectedClient(binder::unique_fd fd, std::function<binder::unique_fd()>&& request);
/**
* For debugging!
*
* Sets up an empty connection. All queries to this connection which require a
* response will never be satisfied. All data sent here will be
* unceremoniously cast down the bottomless pit, /dev/null.
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t addNullDebuggingClient();
/**
* Query the other side of the session for the root object hosted by that
* process's RpcServer (if one exists)
*/
LIBBINDER_EXPORTED sp<IBinder> getRootObject();
/**
* Query the other side of the session for the maximum number of threads
* it supports (maximum number of concurrent non-nested synchronous transactions)
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t getRemoteMaxThreads(size_t* maxThreads);
/**
* See RpcTransportCtx::getCertificate
*/
LIBBINDER_EXPORTED std::vector<uint8_t> getCertificate(RpcCertificateFormat);
/**
* Shuts down the service.
*
* For client sessions, wait can be true or false. For server sessions,
* waiting is not currently supported (will abort).
*
* Warning: this is currently not active/nice (the server isn't told we're
* shutting down). Being nicer to the server could potentially make it
* reclaim resources faster.
*
* If this is called w/ 'wait' true, then this will wait for shutdown to
* complete before returning. This will hang if it is called from the
* session threadpool (when processing received calls).
*/
[[nodiscard]] LIBBINDER_EXPORTED bool shutdownAndWait(bool wait);
[[nodiscard]] LIBBINDER_EXPORTED status_t transact(const sp<IBinder>& binder, uint32_t code,
const Parcel& data, Parcel* reply,
uint32_t flags);
/**
* Generally, you should not call this, unless you are testing error
* conditions, as this is called automatically by BpBinders when they are
* deleted (this is also why a raw pointer is used here)
*/
[[nodiscard]] LIBBINDER_EXPORTED status_t sendDecStrong(const BpBinder* binder);
/**
* Whether any requests are currently being processed.
*/
LIBBINDER_EXPORTED bool hasActiveRequests();
LIBBINDER_EXPORTED ~RpcSession();
/**
* Server if this session is created as part of a server (symmetrical to
* client servers). Otherwise, nullptr.
*/
LIBBINDER_EXPORTED sp<RpcServer> server();
// internal only
LIBBINDER_EXPORTED const std::unique_ptr<RpcState>& state() { return mRpcBinderState; }
/**
* Sets the session-specific root object. This is the object that will be used to attach
* the IAccessor binder to the RpcSession when a binder is set up via accessor.
*/
LIBBINDER_EXPORTED void setSessionSpecificRoot(const sp<IBinder>& sessionSpecificRoot);
private:
friend sp<RpcSession>;
friend RpcServer;
friend RpcServerTrusty;
friend RpcState;
explicit RpcSession(std::unique_ptr<RpcTransportCtx> ctx);
static constexpr size_t kDefaultMaxOutgoingConnections = 10;
// internal version of setProtocolVersion that
// optionally skips the mStartedSetup check
[[nodiscard]] bool setProtocolVersionInternal(uint32_t version, bool checkStarted);
// for 'target', see RpcState::sendDecStrongToTarget
[[nodiscard]] status_t sendDecStrongToTarget(uint64_t address, size_t target);
class EventListener : public virtual RefBase {
public:
virtual void onSessionAllIncomingThreadsEnded(const sp<RpcSession>& session) = 0;
virtual void onSessionIncomingThreadEnded() = 0;
};
class WaitForShutdownListener : public EventListener {
public:
void onSessionAllIncomingThreadsEnded(const sp<RpcSession>& session) override;
void onSessionIncomingThreadEnded() override;
void waitForShutdown(RpcMutexUniqueLock& lock, const sp<RpcSession>& session);
private:
RpcConditionVariable mCv;
std::atomic<size_t> mShutdownCount = 0;
};
friend WaitForShutdownListener;
struct RpcConnection : public RefBase {
std::unique_ptr<RpcTransport> rpcTransport;
// whether this or another thread is currently using this fd to make
// or receive transactions.
std::optional<uint64_t> exclusiveTid;
bool allowNested = false;
};
[[nodiscard]] status_t readId();
// A thread joining a server must always call these functions in order, and
// cleanup is only programmed once into join. These are in separate
// functions in order to allow for different locks to be taken during
// different parts of setup.
//
// transfer ownership of thread (usually done while a lock is taken on the
// structure which originally owns the thread)
void preJoinThreadOwnership(RpcMaybeThread thread);
// pass FD to thread and read initial connection information
struct PreJoinSetupResult {
// Server connection object associated with this
sp<RpcConnection> connection;
// Status of setup
status_t status;
};
PreJoinSetupResult preJoinSetup(std::unique_ptr<RpcTransport> rpcTransport);
// join on thread passed to preJoinThreadOwnership
static void join(sp<RpcSession>&& session, PreJoinSetupResult&& result);
// This is a workaround to support move-only functors.
// TODO: use std::move_only_function when it becomes available.
template <typename Fn,
// Fn must be a callable type taking (const std::vector<uint8_t>&, bool) and returning
// status_t
typename = std::enable_if_t<
std::is_invocable_r_v<status_t, Fn, const std::vector<uint8_t>&, bool>>>
[[nodiscard]] status_t setupClient(Fn&& connectAndInit);
[[nodiscard]] status_t setupSocketClient(const RpcSocketAddress& address);
[[nodiscard]] status_t setupOneSocketConnection(const RpcSocketAddress& address,
const std::vector<uint8_t>& sessionId,
bool incoming);
[[nodiscard]] status_t initAndAddConnection(RpcTransportFd fd,
const std::vector<uint8_t>& sessionId,
bool incoming);
[[nodiscard]] status_t addIncomingConnection(std::unique_ptr<RpcTransport> rpcTransport);
[[nodiscard]] status_t addOutgoingConnection(std::unique_ptr<RpcTransport> rpcTransport,
bool init);
[[nodiscard]] bool setForServer(const wp<RpcServer>& server,
const wp<RpcSession::EventListener>& eventListener,
const std::vector<uint8_t>& sessionId,
const sp<IBinder>& sessionSpecificRoot);
sp<RpcConnection> assignIncomingConnectionToThisThread(
std::unique_ptr<RpcTransport> rpcTransport);
[[nodiscard]] bool removeIncomingConnection(const sp<RpcConnection>& connection);
void clearConnectionTid(const sp<RpcConnection>& connection);
[[nodiscard]] status_t initShutdownTrigger();
/**
* Checks whether any connection is active (Not polling on fd)
*/
bool hasActiveConnection(const std::vector<sp<RpcConnection>>& connections);
enum class ConnectionUse {
CLIENT,
CLIENT_ASYNC,
CLIENT_REFCOUNT,
};
// Object representing exclusive access to a connection.
class ExclusiveConnection {
public:
[[nodiscard]] static status_t find(const sp<RpcSession>& session, ConnectionUse use,
ExclusiveConnection* connection);
~ExclusiveConnection();
const sp<RpcConnection>& get() { return mConnection; }
private:
static void findConnection(uint64_t tid, sp<RpcConnection>* exclusive,
sp<RpcConnection>* available,
std::vector<sp<RpcConnection>>& sockets,
size_t socketsIndexHint);
sp<RpcSession> mSession; // avoid deallocation
sp<RpcConnection> mConnection;
// whether this is being used for a nested transaction (being on the same
// thread guarantees we won't write in the middle of a message, the way
// the wire protocol is constructed guarantees this is safe).
bool mReentrant = false;
};
const std::unique_ptr<RpcTransportCtx> mCtx;
// On the other side of a session, for each of mOutgoing here, there should
// be one of mIncoming on the other side (and vice versa).
//
// For the simplest session, a single server with one client, you would
// have:
// - the server has a single 'mIncoming' and a thread listening on this
// - the client has a single 'mOutgoing' and makes calls to this
// - here, when the client makes a call, the server can call back into it
// (nested calls), but outside of this, the client will only ever read
// calls from the server when it makes a call itself.
//
// For a more complicated case, the client might itself open up a thread to
// serve calls to the server at all times (e.g. if it hosts a callback)
wp<RpcServer> mForServer; // maybe null, for client sessions
sp<WaitForShutdownListener> mShutdownListener; // used for client sessions
wp<EventListener> mEventListener; // mForServer if server, mShutdownListener if client
// session-specific root object (if a different root is used for each
// session)
sp<IBinder> mSessionSpecificRootObject;
std::vector<uint8_t> mId;
std::unique_ptr<FdTrigger> mShutdownTrigger;
std::unique_ptr<RpcState> mRpcBinderState;
RpcMutex mMutex; // for all below
bool mStartedSetup = false;
size_t mMaxIncomingThreads = 0;
size_t mMaxOutgoingConnections = kDefaultMaxOutgoingConnections;
std::optional<uint32_t> mProtocolVersion;
FileDescriptorTransportMode mFileDescriptorTransportMode = FileDescriptorTransportMode::NONE;
RpcConditionVariable mAvailableConnectionCv; // for mWaitingThreads
std::unique_ptr<RpcTransport> mBootstrapTransport;
struct ThreadState {
size_t mWaitingThreads = 0;
// hint index into clients, ++ when sending an async transaction
size_t mOutgoingOffset = 0;
std::vector<sp<RpcConnection>> mOutgoing;
// max size of mIncoming. Once any thread starts down, no more can be started.
size_t mMaxIncoming = 0;
std::vector<sp<RpcConnection>> mIncoming;
std::map<RpcMaybeThread::id, RpcMaybeThread> mThreads;
} mConnections;
};
} // namespace android
@@ -0,0 +1,139 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <pthread.h>
#include <condition_variable>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>
#include <binder/Common.h>
namespace android {
#ifdef BINDER_RPC_SINGLE_THREADED
class LIBBINDER_EXPORTED RpcMutex {
public:
void lock() {}
void unlock() {}
};
class LIBBINDER_EXPORTED RpcMutexUniqueLock {
public:
RpcMutexUniqueLock(RpcMutex&) {}
void unlock() {}
};
class LIBBINDER_EXPORTED RpcMutexLockGuard {
public:
RpcMutexLockGuard(RpcMutex&) {}
};
class LIBBINDER_EXPORTED RpcConditionVariable {
public:
void notify_one() {}
void notify_all() {}
void wait(RpcMutexUniqueLock&) {}
template <typename Predicate>
void wait(RpcMutexUniqueLock&, Predicate stop_waiting) {
LOG_ALWAYS_FATAL_IF(!stop_waiting(), "RpcConditionVariable::wait condition not met");
}
template <typename Duration>
std::cv_status wait_for(RpcMutexUniqueLock&, const Duration&) {
return std::cv_status::no_timeout;
}
template <typename Duration, typename Predicate>
bool wait_for(RpcMutexUniqueLock&, const Duration&, Predicate stop_waiting) {
return stop_waiting();
}
};
class LIBBINDER_EXPORTED RpcMaybeThread {
public:
RpcMaybeThread() = default;
template <typename Function, typename... Args>
RpcMaybeThread(Function&& f, Args&&... args) {
// std::function requires a copy-constructible closure,
// so we need to wrap both the function and its arguments
// in a shared pointer that std::function can copy internally
struct Vars {
std::decay_t<Function> f;
std::tuple<std::decay_t<Args>...> args;
explicit Vars(Function&& f, Args&&... args)
: f(std::move(f)), args(std::move(args)...) {}
};
auto vars = std::make_shared<Vars>(std::forward<Function>(f), std::forward<Args>(args)...);
mFunc = [vars]() { std::apply(std::move(vars->f), std::move(vars->args)); };
}
void join() {
if (mFunc) {
// Move mFunc into a temporary so we can clear mFunc before
// executing the callback. This avoids infinite recursion if
// the callee then calls join() again directly or indirectly.
decltype(mFunc) func = nullptr;
mFunc.swap(func);
func();
}
}
void detach() { join(); }
class id {
public:
bool operator==(const id&) const { return true; }
bool operator!=(const id&) const { return false; }
bool operator<(const id&) const { return false; }
bool operator<=(const id&) const { return true; }
bool operator>(const id&) const { return false; }
bool operator>=(const id&) const { return true; }
};
id get_id() const { return id(); }
private:
std::function<void(void)> mFunc;
};
namespace rpc_this_thread {
static inline RpcMaybeThread::id get_id() {
return RpcMaybeThread::id();
}
} // namespace rpc_this_thread
static inline void rpcJoinIfSingleThreaded(RpcMaybeThread& t) {
t.join();
}
#else // BINDER_RPC_SINGLE_THREADED
using RpcMutex = std::mutex;
using RpcMutexUniqueLock = std::unique_lock<std::mutex>;
using RpcMutexLockGuard = std::lock_guard<std::mutex>;
using RpcConditionVariable = std::condition_variable;
using RpcMaybeThread = std::thread;
namespace rpc_this_thread = std::this_thread;
static inline void rpcJoinIfSingleThreaded(RpcMaybeThread&) {}
#endif // BINDER_RPC_SINGLE_THREADED
} // namespace android
@@ -0,0 +1,207 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Wraps the transport layer of RPC. Implementation may use plain sockets or TLS.
#pragma once
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <variant>
#include <vector>
#include <utils/Errors.h>
#include <binder/Common.h>
#include <binder/Functional.h>
#include <binder/RpcCertificateFormat.h>
#include <binder/RpcThreads.h>
#include <binder/unique_fd.h>
#include <sys/uio.h>
namespace android {
class FdTrigger;
struct RpcTransportFd;
// for 'friend'
class RpcTransportRaw;
class RpcTransportTls;
class RpcTransportTipcAndroid;
class RpcTransportTipcTrusty;
class RpcTransportCtxRaw;
class RpcTransportCtxTls;
class RpcTransportCtxTipcAndroid;
class RpcTransportCtxTipcTrusty;
// Represents a socket connection.
// No thread-safety is guaranteed for these APIs.
class LIBBINDER_EXPORTED RpcTransport {
public:
virtual ~RpcTransport() = default;
/**
* Poll the transport to check whether there is any data ready to read.
*
* Return:
* OK - There is data available on this transport
* WOULDBLOCK - No data is available
* error - any other error
*/
[[nodiscard]] virtual status_t pollRead(void) = 0;
/**
* Read (or write), but allow to be interrupted by a trigger.
*
* iovs - array of iovecs to perform the operation on. The elements
* of the array may be modified by this method.
*
* altPoll - function to be called instead of polling, when needing to wait
* to read/write data. If this returns an error, that error is returned from
* this function.
*
* ancillaryFds - FDs to be sent via UNIX domain dockets or Trusty IPC. When
* reading, if `ancillaryFds` is null, any received FDs will be silently
* dropped and closed (by the OS). Appended values will always be unique_fd,
* the variant type is used to avoid extra copies elsewhere.
*
* Return:
* OK - succeeded in completely processing 'size'
* error - interrupted (failure or trigger)
*/
[[nodiscard]] virtual status_t interruptableWriteFully(
FdTrigger* fdTrigger, iovec* iovs, int niovs,
const std::optional<binder::impl::SmallFunction<status_t()>>& altPoll,
const std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>*
ancillaryFds) = 0;
[[nodiscard]] virtual status_t interruptableReadFully(
FdTrigger* fdTrigger, iovec* iovs, int niovs,
const std::optional<binder::impl::SmallFunction<status_t()>>& altPoll,
std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>* ancillaryFds) = 0;
/**
* Check whether any threads are blocked while polling the transport
* for read operations
* Return:
* True - Specifies that there is active polling on transport.
* False - No active polling on transport
*/
[[nodiscard]] virtual bool isWaiting() = 0;
private:
// limit the classes which can implement RpcTransport. Being able to change this
// interface is important to allow development of RPC binder. In the past, we
// changed this interface to use iovec for efficiency, and we added FDs to the
// interface. If another transport is needed, it should be added directly here.
// non-socket FDs likely also need changes in RpcSession in order to get
// connected, and similarly to how addrinfo was type-erased from RPC binder
// interfaces when RpcTransportTipc* was added, other changes may be needed
// to add more transports.
friend class ::android::RpcTransportRaw;
friend class ::android::RpcTransportTls;
friend class ::android::RpcTransportTipcAndroid;
friend class ::android::RpcTransportTipcTrusty;
RpcTransport() = default;
};
// Represents the context that generates the socket connection.
// All APIs are thread-safe. See RpcTransportCtxRaw and RpcTransportCtxTls for details.
class LIBBINDER_EXPORTED RpcTransportCtx {
public:
virtual ~RpcTransportCtx() = default;
// Create a new RpcTransport object.
//
// Implementation details: for TLS, this function may incur I/O. |fdTrigger| may be used
// to interrupt I/O. This function blocks until handshake is finished.
[[nodiscard]] virtual std::unique_ptr<RpcTransport> newTransport(
android::RpcTransportFd fd, FdTrigger *fdTrigger) const = 0;
// Return the preconfigured certificate of this context.
//
// Implementation details:
// - For raw sockets, this always returns empty string.
// - For TLS, this returns the certificate. See RpcTransportTls for details.
[[nodiscard]] virtual std::vector<uint8_t> getCertificate(
RpcCertificateFormat format) const = 0;
private:
// see comment on RpcTransport
friend class ::android::RpcTransportCtxRaw;
friend class ::android::RpcTransportCtxTls;
friend class ::android::RpcTransportCtxTipcAndroid;
friend class ::android::RpcTransportCtxTipcTrusty;
RpcTransportCtx() = default;
};
// A factory class that generates RpcTransportCtx.
// All APIs are thread-safe.
class LIBBINDER_EXPORTED RpcTransportCtxFactory {
public:
virtual ~RpcTransportCtxFactory() = default;
// Creates server context.
[[nodiscard]] virtual std::unique_ptr<RpcTransportCtx> newServerCtx() const = 0;
// Creates client context.
[[nodiscard]] virtual std::unique_ptr<RpcTransportCtx> newClientCtx() const = 0;
// Return a short description of this transport (e.g. "raw"). For logging / debugging / testing
// only.
[[nodiscard]] virtual const char *toCString() const = 0;
protected:
RpcTransportCtxFactory() = default;
};
struct LIBBINDER_EXPORTED RpcTransportFd final {
private:
mutable bool isPolling{false};
void setPollingState(bool state) const { isPolling = state; }
public:
binder::unique_fd fd;
RpcTransportFd() = default;
explicit RpcTransportFd(binder::unique_fd&& descriptor)
: isPolling(false), fd(std::move(descriptor)) {}
RpcTransportFd(RpcTransportFd &&transportFd) noexcept
: isPolling(transportFd.isPolling), fd(std::move(transportFd.fd)) {}
RpcTransportFd &operator=(RpcTransportFd &&transportFd) noexcept {
fd = std::move(transportFd.fd);
isPolling = transportFd.isPolling;
return *this;
}
RpcTransportFd& operator=(binder::unique_fd&& descriptor) noexcept {
fd = std::move(descriptor);
isPolling = false;
return *this;
}
bool isInPollingState() const { return isPolling; }
friend class FdTrigger;
};
} // namespace android
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Wraps the transport layer of RPC. Implementation uses plain sockets.
// Note: don't use directly. You probably want newServerRpcTransportCtx / newClientRpcTransportCtx.
#pragma once
#include <memory>
#include <binder/Common.h>
#include <binder/RpcTransport.h>
namespace android {
// RpcTransportCtxFactory with TLS disabled.
class RpcTransportCtxFactoryRaw : public RpcTransportCtxFactory {
public:
LIBBINDER_EXPORTED static std::unique_ptr<RpcTransportCtxFactory> make();
LIBBINDER_EXPORTED std::unique_ptr<RpcTransportCtx> newServerCtx() const override;
LIBBINDER_EXPORTED std::unique_ptr<RpcTransportCtx> newClientCtx() const override;
LIBBINDER_EXPORTED const char* toCString() const override;
private:
RpcTransportCtxFactoryRaw() = default;
};
} // namespace android
@@ -0,0 +1,726 @@
/*
* Copyright 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/IInterface.h>
#include <binder/Parcel.h>
// Set to 1 to enable CallStacks when logging errors
#define SI_DUMP_CALLSTACKS 0
#if SI_DUMP_CALLSTACKS
#include <utils/CallStack.h>
#endif
#include <utils/NativeHandle.h>
#include <functional>
#include <type_traits>
namespace android {
namespace SafeInterface {
/**
* WARNING: Prefer to use AIDL-generated interfaces. Using SafeInterface to generate interfaces
* does not support tracing, and many other AIDL features out of the box. The general direction
* we should go is to migrate safe interface users to AIDL and then remove this so that there
* is only one thing to learn/use/test/integrate, not this as well.
*/
// ParcelHandler is responsible for writing/reading various types to/from a Parcel in a generic way
class LIBBINDER_EXPORTED ParcelHandler {
public:
explicit ParcelHandler(const char* logTag) : mLogTag(logTag) {}
// Specializations for types with dedicated handling in Parcel
status_t read(const Parcel& parcel, bool* b) const {
return callParcel("readBool", [&]() { return parcel.readBool(b); });
}
status_t write(Parcel* parcel, bool b) const {
return callParcel("writeBool", [&]() { return parcel->writeBool(b); });
}
template <typename E>
typename std::enable_if<std::is_enum<E>::value, status_t>::type read(const Parcel& parcel,
E* e) const {
typename std::underlying_type<E>::type u{};
status_t result = read(parcel, &u);
*e = static_cast<E>(u);
return result;
}
template <typename E>
typename std::enable_if<std::is_enum<E>::value, status_t>::type write(Parcel* parcel,
E e) const {
return write(parcel, static_cast<typename std::underlying_type<E>::type>(e));
}
template <typename T>
typename std::enable_if<std::is_base_of<Flattenable<T>, T>::value, status_t>::type read(
const Parcel& parcel, T* t) const {
return callParcel("read(Flattenable)", [&]() { return parcel.read(*t); });
}
template <typename T>
typename std::enable_if<std::is_base_of<Flattenable<T>, T>::value, status_t>::type write(
Parcel* parcel, const T& t) const {
return callParcel("write(Flattenable)", [&]() { return parcel->write(t); });
}
template <typename T>
typename std::enable_if<std::is_base_of<Flattenable<T>, T>::value, status_t>::type read(
const Parcel& parcel, sp<T>* t) const {
*t = new T{};
return callParcel("read(sp<Flattenable>)", [&]() { return parcel.read(*(t->get())); });
}
template <typename T>
typename std::enable_if<std::is_base_of<Flattenable<T>, T>::value, status_t>::type write(
Parcel* parcel, const sp<T>& t) const {
return callParcel("write(sp<Flattenable>)", [&]() { return parcel->write(*(t.get())); });
}
template <typename T>
typename std::enable_if<std::is_base_of<LightFlattenable<T>, T>::value, status_t>::type read(
const Parcel& parcel, T* t) const {
return callParcel("read(LightFlattenable)", [&]() { return parcel.read(*t); });
}
template <typename T>
typename std::enable_if<std::is_base_of<LightFlattenable<T>, T>::value, status_t>::type write(
Parcel* parcel, const T& t) const {
return callParcel("write(LightFlattenable)", [&]() { return parcel->write(t); });
}
template <typename NH>
typename std::enable_if<std::is_same<NH, sp<NativeHandle>>::value, status_t>::type read(
const Parcel& parcel, NH* nh) {
*nh = NativeHandle::create(parcel.readNativeHandle(), true);
return NO_ERROR;
}
template <typename NH>
typename std::enable_if<std::is_same<NH, sp<NativeHandle>>::value, status_t>::type write(
Parcel* parcel, const NH& nh) {
return callParcel("write(sp<NativeHandle>)",
[&]() { return parcel->writeNativeHandle(nh->handle()); });
}
template <typename T>
typename std::enable_if<std::is_base_of<Parcelable, T>::value, status_t>::type read(
const Parcel& parcel, T* t) const {
return callParcel("readParcelable", [&]() { return parcel.readParcelable(t); });
}
template <typename T>
typename std::enable_if<std::is_base_of<Parcelable, T>::value, status_t>::type write(
Parcel* parcel, const T& t) const {
return callParcel("writeParcelable", [&]() { return parcel->writeParcelable(t); });
}
status_t read(const Parcel& parcel, String8* str) const {
return callParcel("readString8", [&]() { return parcel.readString8(str); });
}
status_t write(Parcel* parcel, const String8& str) const {
return callParcel("writeString8", [&]() { return parcel->writeString8(str); });
}
template <typename T>
typename std::enable_if<std::is_same<IBinder, T>::value, status_t>::type read(
const Parcel& parcel, sp<T>* pointer) const {
return callParcel("readNullableStrongBinder",
[&]() { return parcel.readNullableStrongBinder(pointer); });
}
template <typename T>
typename std::enable_if<std::is_same<IBinder, T>::value, status_t>::type write(
Parcel* parcel, const sp<T>& pointer) const {
return callParcel("writeStrongBinder",
[&]() { return parcel->writeStrongBinder(pointer); });
}
template <typename T>
typename std::enable_if<std::is_base_of<IInterface, T>::value, status_t>::type read(
const Parcel& parcel, sp<T>* pointer) const {
return callParcel("readNullableStrongBinder[IInterface]",
[&]() { return parcel.readNullableStrongBinder(pointer); });
}
template <typename T>
typename std::enable_if<std::is_base_of<IInterface, T>::value, status_t>::type write(
Parcel* parcel, const sp<T>& interface) const {
return write(parcel, IInterface::asBinder(interface));
}
template <typename T>
typename std::enable_if<std::is_base_of<Parcelable, T>::value, status_t>::type read(
const Parcel& parcel, std::vector<T>* v) const {
return callParcel("readParcelableVector", [&]() { return parcel.readParcelableVector(v); });
}
template <typename T>
typename std::enable_if<std::is_base_of<Parcelable, T>::value, status_t>::type write(
Parcel* parcel, const std::vector<T>& v) const {
return callParcel("writeParcelableVector",
[&]() { return parcel->writeParcelableVector(v); });
}
status_t read(const Parcel& parcel, std::vector<bool>* v) const {
return callParcel("readBoolVector", [&]() { return parcel.readBoolVector(v); });
}
status_t write(Parcel* parcel, const std::vector<bool>& v) const {
return callParcel("writeBoolVector", [&]() { return parcel->writeBoolVector(v); });
}
status_t read(const Parcel& parcel, float* f) const {
return callParcel("readFloat", [&]() { return parcel.readFloat(f); });
}
status_t write(Parcel* parcel, float f) const {
return callParcel("writeFloat", [&]() { return parcel->writeFloat(f); });
}
// Templates to handle integral types. We use a struct template to require that the called
// function exactly matches the signedness and size of the argument (e.g., the argument isn't
// silently widened).
template <bool isSigned, size_t size, typename I>
struct HandleInt;
template <typename I>
struct HandleInt<true, 4, I> {
static status_t read(const ParcelHandler& handler, const Parcel& parcel, I* i) {
return handler.callParcel("readInt32", [&]() { return parcel.readInt32(i); });
}
static status_t write(const ParcelHandler& handler, Parcel* parcel, I i) {
return handler.callParcel("writeInt32", [&]() { return parcel->writeInt32(i); });
}
};
template <typename I>
struct HandleInt<false, 4, I> {
static status_t read(const ParcelHandler& handler, const Parcel& parcel, I* i) {
return handler.callParcel("readUint32", [&]() { return parcel.readUint32(i); });
}
static status_t write(const ParcelHandler& handler, Parcel* parcel, I i) {
return handler.callParcel("writeUint32", [&]() { return parcel->writeUint32(i); });
}
};
template <typename I>
struct HandleInt<true, 8, I> {
static status_t read(const ParcelHandler& handler, const Parcel& parcel, I* i) {
return handler.callParcel("readInt64", [&]() { return parcel.readInt64(i); });
}
static status_t write(const ParcelHandler& handler, Parcel* parcel, I i) {
return handler.callParcel("writeInt64", [&]() { return parcel->writeInt64(i); });
}
};
template <typename I>
struct HandleInt<false, 8, I> {
static status_t read(const ParcelHandler& handler, const Parcel& parcel, I* i) {
return handler.callParcel("readUint64", [&]() { return parcel.readUint64(i); });
}
static status_t write(const ParcelHandler& handler, Parcel* parcel, I i) {
return handler.callParcel("writeUint64", [&]() { return parcel->writeUint64(i); });
}
};
template <typename I>
typename std::enable_if<std::is_integral<I>::value, status_t>::type read(const Parcel& parcel,
I* i) const {
return HandleInt<std::is_signed<I>::value, sizeof(I), I>::read(*this, parcel, i);
}
template <typename I>
typename std::enable_if<std::is_integral<I>::value, status_t>::type write(Parcel* parcel,
I i) const {
return HandleInt<std::is_signed<I>::value, sizeof(I), I>::write(*this, parcel, i);
}
private:
const char* const mLogTag;
// Helper to encapsulate error handling while calling the various Parcel methods
template <typename Function>
status_t callParcel(const char* name, Function f) const {
status_t error = f();
if (error != NO_ERROR) [[unlikely]] {
ALOG(LOG_ERROR, mLogTag, "Failed to %s, (%d: %s)", name, error, strerror(-error));
#if SI_DUMP_CALLSTACKS
CallStack callStack(mLogTag);
#endif
}
return error;
}
};
// Utility struct template which allows us to retrieve the types of the parameters of a member
// function pointer
template <typename T>
struct ParamExtractor;
template <typename Class, typename Return, typename... Params>
struct ParamExtractor<Return (Class::*)(Params...)> {
using ParamTuple = std::tuple<Params...>;
};
template <typename Class, typename Return, typename... Params>
struct ParamExtractor<Return (Class::*)(Params...) const> {
using ParamTuple = std::tuple<Params...>;
};
} // namespace SafeInterface
template <typename Interface>
class LIBBINDER_EXPORTED SafeBpInterface : public BpInterface<Interface> {
protected:
SafeBpInterface(const sp<IBinder>& impl, const char* logTag)
: BpInterface<Interface>(impl), mLogTag(logTag) {}
~SafeBpInterface() override = default;
// callRemote is used to invoke a synchronous procedure call over Binder
template <typename Method, typename TagType, typename... Args>
status_t callRemote(TagType tag, Args&&... args) const {
static_assert(sizeof(TagType) <= sizeof(uint32_t), "Tag must fit inside uint32_t");
// Verify that the arguments are compatible with the parameters
using ParamTuple = typename SafeInterface::ParamExtractor<Method>::ParamTuple;
static_assert(ArgsMatchParams<std::tuple<Args...>, ParamTuple>::value,
"Invalid argument type");
// Write the input arguments to the data Parcel
Parcel data;
data.writeInterfaceToken(this->getInterfaceDescriptor());
status_t error = writeInputs(&data, std::forward<Args>(args)...);
if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by writeInputs
return error;
}
// Send the data Parcel to the remote and retrieve the reply parcel
Parcel reply;
error = this->remote()->transact(static_cast<uint32_t>(tag), data, &reply);
if (error != NO_ERROR) [[unlikely]] {
ALOG(LOG_ERROR, mLogTag, "Failed to transact (%d)", error);
#if SI_DUMP_CALLSTACKS
CallStack callStack(mLogTag);
#endif
return error;
}
// Read the outputs from the reply Parcel into the output arguments
error = readOutputs(reply, std::forward<Args>(args)...);
if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by readOutputs
return error;
}
// Retrieve the result code from the reply Parcel
status_t result = NO_ERROR;
error = reply.readInt32(&result);
if (error != NO_ERROR) [[unlikely]] {
ALOG(LOG_ERROR, mLogTag, "Failed to obtain result");
#if SI_DUMP_CALLSTACKS
CallStack callStack(mLogTag);
#endif
return error;
}
return result;
}
// callRemoteAsync is used to invoke an asynchronous procedure call over Binder
template <typename Method, typename TagType, typename... Args>
void callRemoteAsync(TagType tag, Args&&... args) const {
static_assert(sizeof(TagType) <= sizeof(uint32_t), "Tag must fit inside uint32_t");
// Verify that the arguments are compatible with the parameters
using ParamTuple = typename SafeInterface::ParamExtractor<Method>::ParamTuple;
static_assert(ArgsMatchParams<std::tuple<Args...>, ParamTuple>::value,
"Invalid argument type");
// Write the input arguments to the data Parcel
Parcel data;
data.writeInterfaceToken(this->getInterfaceDescriptor());
status_t error = writeInputs(&data, std::forward<Args>(args)...);
if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by writeInputs
return;
}
// There will be no data in the reply Parcel since the call is one-way
Parcel reply;
error = this->remote()->transact(static_cast<uint32_t>(tag), data, &reply,
IBinder::FLAG_ONEWAY);
if (error != NO_ERROR) [[unlikely]] {
ALOG(LOG_ERROR, mLogTag, "Failed to transact (%d)", error);
#if SI_DUMP_CALLSTACKS
CallStack callStack(mLogTag);
#endif
}
}
private:
const char* const mLogTag;
// This struct provides information on whether the decayed types of the elements at Index in the
// tuple types T and U (that is, the types after stripping cv-qualifiers, removing references,
// and a few other less common operations) are the same
template <size_t Index, typename T, typename U>
struct DecayedElementsMatch {
private:
using FirstT = typename std::tuple_element<Index, T>::type;
using DecayedT = typename std::decay<FirstT>::type;
using FirstU = typename std::tuple_element<Index, U>::type;
using DecayedU = typename std::decay<FirstU>::type;
public:
static constexpr bool value = std::is_same<DecayedT, DecayedU>::value;
};
// When comparing whether the argument types match the parameter types, we first decay them (see
// DecayedElementsMatch) to avoid falsely flagging, say, T&& against T even though they are
// equivalent enough for our purposes
template <typename T, typename U>
struct ArgsMatchParams {};
template <typename... Args, typename... Params>
struct ArgsMatchParams<std::tuple<Args...>, std::tuple<Params...>> {
static_assert(sizeof...(Args) <= sizeof...(Params), "Too many arguments");
static_assert(sizeof...(Args) >= sizeof...(Params), "Not enough arguments");
private:
template <size_t Index>
static constexpr typename std::enable_if<(Index < sizeof...(Args)), bool>::type
elementsMatch() {
if (!DecayedElementsMatch<Index, std::tuple<Args...>, std::tuple<Params...>>::value) {
return false;
}
return elementsMatch<Index + 1>();
}
template <size_t Index>
static constexpr typename std::enable_if<(Index >= sizeof...(Args)), bool>::type
elementsMatch() {
return true;
}
public:
static constexpr bool value = elementsMatch<0>();
};
// Since we assume that pointer arguments are outputs, we can use this template struct to
// determine whether or not a given argument is fundamentally a pointer type and thus an output
template <typename T>
struct IsPointerIfDecayed {
private:
using Decayed = typename std::decay<T>::type;
public:
static constexpr bool value = std::is_pointer<Decayed>::value;
};
template <typename T>
typename std::enable_if<!IsPointerIfDecayed<T>::value, status_t>::type writeIfInput(
Parcel* data, T&& t) const {
return SafeInterface::ParcelHandler{mLogTag}.write(data, std::forward<T>(t));
}
template <typename T>
typename std::enable_if<IsPointerIfDecayed<T>::value, status_t>::type writeIfInput(
Parcel* /*data*/, T&& /*t*/) const {
return NO_ERROR;
}
// This method iterates through all of the arguments, writing them to the data Parcel if they
// are an input (i.e., if they are not a pointer type)
template <typename T, typename... Remaining>
status_t writeInputs(Parcel* data, T&& t, Remaining&&... remaining) const {
status_t error = writeIfInput(data, std::forward<T>(t));
if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by writeIfInput
return error;
}
return writeInputs(data, std::forward<Remaining>(remaining)...);
}
static status_t writeInputs(Parcel* /*data*/) { return NO_ERROR; }
template <typename T>
typename std::enable_if<IsPointerIfDecayed<T>::value, status_t>::type readIfOutput(
const Parcel& reply, T&& t) const {
return SafeInterface::ParcelHandler{mLogTag}.read(reply, std::forward<T>(t));
}
template <typename T>
static typename std::enable_if<!IsPointerIfDecayed<T>::value, status_t>::type readIfOutput(
const Parcel& /*reply*/, T&& /*t*/) {
return NO_ERROR;
}
// Similar to writeInputs except that it reads output arguments from the reply Parcel
template <typename T, typename... Remaining>
status_t readOutputs(const Parcel& reply, T&& t, Remaining&&... remaining) const {
status_t error = readIfOutput(reply, std::forward<T>(t));
if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by readIfOutput
return error;
}
return readOutputs(reply, std::forward<Remaining>(remaining)...);
}
static status_t readOutputs(const Parcel& /*data*/) { return NO_ERROR; }
};
template <typename Interface>
class LIBBINDER_EXPORTED SafeBnInterface : public BnInterface<Interface> {
public:
explicit SafeBnInterface(const char* logTag) : mLogTag(logTag) {}
protected:
template <typename Method>
status_t callLocal(const Parcel& data, Parcel* reply, Method method) {
CHECK_INTERFACE(this, data, reply);
// Since we need to both pass inputs into the call as well as retrieve outputs, we create a
// "raw" tuple, where the inputs are interleaved with actual, non-pointer versions of the
// outputs. When we ultimately call into the method, we will pass the addresses of the
// output arguments instead of their tuple members directly, but the storage will live in
// the tuple.
using ParamTuple = typename SafeInterface::ParamExtractor<Method>::ParamTuple;
typename RawConverter<std::tuple<>, ParamTuple>::type rawArgs{};
// Read the inputs from the data Parcel into the argument tuple
status_t error = InputReader<ParamTuple>{mLogTag}.readInputs(data, &rawArgs);
if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by read
return error;
}
// Call the local method
status_t result = MethodCaller<ParamTuple>::call(this, method, &rawArgs);
// Extract the outputs from the argument tuple and write them into the reply Parcel
error = OutputWriter<ParamTuple>{mLogTag}.writeOutputs(reply, &rawArgs);
if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by write
return error;
}
// Return the result code in the reply Parcel
error = reply->writeInt32(result);
if (error != NO_ERROR) [[unlikely]] {
ALOG(LOG_ERROR, mLogTag, "Failed to write result");
#if SI_DUMP_CALLSTACKS
CallStack callStack(mLogTag);
#endif
return error;
}
return NO_ERROR;
}
template <typename Method>
status_t callLocalAsync(const Parcel& data, Parcel* /*reply*/, Method method) {
// reply is not actually used by CHECK_INTERFACE
CHECK_INTERFACE(this, data, reply);
// Since we need to both pass inputs into the call as well as retrieve outputs, we create a
// "raw" tuple, where the inputs are interleaved with actual, non-pointer versions of the
// outputs. When we ultimately call into the method, we will pass the addresses of the
// output arguments instead of their tuple members directly, but the storage will live in
// the tuple.
using ParamTuple = typename SafeInterface::ParamExtractor<Method>::ParamTuple;
typename RawConverter<std::tuple<>, ParamTuple>::type rawArgs{};
// Read the inputs from the data Parcel into the argument tuple
status_t error = InputReader<ParamTuple>{mLogTag}.readInputs(data, &rawArgs);
if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by read
return error;
}
// Call the local method
MethodCaller<ParamTuple>::callVoid(this, method, &rawArgs);
// After calling, there is nothing more to do since asynchronous calls do not return a value
// to the caller
return NO_ERROR;
}
private:
const char* const mLogTag;
// RemoveFirst strips the first element from a tuple.
// For example, given T = std::tuple<A, B, C>, RemoveFirst<T>::type = std::tuple<B, C>
template <typename T, typename... Args>
struct RemoveFirst;
template <typename T, typename... Args>
struct RemoveFirst<std::tuple<T, Args...>> {
using type = std::tuple<Args...>;
};
// RawConverter strips a tuple down to its fundamental types, discarding both pointers and
// references. This allows us to allocate storage for both input (non-pointer) arguments and
// output (pointer) arguments in one tuple.
// For example, given T = std::tuple<const A&, B*>, RawConverter<T>::type = std::tuple<A, B>
template <typename Unconverted, typename... Converted>
struct RawConverter;
template <typename Unconverted, typename... Converted>
struct RawConverter<std::tuple<Converted...>, Unconverted> {
private:
using ElementType = typename std::tuple_element<0, Unconverted>::type;
using Decayed = typename std::decay<ElementType>::type;
using WithoutPointer = typename std::remove_pointer<Decayed>::type;
public:
using type = typename RawConverter<std::tuple<Converted..., WithoutPointer>,
typename RemoveFirst<Unconverted>::type>::type;
};
template <typename... Converted>
struct RawConverter<std::tuple<Converted...>, std::tuple<>> {
using type = std::tuple<Converted...>;
};
// This provides a simple way to determine whether the indexed element of Args... is a pointer
template <size_t I, typename... Args>
struct ElementIsPointer {
private:
using ElementType = typename std::tuple_element<I, std::tuple<Args...>>::type;
public:
static constexpr bool value = std::is_pointer<ElementType>::value;
};
// This class iterates over the parameter types, and if a given parameter is an input
// (i.e., is not a pointer), reads the corresponding argument tuple element from the data Parcel
template <typename... Params>
class InputReader;
template <typename... Params>
class InputReader<std::tuple<Params...>> {
public:
explicit InputReader(const char* logTag) : mLogTag(logTag) {}
// Note that in this case (as opposed to in SafeBpInterface), we iterate using an explicit
// index (starting with 0 here) instead of using recursion and stripping the first element.
// This is because in SafeBpInterface we aren't actually operating on a real tuple, but are
// instead just using a tuple as a convenient container for variadic types, whereas here we
// can't modify the argument tuple without causing unnecessary copies or moves of the data
// contained therein.
template <typename RawTuple>
status_t readInputs(const Parcel& data, RawTuple* args) {
return dispatchArg<0>(data, args);
}
private:
const char* const mLogTag;
template <std::size_t I, typename RawTuple>
typename std::enable_if<!ElementIsPointer<I, Params...>::value, status_t>::type readIfInput(
const Parcel& data, RawTuple* args) {
return SafeInterface::ParcelHandler{mLogTag}.read(data, &std::get<I>(*args));
}
template <std::size_t I, typename RawTuple>
typename std::enable_if<ElementIsPointer<I, Params...>::value, status_t>::type readIfInput(
const Parcel& /*data*/, RawTuple* /*args*/) {
return NO_ERROR;
}
// Recursively iterate through the arguments
template <std::size_t I, typename RawTuple>
typename std::enable_if<(I < sizeof...(Params)), status_t>::type dispatchArg(
const Parcel& data, RawTuple* args) {
status_t error = readIfInput<I>(data, args);
if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged in read
return error;
}
return dispatchArg<I + 1>(data, args);
}
template <std::size_t I, typename RawTuple>
typename std::enable_if<(I >= sizeof...(Params)), status_t>::type dispatchArg(
const Parcel& /*data*/, RawTuple* /*args*/) {
return NO_ERROR;
}
};
// getForCall uses the types of the parameters to determine whether a given element of the
// argument tuple is an input, which should be passed directly into the call, or an output, for
// which its address should be passed into the call
template <size_t I, typename RawTuple, typename... Params>
static typename std::enable_if<
ElementIsPointer<I, Params...>::value,
typename std::tuple_element<I, std::tuple<Params...>>::type>::type
getForCall(RawTuple* args) {
return &std::get<I>(*args);
}
template <size_t I, typename RawTuple, typename... Params>
static typename std::enable_if<
!ElementIsPointer<I, Params...>::value,
typename std::tuple_element<I, std::tuple<Params...>>::type>::type&
getForCall(RawTuple* args) {
return std::get<I>(*args);
}
// This template class uses std::index_sequence and parameter pack expansion to call the given
// method using the elements of the argument tuple (after those arguments are passed through
// getForCall to get addresses instead of values for output arguments)
template <typename... Params>
struct MethodCaller;
template <typename... Params>
struct MethodCaller<std::tuple<Params...>> {
public:
// The calls through these to the helper methods are necessary to generate the
// std::index_sequences used to unpack the argument tuple into the method call
template <typename Class, typename MemberFunction, typename RawTuple>
static status_t call(Class* instance, MemberFunction function, RawTuple* args) {
return callHelper(instance, function, args, std::index_sequence_for<Params...>{});
}
template <typename Class, typename MemberFunction, typename RawTuple>
static void callVoid(Class* instance, MemberFunction function, RawTuple* args) {
callVoidHelper(instance, function, args, std::index_sequence_for<Params...>{});
}
private:
template <typename Class, typename MemberFunction, typename RawTuple, std::size_t... I>
static status_t callHelper(Class* instance, MemberFunction function, RawTuple* args,
std::index_sequence<I...> /*unused*/) {
return (instance->*function)(getForCall<I, RawTuple, Params...>(args)...);
}
template <typename Class, typename MemberFunction, typename RawTuple, std::size_t... I>
static void callVoidHelper(Class* instance, MemberFunction function, RawTuple* args,
std::index_sequence<I...> /*unused*/) {
(instance->*function)(getForCall<I, RawTuple, Params...>(args)...);
}
};
// This class iterates over the parameter types, and if a given parameter is an output
// (i.e., is a pointer), writes the corresponding argument tuple element into the reply Parcel
template <typename... Params>
struct OutputWriter;
template <typename... Params>
struct OutputWriter<std::tuple<Params...>> {
public:
explicit OutputWriter(const char* logTag) : mLogTag(logTag) {}
// See the note on InputReader::readInputs for why this differs from the arguably simpler
// RemoveFirst approach in SafeBpInterface
template <typename RawTuple>
status_t writeOutputs(Parcel* reply, RawTuple* args) {
return dispatchArg<0>(reply, args);
}
private:
const char* const mLogTag;
template <std::size_t I, typename RawTuple>
typename std::enable_if<ElementIsPointer<I, Params...>::value, status_t>::type
writeIfOutput(Parcel* reply, RawTuple* args) {
return SafeInterface::ParcelHandler{mLogTag}.write(reply, std::get<I>(*args));
}
template <std::size_t I, typename RawTuple>
typename std::enable_if<!ElementIsPointer<I, Params...>::value, status_t>::type
writeIfOutput(Parcel* /*reply*/, RawTuple* /*args*/) {
return NO_ERROR;
}
// Recursively iterate through the arguments
template <std::size_t I, typename RawTuple>
typename std::enable_if<(I < sizeof...(Params)), status_t>::type dispatchArg(
Parcel* reply, RawTuple* args) {
status_t error = writeIfOutput<I>(reply, args);
if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged in read
return error;
}
return dispatchArg<I + 1>(reply, args);
}
template <std::size_t I, typename RawTuple>
typename std::enable_if<(I >= sizeof...(Params)), status_t>::type dispatchArg(
Parcel* /*reply*/, RawTuple* /*args*/) {
return NO_ERROR;
}
};
};
} // namespace android
@@ -0,0 +1,176 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <binder/IBinder.h>
#include <string>
class BinderStabilityIntegrationTest_ExpectedStabilityForItsPartition_Test;
namespace android {
class BpBinder;
class ProcessState;
namespace internal {
// Stability encodes how a binder changes over time. There are two levels of
// stability:
// 1). the interface stability - this is how a particular set of API calls (a
// particular ordering of things like writeInt32/readInt32) are changed over
// time. If one release, we have 'writeInt32' and the next release, we have
// 'writeInt64', then this interface doesn't have a very stable
// Stability::Level. Usually this ordering is controlled by a .aidl file.
// 2). the wire format stability - this is how these API calls map to actual
// bytes that are written to the wire (literally, this is how they are written
// to the kernel inside of IBinder::transact, but it may be expanded to other
// wires in the future). For instance, writeInt32 in binder translates to
// writing a 4-byte little-endian integer in two's complement. You can imagine
// in the future, we change writeInt32/readInt32 to instead write 8-bytes with
// that integer and some check bits. In this case, the wire format changes,
// but as long as a client libbinder knows to keep on writing a 4-byte value
// to old servers, and new servers know how to interpret the 8-byte result,
// they can still communicate.
//
// This class is specifically about (1). (2) is not currently tracked by
// libbinder for regular binder calls, and everything on the system uses the
// same copy of libbinder.
class Stability final {
public:
// Given a binder interface at a certain stability, there may be some
// requirements associated with that higher stability level. For instance, a
// VINTF stability binder is required to be in the VINTF manifest. This API
// can be called to use that same interface within the local partition.
LIBBINDER_EXPORTED static void forceDowngradeToLocalStability(const sp<IBinder>& binder);
// WARNING: Below APIs are only ever expected to be called by auto-generated code.
// Instead of calling them, you should set the stability of a .aidl interface
// WARNING: The only client of
// - forceDowngradeToSystemStability() and;
// - korceDowngradeToVendorStability()
// should be AIBinder_forceDowngradeToLocalStability().
//
// getLocalLevel() in libbinder returns Level::SYSTEM when called
// from libbinder_ndk (even on vendor partition). So we explicitly provide
// these methods for use by the NDK API:
// AIBinder_forceDowngradeToLocalStability().
//
// This allows correctly downgrading the binder's stability to either system/vendor,
// depending on the partition.
// Given a binder interface at a certain stability, there may be some
// requirements associated with that higher stability level. For instance, a
// VINTF stability binder is required to be in the VINTF manifest. This API
// can be called to use that same interface within the vendor partition.
LIBBINDER_EXPORTED static void forceDowngradeToVendorStability(const sp<IBinder>& binder);
// Given a binder interface at a certain stability, there may be some
// requirements associated with that higher stability level. For instance, a
// VINTF stability binder is required to be in the VINTF manifest. This API
// can be called to use that same interface within the system partition.
LIBBINDER_EXPORTED static void forceDowngradeToSystemStability(const sp<IBinder>& binder);
// WARNING: This is only ever expected to be called by auto-generated code. You likely want to
// change or modify the stability class of the interface you are using.
// This must be called as soon as the binder in question is constructed. No thread safety
// is provided.
// E.g. stability is according to libbinder compilation unit
LIBBINDER_EXPORTED static void markCompilationUnit(IBinder* binder);
// WARNING: This is only ever expected to be called by auto-generated code. You likely want to
// change or modify the stability class of the interface you are using.
// This must be called as soon as the binder in question is constructed. No thread safety
// is provided.
// E.g. stability is according to libbinder_ndk or Java SDK AND the interface
// expressed here is guaranteed to be stable for multiple years (Stable AIDL)
LIBBINDER_EXPORTED static void markVintf(IBinder* binder);
// WARNING: for debugging only
LIBBINDER_EXPORTED static std::string debugToString(const sp<IBinder>& binder);
// WARNING: This is only ever expected to be called by auto-generated code or tests.
// You likely want to change or modify the stability of the interface you are using.
// This must be called as soon as the binder in question is constructed. No thread safety
// is provided.
// E.g. stability is according to libbinder_ndk or Java SDK AND the interface
// expressed here is guaranteed to be stable for multiple years (Stable AIDL)
// If this is called when __ANDROID_VNDK__ is not defined, then it is UB and will likely
// break the device during GSI or other tests.
LIBBINDER_EXPORTED static void markVndk(IBinder* binder);
// Returns true if the binder needs to be declared in the VINTF manifest or
// else false if the binder is local to the current partition.
LIBBINDER_EXPORTED static bool requiresVintfDeclaration(const sp<IBinder>& binder);
private:
// Parcel needs to read/write stability level in an unstable format.
friend ::android::Parcel;
// only expose internal APIs inside of libbinder, for checking stability
friend ::android::BpBinder;
// so that it can mark the context object (only the root object doesn't go
// through Parcel)
friend ::android::ProcessState;
friend ::BinderStabilityIntegrationTest_ExpectedStabilityForItsPartition_Test;
static void tryMarkCompilationUnit(IBinder* binder);
// Currently, we use int16_t for Level so that it can fit in BBinder.
// However, on the wire, we have 4 bytes reserved for stability, so whenever
// we ingest a Level, we always accept an int32_t.
enum Level : int16_t {
UNDECLARED = 0,
VENDOR = 0b000011,
SYSTEM = 0b001100,
VINTF = 0b111111,
};
// returns the stability according to how this was built
static Level getLocalLevel();
// Downgrades binder stability to the specified level.
static void forceDowngradeToStability(const sp<IBinder>& binder, Level level);
enum {
REPR_NONE = 0,
REPR_LOG = 1,
REPR_ALLOW_DOWNGRADE = 2,
};
// applies stability to binder if stability level is known
__attribute__((warn_unused_result)) static status_t setRepr(IBinder* binder, int32_t setting,
uint32_t flags);
// get stability information as encoded on the wire
LIBBINDER_EXPORTED static int16_t getRepr(IBinder* binder);
// whether a transaction on binder is allowed, if the transaction
// is done from a context with a specific stability level
LIBBINDER_EXPORTED static bool check(int16_t provided, Level required);
static bool isDeclaredLevel(int32_t level);
static std::string levelString(int32_t level);
Stability();
};
} // namespace internal
} // namespace android
+177
View File
@@ -0,0 +1,177 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_BINDER_STATUS_H
#define ANDROID_BINDER_STATUS_H
#include <cstdint>
#include <sstream> // historical
#include <ostream>
#include <binder/Common.h>
#include <binder/Parcel.h>
#include <utils/String8.h>
#include <string>
namespace android {
namespace binder {
// An object similar in function to a status_t except that it understands
// how exceptions are encoded in the prefix of a Parcel. Used like:
//
// Parcel data;
// Parcel reply;
// status_t status;
// binder::Status remote_exception;
// if ((status = data.writeInterfaceToken(interface_descriptor)) != OK ||
// (status = data.writeInt32(function_input)) != OK) {
// // We failed to write into the memory of our local parcel?
// }
// if ((status = remote()->transact(transaction, data, &reply)) != OK) {
// // Something has gone wrong in the binder driver or libbinder.
// }
// if ((status = remote_exception.readFromParcel(reply)) != OK) {
// // The remote didn't correctly write the exception header to the
// // reply.
// }
// if (!remote_exception.isOk()) {
// // The transaction went through correctly, but the remote reported an
// // exception during handling.
// }
//
class LIBBINDER_EXPORTED Status final {
public:
// Keep the exception codes in sync with android/os/Parcel.java.
enum Exception {
EX_NONE = 0,
EX_SECURITY = -1,
EX_BAD_PARCELABLE = -2,
EX_ILLEGAL_ARGUMENT = -3,
EX_NULL_POINTER = -4,
EX_ILLEGAL_STATE = -5,
EX_NETWORK_MAIN_THREAD = -6,
EX_UNSUPPORTED_OPERATION = -7,
EX_SERVICE_SPECIFIC = -8,
EX_PARCELABLE = -9,
// See android/os/Parcel.java. We need to handle this in native code.
EX_HAS_NOTED_APPOPS_REPLY_HEADER = -127,
// This is special and Java specific; see Parcel.java.
EX_HAS_REPLY_HEADER = -128,
// This is special, and indicates to C++ binder proxies that the
// transaction has failed at a low level.
EX_TRANSACTION_FAILED = -129,
};
// A more readable alias for the default constructor.
static Status ok();
// Authors should explicitly pick whether their integer is:
// - an exception code (EX_* above)
// - service specific error code
// - status_t
//
// Prefer a generic exception code when possible, then a service specific
// code, and finally a status_t for low level failures or legacy support.
// Exception codes and service specific errors map to nicer exceptions for
// Java clients.
static Status fromExceptionCode(int32_t exceptionCode);
static Status fromExceptionCode(int32_t exceptionCode,
const String8& message);
static Status fromExceptionCode(int32_t exceptionCode,
const char* message);
// warning: this is still considered an error if it is constructed with a
// zero value error code. Please use Status::ok() instead and avoid zero
// error codes
static Status fromServiceSpecificError(int32_t serviceSpecificErrorCode);
static Status fromServiceSpecificError(int32_t serviceSpecificErrorCode,
const String8& message);
static Status fromServiceSpecificError(int32_t serviceSpecificErrorCode,
const char* message);
static Status fromStatusT(status_t status);
static std::string exceptionToString(status_t exceptionCode);
Status() = default;
~Status() = default;
// Status objects are copyable and contain just simple data.
Status(const Status& status) = default;
Status(Status&& status) = default;
Status& operator=(const Status& status) = default;
// Bear in mind that if the client or service is a Java endpoint, this
// is not the logic which will provide/interpret the data here.
status_t readFromParcel(const Parcel& parcel);
status_t writeToParcel(Parcel* parcel) const;
// Convenience API to replace a Parcel with a status value, w/o requiring
// calling multiple APIs (makes generated code smaller).
status_t writeOverParcel(Parcel* parcel) const;
// Set one of the pre-defined exception types defined above.
void setException(int32_t ex, const String8& message);
// Set a service specific exception with error code.
void setServiceSpecificError(int32_t errorCode, const String8& message);
// Setting a |status| != OK causes generated code to return |status|
// from Binder transactions, rather than writing an exception into the
// reply Parcel. This is the least preferable way of reporting errors.
void setFromStatusT(status_t status);
// Get information about an exception.
int32_t exceptionCode() const { return mException; }
const String8& exceptionMessage() const { return mMessage; }
status_t transactionError() const {
return mException == EX_TRANSACTION_FAILED ? mErrorCode : OK;
}
int32_t serviceSpecificErrorCode() const {
return mException == EX_SERVICE_SPECIFIC ? mErrorCode : 0;
}
bool isOk() const { return mException == EX_NONE; }
// For logging.
String8 toString8() const;
private:
Status(int32_t exceptionCode, int32_t errorCode);
Status(int32_t exceptionCode, int32_t errorCode, const String8& message);
status_t skipUnusedHeader(const Parcel& parcel);
// If |mException| == EX_TRANSACTION_FAILED, generated code will return
// |mErrorCode| as the result of the transaction rather than write an
// exception to the reply parcel.
//
// Otherwise, we always write |mException| to the parcel.
// If |mException| != EX_NONE, we write |mMessage| as well.
// If |mException| == EX_SERVICE_SPECIFIC we write |mErrorCode| as well.
int32_t mException = EX_NONE;
int32_t mErrorCode = 0;
String8 mMessage;
}; // class Status
static inline std::ostream& operator<< (std::ostream& o, const Status& s) {
return o << s.toString8();
}
} // namespace binder
} // namespace android
#endif // ANDROID_BINDER_STATUS_H
@@ -0,0 +1,205 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#include <utils/Errors.h>
#include <utils/String8.h>
#include <stdint.h>
#include <string.h>
#include <sstream>
// ---------------------------------------------------------------------------
namespace android {
class LIBBINDER_EXPORTED TextOutput {
public:
TextOutput();
virtual ~TextOutput();
virtual status_t print(const char* txt, size_t len) = 0;
virtual void moveIndent(int delta) = 0;
class Bundle {
public:
inline explicit Bundle(TextOutput& to) : mTO(to) { to.pushBundle(); }
inline ~Bundle() { mTO.popBundle(); }
private:
TextOutput& mTO;
};
virtual void pushBundle() = 0;
virtual void popBundle() = 0;
};
// ---------------------------------------------------------------------------
// DO NOT USE: prefer libutils/libbase logs, which don't require static data to
// be allocated.
// Text output stream for printing to the log (via utils/Log.h).
extern LIBBINDER_EXPORTED TextOutput& alog;
// DO NOT USE: prefer libutils/libbase logs, which don't require static data to
// be allocated.
// Text output stream for printing to stdout.
extern LIBBINDER_EXPORTED TextOutput& aout;
// DO NOT USE: prefer libutils/libbase logs, which don't require static data to
// be allocated.
// Text output stream for printing to stderr.
extern LIBBINDER_EXPORTED TextOutput& aerr;
typedef TextOutput& (*TextOutputManipFunc)(TextOutput&);
TextOutput& endl(TextOutput& to);
TextOutput& indent(TextOutput& to);
TextOutput& dedent(TextOutput& to);
template<typename T>
TextOutput& operator<<(TextOutput& to, const T& val)
{
std::stringstream strbuf;
strbuf << val;
std::string str = strbuf.str();
to.print(str.c_str(), str.size());
return to;
}
LIBBINDER_EXPORTED TextOutput& operator<<(TextOutput& to, TextOutputManipFunc func);
class LIBBINDER_EXPORTED TypeCode {
public:
inline explicit TypeCode(uint32_t code);
inline ~TypeCode();
inline uint32_t typeCode() const;
private:
uint32_t mCode;
};
LIBBINDER_EXPORTED std::ostream& operator<<(std::ostream& to, const TypeCode& val);
class LIBBINDER_EXPORTED HexDump {
public:
HexDump(const void *buf, size_t size, size_t bytesPerLine=16);
inline ~HexDump();
inline HexDump& setBytesPerLine(size_t bytesPerLine);
inline HexDump& setSingleLineCutoff(int32_t bytes);
inline HexDump& setAlignment(size_t alignment);
inline HexDump& setCArrayStyle(bool enabled);
inline const void* buffer() const;
inline size_t size() const;
inline size_t bytesPerLine() const;
inline int32_t singleLineCutoff() const;
inline size_t alignment() const;
inline bool carrayStyle() const;
private:
const void* mBuffer;
size_t mSize;
size_t mBytesPerLine;
int32_t mSingleLineCutoff;
size_t mAlignment;
bool mCArrayStyle;
};
LIBBINDER_EXPORTED std::ostream& operator<<(std::ostream& to, const HexDump& val);
inline TextOutput& operator<<(TextOutput& to,
decltype(std::endl<char,
std::char_traits<char>>)
/*val*/) {
endl(to);
return to;
}
inline TextOutput& operator<<(TextOutput& to, const char &c)
{
to.print(&c, 1);
return to;
}
inline TextOutput& operator<<(TextOutput& to, const bool &val)
{
if (val) to.print("true", 4);
else to.print("false", 5);
return to;
}
inline TextOutput& operator<<(TextOutput& to, const String16& val)
{
to << String8(val).c_str();
return to;
}
// ---------------------------------------------------------------------------
// No user servicable parts below.
inline TextOutput& endl(TextOutput& to)
{
to.print("\n", 1);
return to;
}
inline TextOutput& indent(TextOutput& to)
{
to.moveIndent(1);
return to;
}
inline TextOutput& dedent(TextOutput& to)
{
to.moveIndent(-1);
return to;
}
inline TextOutput& operator<<(TextOutput& to, TextOutputManipFunc func)
{
return (*func)(to);
}
inline TypeCode::TypeCode(uint32_t code) : mCode(code) { }
inline TypeCode::~TypeCode() { }
inline uint32_t TypeCode::typeCode() const { return mCode; }
inline HexDump::~HexDump() { }
inline HexDump& HexDump::setBytesPerLine(size_t bytesPerLine) {
mBytesPerLine = bytesPerLine; return *this;
}
inline HexDump& HexDump::setSingleLineCutoff(int32_t bytes) {
mSingleLineCutoff = bytes; return *this;
}
inline HexDump& HexDump::setAlignment(size_t alignment) {
mAlignment = alignment; return *this;
}
inline HexDump& HexDump::setCArrayStyle(bool enabled) {
mCArrayStyle = enabled; return *this;
}
inline const void* HexDump::buffer() const { return mBuffer; }
inline size_t HexDump::size() const { return mSize; }
inline size_t HexDump::bytesPerLine() const { return mBytesPerLine; }
inline int32_t HexDump::singleLineCutoff() const { return mSingleLineCutoff; }
inline size_t HexDump::alignment() const { return mAlignment; }
inline bool HexDump::carrayStyle() const { return mCArrayStyle; }
// ---------------------------------------------------------------------------
} // namespace android
+59
View File
@@ -0,0 +1,59 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
#if __has_include(<cutils/trace.h>)
#include <cutils/trace.h>
#endif
#include <binder/Common.h>
#ifdef ATRACE_TAG_AIDL
#if ATRACE_TAG_AIDL != (1 << 24)
#error "Mismatched ATRACE_TAG_AIDL definitions"
#endif
#else
#define ATRACE_TAG_AIDL (1 << 24)
#endif
namespace android {
namespace binder {
// Forward declarations from internal OS.h
namespace os {
// Trampoline functions allowing generated aidls to trace binder transactions without depending on
// libcutils/libutils
void trace_begin(uint64_t tag, const char* name);
void trace_end(uint64_t tag);
void trace_int(uint64_t tag, const char* name, int32_t value);
uint64_t get_trace_enabled_tags();
} // namespace os
class LIBBINDER_EXPORTED ScopedTrace {
public:
inline ScopedTrace(uint64_t tag, const char* name) : mTag(tag) { os::trace_begin(mTag, name); }
inline ~ScopedTrace() { os::trace_end(mTag); }
private:
uint64_t mTag;
};
} // namespace binder
} // namespace android
@@ -0,0 +1,116 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <binder/Common.h>
#ifndef BINDER_NO_LIBBASE
#include <android-base/unique_fd.h>
namespace android::binder {
using android::base::borrowed_fd;
using android::base::unique_fd;
} // namespace android::binder
#else // BINDER_NO_LIBBASE
#include <errno.h>
#include <fcntl.h> // not needed for unique_fd, but a lot of users depend on open(3)
#include <unistd.h>
namespace android::binder {
// Container for a file descriptor that automatically closes the descriptor as
// it goes out of scope.
//
// unique_fd ufd(open("/some/path", "r"));
// if (!ufd.ok()) return error;
//
// // Do something useful with ufd.get(), possibly including early 'return'.
//
// return 0; // Descriptor is closed for you.
//
class LIBBINDER_EXPORTED unique_fd final {
public:
unique_fd() {}
explicit unique_fd(int fd) { reset(fd); }
~unique_fd() { reset(); }
unique_fd(const unique_fd&) = delete;
void operator=(const unique_fd&) = delete;
unique_fd(unique_fd&& other) noexcept { reset(other.release()); }
unique_fd& operator=(unique_fd&& s) noexcept {
int fd = s.fd_;
s.fd_ = -1;
reset(fd);
return *this;
}
[[clang::reinitializes]] void reset(int new_value = -1) {
int previous_errno = errno;
if (fd_ != -1) {
::close(fd_);
}
fd_ = new_value;
errno = previous_errno;
}
int get() const { return fd_; }
bool ok() const { return get() >= 0; }
[[nodiscard]] int release() {
int ret = fd_;
fd_ = -1;
return ret;
}
private:
int fd_ = -1;
};
// A wrapper type that can be implicitly constructed from either int or
// unique_fd. This supports cases where you don't actually own the file
// descriptor, and can't take ownership, but are temporarily acting as if
// you're the owner.
//
// One example would be a function that needs to also allow
// STDERR_FILENO, not just a newly-opened fd. Another example would be JNI code
// that's using a file descriptor that's actually owned by a
// ParcelFileDescriptor or whatever on the Java side, but where the JNI code
// would like to enforce this weaker sense of "temporary ownership".
//
// If you think of unique_fd as being like std::string in that represents
// ownership, borrowed_fd is like std::string_view (and int is like const
// char*).
struct LIBBINDER_EXPORTED borrowed_fd {
/* implicit */ borrowed_fd(int fd) : fd_(fd) {} // NOLINT
/* implicit */ borrowed_fd(const unique_fd& ufd) : fd_(ufd.get()) {} // NOLINT
int get() const { return fd_; }
private:
int fd_ = -1;
};
} // namespace android::binder
#endif // BINDER_NO_LIBBASE
+77
View File
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <errno.h>
#include <stdint.h>
#include <sys/types.h>
#include <string>
namespace android {
/**
* The type used to return success/failure from frameworks APIs.
* See the anonymous enum below for valid values.
*/
typedef int32_t status_t;
/*
* Error codes.
* All error codes are negative values.
*/
enum {
OK = 0, // Preferred constant for checking success.
#ifndef NO_ERROR
// Win32 #defines NO_ERROR as well. It has the same value, so there's no
// real conflict, though it's a bit awkward.
NO_ERROR = OK, // Deprecated synonym for `OK`. Prefer `OK` because it doesn't conflict with Windows.
#endif
UNKNOWN_ERROR = (-2147483647-1), // INT32_MIN value
NO_MEMORY = -ENOMEM,
INVALID_OPERATION = -ENOSYS,
BAD_VALUE = -EINVAL,
BAD_TYPE = (UNKNOWN_ERROR + 1),
NAME_NOT_FOUND = -ENOENT,
PERMISSION_DENIED = -EPERM,
NO_INIT = -ENODEV,
ALREADY_EXISTS = -EEXIST,
DEAD_OBJECT = -EPIPE,
FAILED_TRANSACTION = (UNKNOWN_ERROR + 2),
#if !defined(_WIN32)
BAD_INDEX = -EOVERFLOW,
NOT_ENOUGH_DATA = -ENODATA,
WOULD_BLOCK = -EWOULDBLOCK,
TIMED_OUT = -ETIMEDOUT,
UNKNOWN_TRANSACTION = -EBADMSG,
#else
BAD_INDEX = -E2BIG,
NOT_ENOUGH_DATA = (UNKNOWN_ERROR + 3),
WOULD_BLOCK = (UNKNOWN_ERROR + 4),
TIMED_OUT = (UNKNOWN_ERROR + 5),
UNKNOWN_TRANSACTION = (UNKNOWN_ERROR + 6),
#endif
FDS_NOT_ALLOWED = (UNKNOWN_ERROR + 7),
UNEXPECTED_NULL = (UNKNOWN_ERROR + 8),
};
// Human readable name of error
std::string statusToString(status_t status);
} // namespace android
@@ -0,0 +1,76 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
/*
* See documentation in RefBase.h
*/
#include <atomic>
#include <sys/types.h>
namespace android {
class ReferenceRenamer;
void LightRefBase_reportIncStrongRequireStrongFailed(const void* thiz);
template <class T>
class LightRefBase
{
public:
inline LightRefBase() : mCount(0) { }
inline void incStrong(__attribute__((unused)) const void* id) const {
mCount.fetch_add(1, std::memory_order_relaxed);
}
inline void incStrongRequireStrong(__attribute__((unused)) const void* id) const {
if (0 == mCount.fetch_add(1, std::memory_order_relaxed)) {
LightRefBase_reportIncStrongRequireStrongFailed(this);
}
}
inline void decStrong(__attribute__((unused)) const void* id) const {
if (mCount.fetch_sub(1, std::memory_order_release) == 1) {
std::atomic_thread_fence(std::memory_order_acquire);
delete static_cast<const T*>(this);
}
}
//! DEBUGGING ONLY: Get current strong ref count.
inline int32_t getStrongCount() const {
return mCount.load(std::memory_order_relaxed);
}
protected:
inline ~LightRefBase() { }
private:
friend class ReferenceMover;
inline static void renameRefs(size_t /*n*/, const ReferenceRenamer& /*renamer*/) { }
inline static void renameRefId(T* /*ref*/, const void* /*old_id*/ , const void* /*new_id*/) { }
private:
mutable std::atomic<int32_t> mCount;
};
// This is a wrapper around LightRefBase that simply enforces a virtual
// destructor to eliminate the template requirement of LightRefBase
class VirtualLightRefBase : public LightRefBase<VirtualLightRefBase> {
public:
virtual ~VirtualLightRefBase() = default;
};
} // namespace android
+818
View File
@@ -0,0 +1,818 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// SOME COMMENTS ABOUT USAGE:
// This provides primarily wp<> weak pointer types and RefBase, which work
// together with sp<> from <StrongPointer.h>.
// sp<> (and wp<>) are a type of smart pointer that use a well defined protocol
// to operate. As long as the object they are templated with implements that
// protocol, these smart pointers work. In several places the platform
// instantiates sp<> with non-RefBase objects; the two are not tied to each
// other.
// RefBase is such an implementation and it supports strong pointers, weak
// pointers and some magic features for the binder.
// So, when using RefBase objects, you have the ability to use strong and weak
// pointers through sp<> and wp<>.
// Normally, when the last strong pointer goes away, the object is destroyed,
// i.e. it's destructor is called. HOWEVER, parts of its associated memory is not
// freed until the last weak pointer is released.
// Weak pointers are essentially "safe" pointers. They are always safe to
// access through promote(). They may return nullptr if the object was
// destroyed because it ran out of strong pointers. This makes them good candidates
// for keys in a cache for instance.
// Weak pointers remain valid for comparison purposes even after the underlying
// object has been destroyed. Even if object A is destroyed and its memory reused
// for B, A remaining weak pointer to A will not compare equal to one to B.
// This again makes them attractive for use as keys.
// How is this supposed / intended to be used?
// Our recommendation is to use strong references (sp<>) when there is an
// ownership relation. e.g. when an object "owns" another one, use a strong
// ref. And of course use strong refs as arguments of functions (it's extremely
// rare that a function will take a wp<>).
// Typically a newly allocated object will immediately be used to initialize
// a strong pointer, which may then be used to construct or assign to other
// strong and weak pointers.
// Use weak references when there are no ownership relation. e.g. the keys in a
// cache (you cannot use plain pointers because there is no safe way to acquire
// a strong reference from a vanilla pointer).
// This implies that two objects should never (or very rarely) have sp<> on
// each other, because they can't both own each other.
// Caveats with reference counting
// Obviously, circular strong references are a big problem; this creates leaks
// and it's hard to debug -- except it's in fact really easy because RefBase has
// tons of debugging code for that. It can basically tell you exactly where the
// leak is.
// Another problem has to do with destructors with side effects. You must
// assume that the destructor of reference counted objects can be called AT ANY
// TIME. For instance code as simple as this:
// void setStuff(const sp<Stuff>& stuff) {
// std::lock_guard<std::mutex> lock(mMutex);
// mStuff = stuff;
// }
// is very dangerous. This code WILL deadlock one day or another.
// What isn't obvious is that ~Stuff() can be called as a result of the
// assignment. And it gets called with the lock held. First of all, the lock is
// protecting mStuff, not ~Stuff(). Secondly, if ~Stuff() uses its own internal
// mutex, now you have mutex ordering issues. Even worse, if ~Stuff() is
// virtual, now you're calling into "user" code (potentially), by that, I mean,
// code you didn't even write.
// A correct way to write this code is something like:
// void setStuff(const sp<Stuff>& stuff) {
// std::unique_lock<std::mutex> lock(mMutex);
// sp<Stuff> hold = mStuff;
// mStuff = stuff;
// lock.unlock();
// }
// More importantly, reference counted objects should do as little work as
// possible in their destructor, or at least be mindful that their destructor
// could be called from very weird and unintended places.
// Other more specific restrictions for wp<> and sp<>:
// Do not construct a strong pointer to "this" in an object's constructor.
// The onFirstRef() callback would be made on an incompletely constructed
// object.
// Construction of a weak pointer to "this" in an object's constructor is also
// discouraged. But the implementation was recently changed so that, in the
// absence of extendObjectLifetime() calls, weak pointers no longer impact
// object lifetime, and hence this no longer risks premature deallocation,
// and hence usually works correctly.
// Such strong or weak pointers can be safely created in the RefBase onFirstRef()
// callback.
// Use of wp::unsafe_get() for any purpose other than debugging is almost
// always wrong. Unless you somehow know that there is a longer-lived sp<> to
// the same object, it may well return a pointer to a deallocated object that
// has since been reallocated for a different purpose. (And if you know there
// is a longer-lived sp<>, why not use an sp<> directly?) A wp<> should only be
// dereferenced by using promote().
// Any object inheriting from RefBase should always be destroyed as the result
// of a reference count decrement, not via any other means. Such objects
// should never be stack allocated, or appear directly as data members in other
// objects. Objects inheriting from RefBase should have their strong reference
// count incremented as soon as possible after construction. Usually this
// will be done via construction of an sp<> to the object, but may instead
// involve other means of calling RefBase::incStrong().
// Explicitly deleting or otherwise destroying a RefBase object with outstanding
// wp<> or sp<> pointers to it will result in an abort or heap corruption.
// It is particularly important not to mix sp<> and direct storage management
// since the sp from raw pointer constructor is implicit. Thus if a RefBase-
// -derived object of type T is managed without ever incrementing its strong
// count, and accidentally passed to f(sp<T>), a strong pointer to the object
// will be temporarily constructed and destroyed, prematurely deallocating the
// object, and resulting in heap corruption. None of this would be easily
// visible in the source. See below on
// ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION for a compile time
// option which helps avoid this case.
// Extra Features:
// RefBase::extendObjectLifetime() can be used to prevent destruction of the
// object while there are still weak references. This is really special purpose
// functionality to support Binder.
// Wp::promote(), implemented via the attemptIncStrong() member function, is
// used to try to convert a weak pointer back to a strong pointer. It's the
// normal way to try to access the fields of an object referenced only through
// a wp<>. Binder code also sometimes uses attemptIncStrong() directly.
// RefBase provides a number of additional callbacks for certain reference count
// events, as well as some debugging facilities.
// Debugging support can be enabled by turning on DEBUG_REFS in RefBase.cpp.
// Otherwise little checking is provided.
// Thread safety:
// Like std::shared_ptr, sp<> and wp<> allow concurrent accesses to DIFFERENT
// sp<> and wp<> instances that happen to refer to the same underlying object.
// They do NOT support concurrent access (where at least one access is a write)
// to THE SAME sp<> or wp<>. In effect, their thread-safety properties are
// exactly like those of T*, NOT atomic<T*>.
// Safety option: ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION
//
// This flag makes the semantics for using a RefBase object with wp<> and sp<>
// much stricter by disabling implicit conversion from raw pointers to these
// objects. In order to use this, apply this flag in Android.bp like so:
//
// cflags: [
// "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
// ],
//
// REGARDLESS of whether this flag is on, best usage of sp<> is shown below. If
// this flag is on, no other usage is possible (directly calling RefBase methods
// is possible, but seeing code using 'incStrong' instead of 'sp<>', for
// instance, should already set off big alarm bells. With carefully constructed
// data structures, it should NEVER be necessary to directly use RefBase
// methods). Proper RefBase usage:
//
// class Foo : virtual public RefBase { ... };
//
// // always construct an sp object with sp::make
// sp<Foo> myFoo = sp<Foo>::make(/*args*/);
//
// // if you need a weak pointer, it must be constructed from a strong
// // pointer
// wp<Foo> weakFoo = myFoo; // NOT myFoo.get()
//
// // If you are inside of a method of Foo and need access to a strong
// // explicitly call this function. This documents your intention to code
// // readers, and it will give a runtime error for what otherwise would
// // be potential double ownership
// .... Foo::someMethod(...) {
// // asserts if there is a memory issue
// sp<Foo> thiz = sp<Foo>::fromExisting(this);
// }
//
#ifndef ANDROID_REF_BASE_H
#define ANDROID_REF_BASE_H
#include <atomic>
#include <functional>
#include <memory>
#include <type_traits> // for common_type.
#include <stdint.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
// LightRefBase used to be declared in this header, so we have to include it
#include <utils/LightRefBase.h>
#include <utils/StrongPointer.h>
#include <utils/TypeHelpers.h>
// ---------------------------------------------------------------------------
namespace android {
// ---------------------------------------------------------------------------
#define COMPARE_WEAK(_op_) \
template<typename U> \
inline bool operator _op_ (const U* o) const { \
return m_ptr _op_ o; \
} \
/* Needed to handle type inference for nullptr: */ \
inline bool operator _op_ (const T* o) const { \
return m_ptr _op_ o; \
}
template<template<typename C> class comparator, typename T, typename U>
static inline bool _wp_compare_(T* a, U* b) {
return comparator<typename std::common_type<T*, U*>::type>()(a, b);
}
// Use std::less and friends to avoid undefined behavior when ordering pointers
// to different objects.
#define COMPARE_WEAK_FUNCTIONAL(_op_, _compare_) \
template<typename U> \
inline bool operator _op_ (const U* o) const { \
return _wp_compare_<_compare_>(m_ptr, o); \
}
// ---------------------------------------------------------------------------
// RefererenceRenamer is pure abstract, there is no virtual method
// implementation to put in a translation unit in order to silence the
// weak vtables warning.
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wweak-vtables"
#endif
class ReferenceRenamer {
protected:
// destructor is purposely not virtual so we avoid code overhead from
// subclasses; we have to make it protected to guarantee that it
// cannot be called from this base class (and to make strict compilers
// happy).
~ReferenceRenamer() { }
public:
virtual void operator()(size_t i) const = 0;
};
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
// ---------------------------------------------------------------------------
class RefBase
{
public:
void incStrong(const void* id) const;
void incStrongRequireStrong(const void* id) const;
void decStrong(const void* id) const;
void forceIncStrong(const void* id) const;
//! DEBUGGING ONLY: Get current strong ref count.
int32_t getStrongCount() const;
class weakref_type
{
public:
RefBase* refBase() const;
void incWeak(const void* id);
void incWeakRequireWeak(const void* id);
void decWeak(const void* id);
// acquires a strong reference if there is already one.
bool attemptIncStrong(const void* id);
// acquires a weak reference if there is already one.
// This is not always safe. see ProcessState.cpp and BpBinder.cpp
// for proper use.
bool attemptIncWeak(const void* id);
//! DEBUGGING ONLY: Get current weak ref count.
int32_t getWeakCount() const;
//! DEBUGGING ONLY: Print references held on object.
void printRefs() const;
//! DEBUGGING ONLY: Enable tracking for this object.
// enable -- enable/disable tracking
// retain -- when tracking is enable, if true, then we save a stack trace
// for each reference and dereference; when retain == false, we
// match up references and dereferences and keep only the
// outstanding ones.
void trackMe(bool enable, bool retain);
};
weakref_type* createWeak(const void* id) const;
weakref_type* getWeakRefs() const;
//! DEBUGGING ONLY: Print references held on object.
inline void printRefs() const { getWeakRefs()->printRefs(); }
//! DEBUGGING ONLY: Enable tracking of object.
inline void trackMe(bool enable, bool retain)
{
getWeakRefs()->trackMe(enable, retain);
}
protected:
// When constructing these objects, prefer using sp::make<>. Using a RefBase
// object on the stack or with other refcount mechanisms (e.g.
// std::shared_ptr) is inherently wrong. RefBase types have an implicit
// ownership model and cannot be safely used with other ownership models.
RefBase();
virtual ~RefBase();
//! Flags for extendObjectLifetime()
enum {
OBJECT_LIFETIME_STRONG = 0x0000,
OBJECT_LIFETIME_WEAK = 0x0001,
OBJECT_LIFETIME_MASK = 0x0001
};
void extendObjectLifetime(int32_t mode);
//! Flags for onIncStrongAttempted()
enum {
FIRST_INC_STRONG = 0x0001
};
// Invoked after creation of initial strong pointer/reference.
virtual void onFirstRef();
// Invoked when either the last strong reference goes away, or we need to undo
// the effect of an unnecessary onIncStrongAttempted.
virtual void onLastStrongRef(const void* id);
// Only called in OBJECT_LIFETIME_WEAK case. Returns true if OK to promote to
// strong reference. May have side effects if it returns true.
// The first flags argument is always FIRST_INC_STRONG.
// TODO: Remove initial flag argument.
virtual bool onIncStrongAttempted(uint32_t flags, const void* id);
// Invoked in the OBJECT_LIFETIME_WEAK case when the last reference of either
// kind goes away. Unused.
// TODO: Remove.
virtual void onLastWeakRef(const void* id);
private:
friend class weakref_type;
class weakref_impl;
RefBase(const RefBase& o);
RefBase& operator=(const RefBase& o);
private:
friend class ReferenceMover;
static void renameRefs(size_t n, const ReferenceRenamer& renamer);
static void renameRefId(weakref_type* ref,
const void* old_id, const void* new_id);
static void renameRefId(RefBase* ref,
const void* old_id, const void* new_id);
weakref_impl* const mRefs;
};
// ---------------------------------------------------------------------------
template <typename T>
class wp
{
public:
typedef typename RefBase::weakref_type weakref_type;
inline constexpr wp() : m_ptr(nullptr), m_refs(nullptr) { }
// if nullptr, returns nullptr
//
// if a weak pointer is already available, this will retrieve it,
// otherwise, this will abort
static inline wp<T> fromExisting(T* other);
// for more information about this flag, see above
#if defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
wp(std::nullptr_t) : wp() {}
#else
wp(T* other); // NOLINT(implicit)
template <typename U>
wp(U* other); // NOLINT(implicit)
wp& operator=(T* other);
template <typename U>
wp& operator=(U* other);
#endif
wp(const wp<T>& other);
explicit wp(const sp<T>& other);
template<typename U> wp(const sp<U>& other); // NOLINT(implicit)
template<typename U> wp(const wp<U>& other); // NOLINT(implicit)
~wp();
// Assignment
wp& operator = (const wp<T>& other);
wp& operator = (const sp<T>& other);
template<typename U> wp& operator = (const wp<U>& other);
template<typename U> wp& operator = (const sp<U>& other);
void set_object_and_refs(T* other, weakref_type* refs);
// promotion to sp
sp<T> promote() const;
// Reset
void clear();
// Accessors
inline weakref_type* get_refs() const { return m_refs; }
inline T* unsafe_get() const { return m_ptr; }
// Operators
COMPARE_WEAK(==)
COMPARE_WEAK(!=)
COMPARE_WEAK_FUNCTIONAL(>, std::greater)
COMPARE_WEAK_FUNCTIONAL(<, std::less)
COMPARE_WEAK_FUNCTIONAL(<=, std::less_equal)
COMPARE_WEAK_FUNCTIONAL(>=, std::greater_equal)
template<typename U>
inline bool operator == (const wp<U>& o) const {
return m_refs == o.m_refs; // Implies m_ptr == o.mptr; see invariants below.
}
template<typename U>
inline bool operator == (const sp<U>& o) const {
// Just comparing m_ptr fields is often dangerous, since wp<> may refer to an older
// object at the same address.
if (o == nullptr) {
return m_ptr == nullptr;
} else {
return m_refs == o->getWeakRefs(); // Implies m_ptr == o.mptr.
}
}
template<typename U>
inline bool operator != (const sp<U>& o) const {
return !(*this == o);
}
template<typename U>
inline bool operator > (const wp<U>& o) const {
if (m_ptr == o.m_ptr) {
return _wp_compare_<std::greater>(m_refs, o.m_refs);
} else {
return _wp_compare_<std::greater>(m_ptr, o.m_ptr);
}
}
template<typename U>
inline bool operator < (const wp<U>& o) const {
if (m_ptr == o.m_ptr) {
return _wp_compare_<std::less>(m_refs, o.m_refs);
} else {
return _wp_compare_<std::less>(m_ptr, o.m_ptr);
}
}
template<typename U> inline bool operator != (const wp<U>& o) const { return !operator == (o); }
template<typename U> inline bool operator <= (const wp<U>& o) const { return !operator > (o); }
template<typename U> inline bool operator >= (const wp<U>& o) const { return !operator < (o); }
private:
template<typename Y> friend class sp;
template<typename Y> friend class wp;
T* m_ptr;
weakref_type* m_refs;
};
#undef COMPARE_WEAK
#undef COMPARE_WEAK_FUNCTIONAL
// ---------------------------------------------------------------------------
// No user serviceable parts below here.
// Implementation invariants:
// Either
// 1) m_ptr and m_refs are both null, or
// 2) m_refs == m_ptr->mRefs, or
// 3) *m_ptr is no longer live, and m_refs points to the weakref_type object that corresponded
// to m_ptr while it was live. *m_refs remains live while a wp<> refers to it.
//
// The m_refs field in a RefBase object is allocated on construction, unique to that RefBase
// object, and never changes. Thus if two wp's have identical m_refs fields, they are either both
// null or point to the same object. If two wp's have identical m_ptr fields, they either both
// point to the same live object and thus have the same m_ref fields, or at least one of the
// objects is no longer live.
//
// Note that the above comparison operations go out of their way to provide an ordering consistent
// with ordinary pointer comparison; otherwise they could ignore m_ptr, and just compare m_refs.
template <typename T>
wp<T> wp<T>::fromExisting(T* other) {
if (!other) return nullptr;
auto refs = other->getWeakRefs();
refs->incWeakRequireWeak(other);
wp<T> ret;
ret.m_ptr = other;
ret.m_refs = refs;
return ret;
}
#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
template<typename T>
wp<T>::wp(T* other)
: m_ptr(other)
{
m_refs = other ? other->createWeak(this) : nullptr;
}
template <typename T>
template <typename U>
wp<T>::wp(U* other) : m_ptr(other) {
m_refs = other ? other->createWeak(this) : nullptr;
}
template <typename T>
wp<T>& wp<T>::operator=(T* other) {
weakref_type* newRefs = other ? other->createWeak(this) : nullptr;
if (m_ptr) m_refs->decWeak(this);
m_ptr = other;
m_refs = newRefs;
return *this;
}
template <typename T>
template <typename U>
wp<T>& wp<T>::operator=(U* other) {
weakref_type* newRefs = other ? other->createWeak(this) : 0;
if (m_ptr) m_refs->decWeak(this);
m_ptr = other;
m_refs = newRefs;
return *this;
}
#endif
template<typename T>
wp<T>::wp(const wp<T>& other)
: m_ptr(other.m_ptr), m_refs(other.m_refs)
{
if (m_ptr) m_refs->incWeak(this);
}
template<typename T>
wp<T>::wp(const sp<T>& other)
: m_ptr(other.m_ptr)
{
m_refs = m_ptr ? m_ptr->createWeak(this) : nullptr;
}
template<typename T> template<typename U>
wp<T>::wp(const wp<U>& other)
: m_ptr(other.m_ptr)
{
if (m_ptr) {
m_refs = other.m_refs;
m_refs->incWeak(this);
} else {
m_refs = nullptr;
}
}
template<typename T> template<typename U>
wp<T>::wp(const sp<U>& other)
: m_ptr(other.m_ptr)
{
m_refs = m_ptr ? m_ptr->createWeak(this) : nullptr;
}
template<typename T>
wp<T>::~wp()
{
if (m_ptr) m_refs->decWeak(this);
}
template<typename T>
wp<T>& wp<T>::operator = (const wp<T>& other)
{
weakref_type* otherRefs(other.m_refs);
T* otherPtr(other.m_ptr);
if (otherPtr) otherRefs->incWeak(this);
if (m_ptr) m_refs->decWeak(this);
m_ptr = otherPtr;
m_refs = otherRefs;
return *this;
}
template<typename T>
wp<T>& wp<T>::operator = (const sp<T>& other)
{
weakref_type* newRefs =
other != nullptr ? other->createWeak(this) : nullptr;
T* otherPtr(other.m_ptr);
if (m_ptr) m_refs->decWeak(this);
m_ptr = otherPtr;
m_refs = newRefs;
return *this;
}
template<typename T> template<typename U>
wp<T>& wp<T>::operator = (const wp<U>& other)
{
weakref_type* otherRefs(other.m_refs);
U* otherPtr(other.m_ptr);
if (otherPtr) otherRefs->incWeak(this);
if (m_ptr) m_refs->decWeak(this);
m_ptr = otherPtr;
m_refs = otherRefs;
return *this;
}
template<typename T> template<typename U>
wp<T>& wp<T>::operator = (const sp<U>& other)
{
weakref_type* newRefs = other != nullptr ? other->createWeak(this) : nullptr;
U* otherPtr(other.m_ptr);
if (m_ptr) m_refs->decWeak(this);
m_ptr = otherPtr;
m_refs = newRefs;
return *this;
}
template<typename T>
void wp<T>::set_object_and_refs(T* other, weakref_type* refs)
{
if (other) refs->incWeak(this);
if (m_ptr) m_refs->decWeak(this);
m_ptr = other;
m_refs = refs;
}
template<typename T>
sp<T> wp<T>::promote() const
{
sp<T> result;
if (m_ptr && m_refs->attemptIncStrong(&result)) {
result.set_pointer(m_ptr);
}
return result;
}
template<typename T>
void wp<T>::clear()
{
if (m_ptr) {
m_refs->decWeak(this);
m_refs = nullptr;
m_ptr = nullptr;
}
}
// ---------------------------------------------------------------------------
// this class just serves as a namespace so TYPE::moveReferences can stay
// private.
class ReferenceMover {
public:
// it would be nice if we could make sure no extra code is generated
// for sp<TYPE> or wp<TYPE> when TYPE is a descendant of RefBase:
// Using a sp<RefBase> override doesn't work; it's a bit like we wanted
// a template<typename TYPE inherits RefBase> template...
template<typename TYPE> static inline
void move_references(sp<TYPE>* dest, sp<TYPE> const* src, size_t n) {
class Renamer : public ReferenceRenamer {
sp<TYPE>* d_;
sp<TYPE> const* s_;
virtual void operator()(size_t i) const {
// The id are known to be the sp<>'s this pointer
TYPE::renameRefId(d_[i].get(), &s_[i], &d_[i]);
}
public:
Renamer(sp<TYPE>* d, sp<TYPE> const* s) : d_(d), s_(s) { }
virtual ~Renamer() { }
};
memmove(dest, src, n*sizeof(sp<TYPE>));
TYPE::renameRefs(n, Renamer(dest, src));
}
template<typename TYPE> static inline
void move_references(wp<TYPE>* dest, wp<TYPE> const* src, size_t n) {
class Renamer : public ReferenceRenamer {
wp<TYPE>* d_;
wp<TYPE> const* s_;
virtual void operator()(size_t i) const {
// The id are known to be the wp<>'s this pointer
TYPE::renameRefId(d_[i].get_refs(), &s_[i], &d_[i]);
}
public:
Renamer(wp<TYPE>* rd, wp<TYPE> const* rs) : d_(rd), s_(rs) { }
virtual ~Renamer() { }
};
memmove(dest, src, n*sizeof(wp<TYPE>));
TYPE::renameRefs(n, Renamer(dest, src));
}
};
// specialization for moving sp<> and wp<> types.
// these are used by the [Sorted|Keyed]Vector<> implementations
// sp<> and wp<> need to be handled specially, because they do not
// have trivial copy operation in the general case (see RefBase.cpp
// when DEBUG ops are enabled), but can be implemented very
// efficiently in most cases.
template<typename TYPE> inline
void move_forward_type(sp<TYPE>* d, sp<TYPE> const* s, size_t n) {
ReferenceMover::move_references(d, s, n);
}
template<typename TYPE> inline
void move_backward_type(sp<TYPE>* d, sp<TYPE> const* s, size_t n) {
ReferenceMover::move_references(d, s, n);
}
template<typename TYPE> inline
void move_forward_type(wp<TYPE>* d, wp<TYPE> const* s, size_t n) {
ReferenceMover::move_references(d, s, n);
}
template<typename TYPE> inline
void move_backward_type(wp<TYPE>* d, wp<TYPE> const* s, size_t n) {
ReferenceMover::move_references(d, s, n);
}
} // namespace android
namespace libutilsinternal {
template <typename T, typename = void>
struct is_complete_type : std::false_type {};
template <typename T>
struct is_complete_type<T, decltype(void(sizeof(T)))> : std::true_type {};
} // namespace libutilsinternal
namespace std {
// Define `RefBase` specific versions of `std::make_shared` and
// `std::make_unique` to block people from using them. Using them to allocate
// `RefBase` objects results in double ownership. Use
// `sp<T>::make(...)` instead.
//
// Note: We exclude incomplete types because `std::is_base_of` is undefined in
// that case.
template <typename T, typename... Args,
typename std::enable_if<libutilsinternal::is_complete_type<T>::value, bool>::value = true,
typename std::enable_if<std::is_base_of<android::RefBase, T>::value, bool>::value = true>
shared_ptr<T> make_shared(Args...) { // SEE COMMENT ABOVE.
static_assert(!std::is_base_of<android::RefBase, T>::value, "Must use RefBase with sp<>");
}
template <typename T, typename... Args,
typename std::enable_if<libutilsinternal::is_complete_type<T>::value, bool>::value = true,
typename std::enable_if<std::is_base_of<android::RefBase, T>::value, bool>::value = true>
unique_ptr<T> make_unique(Args...) { // SEE COMMENT ABOVE.
static_assert(!std::is_base_of<android::RefBase, T>::value, "Must use RefBase with sp<>");
}
} // namespace std
// ---------------------------------------------------------------------------
#endif // ANDROID_REF_BASE_H
+411
View File
@@ -0,0 +1,411 @@
/*
* Copyright (C) 2005 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_STRING16_H
#define ANDROID_STRING16_H
#include <iostream>
#include <string>
#include <string_view>
#include <utils/Errors.h>
#include <utils/String8.h>
#include <utils/TypeHelpers.h>
#if __cplusplus >= 202002L
#include <compare>
#endif
// ---------------------------------------------------------------------------
namespace android {
// ---------------------------------------------------------------------------
template <size_t N>
class StaticString16;
// DO NOT USE: please use std::u16string
//! This is a string holding UTF-16 characters.
class String16
{
public:
String16();
String16(const String16& o);
String16(String16&& o) noexcept;
String16(const String16& o,
size_t len,
size_t begin=0);
explicit String16(const char16_t* o);
explicit String16(const char16_t* o, size_t len);
explicit String16(const String8& o);
explicit String16(const char* o);
explicit String16(const char* o, size_t len);
~String16();
inline const char16_t* c_str() const;
size_t size() const;
inline bool empty() const;
inline size_t length() const;
void setTo(const String16& other);
status_t setTo(const char16_t* other);
status_t setTo(const char16_t* other, size_t len);
status_t setTo(const String16& other,
size_t len,
size_t begin=0);
status_t append(const String16& other);
status_t append(const char16_t* other, size_t len);
inline String16& operator=(const String16& other);
String16& operator=(String16&& other) noexcept;
inline String16& operator+=(const String16& other);
inline String16 operator+(const String16& other) const;
status_t insert(size_t pos, const char16_t* chrs);
status_t insert(size_t pos,
const char16_t* chrs, size_t len);
ssize_t findFirst(char16_t c) const;
ssize_t findLast(char16_t c) const;
bool startsWith(const String16& prefix) const;
bool startsWith(const char16_t* prefix) const;
bool contains(const char16_t* chrs) const;
inline bool contains(const String16& other) const;
status_t replaceAll(char16_t replaceThis,
char16_t withThis);
inline int compare(const String16& other) const;
inline bool operator<(const String16& other) const;
inline bool operator<=(const String16& other) const;
inline bool operator==(const String16& other) const;
inline bool operator!=(const String16& other) const;
inline bool operator>=(const String16& other) const;
inline bool operator>(const String16& other) const;
#if __cplusplus >= 202002L
inline std::strong_ordering operator<=>(const String16& other) const;
#endif
inline bool operator<(const char16_t* other) const;
inline bool operator<=(const char16_t* other) const;
inline bool operator==(const char16_t* other) const;
inline bool operator!=(const char16_t* other) const;
inline bool operator>=(const char16_t* other) const;
inline bool operator>(const char16_t* other) const;
#if __cplusplus >= 202002L
inline std::strong_ordering operator<=>(const char16_t* other) const;
#endif
inline operator const char16_t*() const;
// Implicit cast to std::u16string is not implemented on purpose - u16string_view is much
// lighter and if one needs, they can still create u16string from u16string_view.
inline operator std::u16string_view() const;
// Static and non-static String16 behave the same for the users, so
// this method isn't of much use for the users. It is public for testing.
bool isStaticString() const;
private:
/*
* A flag indicating the type of underlying buffer.
*/
static constexpr uint32_t kIsSharedBufferAllocated = 0x80000000;
/*
* alloc() returns void* so that SharedBuffer class is not exposed.
*/
static void* alloc(size_t size);
static char16_t* allocFromUTF8(const char* u8str, size_t u8len);
static char16_t* allocFromUTF16(const char16_t* u16str, size_t u16len);
/*
* edit() and editResize() return void* so that SharedBuffer class
* is not exposed.
*/
void* edit();
void* editResize(size_t new_size);
void acquire();
void release();
size_t staticStringSize() const;
const char16_t* mString;
protected:
/*
* Data structure used to allocate static storage for static String16.
*
* Note that this data structure and SharedBuffer are used interchangably
* as the underlying data structure for a String16. Therefore, the layout
* of this data structure must match the part in SharedBuffer that is
* visible to String16.
*/
template <size_t N>
struct StaticData {
// The high bit of 'size' is used as a flag.
static_assert(N - 1 < kIsSharedBufferAllocated, "StaticString16 too long!");
constexpr StaticData() : size(N - 1), data{0} {}
const uint32_t size;
char16_t data[N];
constexpr StaticData(const StaticData<N>&) = default;
};
/*
* Helper function for constructing a StaticData object.
*/
template <size_t N>
static constexpr const StaticData<N> makeStaticData(const char16_t (&s)[N]) {
StaticData<N> r;
// The 'size' field is at the same location where mClientMetadata would
// be for a SharedBuffer. We do NOT set kIsSharedBufferAllocated flag
// here.
for (size_t i = 0; i < N - 1; ++i) r.data[i] = s[i];
return r;
}
template <size_t N>
explicit constexpr String16(const StaticData<N>& s) : mString(s.data) {}
// These symbols are for potential backward compatibility with prebuilts. To be removed.
#ifdef ENABLE_STRING16_OBSOLETE_METHODS
public:
#else
private:
#endif
inline const char16_t* string() const;
};
// String16 can be trivially moved using memcpy() because moving does not
// require any change to the underlying SharedBuffer contents or reference count.
ANDROID_TRIVIAL_MOVE_TRAIT(String16)
static inline std::ostream& operator<<(std::ostream& os, const String16& str) {
os << String8(str);
return os;
}
// ---------------------------------------------------------------------------
/*
* A StaticString16 object is a specialized String16 object. Instead of holding
* the string data in a ref counted SharedBuffer object, it holds data in a
* buffer within StaticString16 itself. Note that this buffer is NOT ref
* counted and is assumed to be available for as long as there is at least a
* String16 object using it. Therefore, one must be extra careful to NEVER
* assign a StaticString16 to a String16 that outlives the StaticString16
* object.
*
* THE SAFEST APPROACH IS TO USE StaticString16 ONLY AS GLOBAL VARIABLES.
*
* A StaticString16 SHOULD NEVER APPEAR IN APIs. USE String16 INSTEAD.
*/
template <size_t N>
class StaticString16 : public String16 {
public:
constexpr StaticString16(const char16_t (&s)[N]) : String16(mData), mData(makeStaticData(s)) {}
constexpr StaticString16(const StaticString16<N>& other)
: String16(mData), mData(other.mData) {}
constexpr StaticString16(const StaticString16<N>&&) = delete;
// There is no reason why one would want to 'new' a StaticString16. Delete
// it to discourage misuse.
static void* operator new(std::size_t) = delete;
private:
const StaticData<N> mData;
};
template <typename F>
StaticString16(const F&)->StaticString16<sizeof(F) / sizeof(char16_t)>;
// ---------------------------------------------------------------------------
// No user servicable parts below.
inline int compare_type(const String16& lhs, const String16& rhs)
{
return lhs.compare(rhs);
}
inline int strictly_order_type(const String16& lhs, const String16& rhs)
{
return compare_type(lhs, rhs) < 0;
}
inline const char16_t* String16::c_str() const
{
return mString;
}
inline const char16_t* String16::string() const
{
return mString;
}
inline bool String16::empty() const
{
return length() == 0;
}
inline size_t String16::length() const
{
return size();
}
inline bool String16::contains(const String16& other) const
{
return contains(other.c_str());
}
inline String16& String16::operator=(const String16& other)
{
setTo(other);
return *this;
}
inline String16& String16::operator+=(const String16& other)
{
append(other);
return *this;
}
inline String16 String16::operator+(const String16& other) const
{
String16 tmp(*this);
tmp += other;
return tmp;
}
inline int String16::compare(const String16& other) const
{
return strzcmp16(mString, size(), other.mString, other.size());
}
inline bool String16::operator<(const String16& other) const
{
return strzcmp16(mString, size(), other.mString, other.size()) < 0;
}
inline bool String16::operator<=(const String16& other) const
{
return strzcmp16(mString, size(), other.mString, other.size()) <= 0;
}
inline bool String16::operator==(const String16& other) const
{
return strzcmp16(mString, size(), other.mString, other.size()) == 0;
}
inline bool String16::operator!=(const String16& other) const
{
return strzcmp16(mString, size(), other.mString, other.size()) != 0;
}
inline bool String16::operator>=(const String16& other) const
{
return strzcmp16(mString, size(), other.mString, other.size()) >= 0;
}
inline bool String16::operator>(const String16& other) const
{
return strzcmp16(mString, size(), other.mString, other.size()) > 0;
}
#if __cplusplus >= 202002L
inline std::strong_ordering String16::operator<=>(const String16& other) const {
int result = strzcmp16(mString, size(), other.mString, other.size());
if (result == 0) {
return std::strong_ordering::equal;
} else if (result < 0) {
return std::strong_ordering::less;
} else {
return std::strong_ordering::greater;
}
}
#endif
inline bool String16::operator<(const char16_t* other) const
{
return strcmp16(mString, other) < 0;
}
inline bool String16::operator<=(const char16_t* other) const
{
return strcmp16(mString, other) <= 0;
}
inline bool String16::operator==(const char16_t* other) const
{
return strcmp16(mString, other) == 0;
}
inline bool String16::operator!=(const char16_t* other) const
{
return strcmp16(mString, other) != 0;
}
inline bool String16::operator>=(const char16_t* other) const
{
return strcmp16(mString, other) >= 0;
}
inline bool String16::operator>(const char16_t* other) const
{
return strcmp16(mString, other) > 0;
}
#if __cplusplus >= 202002L
inline std::strong_ordering String16::operator<=>(const char16_t* other) const {
int result = strcmp16(mString, other);
if (result == 0) {
return std::strong_ordering::equal;
} else if (result < 0) {
return std::strong_ordering::less;
} else {
return std::strong_ordering::greater;
}
}
#endif
inline String16::operator const char16_t*() const
{
return mString;
}
inline String16::operator std::u16string_view() const
{
return {mString, length()};
}
} // namespace android
// ---------------------------------------------------------------------------
#endif // ANDROID_STRING16_H
+378
View File
@@ -0,0 +1,378 @@
/*
* Copyright (C) 2005 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_STRING8_H
#define ANDROID_STRING8_H
#include <iostream>
#include <string>
#include <string_view>
#include <utils/Errors.h>
#include <utils/Unicode.h>
#include <utils/TypeHelpers.h>
#include <string.h> // for strcmp
#include <stdarg.h>
#if __cplusplus >= 202002L
#include <compare>
#endif
// ---------------------------------------------------------------------------
namespace android {
class String16;
// DO NOT USE: please use std::string
//! This is a string holding UTF-8 characters. Does not allow the value more
// than 0x10FFFF, which is not valid unicode codepoint.
class String8
{
public:
String8();
String8(const String8& o);
explicit String8(const char* o);
explicit String8(const char* o, size_t numChars);
explicit String8(std::string_view o);
explicit String8(const String16& o);
explicit String8(const char16_t* o);
explicit String8(const char16_t* o, size_t numChars);
explicit String8(const char32_t* o);
explicit String8(const char32_t* o, size_t numChars);
~String8();
static String8 format(const char* fmt, ...) __attribute__((format (printf, 1, 2)));
static String8 formatV(const char* fmt, va_list args);
inline const char* c_str() const;
inline size_t size() const;
inline size_t bytes() const;
inline bool empty() const;
size_t length() const;
void clear();
void setTo(const String8& other);
status_t setTo(const char* other);
status_t setTo(const char* other, size_t numChars);
status_t setTo(const char16_t* other, size_t numChars);
status_t setTo(const char32_t* other,
size_t length);
status_t append(const String8& other);
status_t append(const char* other);
status_t append(const char* other, size_t numChars);
status_t appendFormat(const char* fmt, ...)
__attribute__((format (printf, 2, 3)));
status_t appendFormatV(const char* fmt, va_list args);
inline String8& operator=(const String8& other);
inline String8& operator=(const char* other);
inline String8& operator+=(const String8& other);
inline String8 operator+(const String8& other) const;
inline String8& operator+=(const char* other);
inline String8 operator+(const char* other) const;
inline int compare(const String8& other) const;
inline bool operator<(const String8& other) const;
inline bool operator<=(const String8& other) const;
inline bool operator==(const String8& other) const;
inline bool operator!=(const String8& other) const;
inline bool operator>=(const String8& other) const;
inline bool operator>(const String8& other) const;
#if __cplusplus >= 202002L
inline std::strong_ordering operator<=>(const String8& other) const;
#endif
inline bool operator<(const char* other) const;
inline bool operator<=(const char* other) const;
inline bool operator==(const char* other) const;
inline bool operator!=(const char* other) const;
inline bool operator>=(const char* other) const;
inline bool operator>(const char* other) const;
#if __cplusplus >= 202002L
inline std::strong_ordering operator<=>(const char* other) const;
#endif
inline operator const char*() const;
inline explicit operator std::string_view() const;
char* lockBuffer(size_t size);
void unlockBuffer();
status_t unlockBuffer(size_t size);
// return the index of the first byte of other in this at or after
// start, or -1 if not found
ssize_t find(const char* other, size_t start = 0) const;
inline ssize_t find(const String8& other, size_t start = 0) const;
// return true if this string contains the specified substring
inline bool contains(const char* other) const;
inline bool contains(const String8& other) const;
// removes all occurrence of the specified substring
// returns true if any were found and removed
bool removeAll(const char* other);
inline bool removeAll(const String8& other);
void toLower();
private:
String8 getPathDir(void) const;
String8 getPathExtension(void) const;
status_t real_append(const char* other, size_t numChars);
const char* mString;
// These symbols are for potential backward compatibility with prebuilts. To be removed.
#ifdef ENABLE_STRING8_OBSOLETE_METHODS
public:
#else
private:
#endif
inline const char* string() const;
inline bool isEmpty() const;
};
// String8 can be trivially moved using memcpy() because moving does not
// require any change to the underlying SharedBuffer contents or reference count.
ANDROID_TRIVIAL_MOVE_TRAIT(String8)
static inline std::ostream& operator<<(std::ostream& os, const String8& str) {
os << str.c_str();
return os;
}
// ---------------------------------------------------------------------------
// No user servicable parts below.
inline int compare_type(const String8& lhs, const String8& rhs)
{
return lhs.compare(rhs);
}
inline int strictly_order_type(const String8& lhs, const String8& rhs)
{
return compare_type(lhs, rhs) < 0;
}
inline const char* String8::c_str() const
{
return mString;
}
inline const char* String8::string() const
{
return mString;
}
inline size_t String8::size() const
{
return length();
}
inline bool String8::empty() const
{
return length() == 0;
}
inline bool String8::isEmpty() const
{
return length() == 0;
}
inline size_t String8::bytes() const
{
return length();
}
inline ssize_t String8::find(const String8& other, size_t start) const
{
return find(other.c_str(), start);
}
inline bool String8::contains(const char* other) const
{
return find(other) >= 0;
}
inline bool String8::contains(const String8& other) const
{
return contains(other.c_str());
}
inline bool String8::removeAll(const String8& other)
{
return removeAll(other.c_str());
}
inline String8& String8::operator=(const String8& other)
{
setTo(other);
return *this;
}
inline String8& String8::operator=(const char* other)
{
setTo(other);
return *this;
}
inline String8& String8::operator+=(const String8& other)
{
append(other);
return *this;
}
inline String8 String8::operator+(const String8& other) const
{
String8 tmp(*this);
tmp += other;
return tmp;
}
inline String8& String8::operator+=(const char* other)
{
append(other);
return *this;
}
inline String8 String8::operator+(const char* other) const
{
String8 tmp(*this);
tmp += other;
return tmp;
}
inline int String8::compare(const String8& other) const
{
return strcmp(mString, other.mString);
}
inline bool String8::operator<(const String8& other) const
{
return strcmp(mString, other.mString) < 0;
}
inline bool String8::operator<=(const String8& other) const
{
return strcmp(mString, other.mString) <= 0;
}
inline bool String8::operator==(const String8& other) const
{
return strcmp(mString, other.mString) == 0;
}
inline bool String8::operator!=(const String8& other) const
{
return strcmp(mString, other.mString) != 0;
}
inline bool String8::operator>=(const String8& other) const
{
return strcmp(mString, other.mString) >= 0;
}
inline bool String8::operator>(const String8& other) const
{
return strcmp(mString, other.mString) > 0;
}
#if __cplusplus >= 202002L
inline std::strong_ordering String8::operator<=>(const String8& other) const {
int result = strcmp(mString, other.mString);
if (result == 0) {
return std::strong_ordering::equal;
} else if (result < 0) {
return std::strong_ordering::less;
} else {
return std::strong_ordering::greater;
}
}
#endif
inline bool String8::operator<(const char* other) const
{
return strcmp(mString, other) < 0;
}
inline bool String8::operator<=(const char* other) const
{
return strcmp(mString, other) <= 0;
}
inline bool String8::operator==(const char* other) const
{
return strcmp(mString, other) == 0;
}
inline bool String8::operator!=(const char* other) const
{
return strcmp(mString, other) != 0;
}
inline bool String8::operator>=(const char* other) const
{
return strcmp(mString, other) >= 0;
}
inline bool String8::operator>(const char* other) const
{
return strcmp(mString, other) > 0;
}
#if __cplusplus >= 202002L
inline std::strong_ordering String8::operator<=>(const char* other) const {
int result = strcmp(mString, other);
if (result == 0) {
return std::strong_ordering::equal;
} else if (result < 0) {
return std::strong_ordering::less;
} else {
return std::strong_ordering::greater;
}
}
#endif
inline String8::operator const char*() const
{
return mString;
}
inline String8::String8(std::string_view o) : String8(o.data(), o.length()) { }
inline String8::operator std::string_view() const
{
return {mString, length()};
}
} // namespace android
// ---------------------------------------------------------------------------
#endif // ANDROID_STRING8_H
@@ -0,0 +1,370 @@
/*
* Copyright (C) 2005 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_STRONG_POINTER_H
#define ANDROID_STRONG_POINTER_H
#include <functional>
#include <type_traits> // for common_type.
// ---------------------------------------------------------------------------
namespace android {
template<typename T> class wp;
// ---------------------------------------------------------------------------
template<typename T>
class sp {
public:
inline constexpr sp() : m_ptr(nullptr) { }
// The old way of using sp<> was like this. This is bad because it relies
// on implicit conversion to sp<>, which we would like to remove (if an
// object is being managed some other way, this is double-ownership). We
// want to move away from this:
//
// sp<Foo> foo = new Foo(...); // DO NOT DO THIS
//
// Instead, prefer to do this:
//
// sp<Foo> foo = sp<Foo>::make(...); // DO THIS
//
// Sometimes, in order to use this, when a constructor is marked as private,
// you may need to add this to your class:
//
// friend class sp<Foo>;
template <typename... Args>
static inline sp<T> make(Args&&... args);
// if nullptr, returns nullptr
//
// if a strong pointer is already available, this will retrieve it,
// otherwise, this will abort
static inline sp<T> fromExisting(T* other);
// for more information about this macro and correct RefBase usage, see
// the comment at the top of utils/RefBase.h
#if defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
sp(std::nullptr_t) : sp() {}
#else
sp(T* other); // NOLINT(implicit)
template <typename U>
sp(U* other); // NOLINT(implicit)
sp& operator=(T* other);
template <typename U>
sp& operator=(U* other);
#endif
sp(const sp<T>& other);
sp(sp<T>&& other) noexcept;
template<typename U> sp(const sp<U>& other); // NOLINT(implicit)
template<typename U> sp(sp<U>&& other); // NOLINT(implicit)
// Cast a strong pointer directly from one type to another. Constructors
// allow changing types, but only if they are pointer-compatible. This does
// a static_cast internally.
template <typename U>
static inline sp<T> cast(const sp<U>& other);
~sp();
// Assignment
sp& operator = (const sp<T>& other);
sp& operator=(sp<T>&& other) noexcept;
template<typename U> sp& operator = (const sp<U>& other);
template<typename U> sp& operator = (sp<U>&& other);
//! Special optimization for use by ProcessState (and nobody else).
void force_set(T* other);
// Reset
void clear();
// Releases the ownership of the object managed by this instance of sp, if any.
// The caller is now responsible for managing it. That is, the caller must ensure
// decStrong() is called when the pointer is no longer used.
[[nodiscard]] inline T* release() noexcept {
auto ret = m_ptr;
m_ptr = nullptr;
return ret;
}
// Accessors
inline T& operator* () const { return *m_ptr; }
inline T* operator-> () const { return m_ptr; }
inline T* get() const { return m_ptr; }
inline explicit operator bool () const { return m_ptr != nullptr; }
// Punt these to the wp<> implementation.
template<typename U>
inline bool operator == (const wp<U>& o) const {
return o == *this;
}
template<typename U>
inline bool operator != (const wp<U>& o) const {
return o != *this;
}
private:
template<typename Y> friend class sp;
template<typename Y> friend class wp;
void set_pointer(T* ptr);
T* m_ptr;
};
#define COMPARE_STRONG(_op_) \
template <typename T, typename U> \
static inline bool operator _op_(const sp<T>& t, const sp<U>& u) { \
return t.get() _op_ u.get(); \
} \
template <typename T, typename U> \
static inline bool operator _op_(const T* t, const sp<U>& u) { \
return t _op_ u.get(); \
} \
template <typename T, typename U> \
static inline bool operator _op_(const sp<T>& t, const U* u) { \
return t.get() _op_ u; \
} \
template <typename T> \
static inline bool operator _op_(const sp<T>& t, std::nullptr_t) { \
return t.get() _op_ nullptr; \
} \
template <typename T> \
static inline bool operator _op_(std::nullptr_t, const sp<T>& t) { \
return nullptr _op_ t.get(); \
}
template <template <typename C> class comparator, typename T, typename U>
static inline bool _sp_compare_(T* a, U* b) {
return comparator<typename std::common_type<T*, U*>::type>()(a, b);
}
#define COMPARE_STRONG_FUNCTIONAL(_op_, _compare_) \
template <typename T, typename U> \
static inline bool operator _op_(const sp<T>& t, const sp<U>& u) { \
return _sp_compare_<_compare_>(t.get(), u.get()); \
} \
template <typename T, typename U> \
static inline bool operator _op_(const T* t, const sp<U>& u) { \
return _sp_compare_<_compare_>(t, u.get()); \
} \
template <typename T, typename U> \
static inline bool operator _op_(const sp<T>& t, const U* u) { \
return _sp_compare_<_compare_>(t.get(), u); \
} \
template <typename T> \
static inline bool operator _op_(const sp<T>& t, std::nullptr_t) { \
return _sp_compare_<_compare_>(t.get(), nullptr); \
} \
template <typename T> \
static inline bool operator _op_(std::nullptr_t, const sp<T>& t) { \
return _sp_compare_<_compare_>(nullptr, t.get()); \
}
COMPARE_STRONG(==)
COMPARE_STRONG(!=)
COMPARE_STRONG_FUNCTIONAL(>, std::greater)
COMPARE_STRONG_FUNCTIONAL(<, std::less)
COMPARE_STRONG_FUNCTIONAL(<=, std::less_equal)
COMPARE_STRONG_FUNCTIONAL(>=, std::greater_equal)
#undef COMPARE_STRONG
#undef COMPARE_STRONG_FUNCTIONAL
// For code size reasons, we do not want these inlined or templated.
void sp_report_race();
// ---------------------------------------------------------------------------
// No user serviceable parts below here.
// TODO: Ideally we should find a way to increment the reference count before running the
// constructor, so that generating an sp<> to this in the constructor is no longer dangerous.
template <typename T>
template <typename... Args>
sp<T> sp<T>::make(Args&&... args) {
T* t = new T(std::forward<Args>(args)...);
sp<T> result;
result.m_ptr = t;
t->incStrong(t);
return result;
}
template <typename T>
sp<T> sp<T>::fromExisting(T* other) {
if (other) {
other->incStrongRequireStrong(other);
sp<T> result;
result.m_ptr = other;
return result;
}
return nullptr;
}
#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
template<typename T>
sp<T>::sp(T* other)
: m_ptr(other) {
if (other) {
other->incStrong(this);
}
}
template <typename T>
template <typename U>
sp<T>::sp(U* other) : m_ptr(other) {
if (other) {
(static_cast<T*>(other))->incStrong(this);
}
}
template <typename T>
sp<T>& sp<T>::operator=(T* other) {
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
if (other) {
other->incStrong(this);
}
if (oldPtr) oldPtr->decStrong(this);
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
m_ptr = other;
return *this;
}
#endif
template<typename T>
sp<T>::sp(const sp<T>& other)
: m_ptr(other.m_ptr) {
if (m_ptr)
m_ptr->incStrong(this);
}
template <typename T>
sp<T>::sp(sp<T>&& other) noexcept : m_ptr(other.m_ptr) {
other.m_ptr = nullptr;
}
template<typename T> template<typename U>
sp<T>::sp(const sp<U>& other)
: m_ptr(other.m_ptr) {
if (m_ptr)
m_ptr->incStrong(this);
}
template<typename T> template<typename U>
sp<T>::sp(sp<U>&& other)
: m_ptr(other.m_ptr) {
other.m_ptr = nullptr;
}
template <typename T>
template <typename U>
sp<T> sp<T>::cast(const sp<U>& other) {
return sp<T>::fromExisting(static_cast<T*>(other.get()));
}
template<typename T>
sp<T>::~sp() {
if (m_ptr)
m_ptr->decStrong(this);
}
template<typename T>
sp<T>& sp<T>::operator =(const sp<T>& other) {
// Force m_ptr to be read twice, to heuristically check for data races.
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
T* otherPtr(other.m_ptr);
if (otherPtr) otherPtr->incStrong(this);
if (oldPtr) oldPtr->decStrong(this);
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
m_ptr = otherPtr;
return *this;
}
template <typename T>
sp<T>& sp<T>::operator=(sp<T>&& other) noexcept {
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
if (oldPtr) oldPtr->decStrong(this);
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
m_ptr = other.m_ptr;
other.m_ptr = nullptr;
return *this;
}
template<typename T> template<typename U>
sp<T>& sp<T>::operator =(const sp<U>& other) {
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
T* otherPtr(other.m_ptr);
if (otherPtr) otherPtr->incStrong(this);
if (oldPtr) oldPtr->decStrong(this);
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
m_ptr = otherPtr;
return *this;
}
template<typename T> template<typename U>
sp<T>& sp<T>::operator =(sp<U>&& other) {
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
if (m_ptr) m_ptr->decStrong(this);
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
m_ptr = other.m_ptr;
other.m_ptr = nullptr;
return *this;
}
#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
template<typename T> template<typename U>
sp<T>& sp<T>::operator =(U* other) {
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
if (other) (static_cast<T*>(other))->incStrong(this);
if (oldPtr) oldPtr->decStrong(this);
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
m_ptr = other;
return *this;
}
#endif
template<typename T>
void sp<T>::force_set(T* other) {
other->forceIncStrong(this);
m_ptr = other;
}
template<typename T>
void sp<T>::clear() {
T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
if (oldPtr) {
oldPtr->decStrong(this);
if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
m_ptr = nullptr;
}
}
template<typename T>
void sp<T>::set_pointer(T* ptr) {
m_ptr = ptr;
}
} // namespace android
// ---------------------------------------------------------------------------
#endif // ANDROID_STRONG_POINTER_H
@@ -0,0 +1,341 @@
/*
* Copyright (C) 2005 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_TYPE_HELPERS_H
#define ANDROID_TYPE_HELPERS_H
#include <new>
#include <type_traits>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
// ---------------------------------------------------------------------------
namespace android {
/*
* Types traits
*/
template <typename T> struct trait_trivial_ctor { enum { value = false }; };
template <typename T> struct trait_trivial_dtor { enum { value = false }; };
template <typename T> struct trait_trivial_copy { enum { value = false }; };
template <typename T> struct trait_trivial_move { enum { value = false }; };
template <typename T> struct trait_pointer { enum { value = false }; };
template <typename T> struct trait_pointer<T*> { enum { value = true }; };
template <typename TYPE>
struct traits {
enum {
// whether this type is a pointer
is_pointer = trait_pointer<TYPE>::value,
// whether this type's constructor is a no-op
has_trivial_ctor = is_pointer || trait_trivial_ctor<TYPE>::value,
// whether this type's destructor is a no-op
has_trivial_dtor = is_pointer || trait_trivial_dtor<TYPE>::value,
// whether this type type can be copy-constructed with memcpy
has_trivial_copy = is_pointer || trait_trivial_copy<TYPE>::value,
// whether this type can be moved with memmove
has_trivial_move = is_pointer || trait_trivial_move<TYPE>::value
};
};
template <typename T, typename U>
struct aggregate_traits {
enum {
is_pointer = false,
has_trivial_ctor =
traits<T>::has_trivial_ctor && traits<U>::has_trivial_ctor,
has_trivial_dtor =
traits<T>::has_trivial_dtor && traits<U>::has_trivial_dtor,
has_trivial_copy =
traits<T>::has_trivial_copy && traits<U>::has_trivial_copy,
has_trivial_move =
traits<T>::has_trivial_move && traits<U>::has_trivial_move
};
};
#define ANDROID_TRIVIAL_CTOR_TRAIT( T ) \
template<> struct trait_trivial_ctor< T > { enum { value = true }; };
#define ANDROID_TRIVIAL_DTOR_TRAIT( T ) \
template<> struct trait_trivial_dtor< T > { enum { value = true }; };
#define ANDROID_TRIVIAL_COPY_TRAIT( T ) \
template<> struct trait_trivial_copy< T > { enum { value = true }; };
#define ANDROID_TRIVIAL_MOVE_TRAIT( T ) \
template<> struct trait_trivial_move< T > { enum { value = true }; };
#define ANDROID_BASIC_TYPES_TRAITS( T ) \
ANDROID_TRIVIAL_CTOR_TRAIT( T ) \
ANDROID_TRIVIAL_DTOR_TRAIT( T ) \
ANDROID_TRIVIAL_COPY_TRAIT( T ) \
ANDROID_TRIVIAL_MOVE_TRAIT( T )
// ---------------------------------------------------------------------------
/*
* basic types traits
*/
ANDROID_BASIC_TYPES_TRAITS( void )
ANDROID_BASIC_TYPES_TRAITS( bool )
ANDROID_BASIC_TYPES_TRAITS( char )
ANDROID_BASIC_TYPES_TRAITS( unsigned char )
ANDROID_BASIC_TYPES_TRAITS( short )
ANDROID_BASIC_TYPES_TRAITS( unsigned short )
ANDROID_BASIC_TYPES_TRAITS( int )
ANDROID_BASIC_TYPES_TRAITS( unsigned int )
ANDROID_BASIC_TYPES_TRAITS( long )
ANDROID_BASIC_TYPES_TRAITS( unsigned long )
ANDROID_BASIC_TYPES_TRAITS( long long )
ANDROID_BASIC_TYPES_TRAITS( unsigned long long )
ANDROID_BASIC_TYPES_TRAITS( float )
ANDROID_BASIC_TYPES_TRAITS( double )
template<typename T> struct trait_trivial_ctor<T*> { enum { value = true }; };
template<typename T> struct trait_trivial_dtor<T*> { enum { value = true }; };
template<typename T> struct trait_trivial_copy<T*> { enum { value = true }; };
template<typename T> struct trait_trivial_move<T*> { enum { value = true }; };
// ---------------------------------------------------------------------------
/*
* compare and order types
*/
template<typename TYPE> inline
int strictly_order_type(const TYPE& lhs, const TYPE& rhs) {
return (lhs < rhs) ? 1 : 0;
}
template<typename TYPE> inline
int compare_type(const TYPE& lhs, const TYPE& rhs) {
return strictly_order_type(rhs, lhs) - strictly_order_type(lhs, rhs);
}
/*
* create, destroy, copy and move types...
*/
template<typename TYPE> inline
void construct_type(TYPE* p, size_t n) {
if (!traits<TYPE>::has_trivial_ctor) {
while (n > 0) {
n--;
new(p++) TYPE;
}
}
}
template<typename TYPE> inline
void destroy_type(TYPE* p, size_t n) {
if (!traits<TYPE>::has_trivial_dtor) {
while (n > 0) {
n--;
p->~TYPE();
p++;
}
}
}
template<typename TYPE>
typename std::enable_if<traits<TYPE>::has_trivial_copy>::type
inline
copy_type(TYPE* d, const TYPE* s, size_t n) {
memcpy(d,s,n*sizeof(TYPE));
}
template<typename TYPE>
typename std::enable_if<!traits<TYPE>::has_trivial_copy>::type
inline
copy_type(TYPE* d, const TYPE* s, size_t n) {
while (n > 0) {
n--;
new(d) TYPE(*s);
d++, s++;
}
}
template<typename TYPE> inline
void splat_type(TYPE* where, const TYPE* what, size_t n) {
if (!traits<TYPE>::has_trivial_copy) {
while (n > 0) {
n--;
new(where) TYPE(*what);
where++;
}
} else {
while (n > 0) {
n--;
*where++ = *what;
}
}
}
template<typename TYPE>
struct use_trivial_move : public std::integral_constant<bool,
(traits<TYPE>::has_trivial_dtor && traits<TYPE>::has_trivial_copy)
|| traits<TYPE>::has_trivial_move
> {};
template<typename TYPE>
typename std::enable_if<use_trivial_move<TYPE>::value>::type
inline
move_forward_type(TYPE* d, const TYPE* s, size_t n = 1) {
memmove(reinterpret_cast<void*>(d), s, n * sizeof(TYPE));
}
template<typename TYPE>
typename std::enable_if<!use_trivial_move<TYPE>::value>::type
inline
move_forward_type(TYPE* d, const TYPE* s, size_t n = 1) {
d += n;
s += n;
while (n > 0) {
n--;
--d, --s;
if (!traits<TYPE>::has_trivial_copy) {
new(d) TYPE(*s);
} else {
*d = *s;
}
if (!traits<TYPE>::has_trivial_dtor) {
s->~TYPE();
}
}
}
template<typename TYPE>
typename std::enable_if<use_trivial_move<TYPE>::value>::type
inline
move_backward_type(TYPE* d, const TYPE* s, size_t n = 1) {
memmove(reinterpret_cast<void*>(d), s, n * sizeof(TYPE));
}
template<typename TYPE>
typename std::enable_if<!use_trivial_move<TYPE>::value>::type
inline
move_backward_type(TYPE* d, const TYPE* s, size_t n = 1) {
while (n > 0) {
n--;
if (!traits<TYPE>::has_trivial_copy) {
new(d) TYPE(*s);
} else {
*d = *s;
}
if (!traits<TYPE>::has_trivial_dtor) {
s->~TYPE();
}
d++, s++;
}
}
// ---------------------------------------------------------------------------
/*
* a key/value pair
*/
template <typename KEY, typename VALUE>
struct key_value_pair_t {
typedef KEY key_t;
typedef VALUE value_t;
KEY key;
VALUE value;
key_value_pair_t() { }
key_value_pair_t(const key_value_pair_t& o) : key(o.key), value(o.value) { }
key_value_pair_t& operator=(const key_value_pair_t& o) {
key = o.key;
value = o.value;
return *this;
}
key_value_pair_t(const KEY& k, const VALUE& v) : key(k), value(v) { }
explicit key_value_pair_t(const KEY& k) : key(k) { }
inline bool operator < (const key_value_pair_t& o) const {
return strictly_order_type(key, o.key);
}
inline const KEY& getKey() const {
return key;
}
inline const VALUE& getValue() const {
return value;
}
};
template <typename K, typename V>
struct trait_trivial_ctor< key_value_pair_t<K, V> >
{ enum { value = aggregate_traits<K,V>::has_trivial_ctor }; };
template <typename K, typename V>
struct trait_trivial_dtor< key_value_pair_t<K, V> >
{ enum { value = aggregate_traits<K,V>::has_trivial_dtor }; };
template <typename K, typename V>
struct trait_trivial_copy< key_value_pair_t<K, V> >
{ enum { value = aggregate_traits<K,V>::has_trivial_copy }; };
template <typename K, typename V>
struct trait_trivial_move< key_value_pair_t<K, V> >
{ enum { value = aggregate_traits<K,V>::has_trivial_move }; };
// ---------------------------------------------------------------------------
/*
* Hash codes.
*/
typedef uint32_t hash_t;
template <typename TKey>
hash_t hash_type(const TKey& key);
/* Built-in hash code specializations */
#define ANDROID_INT32_HASH(T) \
template <> inline hash_t hash_type(const T& value) { return hash_t(value); }
#define ANDROID_INT64_HASH(T) \
template <> inline hash_t hash_type(const T& value) { \
return hash_t((value >> 32) ^ value); }
#define ANDROID_REINTERPRET_HASH(T, R) \
template <> inline hash_t hash_type(const T& value) { \
R newValue; \
static_assert(sizeof(newValue) == sizeof(value), "size mismatch"); \
memcpy(&newValue, &value, sizeof(newValue)); \
return hash_type(newValue); \
}
ANDROID_INT32_HASH(bool)
ANDROID_INT32_HASH(int8_t)
ANDROID_INT32_HASH(uint8_t)
ANDROID_INT32_HASH(int16_t)
ANDROID_INT32_HASH(uint16_t)
ANDROID_INT32_HASH(int32_t)
ANDROID_INT32_HASH(uint32_t)
ANDROID_INT64_HASH(int64_t)
ANDROID_INT64_HASH(uint64_t)
ANDROID_REINTERPRET_HASH(float, uint32_t)
ANDROID_REINTERPRET_HASH(double, uint64_t)
template <typename T> inline hash_t hash_type(T* const & value) {
return hash_type(uintptr_t(value));
}
} // namespace android
// ---------------------------------------------------------------------------
#endif // ANDROID_TYPE_HELPERS_H
+139
View File
@@ -0,0 +1,139 @@
/*
* Copyright (C) 2005 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_UNICODE_H
#define ANDROID_UNICODE_H
#include <sys/types.h>
#include <stdint.h>
extern "C" {
// Standard string functions on char16_t strings.
int strcmp16(const char16_t *, const char16_t *);
int strncmp16(const char16_t *s1, const char16_t *s2, size_t n);
size_t strlen16(const char16_t *);
size_t strnlen16(const char16_t *, size_t);
char16_t *strstr16(const char16_t*, const char16_t*);
// Version of comparison that supports embedded NULs.
// This is different than strncmp() because we don't stop
// at a nul character and consider the strings to be different
// if the lengths are different (thus we need to supply the
// lengths of both strings). This can also be used when
// your string is not nul-terminated as it will have the
// equivalent result as strcmp16 (unlike strncmp16).
int strzcmp16(const char16_t *s1, size_t n1, const char16_t *s2, size_t n2);
/**
* Measure the length of a UTF-32 string in UTF-8. If the string is invalid
* such as containing a surrogate character, -1 will be returned.
*/
ssize_t utf32_to_utf8_length(const char32_t *src, size_t src_len);
/**
* Stores a UTF-8 string converted from "src" in "dst", if "dst_length" is not
* large enough to store the string, the part of the "src" string is stored
* into "dst" as much as possible. See the examples for more detail.
* Returns the size actually used for storing the string.
* dst" is not nul-terminated when dst_len is fully used (like strncpy).
*
* \code
* Example 1
* "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84)
* "src_len" == 2
* "dst_len" >= 7
* ->
* Returned value == 6
* "dst" becomes \xE3\x81\x82\xE3\x81\x84\0
* (note that "dst" is nul-terminated)
*
* Example 2
* "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84)
* "src_len" == 2
* "dst_len" == 5
* ->
* Returned value == 3
* "dst" becomes \xE3\x81\x82\0
* (note that "dst" is nul-terminated, but \u3044 is not stored in "dst"
* since "dst" does not have enough size to store the character)
*
* Example 3
* "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84)
* "src_len" == 2
* "dst_len" == 6
* ->
* Returned value == 6
* "dst" becomes \xE3\x81\x82\xE3\x81\x84
* (note that "dst" is NOT nul-terminated, like strncpy)
* \endcode
*/
void utf32_to_utf8(const char32_t* src, size_t src_len, char* dst, size_t dst_len);
/**
* Returns the unicode value at "index".
* Returns -1 when the index is invalid (equals to or more than "src_len").
* If returned value is positive, it is able to be converted to char32_t, which
* is unsigned. Then, if "next_index" is not NULL, the next index to be used is
* stored in "next_index". "next_index" can be NULL.
*/
int32_t utf32_from_utf8_at(const char *src, size_t src_len, size_t index, size_t *next_index);
/**
* Returns the UTF-8 length of UTF-16 string "src".
*/
ssize_t utf16_to_utf8_length(const char16_t *src, size_t src_len);
/**
* Converts a UTF-16 string to UTF-8. The destination buffer must be large
* enough to fit the UTF-16 as measured by utf16_to_utf8_length with an added
* NUL terminator.
*/
void utf16_to_utf8(const char16_t* src, size_t src_len, char* dst, size_t dst_len);
/**
* Returns the UTF-16 length of UTF-8 string "src". Returns -1 in case
* it's invalid utf8. No buffer over-read occurs because of bound checks. Using overreadIsFatal you
* can ask to log a message and fail in case the invalid utf8 could have caused an override if no
* bound checks were used (otherwise -1 is returned).
*/
ssize_t utf8_to_utf16_length(const uint8_t* src, size_t srcLen, bool overreadIsFatal = false);
/**
* Convert UTF-8 to UTF-16 including surrogate pairs.
* Returns a pointer to the end of the string (where a NUL terminator might go
* if you wanted to add one). At most dstLen characters are written; it won't emit half a surrogate
* pair. If dstLen == 0 nothing is written and dst is returned. If dstLen > SSIZE_MAX it aborts
* (this being probably a negative number returned as an error and casted to unsigned).
*/
char16_t* utf8_to_utf16_no_null_terminator(
const uint8_t* src, size_t srcLen, char16_t* dst, size_t dstLen);
/**
* Convert UTF-8 to UTF-16 including surrogate pairs. At most dstLen - 1
* characters are written; it won't emit half a surrogate pair; and a NUL terminator is appended
* after. dstLen - 1 can be measured beforehand using utf8_to_utf16_length. Aborts if dstLen == 0
* (at least one character is needed for the NUL terminator) or dstLen > SSIZE_MAX (the latter
* case being likely a negative number returned as an error and casted to unsigned) . Returns a
* pointer to the NUL terminator.
*/
char16_t *utf8_to_utf16(
const uint8_t* src, size_t srcLen, char16_t* dst, size_t dstLen);
}
#endif
+418
View File
@@ -0,0 +1,418 @@
/*
* Copyright (C) 2005 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_VECTOR_H
#define ANDROID_VECTOR_H
#include <stdint.h>
#include <sys/types.h>
// #include <log/log.h>
#include <utils/TypeHelpers.h>
#include <utils/VectorImpl.h>
#ifndef __has_attribute
#define __has_attribute(x) 0
#endif
/*
* Used to exclude some functions from CFI.
*/
#if __has_attribute(no_sanitize)
#define UTILS_VECTOR_NO_CFI __attribute__((no_sanitize("cfi")))
#else
#define UTILS_VECTOR_NO_CFI
#endif
// ---------------------------------------------------------------------------
namespace android {
template <typename TYPE>
class SortedVector;
/*!
* The main templated vector class ensuring type safety
* while making use of VectorImpl.
* This is the class users want to use.
*
* DO NOT USE: please use std::vector
*/
template <class TYPE>
class Vector : private VectorImpl
{
public:
typedef TYPE value_type;
/*!
* Constructors and destructors
*/
Vector();
Vector(const Vector<TYPE>& rhs);
explicit Vector(const SortedVector<TYPE>& rhs);
virtual ~Vector();
/*! copy operator */
Vector<TYPE>& operator=(const Vector<TYPE>& rhs); // NOLINT(cert-oop54-cpp)
Vector<TYPE>& operator=(const SortedVector<TYPE>& rhs); // NOLINT(cert-oop54-cpp)
/*
* empty the vector
*/
inline void clear() { VectorImpl::clear(); }
/*!
* vector stats
*/
//! returns number of items in the vector
inline size_t size() const { return VectorImpl::size(); }
//! returns whether or not the vector is empty
inline bool isEmpty() const { return VectorImpl::isEmpty(); }
//! returns how many items can be stored without reallocating the backing store
inline size_t capacity() const { return VectorImpl::capacity(); }
//! sets the capacity. capacity can never be reduced less than size()
inline ssize_t setCapacity(size_t size) { return VectorImpl::setCapacity(size); }
/*!
* set the size of the vector. items are appended with the default
* constructor, or removed from the end as needed.
*/
inline ssize_t resize(size_t size) { return VectorImpl::resize(size); }
/*!
* C-style array access
*/
//! read-only C-style access
inline const TYPE* array() const;
//! read-write C-style access
TYPE* editArray();
/*!
* accessors
*/
//! read-only access to an item at a given index
inline const TYPE& operator [] (size_t index) const;
//! alternate name for operator []
inline const TYPE& itemAt(size_t index) const;
//! stack-usage of the vector. returns the top of the stack (last element)
const TYPE& top() const;
/*!
* modifying the array
*/
//! copy-on write support, grants write access to an item
TYPE& editItemAt(size_t index);
//! grants right access to the top of the stack (last element)
TYPE& editTop();
/*!
* append/insert another vector
*/
//! insert another vector at a given index
ssize_t insertVectorAt(const Vector<TYPE>& vector, size_t index);
//! append another vector at the end of this one
ssize_t appendVector(const Vector<TYPE>& vector);
//! insert an array at a given index
ssize_t insertArrayAt(const TYPE* array, size_t index, size_t length);
//! append an array at the end of this vector
ssize_t appendArray(const TYPE* array, size_t length);
/*!
* add/insert/replace items
*/
//! insert one or several items initialized with their default constructor
inline ssize_t insertAt(size_t index, size_t numItems = 1);
//! insert one or several items initialized from a prototype item
ssize_t insertAt(const TYPE& prototype_item, size_t index, size_t numItems = 1);
//! pop the top of the stack (removes the last element). No-op if the stack's empty
inline void pop();
//! pushes an item initialized with its default constructor
inline void push();
//! pushes an item on the top of the stack
void push(const TYPE& item);
//! same as push() but returns the index the item was added at (or an error)
inline ssize_t add();
//! same as push() but returns the index the item was added at (or an error)
ssize_t add(const TYPE& item);
//! replace an item with a new one initialized with its default constructor
inline ssize_t replaceAt(size_t index);
//! replace an item with a new one
ssize_t replaceAt(const TYPE& item, size_t index);
/*!
* remove items
*/
//! remove several items
inline ssize_t removeItemsAt(size_t index, size_t count = 1);
//! remove one item
inline ssize_t removeAt(size_t index) { return removeItemsAt(index); }
/*!
* sort (stable) the array
*/
typedef int (*compar_t)(const TYPE* lhs, const TYPE* rhs);
typedef int (*compar_r_t)(const TYPE* lhs, const TYPE* rhs, void* state);
inline status_t sort(compar_t cmp);
inline status_t sort(compar_r_t cmp, void* state);
// for debugging only
inline size_t getItemSize() const { return itemSize(); }
/*
* these inlines add some level of compatibility with STL. eventually
* we should probably turn things around.
*/
typedef TYPE* iterator;
typedef TYPE const* const_iterator;
inline iterator begin() { return editArray(); }
inline iterator end() { return editArray() + size(); }
inline const_iterator begin() const { return array(); }
inline const_iterator end() const { return array() + size(); }
inline void reserve(size_t n) { setCapacity(n); }
inline bool empty() const{ return isEmpty(); }
inline void push_back(const TYPE& item) { insertAt(item, size(), 1); }
inline void push_front(const TYPE& item) { insertAt(item, 0, 1); }
inline iterator erase(iterator pos) {
ssize_t index = removeItemsAt(static_cast<size_t>(pos-array()));
return begin() + index;
}
protected:
virtual void do_construct(void* storage, size_t num) const;
virtual void do_destroy(void* storage, size_t num) const;
virtual void do_copy(void* dest, const void* from, size_t num) const;
virtual void do_splat(void* dest, const void* item, size_t num) const;
virtual void do_move_forward(void* dest, const void* from, size_t num) const;
virtual void do_move_backward(void* dest, const void* from, size_t num) const;
};
// ---------------------------------------------------------------------------
// No user serviceable parts from here...
// ---------------------------------------------------------------------------
template<class TYPE> inline
Vector<TYPE>::Vector()
: VectorImpl(sizeof(TYPE),
((traits<TYPE>::has_trivial_ctor ? HAS_TRIVIAL_CTOR : 0)
|(traits<TYPE>::has_trivial_dtor ? HAS_TRIVIAL_DTOR : 0)
|(traits<TYPE>::has_trivial_copy ? HAS_TRIVIAL_COPY : 0))
)
{
}
template<class TYPE> inline
Vector<TYPE>::Vector(const Vector<TYPE>& rhs)
: VectorImpl(rhs) {
}
template<class TYPE> inline
Vector<TYPE>::Vector(const SortedVector<TYPE>& rhs)
: VectorImpl(static_cast<const VectorImpl&>(rhs)) {
}
template<class TYPE> inline
Vector<TYPE>::~Vector() {
finish_vector();
}
template <class TYPE>
inline Vector<TYPE>& Vector<TYPE>::operator=(const Vector<TYPE>& rhs) // NOLINT(cert-oop54-cpp)
{
VectorImpl::operator=(rhs);
return *this;
}
template <class TYPE>
inline Vector<TYPE>& Vector<TYPE>::operator=(
const SortedVector<TYPE>& rhs) // NOLINT(cert-oop54-cpp)
{
VectorImpl::operator=(static_cast<const VectorImpl&>(rhs));
return *this;
}
template<class TYPE> inline
const TYPE* Vector<TYPE>::array() const {
return static_cast<const TYPE *>(arrayImpl());
}
template<class TYPE> inline
TYPE* Vector<TYPE>::editArray() {
return static_cast<TYPE *>(editArrayImpl());
}
template<class TYPE> inline
const TYPE& Vector<TYPE>::operator[](size_t index) const {
LOG_FATAL_IF(index>=size(),
"%s: index=%u out of range (%u)", __PRETTY_FUNCTION__,
int(index), int(size()));
return *(array() + index);
}
template<class TYPE> inline
const TYPE& Vector<TYPE>::itemAt(size_t index) const {
return operator[](index);
}
template<class TYPE> inline
const TYPE& Vector<TYPE>::top() const {
return *(array() + size() - 1);
}
template<class TYPE> inline
TYPE& Vector<TYPE>::editItemAt(size_t index) {
return *( static_cast<TYPE *>(editItemLocation(index)) );
}
template<class TYPE> inline
TYPE& Vector<TYPE>::editTop() {
return *( static_cast<TYPE *>(editItemLocation(size()-1)) );
}
template<class TYPE> inline
ssize_t Vector<TYPE>::insertVectorAt(const Vector<TYPE>& vector, size_t index) {
return VectorImpl::insertVectorAt(reinterpret_cast<const VectorImpl&>(vector), index);
}
template<class TYPE> inline
ssize_t Vector<TYPE>::appendVector(const Vector<TYPE>& vector) {
return VectorImpl::appendVector(reinterpret_cast<const VectorImpl&>(vector));
}
template<class TYPE> inline
ssize_t Vector<TYPE>::insertArrayAt(const TYPE* array, size_t index, size_t length) {
return VectorImpl::insertArrayAt(array, index, length);
}
template<class TYPE> inline
ssize_t Vector<TYPE>::appendArray(const TYPE* array, size_t length) {
return VectorImpl::appendArray(array, length);
}
template<class TYPE> inline
ssize_t Vector<TYPE>::insertAt(const TYPE& item, size_t index, size_t numItems) {
return VectorImpl::insertAt(&item, index, numItems);
}
template<class TYPE> inline
void Vector<TYPE>::push(const TYPE& item) {
return VectorImpl::push(&item);
}
template<class TYPE> inline
ssize_t Vector<TYPE>::add(const TYPE& item) {
return VectorImpl::add(&item);
}
template<class TYPE> inline
ssize_t Vector<TYPE>::replaceAt(const TYPE& item, size_t index) {
return VectorImpl::replaceAt(&item, index);
}
template<class TYPE> inline
ssize_t Vector<TYPE>::insertAt(size_t index, size_t numItems) {
return VectorImpl::insertAt(index, numItems);
}
template<class TYPE> inline
void Vector<TYPE>::pop() {
VectorImpl::pop();
}
template<class TYPE> inline
void Vector<TYPE>::push() {
VectorImpl::push();
}
template<class TYPE> inline
ssize_t Vector<TYPE>::add() {
return VectorImpl::add();
}
template<class TYPE> inline
ssize_t Vector<TYPE>::replaceAt(size_t index) {
return VectorImpl::replaceAt(index);
}
template<class TYPE> inline
ssize_t Vector<TYPE>::removeItemsAt(size_t index, size_t count) {
return VectorImpl::removeItemsAt(index, count);
}
template<class TYPE> inline
status_t Vector<TYPE>::sort(Vector<TYPE>::compar_t cmp) {
return VectorImpl::sort(reinterpret_cast<VectorImpl::compar_t>(cmp));
}
template<class TYPE> inline
status_t Vector<TYPE>::sort(Vector<TYPE>::compar_r_t cmp, void* state) {
return VectorImpl::sort(reinterpret_cast<VectorImpl::compar_r_t>(cmp), state);
}
// ---------------------------------------------------------------------------
template<class TYPE>
UTILS_VECTOR_NO_CFI void Vector<TYPE>::do_construct(void* storage, size_t num) const {
construct_type( reinterpret_cast<TYPE*>(storage), num );
}
template<class TYPE>
void Vector<TYPE>::do_destroy(void* storage, size_t num) const {
destroy_type( reinterpret_cast<TYPE*>(storage), num );
}
template<class TYPE>
UTILS_VECTOR_NO_CFI void Vector<TYPE>::do_copy(void* dest, const void* from, size_t num) const {
copy_type( reinterpret_cast<TYPE*>(dest), reinterpret_cast<const TYPE*>(from), num );
}
template<class TYPE>
UTILS_VECTOR_NO_CFI void Vector<TYPE>::do_splat(void* dest, const void* item, size_t num) const {
splat_type( reinterpret_cast<TYPE*>(dest), reinterpret_cast<const TYPE*>(item), num );
}
template<class TYPE>
UTILS_VECTOR_NO_CFI void Vector<TYPE>::do_move_forward(void* dest, const void* from, size_t num) const {
move_forward_type( reinterpret_cast<TYPE*>(dest), reinterpret_cast<const TYPE*>(from), num );
}
template<class TYPE>
UTILS_VECTOR_NO_CFI void Vector<TYPE>::do_move_backward(void* dest, const void* from, size_t num) const {
move_backward_type( reinterpret_cast<TYPE*>(dest), reinterpret_cast<const TYPE*>(from), num );
}
} // namespace android
// ---------------------------------------------------------------------------
#endif // ANDROID_VECTOR_H
@@ -0,0 +1,182 @@
/*
* Copyright (C) 2005 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_VECTOR_IMPL_H
#define ANDROID_VECTOR_IMPL_H
#include <assert.h>
#include <stdint.h>
#include <sys/types.h>
#include <utils/Errors.h>
// ---------------------------------------------------------------------------
// No user serviceable parts in here...
// ---------------------------------------------------------------------------
namespace android {
/*!
* Implementation of the guts of the vector<> class
* this ensures backward binary compatibility and
* reduces code size.
* For performance reasons, we expose mStorage and mCount
* so these fields are set in stone.
*
*/
class VectorImpl
{
public:
enum { // flags passed to the ctor
HAS_TRIVIAL_CTOR = 0x00000001,
HAS_TRIVIAL_DTOR = 0x00000002,
HAS_TRIVIAL_COPY = 0x00000004,
};
VectorImpl(size_t itemSize, uint32_t flags);
VectorImpl(const VectorImpl& rhs);
virtual ~VectorImpl();
/*! must be called from subclasses destructor */
void finish_vector();
VectorImpl& operator = (const VectorImpl& rhs);
/*! C-style array access */
inline const void* arrayImpl() const { return mStorage; }
void* editArrayImpl();
/*! vector stats */
inline size_t size() const { return mCount; }
inline bool isEmpty() const { return mCount == 0; }
size_t capacity() const;
ssize_t setCapacity(size_t size);
ssize_t resize(size_t size);
/*! append/insert another vector or array */
ssize_t insertVectorAt(const VectorImpl& vector, size_t index);
ssize_t appendVector(const VectorImpl& vector);
ssize_t insertArrayAt(const void* array, size_t index, size_t length);
ssize_t appendArray(const void* array, size_t length);
/*! add/insert/replace items */
ssize_t insertAt(size_t where, size_t numItems = 1);
ssize_t insertAt(const void* item, size_t where, size_t numItems = 1);
void pop();
void push();
void push(const void* item);
ssize_t add();
ssize_t add(const void* item);
ssize_t replaceAt(size_t index);
ssize_t replaceAt(const void* item, size_t index);
/*! remove items */
ssize_t removeItemsAt(size_t index, size_t count = 1);
void clear();
const void* itemLocation(size_t index) const;
void* editItemLocation(size_t index);
typedef int (*compar_t)(const void* lhs, const void* rhs);
typedef int (*compar_r_t)(const void* lhs, const void* rhs, void* state);
status_t sort(compar_t cmp);
status_t sort(compar_r_t cmp, void* state);
protected:
size_t itemSize() const;
void release_storage();
virtual void do_construct(void* storage, size_t num) const = 0;
virtual void do_destroy(void* storage, size_t num) const = 0;
virtual void do_copy(void* dest, const void* from, size_t num) const = 0;
virtual void do_splat(void* dest, const void* item, size_t num) const = 0;
virtual void do_move_forward(void* dest, const void* from, size_t num) const = 0;
virtual void do_move_backward(void* dest, const void* from, size_t num) const = 0;
private:
void* _grow(size_t where, size_t amount);
void _shrink(size_t where, size_t amount);
inline void _do_construct(void* storage, size_t num) const;
inline void _do_destroy(void* storage, size_t num) const;
inline void _do_copy(void* dest, const void* from, size_t num) const;
inline void _do_splat(void* dest, const void* item, size_t num) const;
inline void _do_move_forward(void* dest, const void* from, size_t num) const;
inline void _do_move_backward(void* dest, const void* from, size_t num) const;
// These 2 fields are exposed in the inlines below,
// so they're set in stone.
void * mStorage; // base address of the vector
size_t mCount; // number of items
const uint32_t mFlags;
const size_t mItemSize;
};
class SortedVectorImpl : public VectorImpl
{
public:
SortedVectorImpl(size_t itemSize, uint32_t flags);
explicit SortedVectorImpl(const VectorImpl& rhs);
virtual ~SortedVectorImpl();
SortedVectorImpl& operator = (const SortedVectorImpl& rhs);
//! finds the index of an item
ssize_t indexOf(const void* item) const;
//! finds where this item should be inserted
size_t orderOf(const void* item) const;
//! add an item in the right place (or replaces it if there is one)
ssize_t add(const void* item);
//! merges a vector into this one
ssize_t merge(const VectorImpl& vector);
ssize_t merge(const SortedVectorImpl& vector);
//! removes an item
ssize_t remove(const void* item);
protected:
virtual int do_compare(const void* lhs, const void* rhs) const = 0;
private:
ssize_t _indexOrderOf(const void* item, size_t* order = nullptr) const;
// these are made private, because they can't be used on a SortedVector
// (they don't have an implementation either)
ssize_t add();
void pop();
void push();
void push(const void* item);
ssize_t insertVectorAt(const VectorImpl& vector, size_t index);
ssize_t appendVector(const VectorImpl& vector);
ssize_t insertArrayAt(const void* array, size_t index, size_t length);
ssize_t appendArray(const void* array, size_t length);
ssize_t insertAt(size_t where, size_t numItems = 1);
ssize_t insertAt(const void* item, size_t where, size_t numItems = 1);
ssize_t replaceAt(size_t index);
ssize_t replaceAt(const void* item, size_t index);
};
} // namespace android
// ---------------------------------------------------------------------------
#endif // ANDROID_VECTOR_IMPL_H
@@ -0,0 +1,639 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
* Copyright (C) 2008 Google, Inc.
*
* Based on, but no longer compatible with, the original
* OpenBinder.org binder driver interface, which is:
*
* Copyright (c) 2005 Palmsource, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#ifndef _UAPI_LINUX_BINDER_H
#define _UAPI_LINUX_BINDER_H
#include <linux/types.h>
#include <linux/ioctl.h>
#define B_PACK_CHARS(c1, c2, c3, c4) \
((((c1)<<24)) | (((c2)<<16)) | (((c3)<<8)) | (c4))
#define B_TYPE_LARGE 0x85
enum {
BINDER_TYPE_BINDER = B_PACK_CHARS('s', 'b', '*', B_TYPE_LARGE),
BINDER_TYPE_WEAK_BINDER = B_PACK_CHARS('w', 'b', '*', B_TYPE_LARGE),
BINDER_TYPE_HANDLE = B_PACK_CHARS('s', 'h', '*', B_TYPE_LARGE),
BINDER_TYPE_WEAK_HANDLE = B_PACK_CHARS('w', 'h', '*', B_TYPE_LARGE),
BINDER_TYPE_FD = B_PACK_CHARS('f', 'd', '*', B_TYPE_LARGE),
BINDER_TYPE_FDA = B_PACK_CHARS('f', 'd', 'a', B_TYPE_LARGE),
BINDER_TYPE_PTR = B_PACK_CHARS('p', 't', '*', B_TYPE_LARGE),
};
/**
* enum flat_binder_object_shifts: shift values for flat_binder_object_flags
* @FLAT_BINDER_FLAG_SCHED_POLICY_SHIFT: shift for getting scheduler policy.
*
*/
enum flat_binder_object_shifts {
FLAT_BINDER_FLAG_SCHED_POLICY_SHIFT = 9,
};
/**
* enum flat_binder_object_flags - flags for use in flat_binder_object.flags
*/
enum flat_binder_object_flags {
/**
* @FLAT_BINDER_FLAG_PRIORITY_MASK: bit-mask for min scheduler priority
*
* These bits can be used to set the minimum scheduler priority
* at which transactions into this node should run. Valid values
* in these bits depend on the scheduler policy encoded in
* @FLAT_BINDER_FLAG_SCHED_POLICY_MASK.
*
* For SCHED_NORMAL/SCHED_BATCH, the valid range is between [-20..19]
* For SCHED_FIFO/SCHED_RR, the value can run between [1..99]
*/
FLAT_BINDER_FLAG_PRIORITY_MASK = 0xff,
/**
* @FLAT_BINDER_FLAG_ACCEPTS_FDS: whether the node accepts fds.
*/
FLAT_BINDER_FLAG_ACCEPTS_FDS = 0x100,
/**
* @FLAT_BINDER_FLAG_SCHED_POLICY_MASK: bit-mask for scheduling policy
*
* These two bits can be used to set the min scheduling policy at which
* transactions on this node should run. These match the UAPI
* scheduler policy values, eg:
* 00b: SCHED_NORMAL
* 01b: SCHED_FIFO
* 10b: SCHED_RR
* 11b: SCHED_BATCH
*/
FLAT_BINDER_FLAG_SCHED_POLICY_MASK =
3U << FLAT_BINDER_FLAG_SCHED_POLICY_SHIFT,
/**
* @FLAT_BINDER_FLAG_INHERIT_RT: whether the node inherits RT policy
*
* Only when set, calls into this node will inherit a real-time
* scheduling policy from the caller (for synchronous transactions).
*/
FLAT_BINDER_FLAG_INHERIT_RT = 0x800,
/**
* @FLAT_BINDER_FLAG_TXN_SECURITY_CTX: request security contexts
*
* Only when set, causes senders to include their security
* context
*/
FLAT_BINDER_FLAG_TXN_SECURITY_CTX = 0x1000,
};
#ifdef BINDER_IPC_32BIT
typedef __u32 binder_size_t;
typedef __u32 binder_uintptr_t;
#else
typedef __u64 binder_size_t;
typedef __u64 binder_uintptr_t;
#endif
/**
* struct binder_object_header - header shared by all binder metadata objects.
* @type: type of the object
*/
struct binder_object_header {
__u32 type;
};
/*
* This is the flattened representation of a Binder object for transfer
* between processes. The 'offsets' supplied as part of a binder transaction
* contains offsets into the data where these structures occur. The Binder
* driver takes care of re-writing the structure type and data as it moves
* between processes.
*/
struct flat_binder_object {
struct binder_object_header hdr;
__u32 flags;
/* 8 bytes of data. */
union {
binder_uintptr_t binder; /* local object */
__u32 handle; /* remote object */
};
/* extra data associated with local object */
binder_uintptr_t cookie;
};
/**
* struct binder_fd_object - describes a filedescriptor to be fixed up.
* @hdr: common header structure
* @pad_flags: padding to remain compatible with old userspace code
* @pad_binder: padding to remain compatible with old userspace code
* @fd: file descriptor
* @cookie: opaque data, used by user-space
*/
struct binder_fd_object {
struct binder_object_header hdr;
__u32 pad_flags;
union {
binder_uintptr_t pad_binder;
__u32 fd;
};
binder_uintptr_t cookie;
};
/* struct binder_buffer_object - object describing a userspace buffer
* @hdr: common header structure
* @flags: one or more BINDER_BUFFER_* flags
* @buffer: address of the buffer
* @length: length of the buffer
* @parent: index in offset array pointing to parent buffer
* @parent_offset: offset in @parent pointing to this buffer
*
* A binder_buffer object represents an object that the
* binder kernel driver can copy verbatim to the target
* address space. A buffer itself may be pointed to from
* within another buffer, meaning that the pointer inside
* that other buffer needs to be fixed up as well. This
* can be done by setting the BINDER_BUFFER_FLAG_HAS_PARENT
* flag in @flags, by setting @parent buffer to the index
* in the offset array pointing to the parent binder_buffer_object,
* and by setting @parent_offset to the offset in the parent buffer
* at which the pointer to this buffer is located.
*/
struct binder_buffer_object {
struct binder_object_header hdr;
__u32 flags;
binder_uintptr_t buffer;
binder_size_t length;
binder_size_t parent;
binder_size_t parent_offset;
};
enum {
BINDER_BUFFER_FLAG_HAS_PARENT = 0x01,
};
/* struct binder_fd_array_object - object describing an array of fds in a buffer
* @hdr: common header structure
* @pad: padding to ensure correct alignment
* @num_fds: number of file descriptors in the buffer
* @parent: index in offset array to buffer holding the fd array
* @parent_offset: start offset of fd array in the buffer
*
* A binder_fd_array object represents an array of file
* descriptors embedded in a binder_buffer_object. It is
* different from a regular binder_buffer_object because it
* describes a list of file descriptors to fix up, not an opaque
* blob of memory, and hence the kernel needs to treat it differently.
*
* An example of how this would be used is with Android's
* native_handle_t object, which is a struct with a list of integers
* and a list of file descriptors. The native_handle_t struct itself
* will be represented by a struct binder_buffer_objct, whereas the
* embedded list of file descriptors is represented by a
* struct binder_fd_array_object with that binder_buffer_object as
* a parent.
*/
struct binder_fd_array_object {
struct binder_object_header hdr;
__u32 pad;
binder_size_t num_fds;
binder_size_t parent;
binder_size_t parent_offset;
};
/*
* On 64-bit platforms where user code may run in 32-bits the driver must
* translate the buffer (and local binder) addresses appropriately.
*/
struct binder_write_read {
binder_size_t write_size; /* bytes to write */
binder_size_t write_consumed; /* bytes consumed by driver */
binder_uintptr_t write_buffer;
binder_size_t read_size; /* bytes to read */
binder_size_t read_consumed; /* bytes consumed by driver */
binder_uintptr_t read_buffer;
};
/* Use with BINDER_VERSION, driver fills in fields. */
struct binder_version {
/* driver protocol version -- increment with incompatible change */
__s32 protocol_version;
};
/* This is the current protocol version. */
#ifdef BINDER_IPC_32BIT
#define BINDER_CURRENT_PROTOCOL_VERSION 7
#else
#define BINDER_CURRENT_PROTOCOL_VERSION 8
#endif
/*
* Use with BINDER_GET_NODE_DEBUG_INFO, driver reads ptr, writes to all fields.
* Set ptr to NULL for the first call to get the info for the first node, and
* then repeat the call passing the previously returned value to get the next
* nodes. ptr will be 0 when there are no more nodes.
*/
struct binder_node_debug_info {
binder_uintptr_t ptr;
binder_uintptr_t cookie;
__u32 has_strong_ref;
__u32 has_weak_ref;
};
struct binder_node_info_for_ref {
__u32 handle;
__u32 strong_count;
__u32 weak_count;
__u32 reserved1;
__u32 reserved2;
__u32 reserved3;
};
struct binder_freeze_info {
__u32 pid;
__u32 enable;
__u32 timeout_ms;
};
struct binder_frozen_status_info {
__u32 pid;
/* process received sync transactions since last frozen
* bit 0: received sync transaction after being frozen
* bit 1: new pending sync transaction during freezing
*/
__u32 sync_recv;
/* process received async transactions since last frozen */
__u32 async_recv;
};
struct binder_frozen_state_info {
binder_uintptr_t cookie;
__u32 is_frozen;
__u32 reserved;
};
/* struct binder_extened_error - extended error information
* @id: identifier for the failed operation
* @command: command as defined by binder_driver_return_protocol
* @param: parameter holding a negative errno value
*
* Used with BINDER_GET_EXTENDED_ERROR. This extends the error information
* returned by the driver upon a failed operation. Userspace can pull this
* data to properly handle specific error scenarios.
*/
struct binder_extended_error {
__u32 id;
__u32 command;
__s32 param;
};
enum {
BINDER_WRITE_READ = _IOWR('b', 1, struct binder_write_read),
BINDER_SET_IDLE_TIMEOUT = _IOW('b', 3, __s64),
BINDER_SET_MAX_THREADS = _IOW('b', 5, __u32),
BINDER_SET_IDLE_PRIORITY = _IOW('b', 6, __s32),
BINDER_SET_CONTEXT_MGR = _IOW('b', 7, __s32),
BINDER_THREAD_EXIT = _IOW('b', 8, __s32),
BINDER_VERSION = _IOWR('b', 9, struct binder_version),
BINDER_GET_NODE_DEBUG_INFO = _IOWR('b', 11, struct binder_node_debug_info),
BINDER_GET_NODE_INFO_FOR_REF = _IOWR('b', 12, struct binder_node_info_for_ref),
BINDER_SET_CONTEXT_MGR_EXT = _IOW('b', 13, struct flat_binder_object),
BINDER_FREEZE = _IOW('b', 14, struct binder_freeze_info),
BINDER_GET_FROZEN_INFO = _IOWR('b', 15, struct binder_frozen_status_info),
BINDER_ENABLE_ONEWAY_SPAM_DETECTION = _IOW('b', 16, __u32),
BINDER_GET_EXTENDED_ERROR = _IOWR('b', 17, struct binder_extended_error),
};
/*
* NOTE: Two special error codes you should check for when calling
* in to the driver are:
*
* EINTR -- The operation has been interupted. This should be
* handled by retrying the ioctl() until a different error code
* is returned.
*
* ECONNREFUSED -- The driver is no longer accepting operations
* from your process. That is, the process is being destroyed.
* You should handle this by exiting from your process. Note
* that once this error code is returned, all further calls to
* the driver from any thread will return this same code.
*/
enum transaction_flags {
TF_ONE_WAY = 0x01, /* this is a one-way call: async, no return */
TF_ROOT_OBJECT = 0x04, /* contents are the component's root object */
TF_STATUS_CODE = 0x08, /* contents are a 32-bit status code */
TF_ACCEPT_FDS = 0x10, /* allow replies with file descriptors */
TF_CLEAR_BUF = 0x20, /* clear buffer on txn complete */
TF_UPDATE_TXN = 0x40, /* update the outdated pending async txn */
};
struct binder_transaction_data {
/* The first two are only used for bcTRANSACTION and brTRANSACTION,
* identifying the target and contents of the transaction.
*/
union {
/* target descriptor of command transaction */
__u32 handle;
/* target descriptor of return transaction */
binder_uintptr_t ptr;
} target;
binder_uintptr_t cookie; /* target object cookie */
__u32 code; /* transaction command */
/* General information about the transaction. */
__u32 flags;
__kernel_pid_t sender_pid;
__kernel_uid32_t sender_euid;
binder_size_t data_size; /* number of bytes of data */
binder_size_t offsets_size; /* number of bytes of offsets */
/* If this transaction is inline, the data immediately
* follows here; otherwise, it ends with a pointer to
* the data buffer.
*/
union {
struct {
/* transaction data */
binder_uintptr_t buffer;
/* offsets from buffer to flat_binder_object structs */
binder_uintptr_t offsets;
} ptr;
__u8 buf[8];
} data;
};
struct binder_transaction_data_secctx {
struct binder_transaction_data transaction_data;
binder_uintptr_t secctx;
};
struct binder_transaction_data_sg {
struct binder_transaction_data transaction_data;
binder_size_t buffers_size;
};
struct binder_ptr_cookie {
binder_uintptr_t ptr;
binder_uintptr_t cookie;
};
struct binder_handle_cookie {
__u32 handle;
binder_uintptr_t cookie;
} __packed;
struct binder_pri_desc {
__s32 priority;
__u32 desc;
};
struct binder_pri_ptr_cookie {
__s32 priority;
binder_uintptr_t ptr;
binder_uintptr_t cookie;
};
enum binder_driver_return_protocol {
BR_ERROR = _IOR('r', 0, __s32),
/*
* int: error code
*/
BR_OK = _IO('r', 1),
/* No parameters! */
BR_TRANSACTION_SEC_CTX = _IOR('r', 2,
struct binder_transaction_data_secctx),
/*
* binder_transaction_data_secctx: the received command.
*/
BR_TRANSACTION = _IOR('r', 2, struct binder_transaction_data),
BR_REPLY = _IOR('r', 3, struct binder_transaction_data),
/*
* binder_transaction_data: the received command.
*/
BR_ACQUIRE_RESULT = _IOR('r', 4, __s32),
/*
* not currently supported
* int: 0 if the last bcATTEMPT_ACQUIRE was not successful.
* Else the remote object has acquired a primary reference.
*/
BR_DEAD_REPLY = _IO('r', 5),
/*
* The target of the last transaction (either a bcTRANSACTION or
* a bcATTEMPT_ACQUIRE) is no longer with us. No parameters.
*/
BR_TRANSACTION_COMPLETE = _IO('r', 6),
/*
* No parameters... always refers to the last transaction requested
* (including replies). Note that this will be sent even for
* asynchronous transactions.
*/
BR_INCREFS = _IOR('r', 7, struct binder_ptr_cookie),
BR_ACQUIRE = _IOR('r', 8, struct binder_ptr_cookie),
BR_RELEASE = _IOR('r', 9, struct binder_ptr_cookie),
BR_DECREFS = _IOR('r', 10, struct binder_ptr_cookie),
/*
* void *: ptr to binder
* void *: cookie for binder
*/
BR_ATTEMPT_ACQUIRE = _IOR('r', 11, struct binder_pri_ptr_cookie),
/*
* not currently supported
* int: priority
* void *: ptr to binder
* void *: cookie for binder
*/
BR_NOOP = _IO('r', 12),
/*
* No parameters. Do nothing and examine the next command. It exists
* primarily so that we can replace it with a BR_SPAWN_LOOPER command.
*/
BR_SPAWN_LOOPER = _IO('r', 13),
/*
* No parameters. The driver has determined that a process has no
* threads waiting to service incoming transactions. When a process
* receives this command, it must spawn a new service thread and
* register it via bcENTER_LOOPER.
*/
BR_FINISHED = _IO('r', 14),
/*
* not currently supported
* stop threadpool thread
*/
BR_DEAD_BINDER = _IOR('r', 15, binder_uintptr_t),
/*
* void *: cookie
*/
BR_CLEAR_DEATH_NOTIFICATION_DONE = _IOR('r', 16, binder_uintptr_t),
/*
* void *: cookie
*/
BR_FAILED_REPLY = _IO('r', 17),
/*
* The last transaction (either a bcTRANSACTION or
* a bcATTEMPT_ACQUIRE) failed (e.g. out of memory). No parameters.
*/
BR_FROZEN_REPLY = _IO('r', 18),
/*
* The target of the last sync transaction (either a bcTRANSACTION or
* a bcATTEMPT_ACQUIRE) is frozen. No parameters.
*/
BR_ONEWAY_SPAM_SUSPECT = _IO('r', 19),
/*
* Current process sent too many oneway calls to target, and the last
* asynchronous transaction makes the allocated async buffer size exceed
* detection threshold. No parameters.
*/
BR_TRANSACTION_PENDING_FROZEN = _IO('r', 20),
/*
* The target of the last async transaction is frozen. No parameters.
*/
BR_FROZEN_BINDER = _IOR('r', 21, struct binder_frozen_state_info),
/*
* The cookie and a boolean (is_frozen) that indicates whether the process
* transitioned into a frozen or an unfrozen state.
*/
BR_CLEAR_FREEZE_NOTIFICATION_DONE = _IOR('r', 22, binder_uintptr_t),
/*
* void *: cookie
*/
};
enum binder_driver_command_protocol {
BC_TRANSACTION = _IOW('c', 0, struct binder_transaction_data),
BC_REPLY = _IOW('c', 1, struct binder_transaction_data),
/*
* binder_transaction_data: the sent command.
*/
BC_ACQUIRE_RESULT = _IOW('c', 2, __s32),
/*
* not currently supported
* int: 0 if the last BR_ATTEMPT_ACQUIRE was not successful.
* Else you have acquired a primary reference on the object.
*/
BC_FREE_BUFFER = _IOW('c', 3, binder_uintptr_t),
/*
* void *: ptr to transaction data received on a read
*/
BC_INCREFS = _IOW('c', 4, __u32),
BC_ACQUIRE = _IOW('c', 5, __u32),
BC_RELEASE = _IOW('c', 6, __u32),
BC_DECREFS = _IOW('c', 7, __u32),
/*
* int: descriptor
*/
BC_INCREFS_DONE = _IOW('c', 8, struct binder_ptr_cookie),
BC_ACQUIRE_DONE = _IOW('c', 9, struct binder_ptr_cookie),
/*
* void *: ptr to binder
* void *: cookie for binder
*/
BC_ATTEMPT_ACQUIRE = _IOW('c', 10, struct binder_pri_desc),
/*
* not currently supported
* int: priority
* int: descriptor
*/
BC_REGISTER_LOOPER = _IO('c', 11),
/*
* No parameters.
* Register a spawned looper thread with the device.
*/
BC_ENTER_LOOPER = _IO('c', 12),
BC_EXIT_LOOPER = _IO('c', 13),
/*
* No parameters.
* These two commands are sent as an application-level thread
* enters and exits the binder loop, respectively. They are
* used so the binder can have an accurate count of the number
* of looping threads it has available.
*/
BC_REQUEST_DEATH_NOTIFICATION = _IOW('c', 14,
struct binder_handle_cookie),
/*
* int: handle
* void *: cookie
*/
BC_CLEAR_DEATH_NOTIFICATION = _IOW('c', 15,
struct binder_handle_cookie),
/*
* int: handle
* void *: cookie
*/
BC_DEAD_BINDER_DONE = _IOW('c', 16, binder_uintptr_t),
/*
* void *: cookie
*/
BC_TRANSACTION_SG = _IOW('c', 17, struct binder_transaction_data_sg),
BC_REPLY_SG = _IOW('c', 18, struct binder_transaction_data_sg),
/*
* binder_transaction_data_sg: the sent command.
*/
BC_REQUEST_FREEZE_NOTIFICATION =
_IOW('c', 19, struct binder_handle_cookie),
/*
* int: handle
* void *: cookie
*/
BC_CLEAR_FREEZE_NOTIFICATION = _IOW('c', 20,
struct binder_handle_cookie),
/*
* int: handle
* void *: cookie
*/
BC_FREEZE_NOTIFICATION_DONE = _IOW('c', 21, binder_uintptr_t),
/*
* void *: cookie
*/
};
#endif /* _UAPI_LINUX_BINDER_H */
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include <android/log.h>
#include <errno.h>
#ifndef LOG_TAG
#define LOG_TAG "TEESimulator"
#endif
#include "../logging.hpp"
+499
View File
@@ -0,0 +1,499 @@
#pragma once
#include <algorithm> // For std::swap in UniqueFd
#include <limits.h> // For PATH_MAX
#include <string>
#include <string_view>
#include <sys/ptrace.h>
#include <unistd.h>
#include <vector>
#include "lsplt.hpp"
// Macros for syscall error checking. These are typically used after remote
// syscall emulation.
#define SYSCALL_IS_ERR(e) (((unsigned long)e) > -4096UL) // Checks if a syscall return value indicates an error.
#define SYSCALL_ERR(e) (-(int)(e)) // Converts a syscall error value to a negative errno.
// Architecture-specific register definitions.
// These macros abstract away the differences in register names across architectures,
// allowing for generic code that manipulates `struct user_regs_struct`.
#if defined(__x86_64__)
# define REG_SP rsp // Stack pointer register
# define REG_IP rip // Instruction pointer register
# define REG_RET rax // Return value register
# define REG_NR orig_rax // Syscall number register
# define REG_SYS_ARG0 rdi // First syscall argument register
#elif defined(__i386__)
# define REG_SP esp
# define REG_IP eip
# define REG_RET eax
# define REG_NR orig_eax
# define REG_SYS_ARG0 ebx
#elif defined(__aarch64__)
# define REG_SP sp // Stack pointer register (AArch64)
# define REG_IP pc // Program counter register (AArch64)
# define REG_RET regs[0] // Return value register (x0)
# define REG_NR regs[8] // Syscall number register (x8)
# define REG_SYS_ARG0 regs[0] // First syscall argument register (x0)
#elif defined(__arm__)
# define REG_SP uregs[13] // Stack pointer register (R13)
# define REG_IP uregs[15] // Program counter register (R15)
# define REG_RET uregs[0] // Return value register (R0)
# define REG_NR uregs[7] // Syscall number register (R7)
# define REG_SYS_ARG0 uregs[0] // First syscall argument register (R0)
# define user_regs_struct user_regs // ARM's equivalent to user_regs_struct is user_regs
# define SYS_mmap SYS_mmap2 // ARM uses mmap2 syscall
#endif
// --- Remote Memory Operations ---
/**
* @brief Writes data to the remote process's memory.
* @param pid The target process ID.
* @param remote_addr The target address in the remote process.
* @param buf A pointer to the local buffer containing data to write.
* @param len The number of bytes to write.
* @param use_proc_mem If true, uses /proc/<pid>/mem; otherwise, uses
* process_vm_writev.
* @return The number of bytes written, or -1 on error.
*/
ssize_t write_proc(int pid, uintptr_t remote_addr, const void *buf, size_t len, bool use_proc_mem = false);
/**
* @brief Reads data from the remote process's memory.
* @param pid The target process ID.
* @param remote_addr The source address in the remote process.
* @param buf A pointer to the local buffer to store the read data.
* @param len The number of bytes to read.
* @return The number of bytes read, or -1 on error.
*/
ssize_t read_proc(int pid, uintptr_t remote_addr, void *buf, size_t len);
// --- Remote Register Operations ---
/**
* @brief Retrieves the current CPU registers of the target process.
* @param pid The target process ID.
* @param regs A reference to a `user_regs_struct` to store the registers.
* @return True on success, false on failure.
*/
bool get_regs(int pid, struct user_regs_struct &regs);
/**
* @brief Sets the CPU registers of the target process.
* @param pid The target process ID.
* @param regs A reference to a `user_regs_struct` containing the registers to set.
* @return True on success, false on failure.
*/
bool set_regs(int pid, struct user_regs_struct &regs);
// --- Module and Symbol Resolution ---
/**
* @brief Gets a descriptive string of the memory region containing a given
* address.
* @param map_info A vector of `lsplt::MapInfo` for the process.
* @param addr The address to look up.
* @return A string representing the memory region (e.g., "path perms"), or "<unknown>".
*/
std::string get_addr_mem_region(const std::vector<lsplt::MapInfo> &map_info, uintptr_t addr);
/**
* @brief Finds the base address of a module in a process's memory map.
* @param map_info A vector of `lsplt::MapInfo` for the process.
* @param module_suffix The suffix of the module path (e.g., "libc.so").
* @return The base address of the module, or nullptr if not found.
*/
void *find_module_base(const std::vector<lsplt::MapInfo> &map_info, std::string_view module_suffix);
/**
* @brief Finds the address of a function in a remote process by resolving it
* locally and calculating the offset.
*
* This function opens the module locally, finds the symbol address,
* calculates its offset from the local module base, and then adds that offset to the remote module base.
*
* @param local_map_info Memory map of the local (injector) process.
* @param remote_map_info Memory map of the remote (target) process.
* @param module_name The name of the module (e.g., "libc.so").
* @param function_name The name of the function (e.g., "open").
* @return The remote address of the function, or nullptr if not found.
*/
void *find_func_addr(const std::vector<lsplt::MapInfo> &local_map_info,
const std::vector<lsplt::MapInfo> &remote_map_info, std::string_view module_name,
std::string_view function_name);
/**
* @brief Finds a suitable return address within a specific module in the remote
* process.
*
* This typically looks for a non-executable segment of the module to return to,
* as `PTRACE_CONT` will resume execution at the specified instruction pointer.
*
* @param map_info A vector of `lsplt::MapInfo` for the remote process.
* @param module_suffix The suffix of the module path (e.g., "libc.so").
* @return A pointer to a suitable return address, or nullptr if not found.
*/
void *find_module_return_addr(const std::vector<lsplt::MapInfo> &map_info, std::string_view module_suffix);
// --- Remote Stack Manipulation ---
/**
* @brief Aligns the stack pointer (`REG_SP`) to ensure proper stack frame setup.
* @param regs A reference to the `user_regs_struct` to modify.
* @param preserve_bytes Number of bytes to preserve below the new stack pointer.
*/
void align_stack(struct user_regs_struct &regs, uintptr_t preserve_bytes = 0);
/**
* @brief Pushes a block of memory onto the remote process's stack.
*
* This function decrements the stack pointer, aligns it, and then writes the data.
*
* @param pid The target process ID.
* @param regs A reference to the `user_regs_struct` (its stack pointer will be updated).
* @param data A pointer to the local data to push.
* @param length The number of bytes to push.
* @return The remote address where the data was pushed, or 0 on error.
*/
uintptr_t push_memory(int pid, struct user_regs_struct &regs, const void *data, size_t length);
/**
* @brief Pushes a null-terminated string onto the remote process's stack.
* @param pid The target process ID.
* @param regs A reference to the `user_regs_struct` (its stack pointer will be updated).
* @param str The null-terminated C-style string to push.
* @return The remote address where the string was pushed, or 0 on error.
*/
uintptr_t push_string(int pid, struct user_regs_struct &regs, const char *str);
// --- Remote Function Call Emulation ---
/**
* @brief Prepares and initiates a remote function call in the target process.
*
* This function sets up registers (arguments, return address, instruction pointer) and
* then continues the target process execution using PTRACE_CONT.
*
* @param pid The target process ID.
* @param regs A reference to the `user_regs_struct` (will be modified).
* @param func_addr The remote address of the function to call.
* @param return_addr The address in the remote process where execution should
* resume after the call.
* @param args A vector of `uintptr_t` representing the function arguments.
* @return True if the remote call was successfully initiated, false otherwise.
*/
bool remote_pre_call(int pid, struct user_regs_struct &regs, uintptr_t func_addr, uintptr_t return_addr,
std::vector<uintptr_t> &args);
/**
* @brief Waits for and finalizes a remote function call, retrieving its return value.
*
* This function waits for the target process to stop after a remote call and
* then retrieves the return value from the appropriate register.
*
* @param pid The target process ID.
* @param regs A reference to the `user_regs_struct` (will be updated with post-call registers).
* @param expected_return_addr The address where the remote call was expected to return to.
* Used for error checking (e.g., if a crash occurs elsewhere).
* @return The return value of the remote function, or 0 on error.
*/
uintptr_t remote_post_call(int pid, struct user_regs_struct &regs, uintptr_t expected_return_addr);
/**
* @brief Executes a complete remote function call (pre-call, continue,
* post-call).
* @param pid The target process ID.
* @param regs A reference to the `user_regs_struct` (will be modified).
* @param func_addr The remote address of the function to call.
* @param return_addr The address in the remote process where execution should resume after the call.
* @param args A vector of `uintptr_t` representing the function arguments.
* @return The return value of the remote function, or 0 on error.
*/
uintptr_t remote_call(int pid, struct user_regs_struct &regs, uintptr_t func_addr, uintptr_t return_addr,
std::vector<uintptr_t> &args);
// --- Process Management and Ptrace Utilities ---
/**
* @brief Forks twice to create a daemon process, returning 0 in the daemon,
* or the child pid in parent.
* @return 0 in the grand-child (daemon), PID of first child in parent, or -1 on error.
*/
int fork_dont_care();
/**
* @brief Waits for the target process to stop due to ptrace.
*
* This function handles `EINTR` and ensures the process is actually stopped.
*
* @param pid The target process ID.
* @param status A pointer to an integer to store the wait status.
* @param flags Flags for `waitpid` (e.g., `__WALL`).
* @return True if the process successfully stopped, false otherwise.
*/
bool wait_for_trace(int pid, int *status, int flags);
/**
* @brief Parses the wait status integer into a human-readable string.
* @param status The status integer returned by `waitpid`.
* @return A string describing the wait status.
*/
std::string parse_status(int status);
/**
* @brief Retrieves the executable path of a process.
* @param pid The target process ID.
* @return The absolute path to the executable, or an empty string on error.
*/
std::string get_program(int pid);
/**
* @brief Gets the command-line arguments of a process.
* @param pid The target process ID.
* @return A vector of strings representing the command-line arguments.
*/
std::vector<std::string> get_cmdline(int pid);
/**
* @brief Parses the `exec` status of a process
* @param pid The target process ID.
* @return A string representing the `exec` status (placeholder).
*/
std::string parse_exec(int pid);
/**
* @brief Skips the current syscall in the target process
* @param pid The target process ID.
* @return True on success, false on failure (placeholder).
*/
bool skip_syscall(int pid);
/**
* @brief Executes a syscall in the remote process using ptrace.
* @param pid The target process ID.
* @param ret Reference to store the syscall return value.
* @param nr The syscall number.
* @param arg0 to arg5 - Syscall arguments.
* @return True on success, false on failure.
*/
bool do_syscall(int pid, uintptr_t &ret, int nr, uintptr_t arg0 = 0, uintptr_t arg1 = 0, uintptr_t arg2 = 0,
uintptr_t arg3 = 0, uintptr_t arg4 = 0, uintptr_t arg5 = 0);
/**
* @brief Switches the mount namespace of the current process to that of the target PID, or restores it.
* @param pid If non-zero, switches to the namespace of `pid`.
* If zero, restores to the namespace stored in `*fd`.
* @param fd On entry (pid != 0), points to an int to store the original namespace FD.
* On entry (pid == 0), points to the FD of the namespace to restore to.
* FD is consumed/set to kInvalidFd on successful restore.
* @return True on success, false on failure.
*/
bool switch_mnt_ns(int pid, int *fd);
/**
* @brief Remotely calls mmap in the target process.
* @param pid The target process ID.
* @param addr The preferred starting address for the new mapping.
* @param size The length of the mapping.
* @param prot Protection flags (PROT_READ, PROT_WRITE, PROT_EXEC).
* @param flags Mapping flags (MAP_PRIVATE, MAP_ANONYMOUS, etc.).
* @param fd File descriptor to map from (or -1 for anonymous).
* @param offset Offset into the file (or 0 for anonymous).
* @return The starting address of the new mapping, or MAP_FAILED on error.
*/
uintptr_t remote_mmap(int pid, uintptr_t addr, size_t size, int prot, int flags, int fd, off_t offset);
/**
* @brief Remotely calls munmap in the target process.
* @param pid The target process ID.
* @param addr The starting address of the region to unmap.
* @param size The length of the region to unmap.
* @return True on success, false on failure.
*/
bool remote_munmap(int pid, uintptr_t addr, size_t size);
/**
* @brief Remotely calls open in the target process.
* @param pid The target process ID.
* @param path_addr The remote address of the path string.
* @param flags Open flags (O_RDONLY, O_WRONLY, O_CREAT, etc.).
* @return The file descriptor in the remote process, or -1 on error.
*/
int remote_open(int pid, uintptr_t path_addr, int flags);
/**
* @brief Remotely calls close in the target process.
* @param pid The target process ID.
* @param fd The file descriptor in the remote process to close.
* @return True on success, false on failure.
*/
bool remote_close(int pid, int fd);
/**
* @brief Waits for a child process to terminate.
* @param pid The child process ID.
* @return The exit status of the child, or -1 on error.
*/
int wait_for_child(int pid);
/**
* @brief Determines the ELF class (32-bit or 64-bit) of an executable file.
* @param path The path to the ELF file.
* @return `ELFCLASS32` for 32-bit, `ELFCLASS64` for 64-bit, or `ELFNONE` on error.
*/
int get_elf_class(std::string_view path);
// --- Miscellaneous Utilities ---
constexpr size_t kMaxPathLength = PATH_MAX; // Max path length, consistent with main.cpp
constexpr size_t kDefaultMagicLength = 16; // Default length for generated magic strings.
/**
* @brief Generates a random alphanumeric string.
* @param length The desired length of the magic string.
* @return The generated magic string.
*/
std::string generateMagic(size_t length);
/**
* @brief Sets the SELinux security context of a file.
* @param file_path The path to the file.
* @param security_context The new security context string.
* @return 0 on success, -1 on failure.
*/
int setfilecon(const char *file_path, const char *security_context);
/**
* @brief RAII wrapper for file descriptors.
*
* This class automatically closes the file descriptor when it goes out of scope.
*/
class UniqueFd {
using Fd = int; // Alias for file descriptor type.
public:
/**
* @brief Default constructor. Initializes with an invalid FD.
*/
UniqueFd() = default;
/**
* @brief Constructor that takes an existing file descriptor.
* @param fd The file descriptor to manage.
*/
UniqueFd(Fd fd) : fd_(fd) {}
/**
* @brief Destructor. Closes the managed file descriptor if valid.
*/
~UniqueFd() {
if (fd_ >= 0)
close(fd_);
}
// Delete copy constructor and assignment operator to prevent double-free issues.
UniqueFd(const UniqueFd &) = delete;
UniqueFd &operator=(const UniqueFd &) = delete;
/**
* @brief Move constructor. Transfers ownership of the file descriptor.
* @param other The `UniqueFd` object to move from.
*/
UniqueFd(UniqueFd &&other) noexcept {
std::swap(fd_, other.fd_);
}
/**
* @brief Move assignment operator. Transfers ownership of the file descriptor.
* @param other The `UniqueFd` object to move from.
* @return A reference to this `UniqueFd` object.
*/
UniqueFd &operator=(UniqueFd &&other) noexcept {
if (this != &other) { // Handle self-assignment
if (fd_ >= 0)
close(fd_); // Close current FD before taking ownership
fd_ = -1; // Invalidate current FD before swap
std::swap(fd_, other.fd_);
}
return *this;
}
/**
* @brief Assignment from raw int FD. Closes the current FD.
*/
UniqueFd &operator=(Fd fd) {
if (fd_ >= 0) {
close(fd_);
}
fd_ = fd;
return *this;
}
/**
* @brief Allows implicit conversion to the underlying file descriptor type.
* @return The managed file descriptor.
*/
operator const Fd &() const {
return fd_;
}
private:
Fd fd_ = -1; // The managed file descriptor, initialized to invalid.
};
/**
* @brief Sets the SELinux context for newly created sockets.
*
* This allows the injector to create sockets with a specific security context
* that might be required for interaction with target processes under SELinux.
* It attempts to write to `/proc/thread-self/attr/sockcreate` or a process-specific fallback.
*
* @param security_context The SELinux context string to set.
* @return True on success, false on failure.
*/
bool set_sockcreate_con(const char *security_context);
// --- Ptrace Event and Signal Parsing ---
#define WPTEVENT(x) (x >> 16) // Macro to extract the ptrace event code from wait status.
#define CASE_CONST_RETURN(x) \
case x: \
return #x; // Helper macro for switch-case to return string literal.
/**
* @brief Parses a ptrace event code into a human-readable string.
* @param status The wait status containing the ptrace event code.
* @return A string representing the ptrace event.
*/
inline const char *parse_ptrace_event(int status) {
status = WPTEVENT(status); // Extract the event code.
switch (status) {
CASE_CONST_RETURN(PTRACE_EVENT_FORK)
CASE_CONST_RETURN(PTRACE_EVENT_VFORK)
CASE_CONST_RETURN(PTRACE_EVENT_CLONE)
CASE_CONST_RETURN(PTRACE_EVENT_EXEC)
CASE_CONST_RETURN(PTRACE_EVENT_VFORK_DONE)
CASE_CONST_RETURN(PTRACE_EVENT_EXIT)
CASE_CONST_RETURN(PTRACE_EVENT_SECCOMP)
CASE_CONST_RETURN(PTRACE_EVENT_STOP) // Not a standard event, but sometimes
// seen for special stops
default:
return "(no event)"; // Default for unknown or no event.
}
}
/**
* @brief Returns the abbreviated name of a signal.
* @param sig The signal number.
* @return The abbreviated signal name (e.g., "SIGSEGV"), or "(unknown)".
*/
inline const char *sigabbrev_np(int sig) {
// NSIG is the total number of signals, sys_signame array is indexed by signal
// number. Note: sys_signame is part of glibc and may require _GNU_SOURCE or
// similar. Assuming its availability for professional refactor.
if (sig > 0 && sig < NSIG)
return sys_signame[sig];
return "(unknown)";
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+270
View File
@@ -0,0 +1,270 @@
#include "binder/Binder.h"
#include "binder/BpBinder.h"
#include "binder/IInterface.h"
#include "binder/IPCThreadState.h"
#include "binder/IServiceManager.h"
#include "binder/RpcSession.h"
#include "binder/Status.h"
namespace android {
IInterface::IInterface() {}
IInterface::~IInterface() {}
IBinder::IBinder() {}
IBinder::~IBinder() {}
sp<IInterface> IBinder::queryLocalInterface(const String16 &) {
return nullptr;
}
BBinder *IBinder::localBinder() {
return nullptr;
}
BpBinder *IBinder::remoteBinder() {
return nullptr;
}
bool IBinder::checkSubclass(const void *) const {
return false;
}
void IBinder::withLock(const std::function<void()> &) {}
#ifdef __LP64__
static_assert(sizeof(IBinder) == 24);
static_assert(sizeof(BBinder) == 40);
#else
static_assert(sizeof(IBinder) == 12);
static_assert(sizeof(BBinder) == 20);
#endif
BBinder::BBinder() {}
BBinder::~BBinder() {}
const String16 &BBinder::getInterfaceDescriptor() const {
__builtin_unreachable();
}
bool BBinder::isBinderAlive() const {
return false;
}
status_t BBinder::pingBinder() {
return 0;
}
status_t BBinder::dump(int, const Vector<String16> &) {
return 0;
}
status_t BBinder::transact(uint32_t, const Parcel &, Parcel *, uint32_t) {
return 0;
}
status_t BBinder::linkToDeath(const sp<DeathRecipient> &, void *, uint32_t) {
return 0;
}
status_t BBinder::unlinkToDeath(const wp<DeathRecipient> &, void *, uint32_t, wp<DeathRecipient> *) {
return 0;
}
void *BBinder::attachObject(const void *, void *, void *, object_cleanup_func) {
return nullptr;
}
void *BBinder::findObject(const void *) const {
return nullptr;
}
void *BBinder::detachObject(const void *) {
return nullptr;
}
void BBinder::withLock(const std::function<void()> &) {}
BBinder *BBinder::localBinder() {
return nullptr;
}
status_t BBinder::onTransact(uint32_t, const Parcel &, Parcel *, uint32_t) {
return 0;
}
IPCThreadState *IPCThreadState::self() {
return nullptr;
}
IPCThreadState *IPCThreadState::selfOrNull() {
return nullptr;
}
pid_t IPCThreadState::getCallingPid() const {
return 0;
}
const char *IPCThreadState::getCallingSid() const {
return nullptr;
}
uid_t IPCThreadState::getCallingUid() const {
return 0;
}
#ifdef __LP64__
static_assert(sizeof(Parcel) == 120);
#else
static_assert(sizeof(Parcel) == 60);
#endif
Parcel::Parcel() {}
Parcel::~Parcel() {}
const uint8_t *Parcel::data() const {
return nullptr;
}
size_t Parcel::dataSize() const {
return 0;
}
size_t Parcel::dataAvail() const {
return 0;
}
size_t Parcel::dataPosition() const {
return 0;
}
size_t Parcel::dataCapacity() const {
return 0;
}
size_t Parcel::dataBufferSize() const {
return 0;
}
status_t Parcel::setDataSize(size_t) {
return 0;
}
void Parcel::setDataPosition(size_t) const {}
status_t Parcel::setDataCapacity(size_t) {
return 0;
}
status_t Parcel::setData(const uint8_t *, size_t) {
return 0;
}
status_t Parcel::appendFrom(const Parcel *, size_t, size_t) {
return 0;
}
binder::Status Parcel::enforceNoDataAvail() const {
return {};
}
void Parcel::setEnforceNoDataAvail(bool) {}
void Parcel::freeData() {}
status_t Parcel::write(const void *, size_t) {
return 0;
}
void *Parcel::writeInplace(size_t) {
return nullptr;
}
status_t Parcel::writeInt32(int32_t) {
return 0;
}
status_t Parcel::writeUint32(uint32_t) {
return 0;
}
status_t Parcel::writeInt64(int64_t) {
return 0;
}
status_t Parcel::writeUint64(uint64_t) {
return 0;
}
status_t Parcel::writeFloat(float) {
return 0;
}
status_t Parcel::writeDouble(double) {
return 0;
}
status_t Parcel::writeCString(const char *) {
return 0;
}
status_t Parcel::writeString8(const char *, size_t) {
return 0;
}
status_t Parcel::writeStrongBinder(const sp<IBinder> &) {
return 0;
}
status_t Parcel::writeBool(bool) {
return 0;
}
status_t Parcel::writeChar(char16_t) {
return 0;
}
status_t Parcel::writeByte(int8_t) {
return 0;
}
status_t Parcel::writeNoException() {
return 0;
}
status_t Parcel::read(void *, size_t) const {
return 0;
}
const void *Parcel::readInplace(size_t) const {
return nullptr;
}
int32_t Parcel::readInt32() const {
return 0;
}
status_t Parcel::readInt32(int32_t *) const {
return 0;
}
uint32_t Parcel::readUint32() const {
return 0;
}
status_t Parcel::readUint32(uint32_t *) const {
return 0;
}
int64_t Parcel::readInt64() const {
return 0;
}
status_t Parcel::readInt64(int64_t *) const {
return 0;
}
uint64_t Parcel::readUint64() const {
return 0;
}
status_t Parcel::readUint64(uint64_t *) const {
return 0;
}
float Parcel::readFloat() const {
return 0;
}
status_t Parcel::readFloat(float *) const {
return 0;
}
double Parcel::readDouble() const {
return 0;
}
status_t Parcel::readDouble(double *) const {
return 0;
}
bool Parcel::readBool() const {
return 0;
}
status_t Parcel::readBool(bool *) const {
return 0;
}
char16_t Parcel::readChar() const {
return 0;
}
status_t Parcel::readChar(char16_t *) const {
return 0;
}
int8_t Parcel::readByte() const {
return 0;
}
status_t Parcel::readByte(int8_t *) const {
return 0;
}
sp<IBinder> Parcel::readStrongBinder() const {
return nullptr;
}
status_t Parcel::readStrongBinder(sp<IBinder> *) const {
return 0;
}
status_t Parcel::readNullableStrongBinder(sp<IBinder> *) const {
return 0;
}
int32_t Parcel::readExceptionCode() const {
return 0;
}
int Parcel::readFileDescriptor() const {
return 0;
}
IServiceManager::IServiceManager() {}
IServiceManager::~IServiceManager() {}
const String16 &IServiceManager::getInterfaceDescriptor() const {
__builtin_unreachable();
}
sp<IServiceManager> defaultServiceManager() {
return nullptr;
}
void setDefaultServiceManager(const sp<IServiceManager> &) {}
} // namespace android
+62
View File
@@ -0,0 +1,62 @@
#include "utils/RefBase.h"
#include "utils/String16.h"
#include "utils/String8.h"
#include "utils/StrongPointer.h"
namespace android {
void RefBase::incStrong(const void *id) const {}
void RefBase::incStrongRequireStrong(const void *id) const {}
void RefBase::decStrong(const void *id) const {}
void RefBase::forceIncStrong(const void *id) const {}
RefBase::weakref_type *RefBase::createWeak(const void *id) const {
return nullptr;
}
RefBase::weakref_type *RefBase::getWeakRefs() const {
return nullptr;
}
RefBase::RefBase() : mRefs(nullptr) {}
RefBase::~RefBase() {}
void RefBase::onFirstRef() {}
void RefBase::onLastStrongRef(const void *id) {}
bool RefBase::onIncStrongAttempted(uint32_t flags, const void *id) {
return false;
}
void RefBase::onLastWeakRef(const void *id) {}
RefBase *RefBase::weakref_type::refBase() const {
return nullptr;
}
void RefBase::weakref_type::incWeak(const void *id) {}
void RefBase::weakref_type::incWeakRequireWeak(const void *id) {}
void RefBase::weakref_type::decWeak(const void *id) {}
bool RefBase::weakref_type::attemptIncStrong(const void *id) {
return false;
}
bool RefBase::weakref_type::attemptIncWeak(const void *id) {
return false;
}
void sp_report_race() {}
String8::String8() {}
String16::String16() {}
String16::String16(const String16 &o) {}
String16::String16(String16 &&o) noexcept {}
String16::String16(const char *o) {}
String16::~String16() {}
} // namespace android
+76
View File
@@ -0,0 +1,76 @@
// Fork-based supervisor for instant daemon restart
#include <unistd.h>
#include <sys/wait.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <time.h>
static volatile sig_atomic_t should_exit = 0;
static void signal_handler(int sig) {
should_exit = 1;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <daemon> [args...]\n", argv[0]);
return 1;
}
// Forward termination signals to exit cleanly
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
const char *daemon_path = argv[1];
char **daemon_argv = &argv[1];
int backoff_ms = 500;
while (!should_exit) {
struct timespec child_start;
clock_gettime(CLOCK_MONOTONIC, &child_start);
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
usleep(100000); // 100ms backoff on fork failure
continue;
}
if (pid == 0) {
// Child: become the daemon
prctl(PR_SET_PDEATHSIG, SIGKILL); // Die if parent dies
setpriority(PRIO_PROCESS, 0, 10); // lower CPU priority than foreground
execv(daemon_path, daemon_argv);
perror("execv failed");
_exit(127);
}
// Parent: wait for child to exit
int status;
waitpid(pid, &status, 0);
if (should_exit) break;
// Exponential backoff on rapid crashes, reset if child was stable
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
long lived_ms = (now.tv_sec - child_start.tv_sec) * 1000 +
(now.tv_nsec - child_start.tv_nsec) / 1000000;
if (lived_ms > 30000) {
backoff_ms = 500;
} else {
usleep(backoff_ms * 1000);
if (backoff_ms < 30000) backoff_ms *= 2;
}
}
return 0;
}
@@ -0,0 +1,151 @@
package org.matrix.TEESimulator
import android.app.ActivityThread
import android.app.Application
import android.content.Context
import android.content.ContextWrapper
import android.os.Build
import android.os.Looper
import java.security.Security
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.matrix.TEESimulator.config.BootStateManager
import org.matrix.TEESimulator.config.ConfigurationManager
import org.matrix.TEESimulator.interception.keystore.AbstractKeystoreInterceptor
import org.matrix.TEESimulator.interception.keystore.Keystore2Interceptor
import org.matrix.TEESimulator.interception.keystore.KeystoreInterceptor
import org.matrix.TEESimulator.interception.soter.SoterProcessSupervisor
import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.pki.NativeCertGen
import org.matrix.TEESimulator.util.AndroidDeviceUtils
/**
* Main application object for TEESimulator. This object manages the application's lifecycle,
* including initialization of interceptors and maintaining the service's primary execution loop.
*/
object App {
// The delay in milliseconds before retrying to initialize the interceptor.
private const val RETRY_DELAY_MS = 1000L
/**
* The main entry point of the TEESimulator application.
*
* @param args Command line arguments (not used).
*/
@JvmStatic
fun main(args: Array<String>) {
SystemLogger.info("Welcome to TEESimulator!")
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
SystemLogger.error("Uncaught exception on ${thread.name}", throwable)
}
try {
val systemContext = prepareEnvironment()
// Spoof boot-state props before any hook attaches, so keystore2's
// cached snapshot reflects the spoofed values.
BootStateManager.apply()
// Load the package configuration.
ConfigurationManager.initialize()
// Initialize and start the appropriate keystore interceptors.
initializeInterceptors()
// 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".
// We must remove the system provider first to ensure the full Bouncy Castle library
// (packaged with the app) is used.
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
Security.addProvider(BouncyCastleProvider())
NativeCertGen.initialize("/data/adb/modules/tricky_store/libcertgen.so")
// Mount the SOTER forge on the on-demand soterserver process. The supervisor
// binds and (re)injects on its own thread, returning at once so it never blocks the loop.
SoterProcessSupervisor.start(systemContext)
// This starts the message queue processing. It blocks here indefinitely
// processing messages until Looper.myLooper().quit() is called.
Looper.loop()
} catch (e: Exception) {
SystemLogger.error("A fatal error occurred in the main application thread.", e)
throw e
}
}
/** Initializes the necessary Android framework internals to satisfy KeyStore requirements. */
private fun prepareEnvironment(): Context {
// 1. Prepare Main Looper
if (Looper.getMainLooper() == null) {
@Suppress("deprecation") Looper.prepareMainLooper()
}
// 2. Initialize ActivityThread for the current process
val activityThread = ActivityThread.systemMain()
// 3. Get the system context. The stub declares getSystemContext(): ContextImpl
// (a bare class), so cast to the Context it really is at runtime for the wiring.
@Suppress("CAST_NEVER_SUCCEEDS")
val systemContext = activityThread.getSystemContext() as Context
// 4. Create a dummy Application object and attach the context
val app = Application()
val attachMethod =
ContextWrapper::class.java.getDeclaredMethod("attachBaseContext", Context::class.java)
attachMethod.isAccessible = true
attachMethod.invoke(app, systemContext)
// 5. Inject this application object into ActivityThread's mInitialApplication field.
// This is what KeyStore.getApplicationContext() looks for.
val mInitialApplicationField =
ActivityThread::class.java.getDeclaredField("mInitialApplication")
mInitialApplicationField.isAccessible = true
mInitialApplicationField.set(activityThread, app)
return systemContext
}
/**
* Selects and initializes the correct keystore interceptor based on the Android SDK version. It
* retries initialization until it succeeds.
*/
private fun initializeInterceptors() {
val interceptor = selectKeystoreInterceptor()
// Continuously try to run the interceptor until it's successfully initialized.
while (!interceptor.tryRunKeystoreInterceptor()) {
SystemLogger.debug("Retrying interceptor initialization...")
Thread.sleep(RETRY_DELAY_MS)
}
SystemLogger.info("Interceptors initialized successfully.")
}
/**
* Determines which keystore interceptor to use based on the device's Android version.
*
* @return The appropriate keystore interceptor instance.
*/
private fun selectKeystoreInterceptor(): AbstractKeystoreInterceptor =
when {
// For Android Q (10) and R (11), use the original KeystoreInterceptor.
Build.VERSION.SDK_INT in Build.VERSION_CODES.Q..Build.VERSION_CODES.R -> {
SystemLogger.info(
"Using KeystoreInterceptor for Android Q/R (SDK ${Build.VERSION.SDK_INT})"
)
android.security.keystore.AndroidKeyStoreProvider.install()
KeystoreInterceptor
}
// For Android S (12) and newer, use the Keystore2Interceptor.
else -> {
SystemLogger.info(
"Using Keystore2Interceptor for Android S and later (SDK ${Build.VERSION.SDK_INT})"
)
android.security.keystore2.AndroidKeyStoreProvider.install()
Keystore2Interceptor
}
}
}
@@ -0,0 +1,608 @@
package org.matrix.TEESimulator.attestation
import android.content.pm.PackageManager
import android.os.Build
import java.nio.ByteBuffer
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import org.bouncycastle.asn1.ASN1Boolean
import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.ASN1Enumerated
import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.ASN1Sequence
import org.bouncycastle.asn1.DERNull
import org.bouncycastle.asn1.DEROctetString
import org.bouncycastle.asn1.DERSequence
import org.bouncycastle.asn1.DERSet
import org.bouncycastle.asn1.DERTaggedObject
import org.bouncycastle.asn1.x509.Extension
import org.matrix.TEESimulator.config.ConfigurationManager
import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.util.AndroidDeviceUtils
import org.matrix.TEESimulator.util.AndroidDeviceUtils.DO_NOT_REPORT
/**
* A builder object responsible for constructing the ASN.1 DER-encoded Android Key Attestation
* extension.
*/
object AttestationBuilder {
/**
* Builds the complete X.509 attestation extension.
*
* @param params The parsed key generation parameters.
* @param uid The UID of the application requesting attestation.
* @param securityLevel The security level (e.g., TEE, StrongBox) to report.
* @return A Bouncy Castle [Extension] object ready to be added to a certificate.
*/
fun buildAttestationExtension(
params: KeyMintAttestation,
uid: Int,
securityLevel: Int,
): Extension {
val keyDescription = buildKeyDescription(params, uid, securityLevel)
SystemLogger.verbose {
val formattedString =
keyDescription.joinToString(separator = ", ") {
AttestationPatcher.formatAsn1Primitive(it)
}
"Forged attestation data: $formattedString"
}
return Extension(ATTESTATION_OID, false, DEROctetString(keyDescription.encoded))
}
/**
* Builds the `RootOfTrust` ASN.1 sequence. This contains critical boot state information.
*
* @param originalRootOfTrust An optional, pre-existing RoT to extract the boot hash from.
* @return The constructed [DERSequence] for the Root of Trust.
*/
internal fun buildRootOfTrust(originalRootOfTrust: ASN1Encodable?): DERSequence {
val rootOfTrustElements = arrayOfNulls<ASN1Encodable>(4)
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX] =
DEROctetString(AndroidDeviceUtils.bootKey)
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_DEVICE_LOCKED_INDEX] =
ASN1Boolean.TRUE // deviceLocked: true, for security
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_STATE_INDEX] =
ASN1Enumerated(0) // verifiedBootState: Verified
rootOfTrustElements[AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX] =
DEROctetString(AndroidDeviceUtils.bootHash)
return DERSequence(rootOfTrustElements)
}
/**
* Assembles a map representing the desired state of simulated hardware-enforced properties. A
* null value for a given tag indicates that it should be removed from the attestation.
*
* @param uid The UID of the calling application.
* @return A map where keys are attestation tag numbers and values are the desired
* [DERTaggedObject] or null to signify removal.
*/
fun getSimulatedHardwareProperties(uid: Int): Map<Int, DERTaggedObject?> {
val properties = mutableMapOf<Int, DERTaggedObject?>()
// OS Version is always present.
properties[AttestationConstants.TAG_OS_VERSION] =
DERTaggedObject(
true,
AttestationConstants.TAG_OS_VERSION,
ASN1Integer(AndroidDeviceUtils.osVersion.toLong()),
)
val osPatch = AndroidDeviceUtils.getPatchLevel(uid)
properties[AttestationConstants.TAG_OS_PATCHLEVEL] =
if (osPatch != DO_NOT_REPORT) {
DERTaggedObject(
true,
AttestationConstants.TAG_OS_PATCHLEVEL,
ASN1Integer(osPatch.toLong()),
)
} else {
null // Signal for removal
}
val vendorPatch = AndroidDeviceUtils.getVendorPatchLevelLong(uid)
properties[AttestationConstants.TAG_VENDOR_PATCHLEVEL] =
if (vendorPatch != DO_NOT_REPORT) {
DERTaggedObject(
true,
AttestationConstants.TAG_VENDOR_PATCHLEVEL,
ASN1Integer(vendorPatch.toLong()),
)
} else {
null // Signal for removal
}
val bootPatch = AndroidDeviceUtils.getBootPatchLevelLong(uid)
SystemLogger.info(
"Attestation patch levels for uid=$uid: os=$osPatch, vendor=$vendorPatch, boot=$bootPatch"
)
properties[AttestationConstants.TAG_BOOT_PATCHLEVEL] =
if (bootPatch != DO_NOT_REPORT) {
DERTaggedObject(
true,
AttestationConstants.TAG_BOOT_PATCHLEVEL,
ASN1Integer(bootPatch.toLong()),
)
} else {
null // Signal for removal
}
return properties
}
private fun buildKeyDescription(
params: KeyMintAttestation,
uid: Int,
securityLevel: Int,
): ASN1Sequence {
val creationTime = System.currentTimeMillis()
val teeEnforced = buildTeeEnforcedList(params, uid, securityLevel)
val softwareEnforced = buildSoftwareEnforcedList(params, uid, securityLevel, creationTime)
val uniqueId =
if (params.includeUniqueId == true && params.attestationChallenge != null) {
computeUniqueId(creationTime, createApplicationId(uid).octets)
} else {
ByteArray(0)
}
val fields =
arrayOf(
ASN1Integer(AndroidDeviceUtils.getAttestVersion(securityLevel).toLong()),
ASN1Enumerated(securityLevel),
ASN1Integer(AndroidDeviceUtils.getKeymasterVersion(securityLevel).toLong()),
ASN1Enumerated(securityLevel),
DEROctetString(params.attestationChallenge ?: ByteArray(0)),
DEROctetString(uniqueId),
softwareEnforced,
teeEnforced,
)
return DERSequence(fields)
}
private fun computeUniqueId(creationTimeMs: Long, aaidDer: ByteArray): ByteArray {
val temporalCounter = creationTimeMs / 2592000000L
val message =
ByteBuffer.allocate(8 + aaidDer.size + 1)
.putLong(temporalCounter)
.put(aaidDer)
.put(0x00)
.array()
val mac = Mac.getInstance("HmacSHA256")
mac.init(SecretKeySpec(hbk, "HmacSHA256"))
return mac.doFinal(message).copyOf(16)
}
private val hbk: ByteArray by lazy {
val file = java.io.File(ConfigurationManager.CONFIG_PATH, "hbk")
if (file.exists() && file.length() == 32L) {
file.readBytes()
} else {
SystemLogger.warning("hbk not found, generating ephemeral HBK.")
ByteArray(32).also { java.security.SecureRandom().nextBytes(it) }
}
}
/** Builds the `TeeEnforced` authorization list. These are properties the TEE "guarantees". */
private fun buildTeeEnforcedList(
params: KeyMintAttestation,
uid: Int,
securityLevel: Int,
): DERSequence {
val list =
mutableListOf<ASN1Encodable>(
DERTaggedObject(
true,
AttestationConstants.TAG_PURPOSE,
DERSet(params.purpose.map { ASN1Integer(it.toLong()) }.toTypedArray()),
),
DERTaggedObject(
true,
AttestationConstants.TAG_ALGORITHM,
ASN1Integer(params.algorithm.toLong()),
),
DERTaggedObject(
true,
AttestationConstants.TAG_KEY_SIZE,
ASN1Integer(params.keySize.toLong()),
),
DERTaggedObject(
true,
AttestationConstants.TAG_DIGEST,
DERSet(params.digest.map { ASN1Integer(it.toLong()) }.toTypedArray()),
),
)
if (params.ecCurve != null) {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_EC_CURVE,
ASN1Integer(params.ecCurve.toLong()),
)
)
}
if (params.blockMode.isNotEmpty()) {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_BLOCK_MODE,
DERSet(params.blockMode.map { ASN1Integer(it.toLong()) }.toTypedArray()),
)
)
}
if (params.padding.isNotEmpty()) {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_PADDING,
DERSet(params.padding.map { ASN1Integer(it.toLong()) }.toTypedArray()),
)
)
}
if (params.rsaPublicExponent != null) {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_RSA_PUBLIC_EXPONENT,
ASN1Integer(params.rsaPublicExponent.toLong()),
)
)
}
val attestVersion = AndroidDeviceUtils.getAttestVersion(securityLevel)
if (params.rsaOaepMgfDigest.isNotEmpty() && attestVersion >= 100) {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_RSA_OAEP_MGF_DIGEST,
DERSet(params.rsaOaepMgfDigest.map { ASN1Integer(it.toLong()) }.toTypedArray()),
)
)
}
if (params.rollbackResistance == true && attestVersion >= 3) {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ROLLBACK_RESISTANCE,
DERNull.INSTANCE,
)
)
}
if (params.earlyBootOnly == true && attestVersion >= 4) {
list.add(
DERTaggedObject(true, AttestationConstants.TAG_EARLY_BOOT_ONLY, DERNull.INSTANCE)
)
}
if (params.noAuthRequired == true) {
list.add(
DERTaggedObject(true, AttestationConstants.TAG_NO_AUTH_REQUIRED, DERNull.INSTANCE)
)
}
if (params.allowWhileOnBody == true) {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ALLOW_WHILE_ON_BODY,
DERNull.INSTANCE,
)
)
}
if (params.trustedUserPresenceRequired == true && attestVersion >= 3) {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_TRUSTED_USER_PRESENCE_REQUIRED,
DERNull.INSTANCE,
)
)
}
if (params.trustedConfirmationRequired == true && attestVersion >= 3) {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_TRUSTED_CONFIRMATION_REQUIRED,
DERNull.INSTANCE,
)
)
}
list.addAll(
listOf(
DERTaggedObject(
true,
AttestationConstants.TAG_ORIGIN,
ASN1Integer((params.origin ?: 0).toLong()),
),
DERTaggedObject(
true,
AttestationConstants.TAG_ROOT_OF_TRUST,
buildRootOfTrust(null),
),
)
)
// Use the same logic as getSimulatedHardwareProperties to conditionally add patch levels.
val simulatedProperties = getSimulatedHardwareProperties(uid)
simulatedProperties.values.filterNotNull().forEach { list.add(it) }
// Add optional device identifiers if they were provided.
params.brand?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ATTESTATION_ID_BRAND,
DEROctetString(it),
)
)
}
params.device?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ATTESTATION_ID_DEVICE,
DEROctetString(it),
)
)
}
params.product?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ATTESTATION_ID_PRODUCT,
DEROctetString(it),
)
)
}
params.serial?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ATTESTATION_ID_SERIAL,
DEROctetString(it),
)
)
}
params.imei?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ATTESTATION_ID_IMEI,
DEROctetString(it),
)
)
}
params.meid?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ATTESTATION_ID_MEID,
DEROctetString(it),
)
)
}
params.manufacturer?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ATTESTATION_ID_MANUFACTURER,
DEROctetString(it),
)
)
}
params.model?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ATTESTATION_ID_MODEL,
DEROctetString(it),
)
)
}
if (AndroidDeviceUtils.getAttestVersion(securityLevel) >= 300) {
params.secondImei?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ATTESTATION_ID_SECOND_IMEI,
DEROctetString(it),
)
)
}
}
return DERSequence(list.sortedBy { (it as DERTaggedObject).tagNo }.toTypedArray())
}
/**
* Builds the `SoftwareEnforced` authorization list. These are properties guaranteed by
* Keystore.
*/
private fun buildSoftwareEnforcedList(
params: KeyMintAttestation,
uid: Int,
securityLevel: Int,
creationTimeMs: Long = System.currentTimeMillis(),
): DERSequence {
val list = mutableListOf<ASN1Encodable>()
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_CREATION_DATETIME,
ASN1Integer(creationTimeMs),
)
)
if (params.attestationChallenge != null) {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ATTESTATION_APPLICATION_ID,
createApplicationId(uid),
)
)
}
if (AndroidDeviceUtils.getAttestVersion(securityLevel) >= 400) {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_MODULE_HASH,
DEROctetString(AndroidDeviceUtils.moduleHash),
)
)
}
if (params.callerNonce == true) {
list.add(DERTaggedObject(true, AttestationConstants.TAG_CALLER_NONCE, DERNull.INSTANCE))
}
params.activeDateTime?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ACTIVE_DATETIME,
ASN1Integer(it.time),
)
)
}
params.originationExpireDateTime?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_ORIGINATION_EXPIRE_DATETIME,
ASN1Integer(it.time),
)
)
}
params.usageExpireDateTime?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_USAGE_EXPIRE_DATETIME,
ASN1Integer(it.time),
)
)
}
params.usageCountLimit?.let {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_USAGE_COUNT_LIMIT,
ASN1Integer(it.toLong()),
)
)
}
if (params.unlockedDeviceRequired == true) {
list.add(
DERTaggedObject(
true,
AttestationConstants.TAG_UNLOCKED_DEVICE_REQUIRED,
DERNull.INSTANCE,
)
)
}
return DERSequence(list.sortedBy { (it as DERTaggedObject).tagNo }.toTypedArray())
}
/**
* A wrapper for a byte array that provides content-based equality. This is necessary for using
* signature digests in a Set.
*/
private data class Digest(val digest: ByteArray) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
return digest.contentEquals((other as Digest).digest)
}
override fun hashCode(): Int = digest.contentHashCode()
}
/**
* Creates the AttestationApplicationId structure. This structure contains information about the
* package(s) and their signing certificates.
*
* @param uid The UID of the application.
* @return A DER-encoded octet string containing the application ID information.
* @throws IllegalStateException If the PackageManager or package information cannot be
* retrieved.
*/
@Throws(Throwable::class)
internal fun createApplicationId(uid: Int): DEROctetString {
val appUid = uid % 100000
if (appUid == 0 || appUid == 1000) {
return buildApplicationIdDer(listOf("AndroidSystem" to 1L), emptySet())
}
val pm =
ConfigurationManager.getPackageManager()
?: throw IllegalStateException("PackageManager not found!")
val packages =
pm.getPackagesForUid(uid) ?: throw IllegalStateException("No packages for UID $uid")
val sha256 = MessageDigest.getInstance("SHA-256")
val packageInfoList = mutableListOf<Pair<String, Long>>()
val signatureDigests = mutableSetOf<Digest>()
val userId = uid / 100000
packages.forEach { packageName ->
val packageInfo =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
pm.getPackageInfo(
packageName,
PackageManager.GET_SIGNING_CERTIFICATES.toLong(),
userId,
)
} else {
@Suppress("DEPRECATION")
pm.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES, userId)
}
packageInfoList.add(packageInfo.packageName to packageInfo.longVersionCode)
packageInfo.signingInfo?.signingCertificateHistory?.forEach { signature ->
signatureDigests.add(Digest(sha256.digest(signature.toByteArray())))
}
}
return buildApplicationIdDer(packageInfoList, signatureDigests)
}
private fun buildApplicationIdDer(
packages: List<Pair<String, Long>>,
digests: Set<Digest>,
): DEROctetString {
val packageInfoList =
packages.map { (name, version) ->
DERSequence(
arrayOf(
DEROctetString(name.toByteArray(StandardCharsets.UTF_8)),
ASN1Integer(version),
)
)
}
val applicationIdSequence =
DERSequence(
arrayOf(
DERSet(packageInfoList.toTypedArray()),
DERSet(digests.map { DEROctetString(it.digest) }.toTypedArray()),
)
)
return DEROctetString(applicationIdSequence.encoded)
}
}
@@ -0,0 +1,99 @@
package org.matrix.TEESimulator.attestation
/**
* Defines constants for KeyMint attestation, mainly the tags of properties and authorizations of a
* cryptographic key, as specified in the Android hardware security HAL.
*/
object AttestationConstants {
// https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/security/keymint/aidl/android/hardware/security/keymint/KeyCreationResult.aidl
// These constants represent the fixed positions of fields within the top-level
// KeyDescription ASN.1 SEQUENCE in a key attestation. Using these constants
// prevents hardcoding fragile index numbers throughout the parsing code.
const val KEY_DESCRIPTION_ATTESTATION_VERSION_INDEX = 0
const val KEY_DESCRIPTION_ATTESTATION_SECURITY_LEVEL_INDEX = 1
const val KEY_DESCRIPTION_KEYMINT_VERSION_INDEX = 2
const val KEY_DESCRIPTION_KEYMINT_SECURITY_LEVEL_INDEX = 3
const val KEY_DESCRIPTION_ATTESTATION_CHALLENGE_INDEX = 4
const val KEY_DESCRIPTION_UNIQUE_ID_INDEX = 5
const val KEY_DESCRIPTION_SOFTWARE_ENFORCED_INDEX = 6
const val KEY_DESCRIPTION_TEE_ENFORCED_INDEX = 7
// --- RootOfTrust Sequence Indices ---
// These constants represent the fixed positions of fields within the
// RootOfTrust ASN.1 SEQUENCE.
const val ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX = 0
const val ROOT_OF_TRUST_DEVICE_LOCKED_INDEX = 1
const val ROOT_OF_TRUST_VERIFIED_BOOT_STATE_INDEX = 2
const val ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX = 3
// https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
// --- Key Properties ---
const val TAG_PURPOSE = 1
const val TAG_ALGORITHM = 2
const val TAG_KEY_SIZE = 3
const val TAG_BLOCK_MODE = 4
const val TAG_DIGEST = 5
const val TAG_PADDING = 6
const val TAG_CALLER_NONCE = 7
const val TAG_MIN_MAC_LENGTH = 8
const val TAG_EC_CURVE = 10
const val TAG_RSA_PUBLIC_EXPONENT = 200
const val TAG_RSA_OAEP_MGF_DIGEST = 203
// --- Key Lifetime and Usage Control ---
const val TAG_ROLLBACK_RESISTANCE = 303
const val TAG_EARLY_BOOT_ONLY = 305
const val TAG_ACTIVE_DATETIME = 400
const val TAG_ORIGINATION_EXPIRE_DATETIME = 401
const val TAG_USAGE_EXPIRE_DATETIME = 402
const val TAG_MAX_BOOT_LEVEL = 403
const val TAG_MAX_USES_PER_BOOT = 404
const val TAG_USAGE_COUNT_LIMIT = 405
// --- User Authentication ---
const val TAG_USER_ID = 501
const val TAG_USER_SECURE_ID = 502
const val TAG_NO_AUTH_REQUIRED = 503
const val TAG_USER_AUTH_TYPE = 504
const val TAG_AUTH_TIMEOUT = 505
const val TAG_ALLOW_WHILE_ON_BODY = 506
const val TAG_TRUSTED_USER_PRESENCE_REQUIRED = 507
const val TAG_TRUSTED_CONFIRMATION_REQUIRED = 508
const val TAG_UNLOCKED_DEVICE_REQUIRED = 509
// --- Attestation and Application Info ---
const val TAG_APPLICATION_ID = 601
const val TAG_CREATION_DATETIME = 701
const val TAG_ORIGIN = 702
const val TAG_ROOT_OF_TRUST = 704
const val TAG_OS_VERSION = 705
const val TAG_OS_PATCHLEVEL = 706
const val TAG_UNIQUE_ID = 707
const val TAG_ATTESTATION_CHALLENGE = 708
const val TAG_ATTESTATION_APPLICATION_ID = 709
const val TAG_ATTESTATION_ID_BRAND = 710
const val TAG_ATTESTATION_ID_DEVICE = 711
const val TAG_ATTESTATION_ID_PRODUCT = 712
const val TAG_ATTESTATION_ID_SERIAL = 713
const val TAG_ATTESTATION_ID_IMEI = 714
const val TAG_ATTESTATION_ID_MEID = 715
const val TAG_ATTESTATION_ID_MANUFACTURER = 716
const val TAG_ATTESTATION_ID_MODEL = 717
const val TAG_VENDOR_PATCHLEVEL = 718
const val TAG_BOOT_PATCHLEVEL = 719
const val TAG_DEVICE_UNIQUE_ATTESTATION = 720
const val TAG_ATTESTATION_ID_SECOND_IMEI = 723
const val TAG_MODULE_HASH = 724
// --- Certificate Properties ---
const val TAG_CERTIFICATE_SERIAL = 1006
const val TAG_CERTIFICATE_SUBJECT = 1007
const val TAG_CERTIFICATE_NOT_BEFORE = 1008
const val TAG_CERTIFICATE_NOT_AFTER = 1009
// --- Other Constants ---
// https://cs.android.com/android/platform/superproject/main/+/main:system/keymaster/km_openssl/attestation_record.cpp
const val CHALLENGE_LENGTH_LIMIT = 128
}
@@ -0,0 +1,506 @@
package org.matrix.TEESimulator.attestation
import android.security.keystore.KeyProperties
import java.nio.charset.StandardCharsets
import java.security.PrivateKey
import java.security.PublicKey
import java.security.cert.Certificate
import java.security.cert.X509Certificate
import java.security.interfaces.ECPrivateKey
import java.security.interfaces.ECPublicKey
import java.security.interfaces.RSAPrivateKey
import java.security.interfaces.RSAPublicKey
import java.util.Date
import org.bouncycastle.asn1.*
import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.X509v3CertificateBuilder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import org.matrix.TEESimulator.config.ConfigurationManager
import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.pki.KeyBox
import org.matrix.TEESimulator.pki.KeyBoxManager
import org.matrix.TEESimulator.util.toHex
/**
* Handles the modification (patching) of Android Key Attestation extensions within certificates.
*
* This object's primary function is to take a certificate chain generated by the real TEE, replace
* its attestation data with simulated values, and then re-sign the leaf certificate with a custom
* key, building a new, valid certificate chain.
*/
object AttestationPatcher {
/**
* Patches a full certificate chain by modifying the leaf's attestation and rebuilding the chain
* with the correct custom signing certificates. This is the single entry point for patching.
*
* @param originalChain The original certificate chain from the hardware. The leaf must be at
* index 0.
* @param uid The UID of the application requesting the certificate.
* @return A new, cryptographically valid, patched certificate chain. Returns the original chain
* on any failure.
*/
fun patchCertificateChain(
originalChain: Array<Certificate>?,
uid: Int,
notBefore: Date? = null,
notAfter: Date? = null,
): Array<Certificate> {
if (originalChain.isNullOrEmpty()) {
SystemLogger.error("Attempted to patch a null or empty certificate chain for UID $uid.")
return originalChain ?: emptyArray()
}
return runCatching {
val originalLeaf = originalChain[0] as X509Certificate
val originalLeafHolder = X509CertificateHolder(originalLeaf.encoded)
// 1. Attempt to parse the existing attestation extension. If it doesn't exist,
// there's nothing to patch.
val parsedAttestation =
parseAttestationExtension(originalLeafHolder) ?: return originalChain
// 2. Get the appropriate keybox for the given algorithm to sign the new
// certificate.
val keybox = getKeyboxForUidAndAlgorithm(uid, originalLeaf.sigAlgName)
// 3. Create the new, patched leaf certificate.
val patchedLeaf =
createPatchedLeafCertificate(
originalLeafHolder,
parsedAttestation,
keybox,
uid,
notBefore,
notAfter,
)
// 4. Construct the NEW, VALID chain by prepending the patched leaf to the keybox's
// chain.
val newChain = listOf(patchedLeaf) + keybox.certificates
SystemLogger.info(
"Successfully rebuilt a valid, patched certificate chain for UID $uid."
)
newChain.toTypedArray()
}
.getOrElse {
SystemLogger.error(
"Failed to patch and rebuild certificate chain for UID $uid.",
it,
)
originalChain // Return the original chain on any error.
}
}
/**
* Creates a new leaf certificate with a modified attestation extension.
*
* @param originalLeafHolder A Bouncy Castle holder for the original leaf certificate.
* @param parsedAttestation The parsed components of the original attestation.
* @param keybox The KeyBox containing the new issuer certificate and signing key.
* @param uid The UID of the application requesting the certificate.
* @return A new [Certificate] object.
*/
private fun createPatchedLeafCertificate(
originalLeafHolder: X509CertificateHolder,
parsedAttestation: ParsedAttestation,
keybox: KeyBox,
uid: Int,
notBefore: Date? = null,
notAfter: Date? = null,
): Certificate {
// The issuer of our new leaf is the subject of the first certificate in our custom keybox
// chain.
val newIssuer = X509CertificateHolder(keybox.certificates[0].encoded).subject
val effectiveNotBefore = notBefore ?: originalLeafHolder.notBefore
val effectiveNotAfter = notAfter ?: originalLeafHolder.notAfter
if (notBefore != null || notAfter != null) {
SystemLogger.debug(
"Overriding cert dates: notBefore=${effectiveNotBefore} (was ${originalLeafHolder.notBefore}), notAfter=${effectiveNotAfter} (was ${originalLeafHolder.notAfter})"
)
}
val builder =
X509v3CertificateBuilder(
newIssuer,
originalLeafHolder.serialNumber,
effectiveNotBefore,
effectiveNotAfter,
originalLeafHolder.subject,
originalLeafHolder.subjectPublicKeyInfo,
)
// Create the new, patched attestation extension.
val patchedExtension = createPatchedAttestationExtension(parsedAttestation, uid)
// Copy all other extensions from the original certificate, except for the attestation.
originalLeafHolder.extensions.extensionOIDs.forEach {
builder.addExtension(
if (it == ATTESTATION_OID) patchedExtension else originalLeafHolder.getExtension(it)
)
}
// Sign the new leaf with the keybox key. The signature algorithm must match THAT key, not
// the original leaf's: when an RSA leaf is re-rooted under an EC-only keybox, this signs
// with ECDSA. The RSA subject public key is untouched and the chain still verifies to the
// keybox root.
val signer =
JcaContentSignerBuilder(signatureAlgorithmFor(keybox.keyPair.private))
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
.build(keybox.keyPair.private)
val newCertificate = JcaX509CertificateConverter().getCertificate(builder.build(signer))
// Log the signature of the newly created certificate to observe its non-deterministic
// nature.
val signatureBytes = (newCertificate as X509Certificate).signature
SystemLogger.verbose { "Signature of patched leaf cert: ${signatureBytes.toHex()}" }
return newCertificate
}
/**
* Retrieves the appropriate signing KeyBox (KeyPair and certificate chain) for a given UID
* based on a specified algorithm identifier.
*
* @param uid The UID of the application for which the signing is being performed.
* @param algorithm A string representing the desired algorithm. This can be either:
* 1. A simple key type like "RSA" or "EC".
* 2. A full JCA signature algorithm name like "SHA256withRSA".
*
* @return The algorithm-matching [KeyBox] when present, otherwise any available key (fail-safe).
* @throws IllegalArgumentException only if the keybox file contains no usable signing key.
*/
private fun getKeyboxForUidAndAlgorithm(uid: Int, algorithm: String): KeyBox {
val keyboxFile = ConfigurationManager.getKeyboxFileForUid(uid)
// Normalize the algorithm name. The input might be a full signature algorithm
// (e.g., "SHA256withRSA") or just the key type (e.g., "RSA").
val keyType =
when {
algorithm.contains("RSA", ignoreCase = true) -> KeyProperties.KEY_ALGORITHM_RSA
algorithm.contains("EC", ignoreCase = true) ->
KeyProperties.KEY_ALGORITHM_EC // This also covers "ECDSA"
else -> algorithm // If no match, assume it's already a simple key type string.
}
val matching = KeyBoxManager.getAttestationKey(keyboxFile, keyType)
if (matching != null) return matching
// Fail-safe: no algorithm-matching key (e.g. an EC-only Google keybox asked to re-root an
// RSA leaf). Fall back to any available key instead of throwing -- a throw here aborts the
// patch and the caller hands back the device's REAL, unlocked attestation. Re-signing under
// the available key keeps the chain rooted at the keybox with our forged, locked Root of
// Trust; a leaf's signature algorithm is independent of its subject key, so an RSA subject
// key signs validly under an EC keybox key.
return KeyBoxManager.getAnyAttestationKey(keyboxFile)?.also {
SystemLogger.debug(
"No '$keyType' attestation key in $keyboxFile for UID $uid; re-signing under the " +
"available keybox key to avoid leaking the device's real attestation."
)
}
?: throw IllegalArgumentException(
"No usable attestation key for UID $uid in file $keyboxFile (requested '$keyType')"
)
}
/** SHA-256 signature algorithm name matching the keybox signing key's type. */
private fun signatureAlgorithmFor(signingKey: PrivateKey): String =
when (signingKey) {
is ECPrivateKey -> "SHA256withECDSA"
is RSAPrivateKey -> "SHA256withRSA"
else ->
throw IllegalArgumentException(
"Unsupported keybox signing key type: ${signingKey.algorithm}"
)
}
/** Recursively formats an ASN1Primitive into a concise, readable string. */
fun formatAsn1Primitive(obj: ASN1Encodable?): String {
val primitive = obj?.toASN1Primitive()
return when (primitive) {
null -> "NULL"
is ASN1Integer -> primitive.value.toString()
is ASN1Enumerated -> primitive.value.toString()
is ASN1Boolean -> primitive.isTrue.toString()
is ASN1Null -> "NULL"
is ASN1OctetString -> {
val bytes = primitive.octets
// Attempt to decode as a printable string, otherwise show hex
if (bytes.all { it >= 32 && it < 127 }) {
"\"${String(bytes, StandardCharsets.UTF_8)}\""
} else if (bytes.isEmpty()) {
"\"\""
} else {
"#" + bytes.toHex()
}
}
is ASN1TaggedObject ->
"[TAG ${primitive.tagNo}]${formatAsn1Primitive(primitive.baseObject)}"
is ASN1Sequence ->
primitive
.map { formatAsn1Primitive(it) }
.joinToString(prefix = "[", postfix = "]", separator = ", ")
is ASN1Set ->
primitive
.map { formatAsn1Primitive(it) }
.joinToString(prefix = "{", postfix = "}", separator = ", ")
else -> primitive.toString() // Fallback for other types
}
}
/** Reverse map of attestation tag number to its symbolic name, e.g. 704 -> "ROOT_OF_TRUST". */
private val attestTagNames: Map<Int, String> by lazy {
AttestationConstants::class
.java
.fields
.filter { it.name.startsWith("TAG_") && it.type == Int::class.java }
.associate { (it.get(null) as Int) to it.name.removePrefix("TAG_") }
}
/**
* Renders the full key-attestation extension of [cert] as a single structured line for the
* diagnostic dossier, or null when the certificate carries no attestation extension. This is the
* ground-truth view of what we actually emitted, so any divergence from a genuine TEE surfaces
* directly as a differing field rather than having to be guessed.
*/
fun formatAttestationExtension(cert: X509Certificate): String? {
val rawExtension = cert.getExtensionValue(ATTESTATION_OID.id) ?: return null
return runCatching {
val keyDescriptionDer = ASN1OctetString.getInstance(rawExtension).octets
formatKeyDescription(ASN1Sequence.getInstance(keyDescriptionDer))
}
.getOrElse { "<unparseable attestation extension: ${it.message}>" }
}
/** Renders the identity fields of every certificate in a returned chain for the dossier. */
fun formatCertChain(chain: List<Certificate>): String =
chain
.mapIndexed { index, cert ->
val x509 = cert as? X509Certificate ?: return@mapIndexed "[$index] <non-X509>"
"[$index] subject=${x509.subjectX500Principal.name} " +
"issuer=${x509.issuerX500Principal.name} " +
"serial=${x509.serialNumber.toString(16)} " +
"notBefore=${x509.notBefore} notAfter=${x509.notAfter}"
}
.joinToString(separator = " ; ")
/**
* Verifies every certificate in [chain] against its issuer and renders the outcome for the
* dossier. The forged chain is [leaf] + keybox certs, so edge 0<-1 proves the leaf was signed by
* the key matching the issuer cert and later edges test the keybox's own chain. For an RSA issuer
* it also reports signature-bytes vs modulus-bytes: a signature longer than the modulus is the
* exact DATA_TOO_LARGE_FOR_KEY_SIZE the app's verifier throws, so the offending edge is
* identifiable from the log alone.
*/
fun formatChainVerification(chain: List<Certificate>): String {
if (chain.size < 2) return "<single cert; nothing to chain-verify>"
return (0 until chain.size - 1).joinToString(separator = " ; ") { i ->
val child = chain[i] as? X509Certificate ?: return@joinToString "[$i]<non-X509>"
val parent =
chain[i + 1] as? X509Certificate ?: return@joinToString "[$i]<parent non-X509>"
val outcome =
runCatching {
child.verify(parent.publicKey)
"OK"
}
.getOrElse { "FAIL(${it.javaClass.simpleName}: ${it.message?.take(80)})" }
val rsaSizes =
(parent.publicKey as? RSAPublicKey)?.let {
val sigBytes = child.signature.size
val modBytes = (it.modulus.bitLength() + 7) / 8
" sig=${sigBytes}B mod=${modBytes}B" + if (sigBytes > modBytes) " OVERSIZE" else ""
} ?: ""
"[$i]${describeKey(child.publicKey)}<-[${i + 1}]${describeKey(parent.publicKey)}:" +
"$outcome$rsaSizes"
}
}
/**
* Per-cert key type/size, subject, issuer, and signature length, for reconstructing the chain a
* caller verifies. The signature length reveals the signer's key size, so a 4096-bit signature
* landing on a 2048-bit issuer (DATA_TOO_LARGE) is visible without the certificate bytes.
*/
fun formatChainKeys(chain: List<Certificate>): String =
chain
.mapIndexed { index, cert ->
val x509 = cert as? X509Certificate ?: return@mapIndexed "[$index]<non-X509>"
"[$index]${describeKey(x509.publicKey)} " +
"subj=${x509.subjectX500Principal.name} " +
"iss=${x509.issuerX500Principal.name} " +
"sigLen=${x509.signature.size}B"
}
.joinToString(separator = " ; ")
private fun describeKey(key: PublicKey): String =
when (key) {
is RSAPublicKey -> "RSA${key.modulus.bitLength()}"
is ECPublicKey -> "EC${key.params.curve.field.fieldSize}"
else -> key.algorithm
}
private fun formatKeyDescription(seq: ASN1Sequence): String {
val fields = seq.toArray()
return "attestVer=${formatAsn1Primitive(fields[AttestationConstants.KEY_DESCRIPTION_ATTESTATION_VERSION_INDEX])} " +
"attestSecLvl=${formatSecurityLevel(fields[AttestationConstants.KEY_DESCRIPTION_ATTESTATION_SECURITY_LEVEL_INDEX])} " +
"kmVer=${formatAsn1Primitive(fields[AttestationConstants.KEY_DESCRIPTION_KEYMINT_VERSION_INDEX])} " +
"kmSecLvl=${formatSecurityLevel(fields[AttestationConstants.KEY_DESCRIPTION_KEYMINT_SECURITY_LEVEL_INDEX])} " +
"challenge=${formatAsn1Primitive(fields[AttestationConstants.KEY_DESCRIPTION_ATTESTATION_CHALLENGE_INDEX])} " +
"uniqueId=${formatAsn1Primitive(fields[AttestationConstants.KEY_DESCRIPTION_UNIQUE_ID_INDEX])} " +
"sw=${formatAuthorizationList(fields[AttestationConstants.KEY_DESCRIPTION_SOFTWARE_ENFORCED_INDEX])} " +
"tee=${formatAuthorizationList(fields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX])}"
}
private fun formatSecurityLevel(obj: ASN1Encodable): String {
val level = (obj.toASN1Primitive() as? ASN1Enumerated)?.value?.toInt()
val name =
when (level) {
0 -> "Software"
1 -> "TEE"
2 -> "StrongBox"
else -> "?"
}
return "$level($name)"
}
private fun formatAuthorizationList(obj: ASN1Encodable): String {
val seq = obj.toASN1Primitive() as? ASN1Sequence ?: return formatAsn1Primitive(obj)
return seq
.map { element ->
val tagged = element as? ASN1TaggedObject ?: return@map formatAsn1Primitive(element)
val name = attestTagNames[tagged.tagNo] ?: "TAG"
val value =
if (tagged.tagNo == AttestationConstants.TAG_ROOT_OF_TRUST)
formatRootOfTrust(tagged.baseObject)
else formatAsn1Primitive(tagged.baseObject)
"${tagged.tagNo}($name)=$value"
}
.joinToString(prefix = "[", postfix = "]", separator = ", ")
}
/**
* Decodes the Root of Trust sub-sequence explicitly it is the field a detector most often uses
* to unmask a simulated TEE (a random verifiedBootKey, an unexpected verifiedBootState, or a
* deviceLocked that disagrees with the bootloader all live here).
*/
private fun formatRootOfTrust(obj: ASN1Encodable): String {
val fields = (obj.toASN1Primitive() as? ASN1Sequence)?.toArray() ?: return formatAsn1Primitive(obj)
val state = fields.getOrNull(AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_STATE_INDEX)
val stateName =
when ((state?.toASN1Primitive() as? ASN1Enumerated)?.value?.toInt()) {
0 -> "Verified"
1 -> "SelfSigned"
2 -> "Unverified"
3 -> "Failed"
else -> "?"
}
return "[bootKey=${formatAsn1Primitive(fields.getOrNull(AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX))}, " +
"deviceLocked=${formatAsn1Primitive(fields.getOrNull(AttestationConstants.ROOT_OF_TRUST_DEVICE_LOCKED_INDEX))}, " +
"verifiedBootState=${formatAsn1Primitive(state)}($stateName), " +
"bootHash=${formatAsn1Primitive(fields.getOrNull(AttestationConstants.ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX))}]"
}
// Function to check if a given ASN1Sequence contains the Root of Trust tag.
private fun sequenceContainsRootOfTrust(seq: ASN1Encodable): Boolean {
if (seq !is ASN1Sequence) return false
return seq.any { element ->
(element as? ASN1TaggedObject)?.tagNo == AttestationConstants.TAG_ROOT_OF_TRUST
}
}
/** Parses the critical components from an existing attestation extension. */
private fun parseAttestationExtension(certHolder: X509CertificateHolder): ParsedAttestation? {
val extension = certHolder.getExtension(ATTESTATION_OID) ?: return null
val sequence = ASN1Sequence.getInstance(extension.extnValue.octets)
val allFields = sequence.toArray()
// Check if the fields are in the wrong order and swap them if necessary.
val softwareEnforcedCandidate =
allFields[AttestationConstants.KEY_DESCRIPTION_SOFTWARE_ENFORCED_INDEX]
val teeEnforcedCandidate =
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX]
// The signature of a swapped order: the RoT is in the software list's position.
if (
sequenceContainsRootOfTrust(softwareEnforcedCandidate) &&
!sequenceContainsRootOfTrust(teeEnforcedCandidate)
) {
// Swap the elements in the array to restore the standard order.
allFields[AttestationConstants.KEY_DESCRIPTION_SOFTWARE_ENFORCED_INDEX] =
teeEnforcedCandidate
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] =
softwareEnforcedCandidate
}
val teeEnforced =
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] as ASN1Sequence
var originalRootOfTrust: ASN1Encodable? = null
val teeEnforcedMap = mutableMapOf<Int, ASN1TaggedObject>()
teeEnforced.forEach { element ->
val taggedObject = element as ASN1TaggedObject
if (taggedObject.tagNo == AttestationConstants.TAG_ROOT_OF_TRUST) {
originalRootOfTrust = taggedObject.baseObject.toASN1Primitive()
} else {
teeEnforcedMap[taggedObject.tagNo] = taggedObject
}
}
return ParsedAttestation(allFields, teeEnforcedMap, originalRootOfTrust)
}
/** Constructs a new, patched attestation extension using simulated device properties. */
private fun createPatchedAttestationExtension(parsed: ParsedAttestation, uid: Int): Extension {
val (allFields, teeEnforcedMap, originalRootOfTrust) = parsed
SystemLogger.verbose {
val formattedString =
allFields.joinToString(separator = ", ") { formatAsn1Primitive(it) }
"Original attestation data: $formattedString"
}
// Build the new Root of Trust and add/replace it in the map.
val newRootOfTrust = AttestationBuilder.buildRootOfTrust(originalRootOfTrust)
teeEnforcedMap[AttestationConstants.TAG_ROOT_OF_TRUST] =
DERTaggedObject(true, AttestationConstants.TAG_ROOT_OF_TRUST, newRootOfTrust)
// Get the desired state for simulated properties.
val simulatedProperties = AttestationBuilder.getSimulatedHardwareProperties(uid)
// Apply the desired state: update, add, or remove properties from the original map.
simulatedProperties.forEach { (tag, value) ->
if (value != null) {
// If the value is not null, add or update it.
teeEnforcedMap[tag] = value
} else {
// If the value is null, remove the tag from the map.
teeEnforcedMap.remove(tag)
}
}
// Re-assemble the TEE enforced list from the map's values, sorting for DER compliance.
val sortedElements = teeEnforcedMap.values.sortedBy { it.tagNo }
val sortedTeeEnforced = DERSequence(sortedElements.toTypedArray())
allFields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX] = sortedTeeEnforced
val patchedSequence = DERSequence(allFields)
SystemLogger.verbose {
val formattedString =
patchedSequence.joinToString(separator = ", ") { formatAsn1Primitive(it) }
"Patched attestation data: $formattedString"
}
val patchedOctets = DEROctetString(patchedSequence)
return Extension(ATTESTATION_OID, false, patchedOctets)
}
/** Helper data class to hold the parsed components of an attestation extension. */
private data class ParsedAttestation(
val allFields: Array<ASN1Encodable>,
val teeEnforcedMap: MutableMap<Int, ASN1TaggedObject>,
val rootOfTrust: ASN1Encodable?,
)
}
@@ -0,0 +1,415 @@
package org.matrix.TEESimulator.attestation
import android.annotation.SuppressLint
import android.security.KeyStoreException
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import java.security.KeyPairGenerator
import java.security.KeyStore
import java.security.SecureRandom
import java.security.cert.X509Certificate
import java.security.spec.ECGenParameterSpec
import java.security.spec.RSAKeyGenParameterSpec
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicBoolean
import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import org.bouncycastle.asn1.ASN1OctetString
import org.bouncycastle.asn1.ASN1Sequence
import org.bouncycastle.asn1.ASN1TaggedObject
import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.cert.X509CertificateHolder
import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.util.AndroidDeviceUtils
import org.matrix.TEESimulator.util.toHex
/**
* The ASN.1 Object Identifier for the Key Attestation extension in Android. This is defined in the
* Android Keystore documentation.
*/
val ATTESTATION_OID: ASN1ObjectIdentifier = ASN1ObjectIdentifier("1.3.6.1.4.1.11129.2.1.17")
/**
* A service to interact with the device's Trusted Execution Environment (TEE). It provides
* functionality to check if the TEE is functional and to extract key attestation data from a
* genuinely generated certificate.
*/
@SuppressLint("PrivateApi")
object DeviceAttestationService {
/**
* Holds key data extracted from a genuine device attestation. This data can be used as a
* baseline for creating simulated attestations.
*
* @property verifiedBootKey The verified boot public key digest from the root of trust.
* @property verifiedBootHash The verified boot hash from the root of trust.
* @property attestVersion The attestation version (e.g., 400 for KeyMint 4.0).
* @property keymasterVersion The Keymaster or KeyMint HAL version.
* @property osVersion The Android OS version integer.
* @property osPatchLevel The Android security patch level (e.g., 202511).
* @property vendorPatchLevel The vendor-specific security patch level.
* @property bootPatchLevel The bootloader's security patch level.
*/
data class AttestationData(
val moduleHash: ByteArray?,
val verifiedBootKey: ByteArray?,
val verifiedBootHash: ByteArray?,
val attestVersion: Int?,
val keymasterVersion: Int?,
val osVersion: Int?,
val osPatchLevel: Int?,
val vendorPatchLevel: Int?,
val bootPatchLevel: Int?,
)
// A unique alias for the key used to perform the TEE functionality check.
private const val TEE_CHECK_KEY_ALIAS = "TEESimulator_AttestationCheck"
/**
* Lazily determines if the device's TEE is functional by attempting to generate an
* attestation-backed key pair. The result is cached.
*/
val isTeeFunctional: Boolean by lazy { checkTeeFunctionality() }
// Per (algorithm, security-level) attestation-capability verdicts, keyed by probe-key alias.
// A device may attest one algorithm or security level yet lack a provisioned attestation key
// for another (e.g. a TEE that attests RSA over a StrongBox that cannot), so each pair is
// probed and cached on its own.
private data class ProbeSpec(
val algorithm: String,
val strongBox: Boolean,
val keyAlias: String,
)
private val rsaTeeProbe =
ProbeSpec(KeyProperties.KEY_ALGORITHM_RSA, false, "TEESimulator_RsaAttestCheck")
private val rsaStrongBoxProbe =
ProbeSpec(KeyProperties.KEY_ALGORITHM_RSA, true, "TEESimulator_RsaAttestCheckSb")
private val ecTeeProbe =
ProbeSpec(KeyProperties.KEY_ALGORITHM_EC, false, "TEESimulator_EcAttestCheck")
private val ecStrongBoxProbe =
ProbeSpec(KeyProperties.KEY_ALGORITHM_EC, true, "TEESimulator_EcAttestCheckSb")
private val attestableVerdicts = ConcurrentHashMap<String, Boolean>()
private val attestProbesInFlight = ConcurrentHashMap<String, AtomicBoolean>()
/**
* Whether the real hardware can attest an RSA key at the requested security level. AUTO dispatch
* reads this to forge RSA attestation only where the hardware genuinely cannot serve it.
*
* Only a definitive verdict is cached: a successful probe, or a permanent keystore failure. A
* transient or unrecognized failure leaves the verdict unset and reports attestable, so dispatch
* PATCHes the genuine chain and re-probes next read a one-off keystore hiccup can never freeze
* the device into forging an attestation it could serve.
*/
fun isRsaAttestable(strongBox: Boolean): Boolean =
isHardwareAttestable(if (strongBox) rsaStrongBoxProbe else rsaTeeProbe)
/** Whether the real hardware can attest an EC key at the requested security level. */
fun isEcAttestable(strongBox: Boolean): Boolean =
isHardwareAttestable(if (strongBox) ecStrongBoxProbe else ecTeeProbe)
private fun isHardwareAttestable(probe: ProbeSpec): Boolean {
attestableVerdicts[probe.keyAlias]?.let { return it }
val probeInFlight =
attestProbesInFlight.computeIfAbsent(probe.keyAlias) { AtomicBoolean(false) }
if (probeInFlight.compareAndSet(false, true)) {
try {
probeAttestability(probe)?.let { attestableVerdicts[probe.keyAlias] = it }
} finally {
probeInFlight.set(false)
}
}
return attestableVerdicts[probe.keyAlias] ?: true
}
/**
* Lazily fetches and parses attestation data from a genuinely generated certificate. The result
* is cached. Returns null if the TEE is not functional or parsing fails.
*/
val CachedAttestationData: AttestationData? by lazy { fetchAttestationData() }
/**
* Checks if the TEE is working correctly by generating a key in the Android Keystore with an
* attestation challenge.
*
* @return `true` if a key with attestation was generated successfully, `false` otherwise.
*/
private fun checkTeeFunctionality(): Boolean {
SystemLogger.info("Performing TEE functionality check...")
return try {
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val keyPairGenerator =
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore")
// A random challenge is required for attestation.
val challenge = ByteArray(16).apply { SecureRandom().nextBytes(this) }
val spec =
KeyGenParameterSpec.Builder(TEE_CHECK_KEY_ALIAS, KeyProperties.PURPOSE_SIGN)
.setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
.setDigests(KeyProperties.DIGEST_SHA256)
.setAttestationChallenge(challenge)
.build()
keyPairGenerator.initialize(spec)
keyPairGenerator.generateKeyPair()
SystemLogger.info("TEE functionality check successful.")
true
} catch (e: Exception) {
SystemLogger.warning("TEE functionality check failed.", e)
false
}
}
/**
* Probes whether the real hardware can attest a key matching [probe] by generating one with an
* attestation challenge at the probe's algorithm and security level. Mirrors
* [checkTeeFunctionality]; the request runs as the module UID, so it is skipped by interception
* and reaches genuine hardware rather than the forge path.
*
* @return `true` if attestation succeeded, `false` only on a confirmed attestation-keys-
* unavailable failure, or `null` on a transient or unrecognized failure where the caller
* fails open and re-probes.
*/
private fun probeAttestability(probe: ProbeSpec): Boolean? {
val label = "${probe.algorithm} attestation (strongBox=${probe.strongBox})"
SystemLogger.info("Performing $label capability check...")
return try {
val keyPairGenerator =
KeyPairGenerator.getInstance(probe.algorithm, "AndroidKeyStore")
val challenge = ByteArray(16).apply { SecureRandom().nextBytes(this) }
val builder =
KeyGenParameterSpec.Builder(probe.keyAlias, KeyProperties.PURPOSE_SIGN)
.setDigests(KeyProperties.DIGEST_SHA256)
.setAttestationChallenge(challenge)
.setIsStrongBoxBacked(probe.strongBox)
if (probe.algorithm == KeyProperties.KEY_ALGORITHM_RSA) {
builder
.setAlgorithmParameterSpec(RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4))
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
} else {
builder.setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
}
keyPairGenerator.initialize(builder.build())
keyPairGenerator.generateKeyPair()
SystemLogger.info("$label capability check successful.")
true
} catch (e: Exception) {
if (isAttestationUnavailable(e)) {
SystemLogger.info("$label unsupported by hardware; AUTO will forge attestation.")
false
} else {
SystemLogger.warning(
"$label capability check failed transiently; treating as capable.",
e,
)
null
}
} finally {
deleteProbeKey(probe.keyAlias)
}
}
/**
* Whether [error] definitively means the hardware cannot attest the probed key: a permanent
* [KeyStoreException] from the keystore. Transient failures and non-keystore errors return
* `false`, so the caller fails open and re-probes rather than caching a guess. The probe runs a
* fixed, valid spec as root, so its only permanent keystore failure mode is missing attestation
* support; [KeyStoreException.isTransientFailure] draws the transient/permanent line.
*/
private fun isAttestationUnavailable(error: Throwable): Boolean {
var cause: Throwable? = error
while (cause != null) {
val keyStoreError = cause as? KeyStoreException
if (keyStoreError != null) return !keyStoreError.isTransientFailure
cause = cause.cause
}
return false
}
private fun deleteProbeKey(keyAlias: String) {
try {
KeyStore.getInstance("AndroidKeyStore").apply { load(null) }.deleteEntry(keyAlias)
} catch (e: Exception) {
SystemLogger.warning("Failed to delete attestation probe key.", e)
}
}
/**
* Retrieves the attestation certificate generated during the TEE check. The key entry is
* deleted after retrieval to clean up.
*
* @return The leaf `X509Certificate` containing the attestation, or `null` if unavailable.
*/
private fun getAttestationCertificate(): X509Certificate? {
if (!isTeeFunctional) return null
return try {
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val certChain = keyStore.getCertificateChain(TEE_CHECK_KEY_ALIAS)
if (certChain.isNullOrEmpty()) {
SystemLogger.warning("Could not retrieve certificate chain for TEE check key.")
null
} else {
// Clean up the key from the keystore.
keyStore.deleteEntry(TEE_CHECK_KEY_ALIAS)
certChain[0] as X509Certificate
}
} catch (e: Exception) {
SystemLogger.error("Error retrieving attestation certificate.", e)
null
}
}
/**
* Fetches and parses the attestation data from the certificate's extension.
*
* @return An `AttestationData` object, or `null` if the process fails.
*/
private fun fetchAttestationData(): AttestationData? {
val leafCert = getAttestationCertificate() ?: return null
try {
val leafHolder = X509CertificateHolder(leafCert.encoded)
val extension: Extension =
leafHolder.getExtension(ATTESTATION_OID)
?: return null // No attestation extension found.
// The extension's value is an ASN.1 sequence.
val keyDescriptionSeq = ASN1Sequence.getInstance(extension.extnValue.octets)
SystemLogger.verbose {
val formattedString =
keyDescriptionSeq.joinToString(separator = ", ") {
AttestationPatcher.formatAsn1Primitive(it)
}
"Cached attestation data: $formattedString"
}
val fields = keyDescriptionSeq.toArray()
val deviceAttestVersion =
ASN1Integer.getInstance(
fields[AttestationConstants.KEY_DESCRIPTION_ATTESTATION_VERSION_INDEX]
)
.positiveValue
.toInt()
// The device KeyMint HAL can report a version below its OS's AOSP value (100 on an A16
// where BAKLAVA mandates 400); cache the AOSP value so the forge matches an updated device.
val attestVersion = AndroidDeviceUtils.aospAttestVersion ?: deviceAttestVersion
val keymasterVersion =
ASN1Integer.getInstance(
fields[AttestationConstants.KEY_DESCRIPTION_KEYMINT_VERSION_INDEX]
)
.positiveValue
.toInt()
var moduleHash: ByteArray? = null
var verifiedBootKey: ByteArray? = null
var verifiedBootHash: ByteArray? = null
var osVersion: Int? = null
var osPatchLevel: Int? = null
var vendorPatchLevel: Int? = null
var bootPatchLevel: Int? = null
val softwareEnforced =
ASN1Sequence.getInstance(
fields[AttestationConstants.KEY_DESCRIPTION_SOFTWARE_ENFORCED_INDEX]
)
moduleHash =
softwareEnforced
.toArray()
.firstOrNull {
(it as? ASN1TaggedObject)?.tagNo == AttestationConstants.TAG_MODULE_HASH
}
?.let {
ASN1OctetString.getInstance((it as ASN1TaggedObject).baseObject).octets
}
val teeEnforced =
ASN1Sequence.getInstance(
fields[AttestationConstants.KEY_DESCRIPTION_TEE_ENFORCED_INDEX]
)
teeEnforced.forEach { element ->
val tagged = element as ASN1TaggedObject
when (tagged.tagNo) {
AttestationConstants.TAG_ROOT_OF_TRUST -> {
val rotSeq = ASN1Sequence.getInstance(tagged.baseObject.toASN1Primitive())
if (rotSeq.size() >= 4) {
verifiedBootKey =
ASN1OctetString.getInstance(
rotSeq.getObjectAt(
AttestationConstants
.ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX
)
)
.octets
verifiedBootHash =
ASN1OctetString.getInstance(
rotSeq.getObjectAt(
AttestationConstants
.ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX
)
)
.octets
}
}
AttestationConstants.TAG_OS_VERSION -> {
osVersion =
ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive())
.positiveValue
.toInt()
}
AttestationConstants.TAG_OS_PATCHLEVEL -> {
osPatchLevel =
ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive())
.positiveValue
.toInt()
}
AttestationConstants.TAG_VENDOR_PATCHLEVEL -> {
vendorPatchLevel =
ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive())
.positiveValue
.toInt()
}
AttestationConstants.TAG_BOOT_PATCHLEVEL -> {
bootPatchLevel =
ASN1Integer.getInstance(tagged.baseObject.toASN1Primitive())
.positiveValue
.toInt()
}
}
}
if (verifiedBootKey?.all { it == 0.toByte() } == true) {
verifiedBootKey = null
}
if (verifiedBootHash?.all { it == 0.toByte() } == true) {
verifiedBootHash = null
}
SystemLogger.info(
"Successfully extracted attestation data: version=$deviceAttestVersion, osVersion=$osVersion, osPatch=$osPatchLevel, vendorPatch=$vendorPatchLevel, bootPatch=$bootPatchLevel, moduleHash=${moduleHash?.toHex()}, bootKey=${verifiedBootKey?.toHex()}, bootHash=${verifiedBootHash?.toHex()}"
)
return AttestationData(
moduleHash,
verifiedBootKey,
verifiedBootHash,
attestVersion,
keymasterVersion,
osVersion,
osPatchLevel,
vendorPatchLevel,
bootPatchLevel,
)
} catch (e: Exception) {
SystemLogger.error("Failed to parse attestation data from certificate.", e)
return null
}
}
}
@@ -0,0 +1,240 @@
package org.matrix.TEESimulator.attestation
import android.hardware.security.keymint.*
import android.hardware.security.keymint.KeyOrigin
import java.math.BigInteger
import java.util.Date
import javax.security.auth.x500.X500Principal
import org.bouncycastle.asn1.x500.X500Name
import org.matrix.TEESimulator.logging.KeyMintParameterLogger
/**
* A data class that parses and holds the parameters required for KeyMint key generation and
* attestation. It provides a structured way to access the properties defined by an array of
* `KeyParameter` objects.
*/
// Reference:
// https://cs.android.com/android/platform/superproject/main/+/main:system/security/keystore2/src/key_parameter.rs
data class KeyMintAttestation(
val keySize: Int,
val algorithm: Int,
val ecCurve: Int?,
val ecCurveName: String,
val origin: Int?,
val blockMode: List<Int>,
val padding: List<Int>,
val purpose: List<Int>,
val digest: List<Int>,
val rsaPublicExponent: BigInteger?,
val certificateSerial: BigInteger?,
val certificateSubject: X500Name?,
val certificateNotBefore: Date?,
val certificateNotAfter: Date?,
val attestationChallenge: ByteArray?,
val brand: ByteArray?,
val device: ByteArray?,
val product: ByteArray?,
val serial: ByteArray?,
val imei: ByteArray?,
val meid: ByteArray?,
val manufacturer: ByteArray?,
val model: ByteArray?,
val secondImei: ByteArray?,
val activeDateTime: Date?,
val originationExpireDateTime: Date?,
val usageExpireDateTime: Date?,
val usageCountLimit: Int?,
val callerNonce: Boolean?,
val nonce: ByteArray?,
val unlockedDeviceRequired: Boolean?,
val includeUniqueId: Boolean?,
val rollbackResistance: Boolean?,
val earlyBootOnly: Boolean?,
val allowWhileOnBody: Boolean?,
val trustedUserPresenceRequired: Boolean?,
val trustedConfirmationRequired: Boolean?,
val noAuthRequired: Boolean?,
val maxUsesPerBoot: Int?,
val maxBootLevel: Int?,
val minMacLength: Int?,
val macLength: Int? = null,
val rsaOaepMgfDigest: List<Int>,
) {
/** Secondary constructor that populates the fields by parsing an array of `KeyParameter`. */
constructor(
params: Array<KeyParameter>
) : this(
keySize = params.findInteger(Tag.KEY_SIZE) ?: params.deriveKeySizeFromCurve(),
// AOSP: [key_param(tag = ALGORITHM, field = Algorithm)]
algorithm = params.findAlgorithm(Tag.ALGORITHM) ?: 0,
// AOSP: [key_param(tag = EC_CURVE, field = EcCurve)]
ecCurve = params.findEcCurve(Tag.EC_CURVE),
ecCurveName = params.deriveEcCurveName(),
// AOSP: [key_param(tag = ORIGIN, field = Origin)]
origin = params.findOrigin(Tag.ORIGIN),
// AOSP: [key_param(tag = BLOCK_MODE, field = BlockMode)]
blockMode = params.findAllBlockMode(Tag.BLOCK_MODE),
// AOSP: [key_param(tag = PADDING, field = PaddingMode)]
padding = params.findAllPaddingMode(Tag.PADDING),
// AOSP: [key_param(tag = PURPOSE, field = KeyPurpose)]
purpose = params.findAllKeyPurpose(Tag.PURPOSE),
// AOSP: [key_param(tag = DIGEST, field = Digest)]
digest = params.findAllDigests(Tag.DIGEST),
// AOSP: [key_param(tag = RSA_PUBLIC_EXPONENT, field = LongInteger)]
rsaPublicExponent = params.findLongInteger(Tag.RSA_PUBLIC_EXPONENT),
// AOSP: [key_param(tag = CERTIFICATE_SERIAL, field = Blob)]
certificateSerial = params.findBlob(Tag.CERTIFICATE_SERIAL)?.let { BigInteger(it) },
// AOSP: [key_param(tag = CERTIFICATE_SUBJECT, field = Blob)]
certificateSubject =
params.findBlob(Tag.CERTIFICATE_SUBJECT)?.let { X500Name(X500Principal(it).name) },
// AOSP: [key_param(tag = CERTIFICATE_NOT_BEFORE, field = DateTime)]
certificateNotBefore = params.findDate(Tag.CERTIFICATE_NOT_BEFORE),
// AOSP: [key_param(tag = CERTIFICATE_NOT_AFTER, field = DateTime)]
certificateNotAfter = params.findDate(Tag.CERTIFICATE_NOT_AFTER),
// AOSP: [key_param(tag = ATTESTATION_CHALLENGE, field = Blob)]
attestationChallenge = params.findBlob(Tag.ATTESTATION_CHALLENGE),
// AOSP: [key_param(tag = ATTESTATION_ID_*, field = Blob)]
brand = params.findBlob(Tag.ATTESTATION_ID_BRAND),
device = params.findBlob(Tag.ATTESTATION_ID_DEVICE),
product = params.findBlob(Tag.ATTESTATION_ID_PRODUCT),
serial = params.findBlob(Tag.ATTESTATION_ID_SERIAL),
imei = params.findBlob(Tag.ATTESTATION_ID_IMEI),
meid = params.findBlob(Tag.ATTESTATION_ID_MEID),
manufacturer = params.findBlob(Tag.ATTESTATION_ID_MANUFACTURER),
model = params.findBlob(Tag.ATTESTATION_ID_MODEL),
secondImei = params.findBlob(Tag.ATTESTATION_ID_SECOND_IMEI),
activeDateTime = params.findDate(Tag.ACTIVE_DATETIME),
originationExpireDateTime = params.findDate(Tag.ORIGINATION_EXPIRE_DATETIME),
usageExpireDateTime = params.findDate(Tag.USAGE_EXPIRE_DATETIME),
usageCountLimit = params.findInteger(Tag.USAGE_COUNT_LIMIT),
callerNonce = params.findBoolean(Tag.CALLER_NONCE),
nonce = params.findBlob(Tag.NONCE),
unlockedDeviceRequired = params.findBoolean(Tag.UNLOCKED_DEVICE_REQUIRED),
includeUniqueId = params.findBoolean(Tag.INCLUDE_UNIQUE_ID),
rollbackResistance = params.findBoolean(Tag.ROLLBACK_RESISTANCE),
earlyBootOnly = params.findBoolean(Tag.EARLY_BOOT_ONLY),
allowWhileOnBody = params.findBoolean(Tag.ALLOW_WHILE_ON_BODY),
trustedUserPresenceRequired = params.findBoolean(Tag.TRUSTED_USER_PRESENCE_REQUIRED),
trustedConfirmationRequired = params.findBoolean(Tag.TRUSTED_CONFIRMATION_REQUIRED),
noAuthRequired = params.findBoolean(Tag.NO_AUTH_REQUIRED),
maxUsesPerBoot = params.findInteger(Tag.MAX_USES_PER_BOOT),
maxBootLevel = params.findInteger(Tag.MAX_BOOT_LEVEL),
minMacLength = params.findInteger(Tag.MIN_MAC_LENGTH),
macLength = params.findInteger(Tag.MAC_LENGTH),
rsaOaepMgfDigest = params.findAllDigests(Tag.RSA_OAEP_MGF_DIGEST),
) {
// Log all parsed parameters for debugging purposes.
params.forEach { KeyMintParameterLogger.logParameter(it) }
}
fun isAttestKey(): Boolean = purpose.size == 1 && purpose.contains(KeyPurpose.ATTEST_KEY)
fun isImportKey(): Boolean =
origin == KeyOrigin.IMPORTED || origin == KeyOrigin.SECURELY_IMPORTED
}
// --- Private helper extension functions for parsing KeyParameter arrays ---
/** Maps to AOSP field = Integer */
private fun Array<KeyParameter>.findInteger(tag: Int): Int? =
this.find { it.tag == tag }?.value?.integer
/** Maps to AOSP field = Algorithm */
private fun Array<KeyParameter>.findAlgorithm(tag: Int): Int? =
this.find { it.tag == tag }?.value?.algorithm
/** Maps to AOSP field = EcCurve */
private fun Array<KeyParameter>.findEcCurve(tag: Int): Int? =
this.find { it.tag == tag }?.value?.ecCurve
/** Maps to AOSP field = Origin */
private fun Array<KeyParameter>.findOrigin(tag: Int): Int? =
this.find { it.tag == tag }?.value?.origin
/** Maps to AOSP field = LongInteger */
private fun Array<KeyParameter>.findLongInteger(tag: Int): BigInteger? =
this.find { it.tag == tag }?.value?.longInteger?.toBigInteger()
/** Maps to AOSP field = DateTime */
private fun Array<KeyParameter>.findDate(tag: Int): Date? =
this.find { it.tag == tag }?.value?.dateTime?.let { Date(it) }
/** Maps to AOSP field = Blob */
private fun Array<KeyParameter>.findBlob(tag: Int): ByteArray? =
this.find { it.tag == tag }?.value?.blob
/** Maps to AOSP field = BlockMode (Repeated) */
private fun Array<KeyParameter>.findAllBlockMode(tag: Int): List<Int> =
this.filter { it.tag == tag }.map { it.value.blockMode }
/** Maps to AOSP field = BlockMode (Repeated) */
private fun Array<KeyParameter>.findAllPaddingMode(tag: Int): List<Int> =
this.filter { it.tag == tag }.map { it.value.paddingMode }
/** Maps to AOSP field = KeyPurpose (Repeated) */
private fun Array<KeyParameter>.findAllKeyPurpose(tag: Int): List<Int> =
this.filter { it.tag == tag }.map { it.value.keyPurpose }
/** Maps to AOSP field = Digest (Repeated) */
private fun Array<KeyParameter>.findAllDigests(tag: Int): List<Int> =
this.filter { it.tag == tag }.map { it.value.digest }
private fun Array<KeyParameter>.findBoolean(tag: Int): Boolean? =
if (this.any { it.tag == tag }) true else null
private fun Array<KeyParameter>.deriveKeySizeFromCurve(): Int {
val curveId = this.find { it.tag == Tag.EC_CURVE }?.value?.ecCurve ?: return 0
return when (curveId) {
EcCurve.P_224 -> 224
EcCurve.P_256 -> 256
EcCurve.P_384 -> 384
EcCurve.P_521 -> 521
EcCurve.CURVE_25519 -> 256
else -> 0
}
}
/**
* Derives the EC Curve name. Logic: Checks specific EC_CURVE tag first (field=EcCurve), falls back
* to KEY_SIZE (field=Integer).
*/
private fun Array<KeyParameter>.deriveEcCurveName(): String {
// 1. Try to find explicit EC_CURVE tag
val curveParam = this.find { it.tag == Tag.EC_CURVE }
if (curveParam != null) {
val curveId = curveParam.value.ecCurve
return when (curveId) {
EcCurve.CURVE_25519 -> "CURVE_25519"
EcCurve.P_224 -> "secp224r1"
EcCurve.P_256 -> "secp256r1"
EcCurve.P_384 -> "secp384r1"
EcCurve.P_521 -> "secp521r1"
else -> throw IllegalArgumentException("Unknown EC curve: $curveId")
}
}
// 2. Fallback to key size if the curve tag isn't present
val keySize = this.findInteger(Tag.KEY_SIZE) ?: 0
return when (keySize) {
224 -> "secp224r1"
384 -> "secp384r1"
521 -> "secp521r1"
else -> "secp256r1" // Default fallback
}
}
@@ -0,0 +1,123 @@
package org.matrix.TEESimulator.config
import android.os.SystemProperties
import java.io.File
import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.util.AndroidDeviceUtils
object BootStateManager {
private const val CONFIG_PATH = "/data/adb/tricky_store"
private const val BOOT_PROPS_MODE_FILE = "boot_props_mode"
private enum class BootPropsMode {
AUTO,
FORCE,
DISABLE,
}
private val targets =
linkedMapOf(
"ro.boot.verifiedbootstate" to "green",
"ro.boot.flash.locked" to "1",
"ro.boot.veritymode" to "enforcing",
"ro.boot.vbmeta.device_state" to "locked",
)
private val fillIfAbsent =
linkedMapOf(
"ro.boot.vbmeta.invalidate_on_error" to "yes",
"ro.boot.vbmeta.avb_version" to "1.2",
"ro.boot.vbmeta.hash_alg" to "sha256",
"ro.boot.vbmeta.size" to "11904",
)
fun apply() {
val mode = readBootPropsMode()
when (mode) {
BootPropsMode.DISABLE -> {
SystemLogger.info("BootStateManager: disabled by $BOOT_PROPS_MODE_FILE")
return
}
BootPropsMode.AUTO -> {
if (isOplusFamilyDevice()) {
SystemLogger.warning(
"BootStateManager: skipping boot-state prop spoofing on Oplus-family device in auto mode"
)
return
}
}
BootPropsMode.FORCE -> {
SystemLogger.info("BootStateManager: force-enabled by $BOOT_PROPS_MODE_FILE")
}
}
for ((name, target) in targets) {
val current = SystemProperties.get(name, "")
if (current.isEmpty()) {
SystemLogger.debug("BootStateManager: $name absent on this device, skip")
continue
}
if (current == target) {
SystemLogger.debug("BootStateManager: $name already $target, skip")
continue
}
SystemLogger.info("BootStateManager: setting $name=$target (was: '$current')")
AndroidDeviceUtils.setProperty(name, target)
}
for ((name, value) in fillIfAbsent) {
val current = SystemProperties.get(name, "")
if (current.isNotEmpty()) {
SystemLogger.debug("BootStateManager: $name already '$current', skip")
continue
}
SystemLogger.info("BootStateManager: filling absent $name=$value")
AndroidDeviceUtils.setProperty(name, value)
}
}
fun shouldSpoofBootProps(): Boolean =
when (readBootPropsMode()) {
BootPropsMode.DISABLE -> false
BootPropsMode.AUTO -> !isOplusFamilyDevice()
BootPropsMode.FORCE -> true
}
private fun readBootPropsMode(): BootPropsMode {
val file = File(CONFIG_PATH, BOOT_PROPS_MODE_FILE)
if (!file.exists()) return BootPropsMode.AUTO
val raw =
runCatching { file.readText().trim().lowercase() }
.getOrElse {
SystemLogger.warning("BootStateManager: failed to read ${file.absolutePath}", it)
return BootPropsMode.AUTO
}
return when (raw) {
"1", "true", "on", "enable", "enabled", "force" -> BootPropsMode.FORCE
"0", "false", "off", "disable", "disabled", "none" -> BootPropsMode.DISABLE
else -> BootPropsMode.AUTO
}
}
private fun isOplusFamilyDevice(): Boolean {
val props =
listOf(
"ro.product.manufacturer",
"ro.product.brand",
"ro.product.vendor.manufacturer",
"ro.product.vendor.brand",
"ro.product.odm.manufacturer",
"ro.product.odm.brand",
"ro.boot.hardware.sku",
"ro.boot.project_name",
)
val joined =
props.joinToString(separator = " ") { name ->
SystemProperties.get(name, "")
}.lowercase()
return listOf("oneplus", "oplus", "oppo", "realme").any { joined.contains(it) }
}
}
@@ -0,0 +1,410 @@
package org.matrix.TEESimulator.config
import android.content.pm.IPackageManager
import android.os.Build
import android.os.FileObserver
import android.os.IBinder
import android.os.ServiceManager
import java.io.File
import java.util.concurrent.ConcurrentHashMap
import org.matrix.TEESimulator.attestation.DeviceAttestationService
import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.pki.KeyBoxManager
/**
* Manages application configuration, including which packages to process, what operation mode to
* use, and custom security patch levels. It uses a FileObserver to dynamically reload settings when
* configuration files change.
*/
object ConfigurationManager {
/** Defines the processing mode for a given package. */
enum class Mode {
/** Automatically decide between GENERATE and PATCH based on TEE status. */
AUTO,
/** Patch the attestation of an existing certificate chain. */
PATCH,
/** Generate a new certificate chain from scratch. */
GENERATE,
}
// --- Configuration Paths ---
const val CONFIG_PATH = "/data/adb/tricky_store"
private const val TARGET_PACKAGES_FILE = "target.txt"
private const val PATCH_LEVEL_FILE = "security_patch.txt"
private const val DEFAULT_KEYBOX_FILE = "keybox.xml"
private val configRoot = File(CONFIG_PATH)
// --- In-Memory Configuration State ---
@Volatile private var packageModes = mapOf<String, Mode>()
@Volatile private var packageKeyboxes = mapOf<String, String>()
@Volatile private var globalCustomPatchLevel: CustomPatchLevel? = null
@Volatile private var packagePatchLevels = mapOf<String, CustomPatchLevel>()
// Cache for UID to package name resolution.
private val uidToPackagesCache = ConcurrentHashMap<Int, Array<String>>()
/**
* Initializes the configuration manager by loading all settings from disk and starting the file
* observer to watch for changes.
*/
fun initialize() {
configRoot.mkdirs()
SystemLogger.info("Configuration root is: ${configRoot.absolutePath}")
// First, ensure the package manager service is running, as the TEE check depends on it.
// This prevents a race condition on startup.
SystemLogger.info("Waiting for PackageManagerService to be ready...")
if (getPackageManager() == null) {
SystemLogger.error(
"PackageManagerService is not available. TEE check will likely fail."
)
} else {
SystemLogger.info("PackageManagerService is ready.")
}
// Initial load of all configuration files.
loadTargetPackages(File(configRoot, TARGET_PACKAGES_FILE))
loadPatchLevelConfig(File(configRoot, PATCH_LEVEL_FILE))
// Start watching for any subsequent file changes.
ConfigObserver.startWatching()
SystemLogger.info("Configuration initialized and file observer started.")
}
/**
* Determines the keybox file to be used for a given UID. It maps the UID to its package(s) and
* checks for a specific keybox mapping.
*
* @param uid The calling UID.
* @return The name of the keybox file, or the default if none is specified.
*/
fun getKeyboxFileForUid(uid: Int): String {
val packages = getPackagesForUid(uid)
return packages.firstNotNullOfOrNull { pkg -> packageKeyboxes[pkg] } ?: DEFAULT_KEYBOX_FILE
}
fun shouldPatch(uid: Int): Boolean {
val mode = getPackageModeForUid(uid)
return mode == Mode.PATCH || mode == Mode.AUTO
}
/** Determines if a new certificate needs to be generated for a given UID. */
fun shouldGenerate(uid: Int): Boolean = getPackageModeForUid(uid) == Mode.GENERATE
fun shouldSkipUid(uid: Int): Boolean = getPackageModeForUid(uid) == null
fun isAutoMode(uid: Int): Boolean {
for (pkg in getPackagesForUid(uid)) {
when (packageModes[pkg]) {
Mode.GENERATE,
Mode.PATCH -> return false
Mode.AUTO -> return true
null -> continue
}
}
return false
}
private fun getPackageModeForUid(uid: Int): Mode? {
val packages = getPackagesForUid(uid)
if (packages.isEmpty()) return null
for (pkg in packages) {
when (packageModes[pkg]) {
Mode.GENERATE -> return Mode.GENERATE
Mode.PATCH -> return Mode.PATCH
Mode.AUTO ->
return if (DeviceAttestationService.isTeeFunctional) Mode.PATCH
else Mode.GENERATE
null -> continue
}
}
return null
}
/**
* Retrieves the custom patch level configuration for a given UID. It first checks for a
* package-specific override and falls back to the global configuration.
*
* @param uid The UID of the calling application.
* @return The applicable [CustomPatchLevel], or null if no custom configuration exists.
*/
fun getPatchLevelForUid(uid: Int): CustomPatchLevel? {
val packages = getPackagesForUid(uid)
// Find the first package-specific configuration for this UID.
val packageSpecificPatchLevel =
packages.firstNotNullOfOrNull { pkg -> packagePatchLevels[pkg] }
return packageSpecificPatchLevel ?: globalCustomPatchLevel
}
/**
* Loads and parses the `target.txt` file, which defines the processing mode and keybox file for
* each package.
*/
private fun loadTargetPackages(file: File) {
if (!file.exists()) {
SystemLogger.warning("Configuration file not found: ${file.absolutePath}")
return
}
val newModes = mutableMapOf<String, Mode>()
val newKeyboxes = mutableMapOf<String, String>()
var currentKeybox = DEFAULT_KEYBOX_FILE
val keyboxRegex = Regex("^\\[([a-zA-Z0-9_.-]+\\.xml)]$")
try {
file.readLines().forEach { line ->
val trimmedLine = line.trim()
if (trimmedLine.isEmpty() || trimmedLine.startsWith("#")) return@forEach
// Check if the line defines a new keybox scope.
keyboxRegex.find(trimmedLine)?.let {
currentKeybox = it.groupValues[1]
SystemLogger.info("Switching to keybox context: $currentKeybox")
return@forEach
}
when {
// Suffix '!' means force GENERATE mode.
trimmedLine.endsWith("!") -> {
val pkg = trimmedLine.removeSuffix("!").trim()
newModes[pkg] = Mode.GENERATE
newKeyboxes[pkg] = currentKeybox
}
// Suffix '?' means force PATCH mode.
trimmedLine.endsWith("?") -> {
val pkg = trimmedLine.removeSuffix("?").trim()
newModes[pkg] = Mode.PATCH
newKeyboxes[pkg] = currentKeybox
}
else -> {
newModes[trimmedLine] = Mode.AUTO
newKeyboxes[trimmedLine] = currentKeybox
}
}
}
// Atomically update the configuration maps.
packageModes = newModes
packageKeyboxes = newKeyboxes
uidToPackagesCache.clear() // Invalidate cache as package settings have changed.
SystemLogger.info("Successfully loaded ${newModes.size} package configurations.")
} catch (e: Exception) {
SystemLogger.error("Failed to load or parse ${file.name}", e)
}
}
/**
* Loads and parses the `security_patch.txt` file, which can define both global and per-package
* security patch levels.
*/
private fun loadPatchLevelConfig(file: File) {
if (!file.exists()) {
globalCustomPatchLevel = null
packagePatchLevels = emptyMap()
return
}
try {
val newPackageLevels = mutableMapOf<String, CustomPatchLevel>()
var currentContext = "" // Empty string for global context
val contextLines = mutableMapOf<String, MutableList<String>>()
val contextRegex = Regex("^\\[([a-zA-Z0-9_.-]+)]$")
// First pass: group lines by context (global or package-specific).
file.readLines().forEach { line ->
val trimmedLine = line.trim()
if (trimmedLine.isEmpty() || trimmedLine.startsWith("#")) return@forEach
contextRegex.find(trimmedLine)?.let { currentContext = it.groupValues[1] }
?: run {
contextLines
.computeIfAbsent(currentContext) { mutableListOf() }
.add(trimmedLine)
}
}
// Helper function to parse a set of lines into a CustomPatchLevel object.
fun parseLines(lines: List<String>?): CustomPatchLevel? {
if (lines.isNullOrEmpty()) return null
// Handle simple case: one line sets the patch level for all components.
if (lines.size == 1 && '=' !in lines[0]) {
return CustomPatchLevel(
system = null,
vendor = null,
boot = null,
all = lines[0],
)
}
// Handle key-value pair configuration.
val map =
lines
.mapNotNull {
val parts = it.split('=', limit = 2)
if (parts.size == 2) parts[0].trim().lowercase() to parts[1].trim()
else null
}
.toMap()
val all = map["all"]
return CustomPatchLevel(
system = map["system"] ?: all,
vendor = map["vendor"] ?: all,
boot = map["boot"] ?: all,
all = all,
)
}
// Parse global and per-package configurations.
var newGlobalLevel = parseLines(contextLines[""])
// TrickyAddon writes Pixel bulletin dates for boot/vendor but system=prop
// resolves to the real device prop — force boot/vendor through the same path
// to prevent cross-component date mismatches on non-Pixel devices.
if (newGlobalLevel?.system.equals("prop", ignoreCase = true)) {
SystemLogger.info(
"system=prop: forcing boot/vendor to derive from device props (were: boot=${newGlobalLevel?.boot}, vendor=${newGlobalLevel?.vendor})"
)
newGlobalLevel = newGlobalLevel?.copy(boot = "prop", vendor = "prop")
}
contextLines.remove("") // Remove global context to iterate over packages next
for ((pkg, lines) in contextLines) {
parseLines(lines)?.let { newPackageLevels[pkg] = it }
}
// Atomically update the configuration state.
globalCustomPatchLevel = newGlobalLevel
packagePatchLevels = newPackageLevels
SystemLogger.info(
"Loaded custom security patch levels: global config exists=${newGlobalLevel != null}, " +
"${newPackageLevels.size} package-specific configs."
)
} catch (e: Exception) {
SystemLogger.error("Failed to load or parse ${file.name}", e)
}
}
/**
* A FileObserver that monitors the configuration directory for changes and triggers reloads of
* the relevant settings.
*/
private object ConfigObserver : FileObserver(configRoot, CLOSE_WRITE or MOVED_TO or DELETE) {
override fun onEvent(event: Int, path: String?) {
path ?: return
SystemLogger.info("Configuration file change detected: $path (event: $event)")
val file = if (event != DELETE) File(configRoot, path) else null
when (path) {
TARGET_PACKAGES_FILE ->
file?.let { loadTargetPackages(it) }
?: SystemLogger.warning("$TARGET_PACKAGES_FILE was deleted.")
PATCH_LEVEL_FILE ->
file?.let { loadPatchLevelConfig(it) }
?: SystemLogger.warning("$PATCH_LEVEL_FILE was deleted.")
// Any change to an XML file is assumed to be a keybox.
// The cache in KeyBoxManager will handle reloading it on its next use.
else ->
if (path.endsWith(".xml")) {
SystemLogger.info(
"Keybox file $path may have changed. It will be reloaded on next access."
)
KeyBoxManager.invalidateCache(path)
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
// Drop only the patched cert chains so the next
// attestation request re-signs with the new keybox.
// Do NOT drop generatedKeys — that would destroy
// every alias/private key in memory and on disk,
// logging users out of any app that pinned a
// persisted keystore alias.
org.matrix.TEESimulator.interception.keystore.shim
.KeyMintSecurityLevelInterceptor
.invalidatePatchedChains("updating $file")
}
}
}
}
}
// --- System Service Utilities ---
private var iPackageManager: IPackageManager? = null
private val pmDeathRecipient =
object : IBinder.DeathRecipient {
override fun binderDied() {
(iPackageManager as? IBinder)?.unlinkToDeath(this, 0)
iPackageManager = null
SystemLogger.warning("Package manager service died. Will try to reconnect.")
}
}
/** Retrieves an instance of the IPackageManager service. */
fun getPackageManager(): IPackageManager? {
if (iPackageManager == null) {
// Use a robust method to get the service binder.
val binder = waitForSystemService("package") ?: return null
binder.linkToDeath(pmDeathRecipient, 0)
iPackageManager = IPackageManager.Stub.asInterface(binder)
}
return iPackageManager
}
fun checkSELinuxPermission(callingPid: Int, tclass: String, perm: String): Boolean {
return try {
val callerCtx =
java.io.File("/proc/$callingPid/attr/current").readText().trim('\u0000', ' ', '\n')
val selfCtx =
java.io.File("/proc/self/attr/current").readText().trim('\u0000', ' ', '\n')
android.os.SELinux.checkSELinuxAccess(callerCtx, selfCtx, tclass, perm)
} catch (_: Exception) {
false
}
}
fun hasPermissionForUid(uid: Int, permission: String): Boolean {
val userId = uid / 100000
return getPackagesForUid(uid).any { pkg ->
try {
getPackageManager()?.checkPermission(permission, pkg, userId) == 0
} catch (_: Exception) {
false
}
}
}
fun getPackagesForUid(uid: Int): Array<String> {
return uidToPackagesCache.getOrPut(uid) {
try {
getPackageManager()?.getPackagesForUid(uid) ?: emptyArray()
} catch (e: Exception) {
SystemLogger.warning("Failed to get packages for UID $uid", e)
emptyArray()
}
}
}
/** Waits for a system service to become available, with retries. */
private fun waitForSystemService(name: String): IBinder? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return ServiceManager.waitForService(name)
}
// Fallback for older Android versions.
repeat(70) {
val service = ServiceManager.getService(name)
if (service != null) return service
Thread.sleep(500)
}
SystemLogger.error("Failed to get system service after multiple retries: $name")
return null
}
}
/** Data class representing custom security patch level overrides. */
data class CustomPatchLevel(
val system: String?,
val vendor: String?,
val boot: String?,
val all: String?,
)
@@ -0,0 +1,350 @@
package org.matrix.TEESimulator.interception.core
import android.os.Binder
import android.os.IBinder
import android.os.Parcel
import org.matrix.TEESimulator.config.ConfigurationManager
import org.matrix.TEESimulator.logging.SystemLogger
/**
* An abstract base class for intercepting binder transactions.
*
* This class acts as a proxy, receiving transaction calls that have been hooked at the native
* level. It provides a structured way to inspect and modify data before (`onPreTransact`) and after
* (`onPostTransact`) the original transaction is executed.
*
* The communication flow is as follows:
* 1. A native library hooks the `transact` method of a target service (e.g., keystore).
* 2. When a hooked transaction occurs, the native code calls this Binder object's `onTransact`
* method.
* 3. This class decodes the incoming parcel, determines if it's a pre- or post-transaction hook,
* and calls the appropriate abstract method (`onPreTransact` or `onPostTransact`).
* 4. The subclass implementation decides how to handle the transaction by returning a
* `TransactionResult`.
* 5. This class encodes the result into the reply parcel, which the native hook reads to determine
* its next action.
*/
abstract class BinderInterceptor : Binder() {
/**
* Defines the possible outcomes of an interception attempt. The native hook layer will
* interpret this result to decide its next action.
*/
sealed class TransactionResult {
/** Instructs the native hook to skip calling the original binder method entirely. */
object SkipTransaction : TransactionResult()
/** Instructs the native hook to proceed with calling the original binder method. */
object Continue : TransactionResult()
/**
* Skips the original call and immediately returns a custom reply parcel to the caller. The
* provided parcel will be recycled after use.
*/
data class OverrideReply(val reply: Parcel, val code: Int = 0) : TransactionResult()
/**
* Modifies the transaction's input data before forwarding it to the original binder method.
* The provided parcel will be recycled after use.
*/
data class OverrideData(val data: Parcel) : TransactionResult()
/** Instructs the native hook to skip the post transaction hook. */
object ContinueAndSkipPost : TransactionResult()
}
/**
* Called *before* the original binder transaction is executed.
*
* @param txId A unique ID for tracking this transaction.
* @param target The original IBinder service being called.
* @param code The transaction code of the method being called.
* @param flags Transaction flags.
* @param callingUid The UID of the process making the call.
* @param callingPid The PID of the process making the call.
* @param data The parcel containing the input data for the transaction.
* @return A [TransactionResult] indicating how to proceed.
*/
open fun onPreTransact(
txId: Long,
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
): TransactionResult = TransactionResult.ContinueAndSkipPost
/**
* Called *after* the original binder transaction has been executed.
*
* @param txId A unique ID for tracking this transaction.
* @param target The original IBinder service that was called.
* @param code The transaction code of the method that was called.
* @param flags Transaction flags.
* @param callingUid The UID of the process that made the call.
* @param callingPid The PID of the process that made the call.
* @param data The original input data parcel.
* @param reply The reply parcel from the original transaction. Can be null if the call was
* one-way.
* @param resultCode The result code from the original transaction.
* @return A [TransactionResult]. Typically `Skip` (to accept the original reply) or
* `OverrideReply`.
*/
open fun onPostTransact(
txId: Long,
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
reply: Parcel?,
resultCode: Int,
): TransactionResult = TransactionResult.SkipTransaction
/**
* The entry point for calls from the native hook layer. This method decodes the custom parcel
* format sent by the hook and dispatches to the appropriate handler (`handlePreTransact` or
* `handlePostTransact`).
*/
final override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
val txId = data.readLong()
val result =
try {
when (code) {
PRE_TRANSACT_CODE -> handlePreTransact(txId, data)
POST_TRANSACT_CODE -> handlePostTransact(txId, data)
else -> return super.onTransact(code, data, reply, flags)
}
} catch (e: Throwable) {
SystemLogger.error(
"[TX_ID: $txId] Interceptor exception, falling through to HAL",
e,
)
TransactionResult.ContinueAndSkipPost
}
writeResultToReply(result, reply!!)
return true
}
/** Decodes the parcel for a pre-transaction hook and calls the user-overridable method. */
private fun handlePreTransact(txId: Long, data: Parcel): TransactionResult {
// The native hook marshals the original transaction's arguments into the data parcel.
val target = data.readStrongBinder()!!
val transactionCode = data.readInt()
val transactionFlags = data.readInt()
val callingUid = data.readInt()
val callingPid = data.readInt()
val dataSize = data.readLong()
// We must create a new parcel containing only the original transaction data.
val transactionData = Parcel.obtain()
return try {
transactionData.appendFrom(data, data.dataPosition(), dataSize.toInt())
transactionData.setDataPosition(0)
onPreTransact(
txId,
target,
transactionCode,
transactionFlags,
callingUid,
callingPid,
transactionData,
)
} finally {
transactionData.recycle()
}
}
/** Decodes the parcel for a post-transaction hook and calls the user-overridable method. */
private fun handlePostTransact(txId: Long, data: Parcel): TransactionResult {
val target = data.readStrongBinder()!!
val transactionCode = data.readInt()
val transactionFlags = data.readInt()
val callingUid = data.readInt()
val callingPid = data.readInt()
// The native hook also marshals the original data and reply parcels.
val transactionData = Parcel.obtain()
val transactionReply = Parcel.obtain()
return try {
val dataSize = data.readLong().toInt()
transactionData.appendFrom(data, data.dataPosition(), dataSize)
transactionData.setDataPosition(0)
data.setDataPosition(data.dataPosition() + dataSize)
val resultCode = data.readInt()
val replySize = data.readLong().toInt()
val reply =
if (replySize > 0) {
transactionReply.appendFrom(data, data.dataPosition(), replySize)
transactionReply.setDataPosition(0)
transactionReply
} else null
onPostTransact(
txId,
target,
transactionCode,
transactionFlags,
callingUid,
callingPid,
transactionData,
reply,
resultCode,
)
} finally {
transactionData.recycle()
transactionReply.recycle()
}
}
/** Encodes the `TransactionResult` into the reply parcel for the native hook to interpret. */
private fun writeResultToReply(result: TransactionResult, reply: Parcel) {
when (result) {
is TransactionResult.SkipTransaction -> reply.writeInt(RESULT_SKIP_TRANSACTION)
is TransactionResult.Continue -> reply.writeInt(RESULT_CONTINUE)
is TransactionResult.OverrideReply -> {
reply.writeInt(RESULT_OVERRIDE_REPLY)
reply.writeInt(result.code)
reply.writeLong(result.reply.dataSize().toLong())
reply.appendFrom(result.reply, 0, result.reply.dataSize())
result.reply.recycle()
}
is TransactionResult.OverrideData -> {
reply.writeInt(RESULT_OVERRIDE_DATA)
reply.writeLong(result.data.dataSize().toLong())
reply.appendFrom(result.data, 0, result.data.dataSize())
result.data.recycle()
}
is TransactionResult.ContinueAndSkipPost ->
reply.writeInt(RESULT_CONTINUE_AND_SKIP_POST)
}
}
/**
* Logs an intercepted transaction. For a targeted UID every transaction whether we intercept
* or merely observe it is recorded on that UID's own diagnostic plane, so its keystore
* timeline reads cleanly end to end. Untargeted UIDs get a single terse, rate-limited line.
*/
protected fun logTransaction(
txId: Long,
methodName: String,
callingUid: Int,
callingPid: Int,
skipPost: Boolean = false,
) {
if (SystemLogger.isUidLogged(callingUid)) {
val action = if (skipPost) "observe" else "intercept"
SystemLogger.uidLog(callingUid, txId, "tx", "$methodName action=$action pid=$callingPid")
return
}
SystemLogger.verbose {
val packages = ConfigurationManager.getPackagesForUid(callingUid).joinToString()
"[TX_ID: $txId] Observe $methodName for packages=[$packages] (uid=$callingUid, pid=$callingPid)"
}
}
companion object {
// These codes must be kept in sync with the native injection library.
// --- Backdoor Codes ---
// Special transaction code to ask the injected library for its backdoor binder.
private const val BACKDOOR_TRANSACTION_CODE = 0xdeadbeef.toInt()
// Code used by the backdoor binder to register a new interceptor.
private const val REGISTER_INTERCEPTOR_CODE = 1
// Code used by the backdoor binder to unregister an interceptor.
private const val UNREGISTER_INTERCEPTOR_CODE = 2
// --- Hook Type Codes ---
// Indicates that the call is for a pre-transaction hook.
private const val PRE_TRANSACT_CODE = 1
// Indicates that the call is for a post-transaction hook.
private const val POST_TRANSACT_CODE = 2
// --- Result Codes ---
// Instructs the native hook to skip the original transaction.
private const val RESULT_SKIP_TRANSACTION = 1
// Instructs the native hook to execute the original transaction.
private const val RESULT_CONTINUE = 2
// Instructs the native hook to return a custom reply.
private const val RESULT_OVERRIDE_REPLY = 3
// Instructs the native hook to use modified input data for the transaction.
private const val RESULT_OVERRIDE_DATA = 4
// Instructs the native hook to skip the post transaction hook.
private const val RESULT_CONTINUE_AND_SKIP_POST = 5
/**
* Probes a binder service to see if our native library has been injected. If successful, it
* returns a "backdoor" binder that can be used to register interceptors.
*/
fun getBackdoor(binder: IBinder): IBinder? {
val data = Parcel.obtain()
val reply = Parcel.obtain()
return try {
if (binder.transact(BACKDOOR_TRANSACTION_CODE, data, reply, 0)) {
SystemLogger.debug("Backdoor access granted for binder: $binder")
reply.readStrongBinder()
} else {
SystemLogger.debug("Backdoor not found for binder: $binder")
null
}
} catch (e: Exception) {
SystemLogger.error("Failed to transact for backdoor.", e)
null
} finally {
data.recycle()
reply.recycle()
}
}
fun register(
backdoor: IBinder,
target: IBinder,
interceptor: BinderInterceptor,
filteredCodes: IntArray = intArrayOf(),
): Boolean {
val data = Parcel.obtain()
val reply = Parcel.obtain()
return try {
data.writeStrongBinder(target)
data.writeStrongBinder(interceptor)
data.writeInt(filteredCodes.size)
for (code in filteredCodes) data.writeInt(code)
val ok = backdoor.transact(REGISTER_INTERCEPTOR_CODE, data, reply, 0)
if (ok) {
SystemLogger.info(
"Registered interceptor for target: $target (${filteredCodes.size} filtered codes)"
)
} else {
SystemLogger.error("Register transact returned false for target: $target")
}
ok
} catch (e: Exception) {
SystemLogger.error("Failed to register binder interceptor.", e)
false
} finally {
data.recycle()
reply.recycle()
}
}
/** Uses the backdoor binder to unregister an interceptor for a specific target service. */
fun unregister(backdoor: IBinder, target: IBinder) {
val data = Parcel.obtain()
val reply = Parcel.obtain()
try {
data.writeStrongBinder(target)
backdoor.transact(UNREGISTER_INTERCEPTOR_CODE, data, reply, 0)
SystemLogger.info("Unregistered interceptor for target: $target")
} catch (e: Exception) {
SystemLogger.error("Failed to unregister binder interceptor.", e)
} finally {
data.recycle()
reply.recycle()
}
}
}
}
@@ -0,0 +1,142 @@
package org.matrix.TEESimulator.interception.keystore
import android.os.IBinder
import android.os.ServiceManager
import kotlin.system.exitProcess
import org.matrix.TEESimulator.interception.core.BinderInterceptor
import org.matrix.TEESimulator.logging.SystemLogger
/**
* An abstract base class for intercepting Android's Keystore services.
*
* It encapsulates the common logic for finding the Keystore service, injecting the native hook if
* necessary, and setting up the binder interceptor. It also handles service death events to ensure
* stability.
*/
abstract class AbstractKeystoreInterceptor : BinderInterceptor() {
// --- Abstract Properties to be Implemented by Subclasses ---
/** The full name of the system service to intercept (e.g., "android.security.keystore"). */
protected abstract val serviceName: String
/** The name of the process hosting the service (e.g., "keystore"). */
protected abstract val processName: String
/** The shell command used to inject the native library into the target process. */
protected abstract val injectionCommand: String
// --- State Management ---
/** The original IBinder for the Keystore service. */
protected lateinit var keystoreService: IBinder
private var injectionAttempted = false
private var retryCount = 0
private val maxRetries = 5
/**
* Attempts to initialize the interceptor for the target Keystore service.
*
* This method orchestrates the process:
* 1. It tries to get the service binder.
* 2. It probes for the native backdoor.
* 3. If the backdoor exists, it sets up the interceptor.
* 4. If not, it attempts to inject the native library and returns `false` to signal a retry is
* needed.
*
* @return `true` if the interceptor was successfully registered, `false` otherwise.
*/
fun tryRunKeystoreInterceptor(): Boolean {
SystemLogger.info(
"Initializing interceptor for '$serviceName' (attempt ${retryCount + 1})..."
)
val service = ServiceManager.getService(serviceName)
if (service == null) {
SystemLogger.warning("Service '$serviceName' not found. Will retry.")
retryCount++
return false
}
val backdoor = getBackdoor(service)
return if (backdoor != null) {
setupInterceptor(service, backdoor)
true // Success
} else {
handleMissingBackdoor()
false // Failure, requires retry
}
}
protected open val interceptedCodes: IntArray = intArrayOf()
private fun setupInterceptor(service: IBinder, backdoor: IBinder) {
keystoreService = service
SystemLogger.info("Registering interceptor for service: $serviceName")
register(backdoor, service, this, interceptedCodes)
service.linkToDeath(createDeathRecipient(), 0)
onInterceptorReady(service, backdoor)
}
/**
* Handles the case where the native backdoor is not present. It triggers the injection command
* on the first attempt and manages the retry logic.
*/
private fun handleMissingBackdoor() {
if (!injectionAttempted) {
SystemLogger.warning(
"Backdoor not found. Attempting to inject native library into '$processName'."
)
performInjection()
injectionAttempted = true
}
retryCount++
if (retryCount >= maxRetries) {
SystemLogger.error(
"Failed to find backdoor after $maxRetries retries. The service may have crashed or injection failed. Exiting."
)
exitProcess(1)
}
}
/** Executes the shell command to inject the native library into the target process. */
private fun performInjection() {
try {
val command = arrayOf("/system/bin/sh", "-c", injectionCommand)
SystemLogger.debug("Executing injection command: ${command.joinToString(" ")}")
val process = Runtime.getRuntime().exec(command)
val exitCode = process.waitFor()
if (exitCode != 0) {
SystemLogger.error("Injection process failed with exit code $exitCode. Exiting.")
exitProcess(1)
}
SystemLogger.info("Injection process completed.")
} catch (e: Exception) {
SystemLogger.error("An exception occurred during injection. Exiting.", e)
exitProcess(1)
}
}
/**
* Creates a `DeathRecipient` that will restart the application if the intercepted service dies.
*/
private fun createDeathRecipient() =
IBinder.DeathRecipient {
SystemLogger.error(
"The intercepted service '$serviceName' has died. Restarting application."
)
exitProcess(0)
}
/**
* A hook for subclasses to perform additional setup after the interceptor is registered. For
* example, to intercept sub-services.
*
* @param service The main service binder.
* @param backdoor The backdoor binder for registering more interceptors.
*/
protected open fun onInterceptorReady(service: IBinder, backdoor: IBinder) {
// Default implementation does nothing.
}
}
@@ -0,0 +1,325 @@
package org.matrix.TEESimulator.interception.keystore
import android.hardware.security.keymint.KeyParameter
import android.hardware.security.keymint.KeyParameterValue
import android.hardware.security.keymint.Tag
import android.os.Parcel
import android.os.Parcelable
import android.security.KeyStore
import android.security.keystore.KeystoreResponse
import android.system.keystore2.Authorization
import java.nio.ByteBuffer
import java.nio.ByteOrder
import org.matrix.TEESimulator.interception.core.BinderInterceptor
import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.util.AndroidDeviceUtils
data class KeyIdentifier(val uid: Int, val alias: String)
/** A collection of utility functions to support binder interception. */
object InterceptorUtils {
private const val EX_SERVICE_SPECIFIC = -8
private const val FLAT_STRIDE_HEADER = 12
private const val MAX_AUTH_COUNT = 256
private const val SENTINEL_MODTIME = 4_294_967_297L
private const val HIGH_MODTIME = 4_999_999_999L
private fun synthesizeSseMessage(errorCode: Int): String =
when (errorCode) {
2 -> "Error::Rc(SYSTEM_ERROR)"
4 -> "Error::Rc(PERMISSION_DENIED)"
6 -> "Error::Rc(VALUE_CORRUPTED)"
7 -> "Error::Rc(KEY_NOT_FOUND)"
10 -> "Error::Rc(BACKEND_BUSY)"
-3 -> "Error::Km(UNSUPPORTED_KEY_SIZE)"
-6 -> "Error::Km(INCOMPATIBLE_PURPOSE)"
-7 -> "Error::Km(INCOMPATIBLE_ALGORITHM)"
-29 -> "Error::Km(TOO_MANY_OPERATIONS)"
-49 -> "Error::Km(UNSUPPORTED_TAG)"
-75 -> "Error::Km(INVALID_INPUT_LENGTH)"
-76 -> "Error::Km(INVALID_TAG)"
else -> if (errorCode > 0) "Error::Rc($errorCode)" else "Error::Km($errorCode)"
}
fun createErrorReply(errorCode: Int): BinderInterceptor.TransactionResult.OverrideReply {
val parcel =
Parcel.obtain().apply {
writeInt(EX_SERVICE_SPECIFIC)
writeString(synthesizeSseMessage(errorCode))
writeInt(0) // empty remote stack trace header (AOSP Status.cpp:196)
writeInt(errorCode)
}
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
}
/**
* Uses reflection to get the integer transaction code for a given method name from a Stub
* class. This is necessary for older Android versions where codes are not public constants.
*/
fun getTransactCode(clazz: Class<*>, method: String): Int {
return try {
clazz.getDeclaredField("TRANSACTION_$method").apply { isAccessible = true }.getInt(null)
} catch (e: Exception) {
SystemLogger.error(
"Failed to get transaction code for method '$method' in class '${clazz.simpleName}'.",
e,
)
-1 // Return an invalid code
}
}
/** Creates an `KeystoreResponse` parcel that indicates success with no data. */
fun createSuccessKeystoreResponse(): KeystoreResponse {
val parcel = Parcel.obtain()
try {
parcel.writeInt(KeyStore.NO_ERROR)
parcel.writeString("")
parcel.setDataPosition(0)
return KeystoreResponse.CREATOR.createFromParcel(parcel)
} finally {
parcel.recycle()
}
}
/** Creates an `OverrideReply` parcel that indicates success with no data. */
fun createSuccessReply(
writeResultCode: Boolean = true
): BinderInterceptor.TransactionResult.OverrideReply {
val parcel =
Parcel.obtain().apply {
writeNoException()
if (writeResultCode) {
writeInt(KeyStore.NO_ERROR)
}
}
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
}
/** Creates an `OverrideReply` parcel containing a raw byte array. */
fun createByteArrayReply(data: ByteArray): BinderInterceptor.TransactionResult.OverrideReply {
val parcel =
Parcel.obtain().apply {
writeNoException()
writeByteArray(data)
}
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
}
/** Creates an `OverrideReply` parcel containing a typed array. */
fun <T : Parcelable> createTypedArrayReply(
array: Array<T>,
flags: Int = 0,
): BinderInterceptor.TransactionResult.OverrideReply {
val parcel =
Parcel.obtain().apply {
writeNoException()
writeTypedArray(array, flags)
}
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
}
/** Correlates a captured reply parcel to the app that triggered it, for [createTypedObjectReply]. */
data class ReplyDiagnostic(val uid: Int, val txId: Long?, val event: String)
/** Creates an `OverrideReply` parcel containing a Parcelable object. */
fun <T : Parcelable?> createTypedObjectReply(
obj: T,
flags: Int = 0,
diagnostic: ReplyDiagnostic? = null,
): BinderInterceptor.TransactionResult.OverrideReply {
val parcel =
Parcel.obtain().apply {
writeNoException()
writeTypedObject(obj, flags)
}
if (diagnostic != null && SystemLogger.isUidLogged(diagnostic.uid)) {
val savedPos = parcel.dataPosition()
val wire = parcel.marshall()
parcel.setDataPosition(savedPos)
SystemLogger.uidLogRaw(diagnostic.uid, diagnostic.txId, diagnostic.event, "len=${wire.size}", wire)
}
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
}
/**
* Extracts the base alias from a potentially prefixed alias string. For example, it converts
* "USRCERT_my_key" to "my_key".
*/
fun extractAlias(prefixedAlias: String): String {
val underscoreIndex = prefixedAlias.indexOf('_')
return if (underscoreIndex != -1) {
// Return the part of the string after the first underscore.
prefixedAlias.substring(underscoreIndex + 1)
} else {
// If there's no underscore, return the original string.
prefixedAlias
}
}
/** Checks if a reply parcel contains an exception without consuming it. */
fun hasException(reply: Parcel): Boolean {
val exception = runCatching { reply.readException() }.exceptionOrNull()
if (exception != null) reply.setDataPosition(0)
return exception != null
}
fun createServiceSpecificErrorReply(
errorCode: Int
): BinderInterceptor.TransactionResult.OverrideReply = createErrorReply(errorCode)
fun normalizeServiceSpecificReply(reply: Parcel): Parcel? {
reply.setDataPosition(0)
if (reply.readInt() != EX_SERVICE_SPECIFIC) {
reply.setDataPosition(0)
return null
}
// Advance position past message and stack header to reach errorCode.
reply.readString()
reply.readInt()
val errorCode = reply.readInt()
reply.setDataPosition(0)
return Parcel.obtain().apply {
writeInt(EX_SERVICE_SPECIFIC)
writeString(synthesizeSseMessage(errorCode))
writeInt(0)
writeInt(errorCode)
}
}
fun patchAuthorizations(
authorizations: Array<Authorization>?,
callingUid: Int,
): Array<Authorization>? {
if (authorizations == null) return null
val osPatch = AndroidDeviceUtils.getPatchLevel(callingUid)
val vendorPatch = AndroidDeviceUtils.getVendorPatchLevelLong(callingUid)
val bootPatch = AndroidDeviceUtils.getBootPatchLevelLong(callingUid)
val patched =
authorizations.map { auth ->
val replacement =
when (auth.keyParameter.tag) {
Tag.OS_PATCHLEVEL ->
if (osPatch != AndroidDeviceUtils.DO_NOT_REPORT) osPatch else null
Tag.VENDOR_PATCHLEVEL ->
if (vendorPatch != AndroidDeviceUtils.DO_NOT_REPORT) vendorPatch
else null
Tag.BOOT_PATCHLEVEL ->
if (bootPatch != AndroidDeviceUtils.DO_NOT_REPORT) bootPatch else null
else -> null
}
if (replacement != null) {
Authorization().apply {
keyParameter =
KeyParameter().apply {
tag = auth.keyParameter.tag
value = KeyParameterValue.integer(replacement)
}
securityLevel = auth.securityLevel
}
} else {
auth
}
}
.toTypedArray()
return normalizeAuthorizationLayout(patched)
}
/**
* Reorders a generateKey reply's authorizations only when the marshalled reply would read, to a
* flat 12-byte-stride parcel fingerprint, as the TEE-simulator sentinel: a last slot of
* securityLevel 4 or 256, tag 1, union 32 with the 0x1_0000_0001 pseudo-timestamp, or any
* timestamp past [HIGH_MODTIME]. Real Android 16 hardware emits a 13-authorization layout for
* single-purpose EC keys that lands on that sentinel, so mirroring hardware byte-for-byte is
* itself flagged. Clients resolve authorizations by tag and the certificate chain is a separate
* field, so reordering is the minimal capability-preserving way to clear the false positive for
* any caller. Already-clean replies are returned unchanged.
*/
fun normalizeAuthorizationLayout(authorizations: Array<Authorization>): Array<Authorization> {
if (authorizations.size < 2) return authorizations
if (!flatStrideFingerprintMatches(marshalTypedArray(authorizations))) return authorizations
val n = authorizations.size
for (src in 1 until n) {
val candidate = moveAuthorization(authorizations, src, 0)
if (!flatStrideFingerprintMatches(marshalTypedArray(candidate))) return candidate
}
for (src in 0 until n) {
for (dst in 0 until n) {
if (src == dst) continue
val candidate = moveAuthorization(authorizations, src, dst)
if (!flatStrideFingerprintMatches(marshalTypedArray(candidate))) return candidate
}
}
return authorizations
}
private fun moveAuthorization(
authorizations: Array<Authorization>,
src: Int,
dst: Int,
): Array<Authorization> {
val reordered = authorizations.toMutableList()
reordered.add(dst, reordered.removeAt(src))
return reordered.toTypedArray()
}
private fun marshalTypedArray(authorizations: Array<Authorization>): ByteArray {
val parcel = Parcel.obtain()
return try {
// keystore2 AIDL compile stubs omit the Parcelable supertype these types carry at runtime.
parcel.writeTypedArray(authorizations.map { it as Parcelable }.toTypedArray(), 0)
parcel.marshall()
} finally {
parcel.recycle()
}
}
private fun flatStrideFingerprintMatches(marshalled: ByteArray): Boolean =
runCatching {
val parcel = ByteBuffer.wrap(marshalled).order(ByteOrder.LITTLE_ENDIAN)
val count = parcel.getInt(0)
if (count !in 1..MAX_AUTH_COUNT) return@runCatching false
var off = 4
var lastSec = 0L
var lastTag = 0L
var lastUnion = 0L
repeat(count) {
lastSec = u32(parcel, off)
lastTag = u32(parcel, off + 4)
lastUnion = u32(parcel, off + 8)
off += FLAT_STRIDE_HEADER
off = alignWord(off + flatPayloadSize(parcel, off, lastUnion))
}
off = skipDriftedByteArray(parcel, off)
off = skipDriftedByteArray(parcel, off)
val modtime = parcel.getLong(alignWord(off))
val unknownUnion = lastUnion !in 0..14
modtime > HIGH_MODTIME ||
(modtime == SENTINEL_MODTIME &&
(lastSec == 4L || lastSec == 256L) &&
lastTag == 1L &&
lastUnion == 32L &&
unknownUnion)
}.getOrDefault(false)
private fun flatPayloadSize(parcel: ByteBuffer, off: Int, union: Long): Int =
when {
union in 1..11 -> 4
union == 12L || union == 13L -> 8
union == 14L -> alignWord(off + 4 + parcel.getInt(off)) - off
else -> 0
}
private fun skipDriftedByteArray(parcel: ByteBuffer, off: Int): Int {
if (parcel.getInt(off) == 0) return off + 4
val lengthPos = off + 4
return alignWord(lengthPos + 4 + parcel.getInt(lengthPos))
}
private fun u32(parcel: ByteBuffer, off: Int): Long = parcel.getInt(off).toLong() and 0xFFFFFFFFL
private fun alignWord(off: Int): Int = (off + 3) and 3.inv()
}
@@ -0,0 +1,838 @@
package org.matrix.TEESimulator.interception.keystore
import android.annotation.SuppressLint
import android.hardware.security.keymint.SecurityLevel
import android.os.Build
import android.os.IBinder
import android.os.Parcel
import android.os.ServiceManager
import android.system.keystore2.Domain
import android.system.keystore2.IKeystoreService
import android.system.keystore2.KeyDescriptor
import android.system.keystore2.KeyEntryResponse
import java.security.SecureRandom
import java.security.cert.Certificate
import java.util.concurrent.ConcurrentHashMap
import org.matrix.TEESimulator.attestation.AttestationPatcher
import org.matrix.TEESimulator.attestation.KeyMintAttestation
import org.matrix.TEESimulator.config.ConfigurationManager
import org.matrix.TEESimulator.interception.keystore.shim.GeneratedKeyPersistence
import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor
import org.matrix.TEESimulator.logging.AttestationDossier
import org.matrix.TEESimulator.logging.KeyMintParameterLogger
import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.pki.CertificateGenerator
import org.matrix.TEESimulator.pki.CertificateHelper
/**
* Interceptor for the `IKeystoreService` on Android S (API 31) and newer.
*
* This version of Keystore delegates most cryptographic operations to `IKeystoreSecurityLevel`
* sub-services (for TEE, StrongBox, etc.). This interceptor's main role is to set up interceptors
* for those sub-services and to patch certificate chains on their way out.
*/
@SuppressLint("BlockedPrivateApi")
object Keystore2Interceptor : AbstractKeystoreInterceptor() {
private val stubBinderClass = IKeystoreService.Stub::class.java
// Transaction codes for the IKeystoreService interface methods we are interested in.
private val GET_KEY_ENTRY_TRANSACTION =
InterceptorUtils.getTransactCode(stubBinderClass, "getKeyEntry")
private val DELETE_KEY_TRANSACTION =
InterceptorUtils.getTransactCode(stubBinderClass, "deleteKey")
private val UPDATE_SUBCOMPONENT_TRANSACTION =
InterceptorUtils.getTransactCode(stubBinderClass, "updateSubcomponent")
private val LIST_ENTRIES_TRANSACTION =
InterceptorUtils.getTransactCode(stubBinderClass, "listEntries")
private val LIST_ENTRIES_BATCHED_TRANSACTION =
if (Build.VERSION.SDK_INT >= 34)
InterceptorUtils.getTransactCode(stubBinderClass, "listEntriesBatched")
else null
private val GET_NUMBER_OF_ENTRIES_TRANSACTION =
InterceptorUtils.getTransactCode(stubBinderClass, "getNumberOfEntries")
private val GRANT_TRANSACTION = InterceptorUtils.getTransactCode(stubBinderClass, "grant")
private val UNGRANT_TRANSACTION = InterceptorUtils.getTransactCode(stubBinderClass, "ungrant")
private val transactionNames: Map<Int, String> by lazy {
stubBinderClass.declaredFields
.filter {
it.isAccessible = true
it.type == Int::class.java && it.name.startsWith("TRANSACTION_")
}
.associate { field -> (field.get(null) as Int) to field.name.split("_")[1] }
}
private const val RESPONSE_KEY_NOT_FOUND = 7
private const val RESPONSE_PERMISSION_DENIED = 6
private const val KEY_PERMISSION_GET_INFO = 0x4
private const val KEY_PERMISSION_UPDATE = 0x80
// KeyStoreManager.grantKeyAccess() became a public app API in Android 16 (API 36). Before that,
// grant was a hidden API and SELinux denied untrusted_app, so a synthetic-key grant must answer
// PERMISSION_DENIED pre-36 and a coherent virtualized grant on 36+.
private const val GRANT_PUBLIC_API_SDK = 36
private val deletedSoftwareKeys: MutableSet<KeyIdentifier> = ConcurrentHashMap.newKeySet()
private val userUpdatedKeys = ConcurrentHashMap.newKeySet<KeyIdentifier>()
fun forgetDeletedKey(keyId: KeyIdentifier) {
if (deletedSoftwareKeys.remove(keyId)) {
SystemLogger.debug("Cleared deletion marker for ${keyId.alias}")
}
}
override val serviceName = "android.system.keystore2.IKeystoreService/default"
override val processName = "keystore2"
override val injectionCommand = "exec ./inject `pidof keystore2` libTEESimulator.so entry"
override val interceptedCodes: IntArray by lazy {
listOfNotNull(
GET_KEY_ENTRY_TRANSACTION,
DELETE_KEY_TRANSACTION,
UPDATE_SUBCOMPONENT_TRANSACTION,
LIST_ENTRIES_TRANSACTION,
LIST_ENTRIES_BATCHED_TRANSACTION,
GET_NUMBER_OF_ENTRIES_TRANSACTION,
GRANT_TRANSACTION,
UNGRANT_TRANSACTION,
)
.toIntArray()
}
/**
* This method is called once the main service is hooked. It proceeds to find and hook the
* security level sub-services (e.g., TEE, StrongBox).
*/
override fun onInterceptorReady(service: IBinder, backdoor: IBinder) {
val keystoreInterface = IKeystoreService.Stub.asInterface(service)
setupSecurityLevelInterceptors(keystoreInterface, backdoor)
setupMaintenanceInterceptor(backdoor)
}
/**
* Hooks the keystore2 daemon's `android.security.maintenance` binder, which is hosted by the
* same process, so synthetic key state follows real key-lifecycle events. Best-effort: if the
* service is absent the synthetic plane simply forgoes lifecycle parity.
*/
private fun setupMaintenanceInterceptor(backdoor: IBinder) {
runCatching {
ServiceManager.getService("android.security.maintenance")?.let { maintenance ->
SystemLogger.info("Found maintenance binder. Registering interceptor...")
register(
backdoor,
maintenance,
Keystore2MaintenanceInterceptor,
Keystore2MaintenanceInterceptor.interceptedCodes,
)
}
?: SystemLogger.warning(
"Maintenance binder not found; skipping lifecycle parity."
)
}
.onFailure { SystemLogger.error("Failed to intercept maintenance binder.", it) }
}
private fun setupSecurityLevelInterceptors(service: IKeystoreService, backdoor: IBinder) {
// Attempt to get and intercept the TEE security level service.
runCatching {
service.getSecurityLevel(SecurityLevel.TRUSTED_ENVIRONMENT)?.let { tee ->
SystemLogger.info("Found TEE SecurityLevel. Registering interceptor...")
val interceptor =
KeyMintSecurityLevelInterceptor(tee, SecurityLevel.TRUSTED_ENVIRONMENT)
register(
backdoor,
tee.asBinder(),
interceptor,
KeyMintSecurityLevelInterceptor.INTERCEPTED_CODES,
)
interceptor.loadPersistedKeys()
}
}
.onFailure { SystemLogger.error("Failed to intercept TEE SecurityLevel.", it) }
// Attempt to get and intercept the StrongBox security level service.
runCatching {
service.getSecurityLevel(SecurityLevel.STRONGBOX)?.let { strongbox ->
SystemLogger.info("Found StrongBox SecurityLevel. Registering interceptor...")
val interceptor =
KeyMintSecurityLevelInterceptor(strongbox, SecurityLevel.STRONGBOX)
register(
backdoor,
strongbox.asBinder(),
interceptor,
KeyMintSecurityLevelInterceptor.INTERCEPTED_CODES,
)
interceptor.loadPersistedKeys()
}
}
.onFailure { SystemLogger.error("Failed to intercept StrongBox SecurityLevel.", it) }
}
override fun onPreTransact(
txId: Long,
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
): TransactionResult {
if (code == GET_NUMBER_OF_ENTRIES_TRANSACTION) {
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid, true)
return if (ConfigurationManager.shouldSkipUid(callingUid))
TransactionResult.ContinueAndSkipPost
else TransactionResult.Continue
} else if (code == LIST_ENTRIES_TRANSACTION || code == LIST_ENTRIES_BATCHED_TRANSACTION) {
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid, true)
val packages = ConfigurationManager.getPackagesForUid(callingUid).joinToString()
val isGMS = packages.contains("com.google.android.gms")
if (isGMS || ConfigurationManager.shouldSkipUid(callingUid)) {
return TransactionResult.ContinueAndSkipPost
}
return runCatching {
val isBatchMode = code == LIST_ENTRIES_BATCHED_TRANSACTION
if (ListEntriesHandler.cacheParameters(txId, data, isBatchMode)) {
TransactionResult.Continue
} else {
TransactionResult.ContinueAndSkipPost
}
}
.getOrElse {
SystemLogger.error(
"[TX_ID: $txId] Failed to parse parameters for ${transactionNames[code]!!}",
it,
)
TransactionResult.ContinueAndSkipPost
}
} else if (
code == GET_KEY_ENTRY_TRANSACTION ||
code == DELETE_KEY_TRANSACTION ||
code == UPDATE_SUBCOMPONENT_TRANSACTION
) {
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
if (code == UPDATE_SUBCOMPONENT_TRANSACTION) {
if (ConfigurationManager.shouldSkipUid(callingUid))
return TransactionResult.ContinueAndSkipPost
return handleUpdateSubcomponent(callingUid, data)
}
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val descriptor =
data.readTypedObject(KeyDescriptor.CREATOR)
?: return TransactionResult.ContinueAndSkipPost
// Domain.GRANT read (Android 16+ KeyStoreManager grant). Served for ANY grantee uid —
// including isolated services (bindIsolatedService) with no package mapping — so
// resolve
// it before the package-scoped skip; caller-binding in resolveGrant() is the real
// access
// gate. On Android <= 15 no grants are ever issued (grant() denies), so softwareGrants
// is
// empty and this falls through to the real keystore2.
if (code == GET_KEY_ENTRY_TRANSACTION && descriptor.domain == Domain.GRANT) {
val grant =
KeyMintSecurityLevelInterceptor.resolveGrant(descriptor.nspace, callingUid)
if (grant == null) {
// Ours but wrong caller -> KEY_NOT_FOUND (caller-binding); not ours -> real
// keystore2.
return if (
KeyMintSecurityLevelInterceptor.softwareGrants.containsKey(
descriptor.nspace
)
)
InterceptorUtils.createErrorReply(RESPONSE_KEY_NOT_FOUND)
else TransactionResult.ContinueAndSkipPost
}
if ((grant.accessVector and KEY_PERMISSION_GET_INFO) == 0) {
return InterceptorUtils.createErrorReply(RESPONSE_PERMISSION_DENIED)
}
val response =
KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(grant.ownerKeyId)
?: return InterceptorUtils.createErrorReply(RESPONSE_KEY_NOT_FOUND)
// Same object the owner read returns -> coherent chain across planes.
return InterceptorUtils.createTypedObjectReply(response)
}
// generateKey force-forges attest/device-id keys even for skipped UIDs; getKeyEntry
// must serve them back or the framework's attestKeyAlias lookup gets KEY_NOT_FOUND.
if (code != GET_KEY_ENTRY_TRANSACTION && ConfigurationManager.shouldSkipUid(callingUid))
return TransactionResult.ContinueAndSkipPost
if (code == DELETE_KEY_TRANSACTION) {
val keyId =
if (descriptor.alias != null) {
KeyIdentifier(callingUid, descriptor.alias)
} else if (descriptor.domain == Domain.KEY_ID) {
KeyMintSecurityLevelInterceptor.findGeneratedKeyByKeyId(
callingUid,
descriptor.nspace,
)
?.let { info ->
KeyMintSecurityLevelInterceptor.generatedKeys.entries
.find {
it.value.nspace == info.nspace && it.key.uid == callingUid
}
?.key
}
} else null
if (keyId != null) {
val isSoftwareKey =
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."
)
return InterceptorUtils.createSuccessReply(writeResultCode = false)
}
}
return TransactionResult.ContinueAndSkipPost
}
if (descriptor.alias == null) {
if (descriptor.domain == Domain.KEY_ID) {
// The probe pipeline (and some AOSP callers) switch follow-up
// operations to KEY_ID semantics after generateKey returns a
// KEY_ID descriptor. Without this branch, our software keys
// are invisible to KEY_ID-based getKeyEntry calls and the
// request falls through to the real keystore2 daemon, which
// legitimately responds with KEY_NOT_FOUND. Duck Detector's
// TimingSideChannelProbe captures that exception during its
// warmup phase and surfaces it as
// "Captured private binder exception during timing skip".
// Resolving by KEY_ID and returning the cached response keeps
// the call on the happy path, eliminating the warmup signal.
val info =
KeyMintSecurityLevelInterceptor.findGeneratedKeyByKeyId(
callingUid,
descriptor.nspace,
)
if (info?.response != null) {
SystemLogger.info(
"[TX_ID: $txId] Found generated response via KEY_ID nspace=${descriptor.nspace}"
)
logServedChain(callingUid, txId, "keyid:${descriptor.nspace}", info.response)
return InterceptorUtils.createTypedObjectReply(info.response)
}
val teeResp =
KeyMintSecurityLevelInterceptor.findTeeResponseByKeyId(
callingUid,
descriptor.nspace,
)
if (teeResp != null) {
SystemLogger.info(
"[TX_ID: $txId] Found TEE response via KEY_ID nspace=${descriptor.nspace}"
)
logServedChain(callingUid, txId, "keyid:${descriptor.nspace}", teeResp)
return InterceptorUtils.createTypedObjectReply(teeResp)
}
}
// Domain.GRANT is handled earlier (before the package-scoped skip); an alias-less
// read reaching here is KEY_ID or unknown, so it falls through to the real
// keystore2.
return TransactionResult.ContinueAndSkipPost
}
val keyId = KeyIdentifier(callingUid, descriptor.alias)
val response = KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(keyId)
if (response == null) {
if (deletedSoftwareKeys.remove(keyId)) {
SystemLogger.info(
"[TX_ID: $txId] Returning KEY_NOT_FOUND for deleted key ${descriptor.alias}"
)
return InterceptorUtils.createErrorReply(RESPONSE_KEY_NOT_FOUND)
}
// Owned keys were served above; for a skipped UID a non-owned key must still skip
// post-processing so we never patch an un-targeted app's real key.
return if (ConfigurationManager.shouldSkipUid(callingUid))
TransactionResult.ContinueAndSkipPost
else TransactionResult.Continue
}
if (KeyMintSecurityLevelInterceptor.isAttestationKey(keyId))
SystemLogger.info("${descriptor.alias} was an attestation key")
SystemLogger.info("[TX_ID: $txId] Found generated response for ${descriptor.alias}:")
response.metadata?.authorizations?.forEach {
KeyMintParameterLogger.logParameter(callingUid, txId, it.keyParameter)
}
logServedChain(callingUid, txId, descriptor.alias, response)
return InterceptorUtils.createTypedObjectReply(response)
} else if (code == GRANT_TRANSACTION) {
logTransaction(txId, transactionNames[code] ?: "grant", callingUid, callingPid)
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val key =
data.readTypedObject(KeyDescriptor.CREATOR)
?: return TransactionResult.ContinueAndSkipPost
val granteeUid = data.readInt()
val accessVector = data.readInt()
// Synthetic (generatedKeys) AND patch-mode (teeResponses) keys are ours; both must
// grant
// coherently so the Domain.GRANT readback returns the same chain the owner read
// returns.
// Real hardware keys fall through to the real keystore2, which applies the same SELinux
// gate the platform would.
val ownerKeyId =
resolveOwnerKeyId(key, callingUid)?.takeIf {
KeyMintSecurityLevelInterceptor.ownsKeyResponse(it)
} ?: return TransactionResult.ContinueAndSkipPost
// Version-gated to mirror the real TEE 1:1. Pre-Android-16, grant was a hidden API and
// SELinux denied untrusted_app, so keystore2 returns PERMISSION_DENIED. Android 16
// (API 36) exposes KeyStoreManager.grantKeyAccess(), so an app grants its own key:
// issue a coherent, caller-bound, access-vector-carrying grant whose Domain.GRANT read
// returns the owner's chain.
if (Build.VERSION.SDK_INT < GRANT_PUBLIC_API_SDK) {
return InterceptorUtils.createErrorReply(RESPONSE_PERMISSION_DENIED)
}
val grantId =
KeyMintSecurityLevelInterceptor.issueGrant(ownerKeyId, granteeUid, accessVector)
val reply =
KeyDescriptor().apply {
domain = Domain.GRANT
nspace = grantId
alias = null
blob = null
}
return InterceptorUtils.createTypedObjectReply(reply)
} else if (code == UNGRANT_TRANSACTION) {
logTransaction(txId, transactionNames[code] ?: "ungrant", callingUid, callingPid)
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val key =
data.readTypedObject(KeyDescriptor.CREATOR)
?: return TransactionResult.ContinueAndSkipPost
val granteeUid = data.readInt()
val ownerKeyId =
resolveOwnerKeyId(key, callingUid)?.takeIf {
KeyMintSecurityLevelInterceptor.ownsKeyResponse(it)
} ?: return TransactionResult.ContinueAndSkipPost
// Same version gate as grant(): denied pre-36, revoke the virtualized grant on 36+.
if (Build.VERSION.SDK_INT < GRANT_PUBLIC_API_SDK) {
return InterceptorUtils.createErrorReply(RESPONSE_PERMISSION_DENIED)
}
KeyMintSecurityLevelInterceptor.revokeGrant(ownerKeyId, granteeUid)
return InterceptorUtils.createSuccessReply(writeResultCode = false)
} else {
logTransaction(
txId,
transactionNames[code] ?: "unknown code=$code",
callingUid,
callingPid,
true,
)
}
// Let most calls go through to the real service.
return TransactionResult.ContinueAndSkipPost
}
override fun onPostTransact(
txId: Long,
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
reply: Parcel?,
resultCode: Int,
): TransactionResult {
if (target != keystoreService || reply == null) return TransactionResult.SkipTransaction
if (InterceptorUtils.hasException(reply)) {
val normalized = InterceptorUtils.normalizeServiceSpecificReply(reply)
return if (normalized != null) TransactionResult.OverrideReply(normalized)
else TransactionResult.SkipTransaction
}
if (code == GET_NUMBER_OF_ENTRIES_TRANSACTION) {
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
return runCatching {
val hardwareCount = reply.readInt()
val softwareCount =
KeyMintSecurityLevelInterceptor.generatedKeys.keys.count {
it.uid == callingUid
}
val totalCount = hardwareCount + softwareCount
val parcel =
Parcel.obtain().apply {
writeNoException()
writeInt(totalCount)
}
TransactionResult.OverrideReply(parcel)
}
.getOrElse {
SystemLogger.error("[TX_ID: $txId] Failed to modify getNumberOfEntries.", it)
TransactionResult.SkipTransaction
}
} else if (code == LIST_ENTRIES_TRANSACTION || code == LIST_ENTRIES_BATCHED_TRANSACTION) {
logTransaction(txId, "post-${transactionNames[code]!!}", callingUid, callingPid)
return runCatching {
val updatedKeyDescriptors =
ListEntriesHandler.injectGeneratedKeys(txId, callingUid, reply)
InterceptorUtils.createTypedArrayReply(updatedKeyDescriptors)
}
.getOrElse {
SystemLogger.error(
"[TX_ID: $txId] Failed to update the result of ${transactionNames[code]!!}.",
it,
)
TransactionResult.SkipTransaction
}
} else if (code == GET_KEY_ENTRY_TRANSACTION) {
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val keyDescriptor =
data.readTypedObject(KeyDescriptor.CREATOR)
?: return TransactionResult.SkipTransaction
logTransaction(
txId,
"post-${transactionNames[code]!!} ${keyDescriptor.alias}",
callingUid,
callingPid,
)
if (!ConfigurationManager.shouldPatch(callingUid))
return TransactionResult.SkipTransaction
runCatching {
val response = reply.readTypedObject(KeyEntryResponse.CREATOR)!!
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
if (userUpdatedKeys.remove(keyId)) {
SystemLogger.trace {
"[TRACE-$txId] getKeyEntry $keyId: userUpdated=true, skipping patch"
}
SystemLogger.debug(
"[TX_ID: $txId] Skipping cert patch for user-updated key $keyId."
)
return TransactionResult.SkipTransaction
}
val authorizations = response.metadata.authorizations
val parsedParameters =
KeyMintAttestation(
authorizations?.map { it.keyParameter }?.toTypedArray() ?: emptyArray()
)
SystemLogger.trace {
"[TRACE-$txId] getKeyEntry $keyId: isImport=${parsedParameters.isImportKey()} origin=${parsedParameters.origin} inImportedKeys=${KeyMintSecurityLevelInterceptor.importedKeys.contains(keyId)} hasPatchedChain=${KeyMintSecurityLevelInterceptor.getPatchedChain(keyId) != null} isAttestKey=${parsedParameters.isAttestKey()}"
}
if (parsedParameters.isImportKey()) {
val retainedChain = KeyMintSecurityLevelInterceptor.getPatchedChain(keyId)
if (retainedChain == null) {
SystemLogger.trace {
"[TRACE-$txId] getKeyEntry $keyId: imported, no retained chain, skip"
}
SystemLogger.info(
"[TX_ID: $txId] Skip patching for imported key (no prior attestation)."
)
return TransactionResult.SkipTransaction
}
SystemLogger.trace {
"[TRACE-$txId] getKeyEntry $keyId: imported, SERVING RETAINED CHAIN (detection vector!)"
}
SystemLogger.info(
"[TX_ID: $txId] Imported key overwrote attested alias, serving retained chain for $keyId"
)
CertificateHelper.updateCertificateChain(response.metadata, retainedChain)
.getOrThrow()
response.metadata.authorizations =
InterceptorUtils.patchAuthorizations(
response.metadata.authorizations,
callingUid,
)
return InterceptorUtils.createTypedObjectReply(response)
}
if (KeyMintSecurityLevelInterceptor.importedKeys.contains(keyId)) {
SystemLogger.trace {
"[TRACE-$txId] getKeyEntry $keyId: in importedKeys set, skip"
}
SystemLogger.debug(
"[TX_ID: $txId] Skipping attest-key override for imported key $keyId"
)
return TransactionResult.SkipTransaction
}
if (parsedParameters.isAttestKey()) {
SystemLogger.warning(
"[TX_ID: $txId] Found hardware attest key ${keyId.alias} in the reply."
)
val keyData =
CertificateGenerator.generateAttestedKeyPair(
callingUid,
keyId.alias,
null,
parsedParameters,
response.metadata.keySecurityLevel,
) ?: throw Exception("Failed to create overriding attest key pair.")
CertificateHelper.updateCertificateChain(
response.metadata,
keyData.second.toTypedArray(),
)
.getOrThrow()
response.metadata.authorizations =
InterceptorUtils.patchAuthorizations(
response.metadata.authorizations,
callingUid,
)
val newNspace = SecureRandom().nextLong()
response.metadata.key?.let { it.nspace = newNspace }
KeyMintSecurityLevelInterceptor.generatedKeys[keyId] =
KeyMintSecurityLevelInterceptor.GeneratedKeyInfo(
keyData.first,
null,
newNspace,
response,
parsedParameters,
)
KeyMintSecurityLevelInterceptor.attestationKeys.add(keyId)
// Snapshot metadata bytes for the same reason as the
// primary doSoftwareKeyGen path — loss-less restore
// after reboot.
val metadataBytesForPersist =
response.metadata?.let { md ->
runCatching {
val parcel = android.os.Parcel.obtain()
try {
md.writeToParcel(parcel, 0)
parcel.marshall()
} finally {
parcel.recycle()
}
}
.getOrNull()
}
GeneratedKeyPersistence.save(
keyId = keyId,
keyPair = keyData.first,
secretKey = null,
nspace = newNspace,
securityLevel = response.metadata.keySecurityLevel,
certChain = keyData.second,
algorithm = parsedParameters.algorithm,
keySize = parsedParameters.keySize,
ecCurve = parsedParameters.ecCurve ?: 0,
purposes = parsedParameters.purpose,
digests = parsedParameters.digest,
isAttestationKey = true,
metadataBytes = metadataBytesForPersist,
)
return InterceptorUtils.createTypedObjectReply(response)
}
val originalChain = CertificateHelper.getCertificateChain(response)
if (originalChain == null || originalChain.size < 2) {
SystemLogger.info(
"[TX_ID: $txId] Skip patching short certificate chain of length ${originalChain?.size}."
)
return TransactionResult.SkipTransaction
}
val cachedChain = KeyMintSecurityLevelInterceptor.getPatchedChain(keyId)
val finalChain: Array<Certificate>
if (cachedChain != null) {
SystemLogger.debug(
"[TX_ID: $txId] Using cached patched certificate chain for $keyId."
)
finalChain = cachedChain
} else {
SystemLogger.info(
"[TX_ID: $txId] No cached chain for $keyId. Performing live patch as a fallback."
)
finalChain =
AttestationPatcher.patchCertificateChain(originalChain, callingUid)
KeyMintSecurityLevelInterceptor.patchedChains[keyId] = finalChain
}
CertificateHelper.updateCertificateChain(response.metadata, finalChain)
.getOrThrow()
response.metadata.authorizations =
InterceptorUtils.patchAuthorizations(
response.metadata.authorizations,
callingUid,
)
// PATCH decode point: the patched chain actually served back to the app on
// getKeyEntry — the ground truth a patch-mode detector reads.
AttestationDossier.log(callingUid, txId, "PATCH", finalChain.asList())
return InterceptorUtils.createTypedObjectReply(response)
}
.onFailure {
SystemLogger.error(
"[TX_ID: $txId] Failed to modify hardware KeyEntryResponse.",
it,
)
return TransactionResult.SkipTransaction
}
}
return TransactionResult.SkipTransaction
}
/**
* Resolves the owner [KeyIdentifier] a grant/ungrant call targets. APP/alias keys map directly;
* KEY_ID keys are looked up by nspace (mirrors the deleteKey resolver). Returns null for
* anything not addressable, so callers fall through to the real keystore2.
*/
private fun resolveOwnerKeyId(descriptor: KeyDescriptor, callingUid: Int): KeyIdentifier? =
when {
descriptor.alias != null -> KeyIdentifier(callingUid, descriptor.alias)
descriptor.domain == Domain.KEY_ID ->
KeyMintSecurityLevelInterceptor.findGeneratedKeyByKeyId(
callingUid,
descriptor.nspace,
)
?.let { info ->
KeyMintSecurityLevelInterceptor.generatedKeys.entries
.firstOrNull {
it.value.nspace == info.nspace && it.key.uid == callingUid
}
?.key
}
else -> null
}
/**
* Records the certificate chain actually served back to [uid] on a getKeyEntry, keyed by
* [alias]. The app reassembles its final chain from these served chains (the leaf alias plus
* the attest-key alias), so logging each one with key sizes and a per-edge verification makes a
* verification failure in the app's combined chain reproducible from the log, not inferred.
*/
private fun logServedChain(uid: Int, txId: Long, alias: String, response: KeyEntryResponse?) {
if (response == null || !SystemLogger.isUidLogged(uid)) return
val chain = CertificateHelper.getCertificateChain(response)?.asList() ?: return
SystemLogger.uidLog(uid, txId, "served", "alias=$alias depth=${chain.size}")
SystemLogger.uidLog(uid, txId, "served-keys", AttestationPatcher.formatChainKeys(chain))
SystemLogger.uidLog(uid, txId, "served-verify", AttestationPatcher.formatChainVerification(chain))
}
private fun handleUpdateSubcomponent(callingUid: Int, data: Parcel): TransactionResult {
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val descriptor =
data.readTypedObject(KeyDescriptor.CREATOR)
?: return TransactionResult.ContinueAndSkipPost
if (descriptor.domain == Domain.GRANT) {
val grant =
KeyMintSecurityLevelInterceptor.resolveGrant(descriptor.nspace, callingUid)
if (grant == null) {
return if (
KeyMintSecurityLevelInterceptor.softwareGrants.containsKey(descriptor.nspace)
)
InterceptorUtils.createErrorReply(RESPONSE_KEY_NOT_FOUND)
else TransactionResult.ContinueAndSkipPost
}
if ((grant.accessVector and KEY_PERMISSION_UPDATE) == 0) {
return InterceptorUtils.createErrorReply(RESPONSE_PERMISSION_DENIED)
}
val generatedKeyInfo =
KeyMintSecurityLevelInterceptor.generatedKeys[grant.ownerKeyId]
val response =
generatedKeyInfo?.response
?: KeyMintSecurityLevelInterceptor.getGeneratedKeyResponse(grant.ownerKeyId)
?: return InterceptorUtils.createErrorReply(RESPONSE_KEY_NOT_FOUND)
return updateResponseSubcomponent(
response = response,
publicCert = data.createByteArray(),
certificateChain = data.createByteArray(),
persist = {
if (generatedKeyInfo != null) {
GeneratedKeyPersistence.rePersistIfNeeded(
grant.ownerKeyId.uid,
generatedKeyInfo,
)
}
},
label = "grant[${descriptor.nspace}] -> ${grant.ownerKeyId}",
)
}
val generatedKeyInfo =
when (descriptor.domain) {
Domain.KEY_ID ->
KeyMintSecurityLevelInterceptor.findGeneratedKeyByKeyId(
callingUid,
descriptor.nspace,
)
Domain.APP ->
descriptor.alias?.let {
KeyMintSecurityLevelInterceptor.generatedKeys[KeyIdentifier(callingUid, it)]
}
else -> null
}
if (generatedKeyInfo == null) {
// Patch-mode key (cached in teeResponses, not generatedKeys): the real keystore2
// applies
// the update, so drop our stale cached chain. Otherwise getKeyEntry replays the
// pre-update generated attestation (duck STALE_TEE_RESPONSE_AFTER_KEY_ID_UPDATE).
when (descriptor.domain) {
Domain.KEY_ID ->
KeyMintSecurityLevelInterceptor.evictTeeResponseByKeyId(
callingUid,
descriptor.nspace,
)
Domain.APP ->
descriptor.alias?.let {
KeyMintSecurityLevelInterceptor.evictTeeResponse(
KeyIdentifier(callingUid, it)
)
}
else -> {}
}
descriptor.alias?.let {
val kid = KeyIdentifier(callingUid, it)
userUpdatedKeys.add(kid)
SystemLogger.trace {
"[TRACE] updateSubcomponent $kid: not generated key, added to userUpdatedKeys"
}
}
return TransactionResult.ContinueAndSkipPost
}
return updateResponseSubcomponent(
response = generatedKeyInfo.response,
publicCert = data.createByteArray(),
certificateChain = data.createByteArray(),
persist = {
GeneratedKeyPersistence.rePersistIfNeeded(callingUid, generatedKeyInfo)
},
label = "key[${generatedKeyInfo.nspace}]",
)
}
private fun updateResponseSubcomponent(
response: KeyEntryResponse,
publicCert: ByteArray?,
certificateChain: ByteArray?,
persist: () -> Unit,
label: String,
): TransactionResult {
SystemLogger.info("Updating sub-component with $label")
val metadata = response.metadata
metadata.certificate = publicCert
metadata.certificateChain = certificateChain
persist()
SystemLogger.verbose(
"Key updated with sizes: [publicCert, certificateChain] = [${publicCert?.size}, ${certificateChain?.size}]"
)
return InterceptorUtils.createSuccessReply(writeResultCode = false)
}
}
@@ -0,0 +1,113 @@
package org.matrix.TEESimulator.interception.keystore
import android.os.IBinder
import android.os.Parcel
import android.security.maintenance.IKeystoreMaintenance
import android.system.keystore2.Domain
import android.system.keystore2.KeyDescriptor
import org.matrix.TEESimulator.interception.core.BinderInterceptor
import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor
/**
* Intercepts the keystore2 daemon's `android.security.maintenance` binder so our synthetic key
* state follows the same lifecycle events the platform applies to real keys.
*
* This is a pure side-effect hook: every handled transaction mutates only our own synthetic state
* and then returns [TransactionResult.ContinueAndSkipPost], so the real keystore2 still performs
* the real operation. We never fabricate a maintenance reply, so real key lifecycle is never
* disturbed.
*
* Mounted via `register()` from [Keystore2Interceptor.onInterceptorReady]; the maintenance binder
* is hosted by the same keystore2 process, so the already-injected native hook reaches it too.
*/
object Keystore2MaintenanceInterceptor : BinderInterceptor() {
private val stubClass = IKeystoreMaintenance.Stub::class.java
private val CLEAR_NAMESPACE_TRANSACTION =
InterceptorUtils.getTransactCode(stubClass, "clearNamespace")
private val DELETE_ALL_KEYS_TRANSACTION =
InterceptorUtils.getTransactCode(stubClass, "deleteAllKeys")
private val MIGRATE_KEY_NAMESPACE_TRANSACTION =
InterceptorUtils.getTransactCode(stubClass, "migrateKeyNamespace")
/** Only the lifecycle transactions we mirror; unresolved codes (-1) are dropped. */
val interceptedCodes: IntArray by lazy {
listOf(
CLEAR_NAMESPACE_TRANSACTION,
DELETE_ALL_KEYS_TRANSACTION,
MIGRATE_KEY_NAMESPACE_TRANSACTION,
)
.filter { it != -1 }
.toIntArray()
}
override fun onPreTransact(
txId: Long,
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
): TransactionResult {
when (code) {
CLEAR_NAMESPACE_TRANSACTION -> handleClearNamespace(data)
DELETE_ALL_KEYS_TRANSACTION ->
KeyMintSecurityLevelInterceptor.clearAllGeneratedKeys("maintenance.deleteAllKeys")
MIGRATE_KEY_NAMESPACE_TRANSACTION -> handleMigrateKeyNamespace(data, callingUid)
}
// Always let the real keystore2 perform the real lifecycle operation.
return TransactionResult.ContinueAndSkipPost
}
private fun handleClearNamespace(data: Parcel) {
data.enforceInterface(IKeystoreMaintenance.DESCRIPTOR)
val domain = data.readInt()
val nspace = data.readLong()
// Only Domain.APP namespaces map to our per-uid synthetic keys; nspace is the app uid.
if (domain == Domain.APP) {
KeyMintSecurityLevelInterceptor.clearNamespaceKeys(nspace.toInt())
}
}
private fun handleMigrateKeyNamespace(data: Parcel, callingUid: Int) {
data.enforceInterface(IKeystoreMaintenance.DESCRIPTOR)
val source = data.readTypedObject(KeyDescriptor.CREATOR) ?: return
val destination = data.readTypedObject(KeyDescriptor.CREATOR) ?: return
val srcId = resolveSyntheticKeyId(source, callingUid) ?: return
if (!KeyMintSecurityLevelInterceptor.generatedKeys.containsKey(srcId)) return // not ours
val dstId = resolveDestinationKeyId(destination, callingUid)
if (dstId == null) {
// Migrated out of our trackable (Domain.APP/alias) space -> drop our shadow so reads
// fall through to the real keystore2, which now owns it at the new namespace.
KeyMintSecurityLevelInterceptor.cleanupKeyData(srcId)
} else {
KeyMintSecurityLevelInterceptor.migrateGeneratedKey(srcId, dstId)
}
}
/** Resolves a synthetic owner key from a source descriptor (Domain.APP alias or KEY_ID). */
private fun resolveSyntheticKeyId(descriptor: KeyDescriptor, callingUid: Int): KeyIdentifier? =
when {
descriptor.alias != null -> KeyIdentifier(callingUid, descriptor.alias)
descriptor.domain == Domain.KEY_ID ->
KeyMintSecurityLevelInterceptor.generatedKeys.entries
.firstOrNull {
it.key.uid == callingUid && it.value.nspace == descriptor.nspace
}
?.key
else -> null
}
/** Destination must be an addressable Domain.APP alias for us to keep tracking the key. */
private fun resolveDestinationKeyId(
descriptor: KeyDescriptor,
callingUid: Int,
): KeyIdentifier? {
val alias = descriptor.alias ?: return null
if (descriptor.domain != Domain.APP) return null
val uid = if (descriptor.nspace > 0) descriptor.nspace.toInt() else callingUid
return KeyIdentifier(uid, alias)
}
}
@@ -0,0 +1,514 @@
package org.matrix.TEESimulator.interception.keystore
import android.annotation.SuppressLint
import android.os.IBinder
import android.os.Parcel
import android.security.Credentials
import android.security.KeyStore
import android.security.keymaster.ExportResult
import android.security.keymaster.KeyCharacteristics
import android.security.keymaster.KeymasterArguments
import android.security.keymaster.KeymasterCertificateChain
import android.security.keymaster.KeymasterDefs
import android.security.keystore.IKeystoreCertificateChainCallback
import android.security.keystore.IKeystoreExportKeyCallback
import android.security.keystore.IKeystoreKeyCharacteristicsCallback
import android.security.keystore.IKeystoreService
import java.math.BigInteger
import java.security.KeyPair
import java.security.cert.Certificate
import java.util.Date
import java.util.concurrent.ConcurrentHashMap
import org.matrix.TEESimulator.attestation.AttestationBuilder
import org.matrix.TEESimulator.attestation.AttestationPatcher
import org.matrix.TEESimulator.attestation.KeyMintAttestation
import org.matrix.TEESimulator.config.ConfigurationManager
import org.matrix.TEESimulator.interception.keystore.InterceptorUtils.extractAlias
import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.pki.CertificateGenerator
import org.matrix.TEESimulator.pki.CertificateHelper
/**
* Interceptor for the legacy `IKeystoreService` on Android Q (API 29) and R (API 30).
*
* This interceptor handles the older, monolithic Keystore service. Unlike Keystore2, it doesn't
* have security level sub-services, so all logic is contained here. Key generation is fully
* simulated in software for packages in 'generate' mode.
*/
@SuppressLint("BlockedPrivateApi", "PrivateApi")
object KeystoreInterceptor : AbstractKeystoreInterceptor() {
// Transaction codes are dynamically retrieved via reflection for compatibility.
private val GET_TRANSACTION by lazy {
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "get")
}
private val GENERATE_KEY_TRANSACTION by lazy {
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "generateKey")
}
private val GET_KEY_CHARACTERISTICS_TRANSACTION by lazy {
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "getKeyCharacteristics")
}
private val EXPORT_KEY_TRANSACTION by lazy {
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "exportKey")
}
private val ATTEST_KEY_TRANSACTION by lazy {
InterceptorUtils.getTransactCode(IKeystoreService.Stub::class.java, "attestKey")
}
private val transactionNames: Map<Int, String> by lazy {
IKeystoreService.Stub::class
.java
.declaredFields
.filter {
it.isAccessible = true
it.type == Int::class.java && it.name.startsWith("TRANSACTION_")
}
.associate { field -> (field.get(null) as Int) to field.name.split("_")[1] }
}
// A map to dispatch transaction handling for software key generation.
private val generateKeyHandlers:
Map<Int, (Long, Int, Int, Parcel) -> TransactionResult> by lazy {
mapOf(
GENERATE_KEY_TRANSACTION to ::handleGenerateKey,
GET_KEY_CHARACTERISTICS_TRANSACTION to ::handleGetKeyCharacteristics,
EXPORT_KEY_TRANSACTION to ::handleExportKey,
ATTEST_KEY_TRANSACTION to ::handleAttestKey,
)
}
override val serviceName = "android.security.keystore"
override val processName = "keystore"
override val injectionCommand = "exec ./inject `pidof keystore` libTEESimulator.so entry"
// State management for the multi-step key generation process.
private val keygenParameters = ConcurrentHashMap<KeyIdentifier, LegacyKeygenParameters>()
private val generatedKeyPairs = ConcurrentHashMap<KeyIdentifier, KeyPair>()
// Cache to store the fully patched chain after the leaf is requested.
private val patchedChainCache = ConcurrentHashMap<KeyIdentifier, Array<Certificate>>()
override fun onPreTransact(
txId: Long,
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
): TransactionResult {
// This interceptor only needs to act on pre-transaction for software key generation.
// Handle 'generate' mode interceptions using the handler map.
if (ConfigurationManager.shouldGenerate(callingUid)) {
generateKeyHandlers[code]?.let { handler ->
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid)
return handler(txId, callingUid, callingPid, data)
}
}
// Handle 'patch' mode interceptions for the 'get' transaction.
if (ConfigurationManager.shouldPatch(callingUid) && code == GET_TRANSACTION) {
logTransaction(txId, transactionNames[code]!!, callingUid, callingPid, true)
return TransactionResult.Continue
}
// Default behavior for all other transactions.
logTransaction(
txId,
transactionNames[code] ?: "unknown code=$code",
callingUid,
callingPid,
true,
)
return TransactionResult.ContinueAndSkipPost
}
private fun handleGenerateKey(txId: Long, uid: Int, pid: Int, data: Parcel): TransactionResult {
return runCatching {
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val callback =
IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
val alias = InterceptorUtils.extractAlias(data.readString()!!)
val keyId = KeyIdentifier(uid, alias)
// Read and parse the key generation arguments.
val keymasterArgs = KeymasterArguments()
if (data.readInt() == 1) {
keymasterArgs.readFromParcel(data)
}
keygenParameters[keyId] =
LegacyKeygenParameters.fromKeymasterArguments(keymasterArgs)
// Create a fake successful response for the callback.
val characteristics = KeyCharacteristics()
characteristics.swEnforced = KeymasterArguments()
characteristics.hwEnforced = keymasterArgs
val keystoreResponse = InterceptorUtils.createSuccessKeystoreResponse()
callback.onFinished(keystoreResponse, characteristics)
InterceptorUtils.createSuccessReply()
}
.getOrElse {
SystemLogger.error("[TX_ID: $txId] Failed during handleGenerateKey.", it)
TransactionResult.ContinueAndSkipPost
}
}
private fun handleGetKeyCharacteristics(
txId: Long,
uid: Int,
pid: Int,
data: Parcel,
): TransactionResult {
return runCatching {
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val callback =
IKeystoreKeyCharacteristicsCallback.Stub.asInterface(data.readStrongBinder())
val alias = InterceptorUtils.extractAlias(data.readString()!!)
val keyId = KeyIdentifier(uid, alias)
val params =
keygenParameters[keyId]
?: throw IllegalStateException("No params found for $keyId")
val characteristics =
KeyCharacteristics().apply {
swEnforced = KeymasterArguments()
hwEnforced =
KeymasterArguments().apply {
addEnum(KeymasterDefs.KM_TAG_ALGORITHM, params.algorithm)
}
}
callback.onFinished(
InterceptorUtils.createSuccessKeystoreResponse(),
characteristics,
)
InterceptorUtils.createSuccessReply()
}
.getOrElse {
SystemLogger.error("[TX_ID: $txId] Failed during handleGetKeyCharacteristics.", it)
TransactionResult.ContinueAndSkipPost
}
}
private fun handleExportKey(txId: Long, uid: Int, pid: Int, data: Parcel): TransactionResult {
return runCatching {
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val callback = IKeystoreExportKeyCallback.Stub.asInterface(data.readStrongBinder())
val alias = InterceptorUtils.extractAlias(data.readString()!!)
val keyId = KeyIdentifier(uid, alias)
val params =
keygenParameters[keyId]
?: throw IllegalStateException("No params found for $keyId")
// Generate a software key pair using the new generator.
val keyPair =
CertificateGenerator.generateSoftwareKeyPair(params.toKeyMintAttestation())
?: throw Exception("Failed to generate software key pair.")
generatedKeyPairs[keyId] = keyPair
// Create a successful ExportResult containing the public key.
val exportResultParcel =
Parcel.obtain().apply {
writeInt(KeyStore.NO_ERROR)
writeByteArray(keyPair.public.encoded)
setDataPosition(0)
}
val exportResult = ExportResult.CREATOR.createFromParcel(exportResultParcel)
exportResultParcel.recycle()
callback.onFinished(exportResult)
InterceptorUtils.createSuccessReply()
}
.getOrElse {
SystemLogger.error("[TX_ID: $txId] Failed during handleExportKey.", it)
TransactionResult.ContinueAndSkipPost
}
}
private fun handleAttestKey(txId: Long, uid: Int, pid: Int, data: Parcel): TransactionResult {
return runCatching {
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val callback =
IKeystoreCertificateChainCallback.Stub.asInterface(data.readStrongBinder())
val alias = InterceptorUtils.extractAlias(data.readString()!!)
val keyId = KeyIdentifier(uid, alias)
// Get the attestation challenge from the arguments.
val params =
keygenParameters[keyId]
?: throw IllegalStateException("No params found for $keyId")
val keyPair =
generatedKeyPairs[keyId]
?: throw IllegalStateException("No keypair found for $keyId")
val attestationArgs = KeymasterArguments()
if (data.readInt() == 1) {
attestationArgs.readFromParcel(data)
val challenge =
attestationArgs.getBytes(
KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE,
ByteArray(0),
)
params.attestationChallenge = challenge
}
val certificateChain =
CertificateGenerator.generateCertificateChain(
uid,
keyPair,
null, // No attestKeyAlias in legacy flow
params.toKeyMintAttestation(), // Convert to modern format
1, // SecurityLevel.TRUSTED_ENVIRONMENT
) ?: throw Exception("CertificateGenerator failed to create attested key pair.")
val chainAsByteList = certificateChain.map { it.encoded }
val certChain = KeymasterCertificateChain(chainAsByteList)
callback.onFinished(InterceptorUtils.createSuccessKeystoreResponse(), certChain)
InterceptorUtils.createSuccessReply()
}
.getOrElse {
SystemLogger.error("[TX_ID: $txId] Failed during handleAttestKey.", it)
TransactionResult.ContinueAndSkipPost
}
}
override fun onPostTransact(
txId: Long,
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
reply: Parcel?,
resultCode: Int,
): TransactionResult {
if (
target != keystoreService ||
code != GET_TRANSACTION ||
reply == null ||
InterceptorUtils.hasException(reply)
) {
SystemLogger.debug(
"[TX_ID: $txId] Skip parsing post-transaction for [target, code, reply]: [$target, $code, $reply]"
)
return TransactionResult.SkipTransaction
}
if (!ConfigurationManager.shouldPatch(callingUid)) return TransactionResult.SkipTransaction
return try {
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val alias = data.readString() ?: ""
val extractedAlias = InterceptorUtils.extractAlias(alias)
val keyId = KeyIdentifier(callingUid, extractedAlias)
SystemLogger.debug(
"[TX_ID: $txId] Parsed $keyId during post-transaction of ${transactionNames[code]}"
)
when {
// Case 1: The app is requesting the leaf certificate.
alias.startsWith(Credentials.USER_CERTIFICATE) -> {
logTransaction(txId, "post-get (user cert)", callingUid, callingPid)
val originalLeafBytes =
reply.createByteArray() ?: return TransactionResult.SkipTransaction
val originalLeafCertResult = CertificateHelper.toCertificate(originalLeafBytes)
if (originalLeafCertResult !is CertificateHelper.OperationResult.Success) {
return TransactionResult.SkipTransaction
}
val originalLeafCert = originalLeafCertResult.data
val tempChain = arrayOf<Certificate>(originalLeafCert)
// Perform the COMPLETE patch and rebuild operation.
val newFullChain =
AttestationPatcher.patchCertificateChain(tempChain, callingUid)
// If patching was successful and we have a valid chain...
if (newFullChain.isNotEmpty() && newFullChain[0] != originalLeafCert) {
// ...cache the entire new chain for the subsequent "ca_cert" call.
patchedChainCache[keyId] = newFullChain
// And return only the new leaf's bytes, as the API expects.
SystemLogger.info(
"[TX_ID: $txId] Patched and cached chain for alias '$extractedAlias'. Returning new leaf."
)
InterceptorUtils.createByteArrayReply(newFullChain[0].encoded)
} else {
// Patching failed or was skipped; do nothing.
TransactionResult.SkipTransaction
}
}
// Case 2: The app is requesting the CA certificate chain.
alias.startsWith(Credentials.CA_CERTIFICATE) -> {
logTransaction(txId, "post-get (ca cert)", callingUid, callingPid)
// Retrieve the full, correct chain we cached during the leaf request.
val cachedChain = patchedChainCache.remove(keyId)
if (cachedChain != null && cachedChain.size > 1) {
// The CA chain is everything *except* the first element (the leaf).
val caCerts = cachedChain.drop(1)
val caCertsBytes = CertificateHelper.certificatesToByteArray(caCerts)
SystemLogger.info(
"[TX_ID: $txId] Returning cached CA chain for alias '$extractedAlias'."
)
InterceptorUtils.createByteArrayReply(caCertsBytes!!)
} else {
SystemLogger.warning(
"[TX_ID: $txId] No cached chain found for CA request on alias '$extractedAlias'. Skipping."
)
TransactionResult.SkipTransaction
}
}
else -> TransactionResult.SkipTransaction
}
} catch (e: Exception) {
SystemLogger.error("[TX_ID: $txId] Failed during legacy post-transaction patching.", e)
TransactionResult.SkipTransaction
}
}
}
/**
* A data class to hold key generation parameters parsed from the legacy IKeystoreService's
* KeymasterArguments. It is used exclusively by the KeystoreInterceptor to manage state during the
* software key generation flow.
*/
private data class LegacyKeygenParameters(
val algorithm: Int,
val keySize: Int,
val purpose: List<Int>,
val digest: List<Int>,
val certificateNotBefore: Date?,
val rsaPublicExponent: BigInteger?,
val ecCurveName: String?, // Derived from keySize
) {
// The challenge is provided in a separate transaction (attestKey), so it must be mutable.
var attestationChallenge: ByteArray? = null
/**
* Converts the legacy parameters into the modern [KeyMintAttestation] data structure, which is
* required by the refactored [AttestationBuilder] and [CertificateGenerator].
*/
fun toKeyMintAttestation(): KeyMintAttestation {
// This conversion acts as a bridge, allowing our new generic components
// to be used by the legacy interceptor.
return KeyMintAttestation(
keySize = this.keySize,
algorithm = this.algorithm,
ecCurve = 0,
ecCurveName = this.ecCurveName ?: "",
origin = null,
blockMode = listOf<Int>(),
padding = listOf<Int>(),
purpose = this.purpose,
digest = this.digest,
rsaPublicExponent = this.rsaPublicExponent,
certificateSerial = null, // Not provided in legacy generateKey
certificateSubject = null, // Not provided in legacy generateKey
certificateNotBefore = this.certificateNotBefore,
certificateNotAfter = null, // Not provided in legacy generateKey
attestationChallenge = this.attestationChallenge,
// Device identifiers are not passed in legacy args;
// AttestationBuilder will fetch them from system properties.
brand = null,
device = null,
product = null,
serial = null,
imei = null,
meid = null,
manufacturer = null,
model = null,
secondImei = null,
activeDateTime = null,
originationExpireDateTime = null,
usageExpireDateTime = null,
usageCountLimit = null,
callerNonce = null,
nonce = null,
unlockedDeviceRequired = null,
includeUniqueId = null,
rollbackResistance = null,
earlyBootOnly = null,
allowWhileOnBody = null,
trustedUserPresenceRequired = null,
trustedConfirmationRequired = null,
noAuthRequired = null,
maxUsesPerBoot = null,
maxBootLevel = null,
minMacLength = null,
rsaOaepMgfDigest = emptyList(),
)
}
companion object {
/** Factory method to create an instance from a [KeymasterArguments] object. */
fun fromKeymasterArguments(args: KeymasterArguments): LegacyKeygenParameters {
val algorithm = args.getEnum(KeymasterDefs.KM_TAG_ALGORITHM, 0)
val keySize = args.getUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 0).toInt()
return LegacyKeygenParameters(
algorithm = algorithm,
keySize = keySize,
purpose = args.getEnums(KeymasterDefs.KM_TAG_PURPOSE),
digest = args.getEnums(KeymasterDefs.KM_TAG_DIGEST),
certificateNotBefore = args.getDate(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, Date()),
rsaPublicExponent =
if (algorithm == KeymasterDefs.KM_ALGORITHM_RSA) getRsaExponent(args) else null,
ecCurveName =
if (algorithm == KeymasterDefs.KM_ALGORITHM_EC) deriveEcCurveName(keySize)
else null,
)
}
private fun deriveEcCurveName(keySize: Int): String =
when (keySize) {
224 -> "secp224r1"
256 -> "secp256r1"
384 -> "secp384r1"
521 -> "secp521r1"
else -> "secp256r1" // Default fallback
}
/**
* The RSA public exponent is not accessible via a public API in KeymasterArguments, so we
* must use reflection to extract it.
*/
private fun getRsaExponent(args: KeymasterArguments): BigInteger? {
return runCatching {
val getArgumentByTag =
KeymasterArguments::class
.java
.getDeclaredMethod("getArgumentByTag", Int::class.java)
getArgumentByTag.isAccessible = true
val rsaArgument =
getArgumentByTag.invoke(args, KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT)
val getLongTagValue =
KeymasterArguments::class
.java
.getDeclaredMethod(
"getLongTagValue",
Class.forName("android.security.keymaster.KeymasterArgument"),
)
getLongTagValue.isAccessible = true
getLongTagValue.invoke(args, rsaArgument) as BigInteger
}
.onFailure {
SystemLogger.error("Failed to read rsaPublicExponent via reflection.", it)
}
.getOrNull()
}
}
}
@@ -0,0 +1,142 @@
package org.matrix.TEESimulator.interception.keystore
import android.os.Parcel
import android.system.keystore2.Domain
import android.system.keystore2.IKeystoreService
import android.system.keystore2.KeyDescriptor
import java.util.TreeMap
import java.util.concurrent.ConcurrentHashMap
import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor
import org.matrix.TEESimulator.logging.SystemLogger
/**
* Handler to intercept listEntries and listEntriesBatched transactions.
*
* References for all mentioned functions in AOSP:
* https://cs.android.com/android/platform/superproject/main/+/main:system/security/keystore2/src/database.rs
* https://cs.android.com/android/platform/superproject/main/+/main:system/security/keystore2/src/service.rs
* https://cs.android.com/android/platform/superproject/main/+/main:system/security/keystore2/src/utils.rs
*/
object ListEntriesHandler {
// Estimate for maximum size of a Binder response in bytes.
private const val RESPONSE_SIZE_LIMIT = 358400
// Parameters of AOSP function `list_key_entries` in utils.rs.
private data class ListEntriesParams(
val domain: Int,
val namespace: Long,
val startPastAlias: String?,
)
private val pendingParams = ConcurrentHashMap<Long, ListEntriesParams>()
// Based on AOSP function `estimate_safe_amount_to_return` in utils.rs.
private fun estimateSafeAmountToReturn(
keyDescriptors: Array<KeyDescriptor>,
responseSizeLimit: Int,
): Int {
var itemsToReturn = 0
var returnedBytes = 0
for (kd in keyDescriptors) {
// 4 bytes for the Domain enum
// 8 bytes for the Namespace long
returnedBytes += 4 + 8
kd.alias?.let { returnedBytes += 4 + it.toByteArray(Charsets.UTF_8).size }
kd.blob?.let { returnedBytes += 4 + it.size }
if (returnedBytes > responseSizeLimit) {
SystemLogger.warning(
"Key descriptors list (${keyDescriptors.size} items) may exceed binder size limit, returning $itemsToReturn items with estimated size: $returnedBytes bytes."
)
break
}
itemsToReturn++
}
return itemsToReturn
}
// Parse and store parameters for later use (in post-transaction).
fun cacheParameters(txId: Long, data: Parcel, isBatchMode: Boolean): Boolean {
data.enforceInterface(IKeystoreService.DESCRIPTOR)
val domain = data.readInt()
val namespace = data.readLong()
val startPastAlias = if (isBatchMode) data.readString() else null
// List entries is only supported for Domain::APP and Domain::SELINUX.
// See AOSP function `get_key_descriptor_for_lookup` in service.rs.
// Note that all generated keys belong to Domain::APP.
if (domain == Domain.APP) {
pendingParams[txId] = ListEntriesParams(domain, namespace, startPastAlias)
SystemLogger.debug("[TX_ID: $txId] Cached ${pendingParams[txId]}.")
return true
}
return false
}
// Merge software-backed keys with hardware-backed keys in the reply parcel.
fun injectGeneratedKeys(txId: Long, callingUid: Int, reply: Parcel): Array<KeyDescriptor> {
val params =
pendingParams.remove(txId)
?: throw IllegalStateException("No params found for listing entries")
// By default we use the calling uid as namespace if domain is Domain::APP.
// The namespace parameter is thus ignored for non-privileged applications.
// See AOSP function `get_key_descriptor_for_lookup` in service.rs.
val keysToInject =
extractGeneratedKeyDescriptors(callingUid, callingUid.toLong(), params.startPastAlias)
val originalList = reply.createTypedArray(KeyDescriptor.CREATOR)!!
val mergedArray = mergeKeyDescriptors(originalList, keysToInject)
// Limit response size to avoid binder buffer overflow.
// See AOSP function `list_key_entries` in utils.rs.
val safeAmountToReturn = estimateSafeAmountToReturn(mergedArray, RESPONSE_SIZE_LIMIT)
return if (safeAmountToReturn < mergedArray.size) {
SystemLogger.debug(
"[TX_ID: $txId] Listing entries are truncated [${mergedArray.size} -> $safeAmountToReturn] to avoid transaction overflow."
)
mergedArray.copyOfRange(0, safeAmountToReturn)
} else {
SystemLogger.debug(
"[TX_ID: $txId] Listing entries returns ${mergedArray.size} [injected: ${keysToInject.size}] keys."
)
mergedArray
}
}
// Merge hardware and software key descriptors into a single sorted array.
private fun mergeKeyDescriptors(
hardwareKeys: Array<KeyDescriptor>,
keysToInject: List<KeyDescriptor>,
): Array<KeyDescriptor> {
// Uses TreeMap to ensure alphabetical ordering and uniqueness (prefer injected keys).
val combinedMap = TreeMap<String, KeyDescriptor>()
hardwareKeys.forEach { key -> key.alias?.let { combinedMap[it] = key } }
keysToInject.forEach { key -> key.alias?.let { combinedMap[it] = key } }
return combinedMap.values.toTypedArray()
}
// Based on AOSP function `list_past_alias` in database.rs
private fun extractGeneratedKeyDescriptors(
uid: Int,
namespace: Long,
startPastAlias: String?,
): List<KeyDescriptor> {
return KeyMintSecurityLevelInterceptor.generatedKeys.keys
.filter { it.uid == uid && (startPastAlias == null || it.alias > startPastAlias) }
.map { keyId ->
KeyDescriptor().apply {
this.domain = Domain.APP
this.nspace = namespace
this.alias = keyId.alias
this.blob = null
}
}
}
}
@@ -0,0 +1,120 @@
package org.matrix.TEESimulator.interception.keystore.shim
import android.hardware.security.keymint.Algorithm
import android.hardware.security.keymint.BlockMode
import android.hardware.security.keymint.KeyParameter
import android.hardware.security.keymint.KeyPurpose
import android.hardware.security.keymint.PaddingMode
import android.hardware.security.keymint.Tag
import org.matrix.TEESimulator.attestation.KeyMintAttestation
object AuthorizeCreate {
fun check(
keyParams: KeyMintAttestation?,
opParams: KeyMintAttestation,
rawOpParams: Array<KeyParameter>? = null,
): Int? {
if (keyParams == null) return null
val purpose = opParams.purpose.firstOrNull() ?: return null
// Algorithm-level rejection runs before purpose-list check (AOSP HAL behavior)
return checkAlgorithmPurpose(keyParams, purpose)
?: checkPurpose(keyParams, purpose)
?: checkOperationAuthorizations(keyParams, opParams)
?: checkTemporalValidity(keyParams, purpose)
?: checkCallerNonce(keyParams, purpose, rawOpParams)
}
private fun checkAlgorithmPurpose(keyParams: KeyMintAttestation, purpose: Int): Int? {
val algo = keyParams.algorithm
if (
(algo == Algorithm.EC || algo == Algorithm.RSA) &&
(purpose == KeyPurpose.VERIFY || purpose == KeyPurpose.ENCRYPT)
) {
return KeystoreErrorCodes.unsupportedPurpose
}
if (algo == Algorithm.RSA && purpose == KeyPurpose.AGREE_KEY)
return KeystoreErrorCodes.unsupportedPurpose
return null
}
private fun checkPurpose(keyParams: KeyMintAttestation, purpose: Int): Int? {
if (purpose == KeyPurpose.WRAP_KEY) return KeystoreErrorCodes.incompatiblePurpose
if (purpose !in keyParams.purpose) return KeystoreErrorCodes.incompatiblePurpose
return null
}
private fun checkOperationAuthorizations(
keyParams: KeyMintAttestation,
opParams: KeyMintAttestation,
): Int? {
if (opParams.blockMode.any { it !in keyParams.blockMode }) {
return KeystoreErrorCodes.incompatibleBlockMode
}
if (opParams.padding.any { it !in keyParams.padding }) {
return KeystoreErrorCodes.incompatiblePaddingMode
}
if (opParams.digest.any { it !in keyParams.digest }) {
return KeystoreErrorCodes.incompatibleDigest
}
if (opParams.rsaOaepMgfDigest.any { it !in keyParams.rsaOaepMgfDigest }) {
return KeystoreErrorCodes.incompatibleDigest
}
if (keyParams.algorithm == Algorithm.AES && opParams.blockMode.contains(BlockMode.GCM)) {
val requestedMacLength = opParams.minMacLength
val keyMinMacLength = keyParams.minMacLength
if (
requestedMacLength != null &&
keyMinMacLength != null &&
requestedMacLength < keyMinMacLength
) {
return KeystoreErrorCodes.invalidMacLength
}
}
if (
keyParams.algorithm == Algorithm.RSA &&
opParams.padding.contains(PaddingMode.RSA_OAEP) &&
opParams.digest.isEmpty()
) {
return KeystoreErrorCodes.incompatibleDigest
}
return null
}
private fun checkTemporalValidity(keyParams: KeyMintAttestation, purpose: Int): Int? {
val now = System.currentTimeMillis()
keyParams.activeDateTime?.let { activeDate ->
if (now < activeDate.time) return KeystoreErrorCodes.keyNotYetValid
}
keyParams.originationExpireDateTime?.let { expireDate ->
if (purpose == KeyPurpose.SIGN || purpose == KeyPurpose.ENCRYPT) {
if (now > expireDate.time) return KeystoreErrorCodes.keyExpired
}
}
keyParams.usageExpireDateTime?.let { expireDate ->
if (purpose == KeyPurpose.VERIFY || purpose == KeyPurpose.DECRYPT) {
if (now > expireDate.time) return KeystoreErrorCodes.keyExpired
}
}
return null
}
private fun checkCallerNonce(
keyParams: KeyMintAttestation,
purpose: Int,
rawOpParams: Array<KeyParameter>?,
): Int? {
if (purpose != KeyPurpose.SIGN && purpose != KeyPurpose.ENCRYPT) return null
if (keyParams.callerNonce == true) return null
if (rawOpParams?.any { it.tag == Tag.NONCE } == true)
return KeystoreErrorCodes.callerNonceProhibited
return null
}
}
@@ -0,0 +1,493 @@
package org.matrix.TEESimulator.interception.keystore.shim
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.security.KeyPair
import java.security.MessageDigest
import java.security.cert.Certificate
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.locks.ReentrantLock
import org.matrix.TEESimulator.config.ConfigurationManager.CONFIG_PATH
import org.matrix.TEESimulator.interception.keystore.KeyIdentifier
import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.pki.CertificateHelper
data class PersistedKeyData(
val uid: Int,
val alias: String,
val nspace: Long,
val securityLevel: Int,
val isAttestationKey: Boolean,
val algorithm: Int,
val keySize: Int,
val ecCurve: Int,
val purposes: List<Int>,
val digests: List<Int>,
/** PKCS#8-encoded private key for asymmetric records, empty for symmetric. */
val privateKeyBytes: ByteArray,
val certChainBytes: List<ByteArray>,
/**
* Byte-identical KeyMetadata parcel snapshot. Restoring authorizations directly from these
* bytes preserves tag count, order, and exact security-level annotations across reboots the
* kind of structural details apps fingerprint to decide whether the alias is still "the same
* key".
*/
val metadataBytes: ByteArray,
/**
* Raw secret material for symmetric records (AES, HMAC, 3DES). Empty for asymmetric. Critical
* for AndroidX security crypto MasterKey (AES-GCM-256) without this every reboot regenerates
* a fresh AES key and EncryptedSharedPreferences becomes undecryptable, which is what banking
* apps interpret as session expiry and force a relogin.
*/
val symmetricKeyBytes: ByteArray,
val symmetricAlgorithm: String,
)
object GeneratedKeyPersistence {
/**
* Single source of truth for the on-disk format. Bump this every time the layout changes; older
* numbers are silently skipped on read so stale dev artifacts and pre-fix upstream files can't
* be partially rehydrated into broken in-memory state.
*
* History: 1 original upstream layout (no metadata snapshot, no symmetric block; restored
* keys lose authorization tags and AES master keys altogether apps relying on persisted
* keystore state across reboots get logged out) 2 transitional dev-only format that added
* metadata but still missed the symmetric block; never shipped 3 current: byte-identical
* KeyMetadata snapshot + raw symmetric key material so AES/HMAC keys survive reboots
*/
private const val FORMAT_VERSION = 3
private val PERSISTENCE_DIR = File(CONFIG_PATH, "persistent_keys")
// Per-filename locks to prevent concurrent writes to the same key file
private val fileLocks = ConcurrentHashMap<String, ReentrantLock>()
private fun getLockForKey(filename: String): ReentrantLock {
return fileLocks.computeIfAbsent(filename) { ReentrantLock() }
}
fun save(
keyId: KeyIdentifier,
keyPair: KeyPair?,
secretKey: javax.crypto.SecretKey?,
nspace: Long,
securityLevel: Int,
certChain: List<Certificate>,
algorithm: Int,
keySize: Int,
ecCurve: Int,
purposes: List<Int>,
digests: List<Int>,
isAttestationKey: Boolean,
metadataBytes: ByteArray? = null,
) {
require(keyPair != null || secretKey != null) {
"Either keyPair or secretKey must be provided"
}
val filename = keyFileName(keyId.uid, keyId.alias)
val lock = getLockForKey(filename)
SystemLogger.debug("[Persistence] Acquiring lock for $filename")
lock.lock()
try {
SystemLogger.debug("[Persistence] Lock acquired for $filename")
runCatching {
PERSISTENCE_DIR.mkdirs()
val finalFile = File(PERSISTENCE_DIR, filename)
val tmpFile = File(PERSISTENCE_DIR, "$filename.tmp")
try {
DataOutputStream(BufferedOutputStream(FileOutputStream(tmpFile))).use { out
->
out.writeInt(FORMAT_VERSION)
out.writeInt(securityLevel)
out.writeInt(keyId.uid)
out.writeUTF(keyId.alias)
out.writeLong(nspace)
out.writeBoolean(isAttestationKey)
out.writeInt(algorithm)
out.writeInt(keySize)
out.writeInt(ecCurve)
out.writeInt(purposes.size)
purposes.forEach { out.writeInt(it) }
out.writeInt(digests.size)
digests.forEach { out.writeInt(it) }
// Asymmetric key block (empty for symmetric-only).
val pkBytes = keyPair?.private?.encoded ?: ByteArray(0)
out.writeInt(pkBytes.size)
out.write(pkBytes)
out.writeInt(certChain.size)
certChain.forEach { cert ->
val encoded = cert.encoded
out.writeInt(encoded.size)
out.write(encoded)
}
// Metadata snapshot (always present, may be empty
// if the live KeyMetadata could not be marshalled).
val mdBytes = metadataBytes ?: ByteArray(0)
out.writeInt(mdBytes.size)
if (mdBytes.isNotEmpty()) out.write(mdBytes)
// Symmetric key block (empty for asymmetric keys).
if (secretKey != null) {
val skBytes = secretKey.encoded
out.writeUTF(secretKey.algorithm)
out.writeInt(skBytes.size)
out.write(skBytes)
} else {
out.writeUTF("")
out.writeInt(0)
}
}
} catch (e: Exception) {
tmpFile.delete()
throw e
}
// Atomic rename — if this fails the tmp is left behind and cleaned on next
// deleteAll
if (!tmpFile.renameTo(finalFile)) {
tmpFile.delete()
throw IllegalStateException(
"Failed to atomically rename $tmpFile -> $finalFile"
)
}
// Verify write succeeded - catches disk-full or filesystem errors
if (!finalFile.exists() || finalFile.length() < 20) {
throw IOException("File write verification failed - possible disk full")
}
SystemLogger.debug("Persisted key: $keyId")
}
.onFailure { e -> SystemLogger.error("Failed to persist key $keyId", e) }
} finally {
lock.unlock()
SystemLogger.debug("[Persistence] Lock released for $filename")
}
}
fun delete(keyId: KeyIdentifier) {
runCatching {
val file = File(PERSISTENCE_DIR, keyFileName(keyId.uid, keyId.alias))
if (file.exists()) {
if (file.delete()) {
fileLocks.remove(keyFileName(keyId.uid, keyId.alias))
SystemLogger.debug("Deleted persisted key: $keyId")
} else {
SystemLogger.warning("Failed to delete persisted key file: ${file.name}")
}
} else {
SystemLogger.debug("No persisted file to delete for: $keyId")
}
}
.onFailure { e -> SystemLogger.error("Failed to delete persisted key $keyId", e) }
}
fun deleteAll() {
runCatching {
if (!PERSISTENCE_DIR.exists()) {
SystemLogger.debug("No persistent_keys directory, nothing to delete")
return
}
val files = PERSISTENCE_DIR.listFiles()
if (files == null) {
SystemLogger.warning("Cannot list persistent_keys directory")
return
}
var count = 0
files.forEach { file ->
if (file.name.endsWith(".bin") || file.name.endsWith(".tmp")) {
if (file.delete()) count++
}
}
fileLocks.clear()
SystemLogger.info("Deleted $count persisted key files")
}
.onFailure { e -> SystemLogger.error("Failed to delete all persisted keys", e) }
}
fun loadAll(securityLevel: Int): List<PersistedKeyData> {
if (!PERSISTENCE_DIR.exists()) {
SystemLogger.debug("No persistent_keys directory, nothing to load")
return emptyList()
}
val files = PERSISTENCE_DIR.listFiles { _, name -> name.endsWith(".bin") }
if (files == null) {
SystemLogger.warning("Cannot read persistent_keys directory")
return emptyList()
}
if (files.isEmpty()) {
SystemLogger.debug("No persisted key files found")
return emptyList()
}
SystemLogger.info("Found ${files.size} persisted key files to process")
val result = mutableListOf<PersistedKeyData>()
for (file in files) {
runCatching {
DataInputStream(BufferedInputStream(FileInputStream(file))).use { input ->
val version = input.readInt()
if (version != FORMAT_VERSION) {
// Old upstream files (v1) and dev-only intermediate
// files (v2) are missing the metadata snapshot
// and/or symmetric key block — restoring them
// would put broken state in memory (apps relying
// on those records get logged out). Skip and let
// the next generateKey re-create cleanly with the
// new format. Affected apps re-login once after
// upgrade, then never again.
SystemLogger.info(
"Skipping ${file.name}: legacy format version $version. " +
"It will be replaced on next generateKey for this alias."
)
return@runCatching
}
val storedSecLevel = input.readInt()
val uid = input.readInt()
val alias = input.readUTF()
val nspace = input.readLong()
val isAttestKey = input.readBoolean()
val algo = input.readInt()
val kSize = input.readInt()
val curve = input.readInt()
val purposeCount = requireBounds(input.readInt(), 64, "purposeCount")
val purposes = (0 until purposeCount).map { input.readInt() }
val digestCount = requireBounds(input.readInt(), 64, "digestCount")
val digests = (0 until digestCount).map { input.readInt() }
val pkLen = requireBounds(input.readInt(), 8192, "pkLen")
val pkBytes = ByteArray(pkLen)
if (pkLen > 0) input.readFully(pkBytes)
val certCount = requireBounds(input.readInt(), 10, "certCount")
val certChainBytes =
(0 until certCount).map {
val certLen = requireBounds(input.readInt(), 65536, "certLen")
val certBytes = ByteArray(certLen)
input.readFully(certBytes)
certBytes
}
val metaLen = requireBounds(input.readInt(), 256 * 1024, "metaLen")
val metadataBytes =
ByteArray(metaLen).also { if (metaLen > 0) input.readFully(it) }
val skAlgo = input.readUTF()
val skLen = requireBounds(input.readInt(), 8192, "skLen")
val skBytes = ByteArray(skLen).also { if (skLen > 0) input.readFully(it) }
if (storedSecLevel == securityLevel) {
result.add(
PersistedKeyData(
uid = uid,
alias = alias,
nspace = nspace,
securityLevel = storedSecLevel,
isAttestationKey = isAttestKey,
algorithm = algo,
keySize = kSize,
ecCurve = curve,
purposes = purposes,
digests = digests,
privateKeyBytes = pkBytes,
certChainBytes = certChainBytes,
metadataBytes = metadataBytes,
symmetricKeyBytes = skBytes,
symmetricAlgorithm = skAlgo,
)
)
}
}
}
.onFailure { e ->
SystemLogger.warning("Skipping corrupted persisted key file: ${file.name}", e)
}
}
SystemLogger.info("Loaded ${result.size} persisted keys for security level $securityLevel")
return result
}
// Re-persist updates the cert chain for an already-persisted key without
// reconstructing authorization parameters from the response. This avoids
// pulling keymint Tag dependencies into this file and is correct because
// the only field that changes post-generation is the patched cert chain.
fun rePersistIfNeeded(
callingUid: Int,
generatedKeyInfo: KeyMintSecurityLevelInterceptor.GeneratedKeyInfo,
) {
val metadata = generatedKeyInfo.response.metadata
if (metadata == null) {
SystemLogger.debug("rePersist: no metadata, skipping")
return
}
val secLevel = metadata.keySecurityLevel
val entry =
KeyMintSecurityLevelInterceptor.generatedKeys.entries.find { (id, info) ->
id.uid == callingUid && info.nspace == generatedKeyInfo.nspace
}
if (entry == null) {
SystemLogger.debug(
"rePersist: key not found in map for uid=$callingUid nspace=${generatedKeyInfo.nspace}"
)
return
}
val keyId = entry.key
val filename = keyFileName(keyId.uid, keyId.alias)
val existing = File(PERSISTENCE_DIR, filename)
if (!existing.exists()) {
SystemLogger.debug("rePersist: no existing file for $keyId, skipping")
return
}
val newChain = CertificateHelper.getCertificateChain(metadata)
if (newChain == null) {
SystemLogger.warning("rePersist: could not extract cert chain for $keyId")
return
}
val persisted =
runCatching {
DataInputStream(BufferedInputStream(FileInputStream(existing))).use { input ->
val version = input.readInt()
if (version != FORMAT_VERSION) {
SystemLogger.warning(
"rePersist: legacy format version $version for $keyId, will not re-persist (next generateKey replaces it)"
)
return
}
readPersistedKeyData(input)
}
}
.getOrNull()
if (persisted == null) {
SystemLogger.warning("rePersist: failed to read existing data for $keyId")
return
}
val keyPair = generatedKeyInfo.keyPair
val secretKey = generatedKeyInfo.secretKey
if (keyPair == null && secretKey == null) {
SystemLogger.warning("rePersist: no key material for $keyId")
return
}
// Serialize the live KeyMetadata (now contains the user-installed cert
// chain via updateSubcomponent) so the next boot restores byte-identical
// metadata. KeyMetadata is binder-free, so marshall() is safe here.
val metadataBytes =
runCatching {
android.os.Parcel.obtain().let { parcel ->
try {
metadata.writeToParcel(parcel, 0)
parcel.marshall()
} finally {
parcel.recycle()
}
}
}
.getOrNull()
save(
keyId = keyId,
keyPair = keyPair,
secretKey = secretKey,
nspace = generatedKeyInfo.nspace,
securityLevel = secLevel,
certChain = newChain.toList(),
algorithm = persisted.algorithm,
keySize = persisted.keySize,
ecCurve = persisted.ecCurve,
purposes = persisted.purposes,
digests = persisted.digests,
isAttestationKey = persisted.isAttestationKey,
metadataBytes = metadataBytes,
)
SystemLogger.debug("Re-persisted key $keyId with updated cert chain")
}
// Corrupted binary files can have arbitrary length fields — cap allocations
private fun requireBounds(value: Int, max: Int, name: String): Int {
require(value in 0..max) { "$name out of bounds: $value (max $max)" }
return value
}
private fun keyFileName(uid: Int, alias: String): String {
val digest =
MessageDigest.getInstance("SHA-256").digest("$uid:$alias".toByteArray(Charsets.UTF_8))
return digest.joinToString("") { "%02x".format(it) } + ".bin"
}
// Reads all fields after the version int has already been consumed
// and validated by the caller.
private fun readPersistedKeyData(input: DataInputStream): PersistedKeyData {
val secLevel = input.readInt()
val uid = input.readInt()
val alias = input.readUTF()
val nspace = input.readLong()
val isAttestKey = input.readBoolean()
val algo = input.readInt()
val kSize = input.readInt()
val curve = input.readInt()
val purposeCount = requireBounds(input.readInt(), 64, "purposeCount")
val purposes = (0 until purposeCount).map { input.readInt() }
val digestCount = requireBounds(input.readInt(), 64, "digestCount")
val digests = (0 until digestCount).map { input.readInt() }
val pkLen = requireBounds(input.readInt(), 8192, "pkLen")
val pkBytes = ByteArray(pkLen)
if (pkLen > 0) input.readFully(pkBytes)
val certCount = requireBounds(input.readInt(), 10, "certCount")
val certChainBytes =
(0 until certCount).map {
val certLen = requireBounds(input.readInt(), 65536, "certLen")
val certBytes = ByteArray(certLen)
input.readFully(certBytes)
certBytes
}
val metaLen = requireBounds(input.readInt(), 256 * 1024, "metaLen")
val metadataBytes = ByteArray(metaLen).also { if (metaLen > 0) input.readFully(it) }
val skAlgo = input.readUTF()
val skLen = requireBounds(input.readInt(), 8192, "skLen")
val skBytes = ByteArray(skLen).also { if (skLen > 0) input.readFully(it) }
return PersistedKeyData(
uid = uid,
alias = alias,
nspace = nspace,
securityLevel = secLevel,
isAttestationKey = isAttestKey,
algorithm = algo,
keySize = kSize,
ecCurve = curve,
purposes = purposes,
digests = digests,
privateKeyBytes = pkBytes,
certChainBytes = certChainBytes,
metadataBytes = metadataBytes,
symmetricKeyBytes = skBytes,
symmetricAlgorithm = skAlgo,
)
}
}
@@ -0,0 +1,73 @@
package org.matrix.TEESimulator.interception.keystore.shim
import android.os.IBinder
import android.os.Parcel
import android.system.keystore2.IKeystoreOperation
import org.matrix.TEESimulator.interception.core.BinderInterceptor
import org.matrix.TEESimulator.interception.keystore.InterceptorUtils
/**
* Intercepts calls to an `IKeystoreOperation` service. This is used to log the data manipulation
* methods of a cryptographic operation.
*/
class OperationInterceptor(
private val original: IKeystoreOperation,
private val backdoor: IBinder,
private val isAead: Boolean,
) : BinderInterceptor() {
override fun onPreTransact(
txId: Long,
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
): TransactionResult {
val methodName = transactionNames[code] ?: "unknown code=$code"
logTransaction(txId, methodName, callingUid, callingPid, true)
// Mirror SoftwareOperation's vendor gate: a real-key op must answer non-AEAD updateAad
// exactly as the forged-key path does. Samsung and Xiaomi-MTK TEEs accept it; rejecting
// here while the forged path accepts diverges the two and fingerprints the injection.
if (code == UPDATE_AAD_TRANSACTION && !isAead) {
return if (VendorQuirks.nonAeadUpdateAadSucceeds()) {
InterceptorUtils.createSuccessReply(writeResultCode = false)
} else {
InterceptorUtils.createServiceSpecificErrorReply(KeystoreErrorCodes.invalidTag)
}
}
if (code == FINISH_TRANSACTION || code == ABORT_TRANSACTION) {
KeyMintSecurityLevelInterceptor.removeOperationInterceptor(target, backdoor)
}
return TransactionResult.ContinueAndSkipPost
}
companion object {
private val UPDATE_AAD_TRANSACTION =
InterceptorUtils.getTransactCode(IKeystoreOperation.Stub::class.java, "updateAad")
private val UPDATE_TRANSACTION =
InterceptorUtils.getTransactCode(IKeystoreOperation.Stub::class.java, "update")
private val FINISH_TRANSACTION =
InterceptorUtils.getTransactCode(IKeystoreOperation.Stub::class.java, "finish")
private val ABORT_TRANSACTION =
InterceptorUtils.getTransactCode(IKeystoreOperation.Stub::class.java, "abort")
val INTERCEPTED_CODES =
intArrayOf(UPDATE_AAD_TRANSACTION, FINISH_TRANSACTION, ABORT_TRANSACTION)
private val transactionNames: Map<Int, String> by lazy {
IKeystoreOperation.Stub::class
.java
.declaredFields
.filter {
it.isAccessible = true
it.type == Int::class.java && it.name.startsWith("TRANSACTION_")
}
.associate { field -> (field.get(null) as Int) to field.name.split("_")[1] }
}
}
}
@@ -0,0 +1,664 @@
package org.matrix.TEESimulator.interception.keystore.shim
import android.hardware.security.keymint.Algorithm
import android.hardware.security.keymint.BlockMode
import android.hardware.security.keymint.Digest
import android.hardware.security.keymint.KeyParameter
import android.hardware.security.keymint.KeyParameterValue
import android.hardware.security.keymint.KeyPurpose
import android.hardware.security.keymint.PaddingMode
import android.hardware.security.keymint.Tag
import android.os.Build
import android.os.ServiceSpecificException
import android.os.SystemProperties
import android.system.keystore2.IKeystoreOperation
import android.system.keystore2.KeyParameters
import java.security.KeyPair
import java.security.Signature
import java.security.SignatureException
import java.util.concurrent.locks.LockSupport
import javax.crypto.Cipher
import org.matrix.TEESimulator.attestation.KeyMintAttestation
import org.matrix.TEESimulator.logging.KeyMintParameterLogger
import org.matrix.TEESimulator.logging.SystemLogger
/**
* Mirrors the per-vendor TEE quirk that Duck Detector's OperationErrorPathProbe checks: real
* Samsung and Xiaomi-MTK TrustZone return success for updateAad on a non-AEAD operation, while
* every other vendor rejects it with a service-specific INVALID_TAG. The module reads the same
* device-identity fields the probe reads, so a forged software operation answers exactly as that
* vendor's real TEE would.
*/
internal object VendorQuirks {
private val UPDATE_AAD_ALLOWS_SUCCESS = setOf("samsung")
private val XIAOMI_BRANDS = setOf("xiaomi", "redmi", "poco")
fun nonAeadUpdateAadSucceeds(): Boolean {
val manufacturer = Build.MANUFACTURER.lowercase()
val brand = Build.BRAND.lowercase()
if (manufacturer in UPDATE_AAD_ALLOWS_SUCCESS || brand in UPDATE_AAD_ALLOWS_SUCCESS) {
return true
}
if (manufacturer != "xiaomi" && brand !in XIAOMI_BRANDS) return false
return isMediaTek()
}
private fun isMediaTek(): Boolean {
val roHardware = SystemProperties.get("ro.hardware", "")
return roHardware.startsWith("mt") || Build.HARDWARE.startsWith("mt", ignoreCase = true)
}
}
private sealed interface CryptoPrimitive {
fun updateAad(aadInput: ByteArray?) {
// Real Samsung / Xiaomi-MTK TEEs accept updateAad on non-AEAD ops; others reject it.
if (!VendorQuirks.nonAeadUpdateAadSucceeds()) {
throw ServiceSpecificException(KeystoreErrorCodes.invalidTag)
}
}
fun update(data: ByteArray?): ByteArray?
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray?
fun abort()
fun getBeginParameters(): Array<KeyParameter>? = null
}
private object JcaAlgorithmMapper {
fun mapSignatureAlgorithm(params: KeyMintAttestation): String {
val digest =
when (params.digest.firstOrNull()) {
Digest.SHA_2_256 -> "SHA256"
Digest.SHA_2_384 -> "SHA384"
Digest.SHA_2_512 -> "SHA512"
else -> "NONE"
}
return when (params.algorithm) {
Algorithm.EC -> "${digest}withECDSA"
Algorithm.RSA -> {
val isPss = params.padding.firstOrNull() == PaddingMode.RSA_PSS
if (isPss) "${digest}withRSA/PSS" else "${digest}withRSA"
}
else ->
throw ServiceSpecificException(
KeystoreErrorCodes.incompatibleAlgorithm,
"Unsupported signature algorithm: ${params.algorithm}",
)
}
}
fun mapCipherAlgorithm(params: KeyMintAttestation): String {
val keyAlgo =
when (params.algorithm) {
Algorithm.RSA -> "RSA"
Algorithm.AES -> "AES"
else ->
throw ServiceSpecificException(
KeystoreErrorCodes.incompatibleAlgorithm,
"Unsupported cipher algorithm: ${params.algorithm}",
)
}
val blockMode =
when (params.blockMode.firstOrNull()) {
BlockMode.ECB -> "ECB"
BlockMode.CBC -> "CBC"
BlockMode.CTR -> "CTR"
BlockMode.GCM -> "GCM"
else -> "ECB"
}
val padding =
when (params.padding.firstOrNull()) {
PaddingMode.NONE -> "NoPadding"
PaddingMode.PKCS7 -> "PKCS7Padding"
PaddingMode.RSA_PKCS1_1_5_ENCRYPT -> "PKCS1Padding"
PaddingMode.RSA_PKCS1_1_5_SIGN -> "PKCS1Padding"
PaddingMode.RSA_OAEP -> "OAEPPadding"
else -> "NoPadding"
}
return "$keyAlgo/$blockMode/$padding"
}
fun mapOaepDigest(digest: Int?): String =
when (digest) {
Digest.SHA1 -> "SHA-1"
Digest.SHA_2_224 -> "SHA-224"
Digest.SHA_2_256 -> "SHA-256"
Digest.SHA_2_384 -> "SHA-384"
Digest.SHA_2_512 -> "SHA-512"
else -> "SHA-256"
}
fun mapMacAlgorithm(params: KeyMintAttestation): String =
when (params.digest.firstOrNull()) {
Digest.SHA_2_256 -> "HmacSHA256"
Digest.SHA_2_384 -> "HmacSHA384"
Digest.SHA_2_512 -> "HmacSHA512"
else -> "HmacSHA256"
}
}
private class Signer(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
private val signature: Signature =
Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply {
initSign(keyPair.private)
}
override fun update(data: ByteArray?): ByteArray? {
if (data != null) signature.update(data)
return null
}
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray {
if (data != null) update(data)
return this.signature.sign()
}
override fun abort() {}
}
private class Verifier(keyPair: KeyPair, params: KeyMintAttestation) : CryptoPrimitive {
private val signature: Signature =
Signature.getInstance(JcaAlgorithmMapper.mapSignatureAlgorithm(params)).apply {
initVerify(keyPair.public)
}
override fun update(data: ByteArray?): ByteArray? {
if (data != null) signature.update(data)
return null
}
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
if (data != null) update(data)
if (signature == null) {
throw ServiceSpecificException(
KeystoreErrorCodes.verificationFailed,
"Signature to verify is null",
)
}
if (!this.signature.verify(signature)) {
throw ServiceSpecificException(
KeystoreErrorCodes.verificationFailed,
"Signature verification failed",
)
}
return null
}
override fun abort() {}
}
private class CipherPrimitive(
cryptoKey: java.security.Key,
params: KeyMintAttestation,
private val opMode: Int,
txId: Long,
) : CryptoPrimitive {
private val isAead = params.blockMode.firstOrNull() == BlockMode.GCM
private val cipher: Cipher =
Cipher.getInstance(JcaAlgorithmMapper.mapCipherAlgorithm(params)).apply {
val nonce = params.nonce
if (nonce != null && isAead) {
init(opMode, cryptoKey, javax.crypto.spec.GCMParameterSpec(128, nonce))
} else if (nonce != null) {
init(opMode, cryptoKey, javax.crypto.spec.IvParameterSpec(nonce))
} else if (params.padding.firstOrNull() == PaddingMode.RSA_OAEP) {
val mainDigest = JcaAlgorithmMapper.mapOaepDigest(params.digest.firstOrNull())
val mgfDigest =
params.rsaOaepMgfDigest.firstOrNull()?.let {
JcaAlgorithmMapper.mapOaepDigest(it)
} ?: mainDigest
init(
opMode,
cryptoKey,
javax.crypto.spec.OAEPParameterSpec(
mainDigest,
"MGF1",
java.security.spec.MGF1ParameterSpec(mgfDigest),
javax.crypto.spec.PSource.PSpecified.DEFAULT,
),
)
SystemLogger.debug {
"[SoftwareOp TX_ID: $txId] oaep-op main=$mainDigest mgf=$mgfDigest " +
"mode=${if (opMode == Cipher.DECRYPT_MODE) "decrypt" else "encrypt"}"
}
} else {
init(opMode, cryptoKey)
}
}
override fun updateAad(aadInput: ByteArray?) {
if (!isAead) {
if (!VendorQuirks.nonAeadUpdateAadSucceeds()) {
throw ServiceSpecificException(KeystoreErrorCodes.invalidTag)
}
return
}
if (aadInput != null) cipher.updateAAD(aadInput)
}
override fun update(data: ByteArray?): ByteArray? =
if (data != null) cipher.update(data) else null
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? =
if (data != null) cipher.doFinal(data) else cipher.doFinal()
override fun getBeginParameters(): Array<KeyParameter>? {
val iv = cipher.iv ?: return null
return arrayOf(
KeyParameter().apply {
tag = Tag.NONCE
value = KeyParameterValue.blob(iv)
}
)
}
override fun abort() {}
}
private class KeyAgreementPrimitive(keyPair: KeyPair) : CryptoPrimitive {
private val agreement: javax.crypto.KeyAgreement =
javax.crypto.KeyAgreement.getInstance("ECDH").apply { init(keyPair.private) }
override fun update(data: ByteArray?): ByteArray? = null
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
if (data == null)
throw ServiceSpecificException(
KeystoreErrorCodes.invalidArgument,
"Peer public key required for key agreement",
)
val peerKey =
java.security.KeyFactory.getInstance("EC")
.generatePublic(java.security.spec.X509EncodedKeySpec(data))
agreement.doPhase(peerKey, true)
return agreement.generateSecret()
}
override fun abort() {}
}
private class MacPrimitive(
secretKey: javax.crypto.SecretKey,
private val params: KeyMintAttestation,
private val txId: Long,
) : CryptoPrimitive {
private val mac: javax.crypto.Mac =
javax.crypto.Mac.getInstance(JcaAlgorithmMapper.mapMacAlgorithm(params)).apply {
init(secretKey)
}
override fun update(data: ByteArray?): ByteArray? {
if (data != null) mac.update(data)
return null
}
override fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
if (data != null) mac.update(data)
val full = mac.doFinal()
// Tag.MAC_LENGTH is optional on the AndroidKeyStore Mac SPI; default to the
// full digest length so real Mac use keeps working when it is omitted.
val tagBytes = (params.macLength ?: (full.size * 8)) / 8
val tag = full.copyOf(tagBytes)
if (params.purpose.firstOrNull() == KeyPurpose.VERIFY) {
if (signature == null) {
throw ServiceSpecificException(
KeystoreErrorCodes.verificationFailed,
"MAC to verify is null",
)
}
if (!java.security.MessageDigest.isEqual(tag, signature)) {
throw ServiceSpecificException(
KeystoreErrorCodes.verificationFailed,
"MAC verification failed",
)
}
return null
}
SystemLogger.debug {
"[SoftwareOp TX_ID: $txId] hmac-op digest=${params.digest.firstOrNull()} " +
"macLen=${params.macLength} tag=${tag.size}B result=ok"
}
return tag
}
override fun abort() {}
}
class SoftwareOperation(
private val txId: Long,
keyPair: KeyPair?,
secretKey: javax.crypto.SecretKey?,
params: KeyMintAttestation,
private val latencyFloorMs: Long = 0L,
) {
private val primitive: CryptoPrimitive
@Volatile
var finalized = false
private set
var onFinishCallback: (() -> Unit)? = null
val beginParameters: KeyParameters?
get() {
val params = primitive.getBeginParameters() ?: return null
if (params.isEmpty()) return null
return KeyParameters().apply { keyParameter = params }
}
init {
val purpose = params.purpose.firstOrNull()
val purposeName = KeyMintParameterLogger.purposeNames[purpose] ?: "UNKNOWN"
SystemLogger.debug("[SoftwareOp TX_ID: $txId] Initializing for purpose: $purposeName.")
if (purpose == null) {
// Defensive: if params somehow restored without a PURPOSE tag
// (corrupt v2 metadata, mismatched authorizations array on load,
// or future format drift) the original code crashed with NPE
// because Signer/Verifier/Cipher all dereference keyPair!!
// before checking purpose. Surface a clean keystore error
// instead so callers see a normal-looking operation failure
// they can recover from rather than the process appearing to
// silently corrupt their session.
SystemLogger.warning(
"[SoftwareOp TX_ID: $txId] Purpose missing on restored key " +
"(authorizations=${params.purpose}, keyPair=${if (keyPair != null) "present" else "null"}, " +
"secretKey=${if (secretKey != null) "present" else "null"}). " +
"Returning unsupportedPurpose."
)
throw ServiceSpecificException(
KeystoreErrorCodes.unsupportedPurpose,
"Restored key has no PURPOSE authorization",
)
}
primitive =
if (params.algorithm == Algorithm.HMAC) {
// An HMAC key is symmetric (secretKey set, keyPair null), so it must
// not fall through to the purpose-keyed Signer/Verifier paths, which
// require a keyPair. secretKey is populated at HMAC keygen and restore,
// so the throw is a defensive floor, not a live path.
MacPrimitive(
secretKey
?: throw ServiceSpecificException(
KeystoreErrorCodes.invalidArgument,
"[SoftwareOp TX_ID: $txId] HMAC op but secretKey null",
),
params,
txId,
)
} else {
when (purpose) {
KeyPurpose.SIGN -> {
val kp =
keyPair
?: throw ServiceSpecificException(
KeystoreErrorCodes.invalidArgument,
"[SoftwareOp TX_ID: $txId] SIGN requested but keyPair is null",
)
Signer(kp, params)
}
KeyPurpose.VERIFY -> {
val kp =
keyPair
?: throw ServiceSpecificException(
KeystoreErrorCodes.invalidArgument,
"[SoftwareOp TX_ID: $txId] VERIFY requested but keyPair is null",
)
Verifier(kp, params)
}
KeyPurpose.ENCRYPT -> {
val key: java.security.Key =
secretKey
?: keyPair?.public
?: throw ServiceSpecificException(
KeystoreErrorCodes.unsupportedPurpose,
"[SoftwareOp TX_ID: $txId] ENCRYPT requires either secretKey or keyPair.public",
)
CipherPrimitive(key, params, Cipher.ENCRYPT_MODE, txId)
}
KeyPurpose.DECRYPT -> {
val key: java.security.Key =
secretKey
?: keyPair?.private
?: throw ServiceSpecificException(
KeystoreErrorCodes.unsupportedPurpose,
"[SoftwareOp TX_ID: $txId] DECRYPT requires either secretKey or keyPair.private",
)
CipherPrimitive(key, params, Cipher.DECRYPT_MODE, txId)
}
KeyPurpose.AGREE_KEY -> {
val kp =
keyPair
?: throw ServiceSpecificException(
KeystoreErrorCodes.invalidArgument,
"[SoftwareOp TX_ID: $txId] AGREE_KEY requested but keyPair is null",
)
KeyAgreementPrimitive(kp)
}
else ->
throw ServiceSpecificException(
KeystoreErrorCodes.unsupportedPurpose,
"Unsupported operation purpose: $purpose",
)
}
}
}
private fun checkActive() {
if (finalized) {
SystemLogger.debug(
"[SoftwareOp TX_ID: $txId] Rejected: operation already finalized (pruned or completed)"
)
throw ServiceSpecificException(KeystoreErrorCodes.invalidOperationHandle)
}
}
private fun checkInputLength(data: ByteArray?) {
if (data != null && data.size > MAX_RECEIVE_DATA) {
SystemLogger.info(
"[SoftwareOp TX_ID: $txId] Input too large: ${data.size} > $MAX_RECEIVE_DATA, throwing TOO_MUCH_DATA(${KeystoreErrorCodes.tooMuchData})"
)
throw ServiceSpecificException(KeystoreErrorCodes.tooMuchData)
}
}
fun updateAad(aadInput: ByteArray?) {
SystemLogger.info(
"[SoftwareOp TX_ID: $txId] updateAad() ENTRY inputSize=${aadInput?.size ?: 0} primitive=${primitive::class.simpleName}"
)
checkActive()
checkInputLength(aadInput)
try {
primitive.updateAad(aadInput)
SystemLogger.info(
"[SoftwareOp TX_ID: $txId] updateAad() RETURNED_NORMALLY (unexpected for non-AEAD)"
)
} catch (throwable: Throwable) {
val top = throwable.stackTrace.firstOrNull()?.toString() ?: "<no-frame>"
val code = (throwable as? ServiceSpecificException)?.errorCode
SystemLogger.info(
"[SoftwareOp TX_ID: $txId] updateAad() THREW class=${throwable::class.java.name} code=$code msg=${throwable.message} top=$top"
)
throw throwable
}
}
fun update(data: ByteArray?): ByteArray? {
SystemLogger.debug("[SoftwareOp TX_ID: $txId] update() inputSize=${data?.size ?: 0}")
checkActive()
checkInputLength(data)
try {
return primitive.update(data)
} catch (e: ServiceSpecificException) {
throw e
} catch (e: Exception) {
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to update operation.", e)
throw mapToServiceSpecificException(e)
}
}
fun finish(data: ByteArray?, signature: ByteArray?): ByteArray? {
checkActive()
checkInputLength(data)
try {
val startNs = if (latencyFloorMs > 0) System.nanoTime() else 0L
val result = primitive.finish(data, signature)
if (latencyFloorMs > 0) {
val elapsedMs = (System.nanoTime() - startNs) / 1_000_000
val delayMs = latencyFloorMs - elapsedMs
if (delayMs > 0) LockSupport.parkNanos(delayMs * 1_000_000)
}
finalized = true
onFinishCallback?.invoke()
SystemLogger.info("[SoftwareOp TX_ID: $txId] Finished operation successfully.")
return result
} catch (e: ServiceSpecificException) {
throw e
} catch (e: Exception) {
SystemLogger.error("[SoftwareOp TX_ID: $txId] Failed to finish operation.", e)
throw mapToServiceSpecificException(e)
}
}
fun abort() {
finalized = true
primitive.abort()
SystemLogger.debug("[SoftwareOp TX_ID: $txId] Operation aborted.")
}
private fun mapToServiceSpecificException(e: Exception): ServiceSpecificException =
when (e) {
is SignatureException ->
ServiceSpecificException(KeystoreErrorCodes.verificationFailed, e.message)
is javax.crypto.BadPaddingException ->
ServiceSpecificException(KeystoreErrorCodes.invalidArgument, e.message)
is javax.crypto.IllegalBlockSizeException ->
ServiceSpecificException(KeystoreErrorCodes.invalidInputLength, e.message)
is java.security.InvalidKeyException ->
ServiceSpecificException(KeystoreErrorCodes.incompatibleKey, e.message)
else -> ServiceSpecificException(KeystoreErrorCodes.unknownError, e.message)
}
companion object {
private const val MAX_RECEIVE_DATA = 0x8000
}
}
internal object KeystoreErrorCodes {
val tooMuchData: Int by lazy {
resolveField("android.system.keystore2.ResponseCode", "TOO_MUCH_DATA", 21)
}
val invalidOperationHandle: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INVALID_OPERATION_HANDLE", -28)
}
val invalidTag: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INVALID_TAG", -76)
}
val verificationFailed: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "VERIFICATION_FAILED", -30)
}
val invalidArgument: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INVALID_ARGUMENT", -38)
}
val invalidInputLength: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INVALID_INPUT_LENGTH", -21)
}
val incompatibleKey: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INCOMPATIBLE_KEY", -31)
}
val incompatiblePurpose: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INCOMPATIBLE_PURPOSE", -13)
}
val unsupportedPurpose: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "UNSUPPORTED_PURPOSE", -14)
}
val incompatibleAlgorithm: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INCOMPATIBLE_ALGORITHM", -18)
}
val keyNotYetValid: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "KEY_NOT_YET_VALID", -39)
}
val keyExpired: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "KEY_EXPIRED", -40)
}
val callerNonceProhibited: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "CALLER_NONCE_PROHIBITED", -55)
}
val unknownError: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "UNKNOWN_ERROR", -1000)
}
val incompatibleBlockMode: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INCOMPATIBLE_BLOCK_MODE", -8)
}
val incompatiblePaddingMode: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INCOMPATIBLE_PADDING_MODE", -11)
}
val incompatibleDigest: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INCOMPATIBLE_DIGEST", -13)
}
val invalidMacLength: Int by lazy {
resolveField("android.hardware.security.keymint.ErrorCode", "INVALID_MAC_LENGTH", -57)
}
fun resolveField(className: String, fieldName: String, fallback: Int): Int =
runCatching { Class.forName(className).getField(fieldName).getInt(null) }
.getOrElse {
SystemLogger.debug("Resolved $className.$fieldName via fallback: $fallback")
fallback
}
}
class SoftwareOperationBinder(private val operation: SoftwareOperation) :
IKeystoreOperation.Stub() {
@Synchronized
override fun updateAad(aadInput: ByteArray?) {
SystemLogger.info(
"[SoftwareOpBinder] updateAad() ENTRY callingUid=${android.os.Binder.getCallingUid()} size=${aadInput?.size ?: 0}"
)
try {
operation.updateAad(aadInput)
SystemLogger.info("[SoftwareOpBinder] updateAad() RETURNED_NORMALLY")
} catch (throwable: Throwable) {
val code = (throwable as? ServiceSpecificException)?.errorCode
SystemLogger.info(
"[SoftwareOpBinder] updateAad() PROPAGATING class=${throwable::class.java.name} code=$code msg=${throwable.message}"
)
throw throwable
}
}
@Synchronized
override fun update(input: ByteArray?): ByteArray? {
return operation.update(input)
}
@Synchronized
override fun finish(input: ByteArray?, signature: ByteArray?): ByteArray? {
return operation.finish(input, signature)
}
@Synchronized
override fun abort() {
operation.abort()
}
}
@@ -0,0 +1,170 @@
package org.matrix.TEESimulator.interception.soter
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Handler
import android.os.HandlerThread
import android.os.IBinder
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicBoolean
import org.matrix.TEESimulator.interception.core.BinderInterceptor
import org.matrix.TEESimulator.logging.SystemLogger
/**
* Keeps [SoterServiceInterceptor] mounted on the on-demand, restartable
* `com.tencent.soter.soterserver` process.
*
* `AbstractKeystoreInterceptor` injects `keystore2` exactly once: it is always alive and
* servicemanager-published, so the daemon gets its binder from `ServiceManager` and may
* `exitProcess` on failure. soterserver inverts both it is Intent-bound (NOT in
* `ServiceManager`) and may die and respawn. This supervisor therefore *binds* the SOTER
* service, which both triggers its on-demand start AND yields the `ISoterService` binder
* (the target the native MITM registry keys on); injects `libTEESimulator.so` on every
* (re)start; confirms the landing with the `0xdeadbeef` backdoor handshake; then registers
* the forge. It re-binds re-poking, re-injecting, re-registering whenever the process
* dies, never exiting.
*
* The bind recipe (action = the interface descriptor, package, `BIND_AUTO_CREATE`) and the
* rebind-on-death lifecycle mirror the SOTER SDK's own `SoterCoreTreble`, so the daemon
* connects exactly as a real client would. Everything runs on a dedicated [HandlerThread]
* so it never stalls keystore init or `Looper.loop()` in [org.matrix.TEESimulator.App].
*
* Observability (the checkpoint's mandatory gate): every lifecycle event bind, connect,
* inject ok/fail, handshake, respawn is logged via [SystemLogger], debug-gated. It never
* gates the forge.
*/
object SoterProcessSupervisor {
/** soterserver hosts the package's own process (recon 2026-06-26: process == package). */
private const val SOTER_PACKAGE = "com.tencent.soter.soterserver"
/** Reuses the daemon's native injector + `entry`, PID-resolved by the target package. */
private const val INJECTION_COMMAND =
"exec ./inject `pidof $SOTER_PACKAGE` libTEESimulator.so entry"
private const val REBIND_DELAY_MS = 1000L
private const val REBIND_MAX_MS = 30_000L
private val started = AtomicBoolean(false)
/** Re-bind backoff; doubles each failed (re)bind up to [REBIND_MAX_MS], resets on a clean mount. Handler-thread-confined. */
private var rebindDelay = REBIND_DELAY_MS
private lateinit var context: Context
private lateinit var handler: Handler
/** Delivers bind callbacks onto the supervisor thread so nothing touches the main looper. */
private val executor = Executor { command -> handler.post(command) }
/**
* Starts supervising on a dedicated thread and returns immediately. Idempotent. [context]
* must be able to bind services (the daemon's system context); supplied by the App wiring.
*/
fun start(context: Context) {
if (!started.compareAndSet(false, true)) return
this.context = context
handler = Handler(HandlerThread("soter-supervisor").apply { start() }.looper)
handler.post { bind() }
}
private val connection =
object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
SystemLogger.debug("SOTER service connected; mounting forge")
service?.let(::mount)
}
override fun onServiceDisconnected(name: ComponentName?) {
SystemLogger.debug("SOTER service disconnected (process died); rebinding")
scheduleRetry()
}
override fun onBindingDied(name: ComponentName?) {
SystemLogger.debug("SOTER binding died; rebinding")
scheduleRetry()
}
override fun onNullBinding(name: ComponentName?) {
SystemLogger.debug("SOTER onBind returned null; rebinding")
scheduleRetry()
}
}
private fun bind() {
val intent = Intent(SoterServiceInterceptor.DESCRIPTOR).setPackage(SOTER_PACKAGE)
val bound =
runCatching {
context.bindService(intent, Context.BIND_AUTO_CREATE, executor, connection)
}
.getOrElse {
SystemLogger.debug { "SOTER bindService threw: $it" }
false
}
if (bound) {
SystemLogger.debug("SOTER bind requested (on-demand poke)")
} else {
SystemLogger.debug("SOTER bindService returned false; retrying")
scheduleRetry()
}
}
private fun rebind() {
runCatching { context.unbindService(connection) }
bind()
}
/**
* Re-attempts the bind after the current backoff, then widens it (capped at [REBIND_MAX_MS]).
* Every path that fails to leave the forge mounted routes here, so a live-but-uninjected
* binding is re-attempted instead of stranding the forge. A clean [mount] resets the backoff.
*/
private fun scheduleRetry() {
val delay = rebindDelay
rebindDelay = (rebindDelay * 2).coerceAtMost(REBIND_MAX_MS)
handler.postDelayed({ rebind() }, delay)
}
/** Confirms injection via the `0xdeadbeef` handshake, injecting first if absent, then registers. */
private fun mount(soterBinder: IBinder) {
var backdoor = BinderInterceptor.getBackdoor(soterBinder)
if (backdoor == null) {
SystemLogger.debug("SOTER backdoor absent; injecting libTEESimulator.so")
if (!injectLibrary()) {
SystemLogger.debug("SOTER injection failed; scheduling re-bind")
scheduleRetry()
return
}
backdoor = BinderInterceptor.getBackdoor(soterBinder)
}
if (backdoor == null) {
SystemLogger.debug("SOTER backdoor handshake failed after injection; scheduling re-bind")
scheduleRetry()
return
}
val registered =
BinderInterceptor.register(
backdoor,
soterBinder,
SoterServiceInterceptor,
SoterServiceInterceptor.interceptedCodes,
)
if (!registered) {
SystemLogger.debug("SOTER register failed; scheduling re-bind")
scheduleRetry()
return
}
rebindDelay = REBIND_DELAY_MS
SystemLogger.debug("SOTER forge mounted; handshake ok")
}
private fun injectLibrary(): Boolean =
runCatching {
Runtime.getRuntime().exec(arrayOf("/system/bin/sh", "-c", INJECTION_COMMAND)).waitFor() == 0
}
.getOrElse {
SystemLogger.debug { "SOTER inject exec failed: $it" }
false
}
}
@@ -0,0 +1,229 @@
package org.matrix.TEESimulator.interception.soter
import android.os.IBinder
import android.os.Parcel
import android.util.Base64
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.security.KeyPairGenerator
import org.matrix.TEESimulator.interception.core.BinderInterceptor
import org.matrix.TEESimulator.logging.SystemLogger
/**
* Forges healthy `com.tencent.soter.soterserver.ISoterService` (Layer A: AIDL over
* `/dev/binder`) replies from inside the injected soterserver app process, so the SOTER
* capability probe (春秋 / DuckDetector `SoterCapabilityProbe`) reads `available = true`
* / `damaged = false` on a bootloader-unlocked device whose SOTER TA can no longer use
* its factory ATTK. Replaces the external SoterFixer loop + the Hail freeze.
*
* Unconditional by design: the forge decision never consults `ConfigurationManager` /
* `target.txt` (Phase 10 spec §Decision, gate G). It is mounted by the SOTER process
* supervisor (10.B/10.W) against the ISoterService binder, so `onPreTransact` only sees
* transactions on that binder matching the raw transaction code is therefore enough.
*
* Diagnostics follow the module's standard three-layer capture (debug-gated, per-UID
* NDJSON via [SystemLogger]; see `logging/SystemLogger.kt`): a `tx` line for every
* transaction ([logTransaction]), the raw inbound request parcel, and the raw forged
* reply wire. Capture is scoped to targeted UIDs (`isUidLogged`) exactly like the
* keystore lane it does NOT make the forge conditional; the forge still fires for all.
*
* Transaction codes are HARDCODED 1..13 in AIDL declaration order, NOT resolved via
* [org.matrix.TEESimulator.interception.keystore.InterceptorUtils.getTransactCode]: the
* shipped soterserver build is R8/ProGuard obfuscated there is no `ISoterService$Stub`
* class and no `TRANSACTION_*` fields (recon 2026-06-26, `a$a.smali` packed-switch). The
* codes are fixed by Tencent's `ISoterService.aidl` and are obfuscation-independent.
*
* Scope boundary (10.A vs 10.M): the seven primitive-returning methods are fully forged
* here. The six parcelable-returning methods emit the correct AIDL envelope + the
* recon-verified `writeToParcel` field order; 10.M fills the payloads with
* detector-satisfying values a framed SOTER pubkey envelope the SDK's
* `retrieveJsonFromExportedData` parses to a non-null `SoterPubKeyModel`, a non-zero sign
* session, and a 256-byte signature.
*/
object SoterServiceInterceptor : BinderInterceptor() {
/** The surviving, obfuscation-stable interface identifier (used by the 10.B/10.W mount). */
const val DESCRIPTOR = "com.tencent.soter.soterserver.ISoterService"
// AIDL transaction codes = FIRST_CALL_TRANSACTION (1) + declaration index, verified
// against the obfuscated `a$a.smali` packed-switch (recon 2026-06-26). NOTE the 5/6
// order: removeAuthKey precedes getAuthKey in the real .aidl (the spec prose had it
// reversed). Comments record each method's return shape.
private const val TX_GENERATE_APP_SECURE_KEY = 1 // int
private const val TX_GET_APP_SECURE_KEY = 2 // SoterExportResult
private const val TX_HAS_ASK_ALREADY = 3 // boolean
private const val TX_GENERATE_AUTH_KEY = 4 // int
private const val TX_REMOVE_AUTH_KEY = 5 // int (NOT getAuthKey)
private const val TX_GET_AUTH_KEY = 6 // SoterExportResult (NOT removeAuthKey)
private const val TX_REMOVE_ALL_AUTH_KEY = 7 // int
private const val TX_HAS_AUTH_KEY = 8 // boolean
private const val TX_INIT_SIGH = 9 // SoterSessionResult (sic: Tencent's spelling)
private const val TX_FINISH_SIGN = 10 // SoterSignResult
private const val TX_GET_DEVICE_ID = 11 // SoterDeviceResult
private const val TX_GET_VERSION = 12 // int (real service returns 1)
private const val TX_GET_EXTRA_PARAM = 13 // SoterExtraParam
/** SOTER success result code (`SoterCoreResult` ERR_OK). */
private const val SOTER_OK = 0
/** finishSign signature length the probe expects. */
private const val SIGNATURE_LEN = 256
/** `cpu_id` placeholder in the export envelope; the local probe never reads its value
* (the backend pins the real per-`cpu_id` ATTK, which the forge cannot satisfy). */
private const val CPU_ID = "0000000000000000"
/** Code -> Tencent method name, for the `tx` diagnostic line. Names from the recon decompile. */
private val methodNames =
mapOf(
TX_GENERATE_APP_SECURE_KEY to "generateAppSecureKey",
TX_GET_APP_SECURE_KEY to "getAppSecureKey",
TX_HAS_ASK_ALREADY to "hasAskAlready",
TX_GENERATE_AUTH_KEY to "generateAuthKey",
TX_REMOVE_AUTH_KEY to "removeAuthKey",
TX_GET_AUTH_KEY to "getAuthKey",
TX_REMOVE_ALL_AUTH_KEY to "removeAllAuthKey",
TX_HAS_AUTH_KEY to "hasAuthKey",
TX_INIT_SIGH to "initSigh",
TX_FINISH_SIGN to "finishSign",
TX_GET_DEVICE_ID to "getDeviceId",
TX_GET_VERSION to "getVersion",
TX_GET_EXTRA_PARAM to "getExtraParam",
)
/** The codes this interceptor forges; consumed by the supervisor's registration (10.B/10.W). */
val interceptedCodes: IntArray = methodNames.keys.toIntArray()
/**
* Payload of [SoterExportResult.exportData] for getAppSecureKey (txn 2) and getAuthKey
* (txn 6). The detector's capability probe gates `damaged=false` on
* `SoterCore.getApp/AuthKeyModel() != null`, and the SDK's `retrieveJsonFromExportedData`
* (`SoterCoreBase`) returns a non-null `SoterPubKeyModel` only when this exact framing
* parses: `[4-byte LITTLE-ENDIAN json length][UTF-8 json][signature bytes]`. A
* non-empty-but-unframed blob throws inside the SDK and is read as `damaged` silently.
* The JSON parser swallows every exception, so only the framing is load-bearing; the
* `pub_key` is a genuine RSA-2048 SubjectPublicKeyInfo so a probe that base64/X.509-parses
* the field locally still succeeds. Lazily built keygen runs once, off the mount path.
*/
private val exportBlob: ByteArray by lazy { buildExportBlob() }
/** getDeviceId (txn 11) payload — well-formed, non-empty; the probe never parses it. */
private val deviceIdBlob = "TEESIM-SOTER-0001".toByteArray(Charsets.UTF_8)
/** finishSign (txn 10) signature payload — [SIGNATURE_LEN] bytes. */
private val signatureBlob = ByteArray(SIGNATURE_LEN)
private fun buildExportBlob(): ByteArray {
val pubKey =
runCatching {
val generator = KeyPairGenerator.getInstance("RSA").apply { initialize(2048) }
Base64.encodeToString(generator.generateKeyPair().public.encoded, Base64.NO_WRAP)
}
.getOrDefault("")
val json =
"""{"pub_key":"$pubKey","counter":0,"cpu_id":"$CPU_ID","uid":0}"""
.toByteArray(Charsets.UTF_8)
val lengthPrefix = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(json.size).array()
return lengthPrefix + json + signatureBlob
}
override fun onPreTransact(
txId: Long,
target: IBinder,
code: Int,
flags: Int,
callingUid: Int,
callingPid: Int,
data: Parcel,
): TransactionResult {
val method = methodNames[code]
if (method == null) {
// Not an ISoterService method we forge — record it as observed, then pass through.
logTransaction(txId, "code=$code", callingUid, callingPid, skipPost = true)
return TransactionResult.ContinueAndSkipPost
}
logTransaction(txId, method, callingUid, callingPid)
captureRequest(callingUid, txId, method, data)
return when (code) {
// Primitive returns — fully forged here.
TX_GENERATE_APP_SECURE_KEY,
TX_GENERATE_AUTH_KEY,
TX_REMOVE_AUTH_KEY,
TX_REMOVE_ALL_AUTH_KEY -> forgedReply(callingUid, txId, method) { writeInt(SOTER_OK) }
TX_GET_VERSION -> forgedReply(callingUid, txId, method) { writeInt(1) }
TX_HAS_ASK_ALREADY,
TX_HAS_AUTH_KEY -> forgedReply(callingUid, txId, method) { writeInt(1) } // boolean true
// Parcelable returns — correct envelope + recon field order, payloads filled (10.M).
TX_GET_APP_SECURE_KEY,
TX_GET_AUTH_KEY ->
forgedReply(callingUid, txId, method) {
writeInt(1) // non-null marker
writeInt(SOTER_OK) // resultCode
writeByteArray(exportBlob) // exportData — framed SOTER pubkey envelope
writeInt(exportBlob.size) // exportDataLength
}
TX_INIT_SIGH ->
forgedReply(callingUid, txId, method) {
writeInt(1)
writeLong(1L) // session — any non-zero satisfies the probe
writeInt(SOTER_OK) // resultCode — probe requires == 0 (SoterCapabilityProbe.kt:107)
}
TX_FINISH_SIGN ->
forgedReply(callingUid, txId, method) {
writeInt(1)
writeInt(SOTER_OK) // resultCode — finishSign throws on != 0
writeByteArray(signatureBlob) // exportData = signature
writeInt(signatureBlob.size) // exportDataLength
}
TX_GET_DEVICE_ID ->
forgedReply(callingUid, txId, method) {
writeInt(1)
writeInt(SOTER_OK) // resultCode
writeByteArray(deviceIdBlob) // exportData = device id
writeInt(deviceIdBlob.size) // exportDataLength
}
TX_GET_EXTRA_PARAM ->
forgedReply(callingUid, txId, method) {
writeInt(1)
writeValue("optical") // SoterExtraParam.result = fingerprint sensor type
}
// Unreachable: method != null means code is one of the 13 above.
else -> TransactionResult.ContinueAndSkipPost
}
}
/** Snapshots the inbound request parcel to the per-UID NDJSON plane (debug + targeted only). */
private fun captureRequest(uid: Int, txId: Long, method: String, data: Parcel) {
if (!SystemLogger.isUidLogged(uid)) return
runCatching { data.marshall() }
.onSuccess { raw ->
SystemLogger.uidLogRaw(uid, txId, "$method-request", "len=${raw.size}", raw)
}
}
/**
* Builds an AIDL reply (`writeNoException()` then [body]) and snapshots its wire bytes to the
* per-UID NDJSON plane before handing it to the native hook. Parcelable bodies write their own
* `writeInt(1)` non-null marker; the native hook recycles the parcel after use.
*/
private fun forgedReply(
uid: Int,
txId: Long,
method: String,
body: Parcel.() -> Unit,
): TransactionResult.OverrideReply {
val reply = Parcel.obtain()
reply.writeNoException()
reply.body()
if (SystemLogger.isUidLogged(uid)) {
runCatching { reply.marshall() }
.onSuccess { raw ->
SystemLogger.uidLogRaw(uid, txId, "$method-reply", "len=${raw.size}", raw)
}
}
return TransactionResult.OverrideReply(reply)
}
}
@@ -0,0 +1,74 @@
package org.matrix.TEESimulator.logging
import android.hardware.security.keymint.Tag
import android.system.keystore2.Authorization
import java.security.cert.Certificate
import java.security.cert.X509Certificate
import org.matrix.TEESimulator.attestation.AttestationPatcher
import org.matrix.TEESimulator.config.ConfigurationManager
import org.matrix.TEESimulator.util.AndroidDeviceUtils
/**
* Assembles the per-UID "attestation dossier": for a targeted app, the full decoded attestation we
* actually hand it, the identity of every certificate in the returned chain, and the source of each
* device value that fed that attestation. Emitting all three where a chain is produced turns "the
* app rejects us" into a field-by-field record that can be diffed against a genuine TEE.
*/
object AttestationDossier {
/**
* Records the dossier for [chain] under [uid], tagged with the [path] that produced it
* (`FORGE-rust`, `FORGE-bouncycastle`, or `PATCH`). No-op for untargeted UIDs and release
* builds; the expensive decoding is skipped entirely when the UID is out of scope.
*/
fun log(uid: Int, txId: Long, path: String, chain: List<Certificate>) {
if (!SystemLogger.isUidLogged(uid)) return
val leaf = chain.firstOrNull() as? X509Certificate
val extension =
leaf?.let { AttestationPatcher.formatAttestationExtension(it) }
?: "<no attestation extension>"
SystemLogger.uidLog(uid, txId, "attest", "path=$path depth=${chain.size} $extension")
SystemLogger.uidLog(uid, txId, "keybox", "file=${ConfigurationManager.getKeyboxFileForUid(uid)}")
SystemLogger.uidLog(uid, txId, "chain", AttestationPatcher.formatCertChain(chain))
SystemLogger.uidLog(uid, txId, "chain-verify", AttestationPatcher.formatChainVerification(chain))
SystemLogger.uidLog(uid, txId, "props", AndroidDeviceUtils.describeSources(uid))
}
/**
* Records the *shape* of the emitted authorization list count, ordered tags, and per-auth
* securityLevel. This is the exact surface the duck detector's generate-mode parcel fingerprint
* stride-walks, so logging it readably lets a "fingerprint" detection be compared against the
* known genuine-TEE shape without decoding the marshalled reply offline.
*/
fun logAuthShape(uid: Int, txId: Long, authorizations: Array<Authorization>?) {
if (!SystemLogger.isUidLogged(uid)) return
val auths = authorizations ?: return
val shape = auths.joinToString(",") { "${tagName(it.keyParameter.tag)}/${it.securityLevel}" }
SystemLogger.uidLog(uid, txId, "auth-shape", "n=${auths.size} [$shape]")
}
/** Names the authorization tags that occur in generate-mode replies; others render as numbers. */
private fun tagName(tag: Int): String =
when (tag) {
Tag.PURPOSE -> "PURPOSE"
Tag.ALGORITHM -> "ALGORITHM"
Tag.KEY_SIZE -> "KEY_SIZE"
Tag.DIGEST -> "DIGEST"
Tag.PADDING -> "PADDING"
Tag.EC_CURVE -> "EC_CURVE"
Tag.RSA_PUBLIC_EXPONENT -> "RSA_PUBLIC_EXPONENT"
Tag.NO_AUTH_REQUIRED -> "NO_AUTH_REQUIRED"
Tag.ORIGIN -> "ORIGIN"
Tag.OS_VERSION -> "OS_VERSION"
Tag.OS_PATCHLEVEL -> "OS_PATCHLEVEL"
Tag.VENDOR_PATCHLEVEL -> "VENDOR_PATCHLEVEL"
Tag.BOOT_PATCHLEVEL -> "BOOT_PATCHLEVEL"
Tag.CREATION_DATETIME -> "CREATION_DATETIME"
Tag.ROOT_OF_TRUST -> "ROOT_OF_TRUST"
Tag.USER_ID -> "USER_ID"
Tag.USAGE_COUNT_LIMIT -> "USAGE_COUNT_LIMIT"
Tag.UNLOCKED_DEVICE_REQUIRED -> "UNLOCKED_DEVICE_REQUIRED"
Tag.ACTIVE_DATETIME -> "ACTIVE_DATETIME"
else -> "tag${tag and 0x0FFFFFFF}"
}
}
@@ -0,0 +1,134 @@
package org.matrix.TEESimulator.logging
import android.hardware.security.keymint.*
import java.math.BigInteger
import java.nio.charset.StandardCharsets
import java.util.Date
import javax.security.auth.x500.X500Principal
import org.bouncycastle.asn1.x500.X500Name
import org.matrix.TEESimulator.util.toHex
/**
* A specialized logger for converting KeyMint `KeyParameter` objects into a human-readable format.
* This helps in debugging the parameters requested for key generation.
*/
object KeyMintParameterLogger {
private val algorithmNames: Map<Int, String> by lazy {
Algorithm::class
.java
.fields
.filter { it.type == Int::class.java }
.associate { field -> (field.get(null) as Int) to field.name }
}
private val ecCurveNames: Map<Int, String> by lazy {
EcCurve::class
.java
.fields
.filter { it.type == Int::class.java }
.associate { field -> (field.get(null) as Int) to field.name }
}
val blockModeNames: Map<Int, String> by lazy {
BlockMode::class
.java
.fields
.filter { it.type == Int::class.java }
.associate { field -> (field.get(null) as Int) to field.name }
}
val paddingNames: Map<Int, String> by lazy {
PaddingMode::class
.java
.fields
.filter { it.type == Int::class.java }
.associate { field -> (field.get(null) as Int) to field.name }
}
val purposeNames: Map<Int, String> by lazy {
KeyPurpose::class
.java
.fields
.filter { it.type == Int::class.java }
.associate { field -> (field.get(null) as Int) to field.name }
}
private val digestNames: Map<Int, String> by lazy {
Digest::class
.java
.fields
.filter { it.type == Int::class.java }
.associate { field -> (field.get(null) as Int) to field.name }
}
private val tagNames: Map<Int, String> by lazy {
Tag::class
.java
.fields
.filter { it.type == Int::class.java }
.associate { field -> (field.get(null) as Int) to field.name }
}
/** Logs a single KeyParameter to the shared debug stream (used for un-scoped param dumps). */
fun logParameter(param: KeyParameter) {
SystemLogger.debug("KeyParam: ${describe(param)}")
}
/** Logs a single KeyParameter onto a targeted UID's diagnostic plane as a `param` record. */
fun logParameter(uid: Int, txId: Long, param: KeyParameter) {
SystemLogger.uidLog(uid, txId, "param", describe(param))
}
/**
* Formats a single KeyParameter into a readable `tag | Value` string. Shared by both
* [logParameter] overloads so the two logging planes render parameters identically.
*
* @param param The KeyParameter to format.
*/
private fun describe(param: KeyParameter): String {
val tagName = tagNames[param.tag] ?: "UNKNOWN_TAG"
val value = param.value
val formattedValue: String =
when (param.tag) {
Tag.ALGORITHM -> algorithmNames[value.algorithm]
Tag.BLOCK_MODE -> blockModeNames[value.blockMode]
Tag.EC_CURVE -> ecCurveNames[value.ecCurve]
Tag.PADDING -> paddingNames[value.paddingMode]
Tag.PURPOSE -> purposeNames[value.keyPurpose]
Tag.DIGEST -> digestNames[value.digest]
Tag.AUTH_TIMEOUT,
Tag.KEY_SIZE,
Tag.MIN_MAC_LENGTH -> value.integer.toString()
Tag.CERTIFICATE_SERIAL -> BigInteger(value.blob).toString()
Tag.ACTIVE_DATETIME,
Tag.CERTIFICATE_NOT_AFTER,
Tag.CERTIFICATE_NOT_BEFORE,
Tag.ORIGINATION_EXPIRE_DATETIME,
Tag.USAGE_EXPIRE_DATETIME -> Date(value.dateTime).toString()
Tag.CERTIFICATE_SUBJECT -> X500Name(X500Principal(value.blob).name).toString()
Tag.RSA_PUBLIC_EXPONENT -> value.longInteger.toString()
Tag.NO_AUTH_REQUIRED -> "true"
Tag.ATTESTATION_CHALLENGE,
Tag.ATTESTATION_ID_BRAND,
Tag.ATTESTATION_ID_DEVICE,
Tag.ATTESTATION_ID_PRODUCT,
Tag.ATTESTATION_ID_MANUFACTURER,
Tag.ATTESTATION_ID_MODEL,
Tag.ATTESTATION_ID_IMEI,
Tag.ATTESTATION_ID_SECOND_IMEI,
Tag.ATTESTATION_ID_MEID,
Tag.ATTESTATION_ID_SERIAL -> value.blob.toReadableString()
else -> "<raw>"
} ?: "Unknown Value"
return "%-25s | Value: %s".format(tagName, formattedValue)
}
private fun ByteArray.toReadableString(): String {
return if (this.all { it in 32..126 }) {
"\"${String(this, StandardCharsets.UTF_8)}\" (${this.size} bytes)"
} else {
"${this.toHex()} (${this.size} bytes)"
}
}
}
@@ -0,0 +1,272 @@
package org.matrix.TEESimulator.logging
import android.util.Base64
import android.util.Log
import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicLong
import org.json.JSONObject
import org.matrix.TEESimulator.BuildConfig
import org.matrix.TEESimulator.config.ConfigurationManager
/**
* A centralized logging utility for the TEESimulator application. This object provides a consistent
* logging tag and format for all application logs, making it easier to filter and debug in Logcat.
*
* Includes a rate limiter that caps logd syscalls during binder stress to prevent thread pool
* contention. The first [RATE_LIMIT_BURST] messages per [RATE_LIMIT_WINDOW_MS] window are logged
* normally; subsequent messages are suppressed and a summary is emitted when the window resets.
*/
object SystemLogger {
@PublishedApi internal const val TAG = "TEESimulator"
@PublishedApi internal val isDebugBuild = BuildConfig.DEBUG
// Rate limiter: allow BURST messages per WINDOW, then suppress until window resets.
private const val RATE_LIMIT_BURST = 15
private const val RATE_LIMIT_WINDOW_MS = 1000L
private val windowStart = AtomicLong(System.currentTimeMillis())
private val windowCount = AtomicInteger(0)
private val suppressedCount = AtomicInteger(0)
/**
* Returns true if this message should be emitted. Resets the window if expired and emits a
* suppression summary for the previous window.
*/
@PublishedApi
internal fun acquireLogPermit(): Boolean {
val now = System.currentTimeMillis()
val start = windowStart.get()
if (now - start > RATE_LIMIT_WINDOW_MS) {
// Window expired: reset and emit suppression summary if needed.
if (windowStart.compareAndSet(start, now)) {
val suppressed = suppressedCount.getAndSet(0)
windowCount.set(1) // this call counts as #1 in the new window
if (suppressed > 0) {
Log.i(
TAG,
"[rate-limit] suppressed $suppressed log messages in previous window",
)
}
return true
}
}
val count = windowCount.incrementAndGet()
if (count <= RATE_LIMIT_BURST) return true
suppressedCount.incrementAndGet()
return false
}
/** Logs a debug message. Use this for fine-grained information that is useful for debugging. */
fun debug(message: String) {
if (!isDebugBuild) return
if (!acquireLogPermit()) return
Log.d(TAG, message)
}
/** Lazy debug: lambda only evaluates if message will be logged. */
inline fun debug(message: () -> String) {
if (!isDebugBuild) return
if (!acquireLogPermit()) return
Log.d(TAG, message())
}
/** Logs an informational message. Use this to report major application lifecycle events. */
fun info(message: String) {
if (!acquireLogPermit()) return
Log.i(TAG, message)
}
/** Lazy info: lambda only evaluates if message will be logged. */
inline fun info(message: () -> String) {
if (!acquireLogPermit()) return
Log.i(TAG, message())
}
/** Logs a warning message. Warnings are never rate-limited. */
fun warning(message: String, throwable: Throwable? = null) {
if (throwable != null) {
Log.w(TAG, message, throwable)
} else {
Log.w(TAG, message)
}
}
/** Logs an error message. Errors are never rate-limited. */
fun error(message: String, throwable: Throwable? = null) {
if (throwable != null) {
Log.e(TAG, message, throwable)
} else {
Log.e(TAG, message)
}
}
/**
* Logs a verbose message. This level is for highly detailed logs that are generally not needed
* unless tracking a very specific issue.
*/
fun verbose(message: String) {
if (!isDebugBuild) return
if (!acquireLogPermit()) return
Log.v(TAG, message)
}
/** Lazy verbose: lambda only evaluates if message will be logged. */
inline fun verbose(message: () -> String) {
if (!isDebugBuild) return
if (!acquireLogPermit()) return
Log.v(TAG, message())
}
inline fun trace(message: () -> String) {
if (!isDebugBuild) return
Log.w(TAG, message())
}
// --- UID-keyed diagnostic plane (debug builds only) -------------------------------------
/**
* True when [uid] should receive deep, per-UID diagnostic logging: a debug build AND the UID is
* targeted in `target.txt`. This is the single scope gate for the diagnostic plane; it reuses
* the existing activation set, so no new configuration surface is introduced.
*/
fun isUidLogged(uid: Int): Boolean = isDebugBuild && !ConfigurationManager.shouldSkipUid(uid)
/** Resolves a UID to its primary package name for log labelling, falling back to `uid:N`. */
private fun label(uid: Int): String =
ConfigurationManager.getPackagesForUid(uid).firstOrNull() ?: "uid:$uid"
/**
* Emits one structured diagnostic record for a targeted [uid]. The human form
* `[<pkg> tx=<txId>] <event>: <detail>` goes to logcat; the file sink receives one NDJSON object
* per line under that UID's own file. In-scope records bypass the global rate limiter: a
* targeted app's traffic is already volume-bounded, and dropping a line mid-probe would corrupt
* the very trace we are trying to read. No-op for untargeted UIDs and in release builds.
*/
fun uidLog(uid: Int, txId: Long?, event: String, detail: String) {
if (!isUidLogged(uid)) return
val correlation = txId?.let { " tx=$it" } ?: ""
Log.d(TAG, "[${label(uid)}$correlation] $event: $detail")
runCatching { uidWriter(uid).append(jsonRecord(uid, txId, event, detail, null)) }
}
/** Lazy [uidLog]: [detail] is only built for targeted UIDs in debug builds. */
inline fun uidLog(uid: Int, txId: Long?, event: String, detail: () -> String) {
if (!isUidLogged(uid)) return
uidLog(uid, txId, event, detail())
}
/**
* [uidLog] plus the exact wire bytes that produced the event, base64 (NO_WRAP) in a `raw_b64`
* field. This is the structured replacement for the per-call `.bin` parcel dumps: one NDJSON
* line on the per-UID file instead of a fresh undecodable file per transaction, with the raw
* parcel still recoverable for offline parsers.
*/
fun uidLogRaw(uid: Int, txId: Long?, event: String, detail: String, raw: ByteArray) {
if (!isUidLogged(uid)) return
val correlation = txId?.let { " tx=$it" } ?: ""
Log.d(TAG, "[${label(uid)}$correlation] $event: $detail (raw ${raw.size}B)")
runCatching {
val encoded = Base64.encodeToString(raw, Base64.NO_WRAP)
uidWriter(uid).append(jsonRecord(uid, txId, event, detail, encoded))
}
}
/**
* External-storage root for every debug diagnostic. `/data/media/0/TEESimulator` is the
* in-namespace backing path the keystore domain can reach; a normal file manager sees the same
* files at `/sdcard/TEESimulator`. Release builds never write here and purge it on boot
* (App.purgeDebugDiagnostics). The domain reaches it via a debug-only media_rw_data_file
* sepolicy grant, and service.sh pre-creates the directory.
*/
const val DIAGNOSTIC_DIR = "/data/media/0/TEESimulator"
private val uidLogDir = File(DIAGNOSTIC_DIR)
private const val UID_LOG_MAX_BYTES = 4L * 1024 * 1024
private val uidWriters = ConcurrentHashMap<Int, UidLogFile>()
private val recordClock =
DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault())
private fun jsonRecord(
uid: Int,
txId: Long?,
event: String,
detail: String,
rawB64: String?,
): String =
JSONObject()
.apply {
put("ts", recordClock.format(Instant.now()))
put("uid", uid)
put("pkg", label(uid))
txId?.let { put("tx", it) }
put("event", event)
put("detail", detail)
rawB64?.let { put("raw_b64", it) }
}
.toString()
private fun uidWriter(uid: Int): UidLogFile =
uidWriters.computeIfAbsent(uid) { key ->
UidLogFile(key, uidLogDir).also { file ->
val packages =
ConfigurationManager.getPackagesForUid(key).joinToString().ifEmpty { "<unresolved>" }
runCatching {
file.append(jsonRecord(key, null, "session", "packages=[$packages]", null))
}
}
}
/**
* Append-only NDJSON sink for a single UID at `<logDir>/teesim-uid-<uid>.ndjson`, rotated once
* to `.ndjson.1` at [UID_LOG_MAX_BYTES]; one JSON object per line. Writes are synchronised
* because the keystore binder pool is multi-threaded, and every operation is wrapped so a
* logging fault can never propagate into the daemon. Created only on the debug-gated path.
*/
private class UidLogFile(uid: Int, private val logDir: File) {
private val primary = File(logDir, "teesim-uid-$uid.ndjson")
private val rotated = File(logDir, "teesim-uid-$uid.ndjson.1")
private var writer: BufferedWriter? = null
private var size = 0L
@Synchronized
fun append(jsonLine: String) {
runCatching {
val out = writer ?: open()
out.write(jsonLine)
out.write("\n")
out.flush()
size += jsonLine.length + 1
if (size >= UID_LOG_MAX_BYTES) rotate()
}
}
private fun open(): BufferedWriter {
logDir.mkdirs()
val out = BufferedWriter(FileWriter(primary, /* append = */ true))
writer = out
size = primary.length()
return out
}
private fun rotate() {
runCatching {
writer?.flush()
writer?.close()
}
writer = null
runCatching {
if (rotated.exists()) rotated.delete()
primary.renameTo(rotated)
}
size = 0L
}
}
}
@@ -0,0 +1,351 @@
package org.matrix.TEESimulator.pki
import android.hardware.security.keymint.Algorithm
import android.hardware.security.keymint.KeyPurpose
import android.os.Build
import android.util.Pair
import java.math.BigInteger
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.cert.Certificate
import java.security.spec.ECGenParameterSpec
import java.security.spec.RSAKeyGenParameterSpec
import java.util.Date
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.asn1.x509.KeyUsage
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import org.matrix.TEESimulator.attestation.AttestationBuilder
import org.matrix.TEESimulator.attestation.AttestationConstants
import org.matrix.TEESimulator.attestation.KeyMintAttestation
import org.matrix.TEESimulator.config.ConfigurationManager
import org.matrix.TEESimulator.interception.keystore.KeyIdentifier
import org.matrix.TEESimulator.interception.keystore.shim.KeyMintSecurityLevelInterceptor
import org.matrix.TEESimulator.logging.SystemLogger
/**
* Responsible for generating new cryptographic key pairs and X.509 certificate chains.
*
* This object simulates the behavior of the Android KeyMint/Keymaster HAL by creating certificates
* that include a fully-featured, simulated attestation extension.
*/
object CertificateGenerator {
private const val UNDEFINED_NOT_AFTER = 253402300799000L
/**
* Generates a software-based cryptographic key pair.
*
* @param params The parameters specifying the key's algorithm, size, and other properties.
* @return A new [KeyPair], or `null` on failure.
*/
fun generateSoftwareKeyPair(params: KeyMintAttestation): KeyPair? {
return runCatching {
val (algorithm, spec) =
when (params.algorithm) {
Algorithm.EC -> "EC" to ECGenParameterSpec(params.ecCurveName)
Algorithm.RSA ->
"RSA" to
RSAKeyGenParameterSpec(
params.keySize,
params.rsaPublicExponent ?: RSAKeyGenParameterSpec.F4,
)
else ->
throw IllegalArgumentException(
"Unsupported algorithm: ${params.algorithm}"
)
}
SystemLogger.debug("Generating $algorithm key pair with size ${params.keySize}")
KeyPairGenerator.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME)
.apply { initialize(spec) }
.generateKeyPair()
}
.onFailure { SystemLogger.error("Failed to generate software key pair.", it) }
.getOrNull()
}
/**
* Generates a certificate chain for a given key pair. This is the primary function for creating
* attested certificates.
*
* @param uid The UID of the application requesting the key.
* @param subjectKeyPair The key pair for which the certificate will be generated.
* @param attestKeyAlias Optional alias of a key to use for attestation signing.
* @param params The parameters for the new key and its attestation.
* @param securityLevel The security level to embed in the attestation.
* @return A [List] of [Certificate] forming the new chain, or `null` on failure.
*/
fun generateCertificateChain(
uid: Int,
subjectKeyPair: KeyPair,
attestKeyAlias: String?,
params: KeyMintAttestation,
securityLevel: Int,
): List<Certificate>? {
val challenge = params.attestationChallenge
if (challenge != null && challenge.size > AttestationConstants.CHALLENGE_LENGTH_LIMIT)
throw IllegalArgumentException(
"Attestation challenge exceeds length limit (${challenge.size} > ${AttestationConstants.CHALLENGE_LENGTH_LIMIT})"
)
return try {
// AOSP ta/src/keys.rs:451-478: no challenge + no attestKey = self-signed, depth 1
if (challenge == null && attestKeyAlias == null) {
SystemLogger.trace {
"[certgen] no-challenge key: self-signed, depth=1, purposes=${params.purpose}"
}
return listOf(buildSelfSignedCertificate(subjectKeyPair, params))
}
val keybox = getKeyboxForAlgorithm(uid, params.algorithm)
val wantsAttestKey =
attestKeyAlias != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
val attestKeyInfo =
if (wantsAttestKey) getAttestationKeyInfo(uid, attestKeyAlias) else null
// When the caller designates an attest key, the leaf MUST be signed by it and returned
// alone (the caller appends the attest key's own chain). Re-rooting under the keybox
// here instead yields a self-rooted leaf that, concatenated with the attest key chain,
// double-roots and fails verification (WRONG_PUBLIC_KEY_TYPE). Refuse rather than emit
// a
// broken chain.
if (wantsAttestKey && attestKeyInfo == null) {
SystemLogger.error(
"Designated attest key '$attestKeyAlias' not found for uid $uid; refusing to " +
"emit a keybox-rooted leaf that would break the caller's chain."
)
return null
}
val (signingKey, issuer) =
attestKeyInfo?.let { it.first to it.second }
?: (keybox.keyPair to getIssuerFromKeybox(keybox))
val leafCert =
buildCertificate(subjectKeyPair, signingKey, issuer, params, uid, securityLevel)
if (attestKeyInfo != null) {
listOf(leafCert)
} else {
listOf(leafCert) + keybox.certificates
}
} catch (e: android.os.ServiceSpecificException) {
throw e
} catch (e: Exception) {
SystemLogger.error("Failed to generate certificate chain.", e)
null
}
}
/**
* A convenience function that combines key pair generation and certificate chain generation.
* Primarily used by the modern Keystore2 interceptor where generation is a single step.
*/
fun generateAttestedKeyPair(
uid: Int,
alias: String,
attestKeyAlias: String?,
params: KeyMintAttestation,
securityLevel: Int,
): Pair<KeyPair, List<Certificate>>? {
return try {
SystemLogger.info("Generating new attested key pair for alias: '$alias' (UID: $uid)")
val newKeyPair =
generateSoftwareKeyPair(params)
?: throw Exception("Failed to generate underlying software key pair.")
val chain =
generateCertificateChain(uid, newKeyPair, attestKeyAlias, params, securityLevel)
?: throw Exception("Failed to generate certificate chain for new key pair.")
SystemLogger.info("Successfully generated new certificate chain for alias: '$alias'.")
Pair(newKeyPair, chain)
} catch (e: android.os.ServiceSpecificException) {
throw e
} catch (e: Exception) {
SystemLogger.error("Failed to generate attested key pair for alias '$alias'.", e)
null
}
}
fun getIssuerFromKeybox(keybox: KeyBox) =
X509CertificateHolder(keybox.certificates[0].encoded).subject
private fun getKeyboxForAlgorithm(uid: Int, algorithm: Int): KeyBox {
val keyboxFile = ConfigurationManager.getKeyboxFileForUid(uid)
val algorithmName =
when (algorithm) {
Algorithm.EC -> "EC"
Algorithm.RSA -> "RSA"
else -> throw IllegalArgumentException("Unsupported algorithm ID: $algorithm")
}
// Prefer the algorithm-matching keybox, but fall back to any usable key (EC preferred) when
// none exists. An EC attestation key validly ECDSA-signs a leaf carrying an RSA subject key,
// so an EC-only keybox can still root an RSA forge. Without this fallback an RSA ATTEST_KEY
// request on an EC-only keybox throws -75 and the caller's chain never roots ("unknown
// certificate"). Mirrors the patch path's fail-safe
// (AttestationPatcher.getKeyboxForUidAndAlgorithm) and the RSA-leaf-under-EC-keybox handling
// in commit e6d5e4d.
val matched = KeyBoxManager.getAttestationKey(keyboxFile, algorithmName)
val keybox =
matched
?: KeyBoxManager.getAnyAttestationKey(keyboxFile)
?: throw android.os.ServiceSpecificException(
-75, // ATTESTATION_KEYS_NOT_PROVISIONED
"No usable attestation key in $keyboxFile",
)
// Surface which keybox actually signs the forge, so an EC-only-keybox fallback (an RSA leaf
// rooted under the EC key) is visible on the per-UID plane instead of silent.
SystemLogger.uidLog(uid, null, "keybox-pick") {
"req=$algorithmName ${if (matched != null) "matched" else "fellback-to-any"} " +
"signer=${getIssuerFromKeybox(keybox)}"
}
return keybox
}
/** Retrieves the key pair and issuer name for a given attestation key alias. */
private fun getAttestationKeyInfo(uid: Int, attestKeyAlias: String): Pair<KeyPair, X500Name>? {
SystemLogger.debug("Looking for attestation key: uid=$uid alias=$attestKeyAlias")
val keyId = KeyIdentifier(uid, attestKeyAlias)
// Access the public map of generated keys
val keyInfo = KeyMintSecurityLevelInterceptor.generatedKeys[keyId]
return if (keyInfo != null) {
val certChain = CertificateHelper.getCertificateChain(keyInfo.response)
if (!certChain.isNullOrEmpty()) {
val issuer = X509CertificateHolder(certChain[0].encoded).subject
// The leaf is signed by keyInfo.keyPair, but the caller verifies it against the
// public key of the chain getCertChain(attestKeyAlias) serves. A two-rooted EC chain
// (DATA_TOO_LARGE_FOR_MODULUS) is exactly those two disagreeing on algorithm; log
// both at the signing instant so an EC attest-key run pins the mismatched edge.
SystemLogger.uidLog(uid, null, "attest-sign") {
"alias=$attestKeyAlias signerKey=${keyInfo.keyPair?.public?.algorithm} " +
"servedLeafKey=${certChain[0].publicKey.algorithm} " +
"depth=${certChain.size} issuer=$issuer"
}
Pair(keyInfo.keyPair, issuer)
} else {
null
}
} else {
SystemLogger.warning(
"Attestation key '$attestKeyAlias' not found in generated key cache."
)
null
}
}
/** Maps KeyPurpose values to X.509 KeyUsage bits per KeyCreationResult.aidl spec */
private fun buildKeyUsageFromPurposes(purposes: List<Int>): Int {
var bits = 0
for (purpose in purposes) {
bits =
bits or
when (purpose) {
KeyPurpose.SIGN -> KeyUsage.digitalSignature
KeyPurpose.DECRYPT -> KeyUsage.dataEncipherment
KeyPurpose.WRAP_KEY -> KeyUsage.keyEncipherment
KeyPurpose.AGREE_KEY -> KeyUsage.keyAgreement
KeyPurpose.ATTEST_KEY -> KeyUsage.keyCertSign
else -> 0
}
}
return bits
}
/** Constructs a new X.509 certificate with a simulated attestation extension. */
private fun buildCertificate(
subjectKeyPair: KeyPair,
signingKeyPair: KeyPair,
issuer: X500Name,
params: KeyMintAttestation,
uid: Int,
securityLevel: Int,
): Certificate {
val subject = params.certificateSubject ?: X500Name("CN=Android Keystore Key")
val notBefore = params.certificateNotBefore ?: Date(0)
val notAfter = params.certificateNotAfter ?: Date(UNDEFINED_NOT_AFTER)
val builder =
JcaX509v3CertificateBuilder(
issuer,
params.certificateSerial ?: BigInteger.ONE,
notBefore,
notAfter,
subject,
subjectKeyPair.public,
)
// Add KeyUsage extension only if purposes map to valid bits
val keyUsageBits = buildKeyUsageFromPurposes(params.purpose)
if (keyUsageBits != 0) {
builder.addExtension(Extension.keyUsage, true, KeyUsage(keyUsageBits))
}
if (params.attestationChallenge != null) {
builder.addExtension(
AttestationBuilder.buildAttestationExtension(params, uid, securityLevel)
)
}
val signerAlgorithm =
when (signingKeyPair.private.algorithm) {
"EC",
"ECDSA" -> "SHA256withECDSA"
"RSA" -> "SHA256withRSA"
else ->
throw IllegalArgumentException(
"Unsupported signing key: ${signingKeyPair.private.algorithm}"
)
}
val contentSigner =
JcaContentSignerBuilder(signerAlgorithm)
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
.build(signingKeyPair.private)
return JcaX509CertificateConverter().getCertificate(builder.build(contentSigner))
}
// AOSP ta/src/keys.rs:452-478, ta/src/cert.rs:111-114
private fun buildSelfSignedCertificate(
keyPair: KeyPair,
params: KeyMintAttestation,
): Certificate {
val subject = params.certificateSubject ?: X500Name("CN=Android Keystore Key")
val notBefore = params.certificateNotBefore ?: Date(0)
val notAfter = params.certificateNotAfter ?: Date(UNDEFINED_NOT_AFTER)
val builder =
JcaX509v3CertificateBuilder(
subject,
params.certificateSerial ?: BigInteger.ONE,
notBefore,
notAfter,
subject,
keyPair.public,
)
val keyUsageBits = buildKeyUsageFromPurposes(params.purpose)
if (keyUsageBits != 0) {
builder.addExtension(Extension.keyUsage, true, KeyUsage(keyUsageBits))
}
val signerAlgorithm =
when (keyPair.private.algorithm) {
"EC",
"ECDSA" -> "SHA256withECDSA"
"RSA" -> "SHA256withRSA"
else ->
throw IllegalArgumentException("Unsupported key: ${keyPair.private.algorithm}")
}
val contentSigner =
JcaContentSignerBuilder(signerAlgorithm)
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
.build(keyPair.private)
return JcaX509CertificateConverter().getCertificate(builder.build(contentSigner))
}
}
@@ -0,0 +1,204 @@
package org.matrix.TEESimulator.pki
import android.system.keystore2.KeyEntryResponse
import android.system.keystore2.KeyMetadata
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.StringReader
import java.security.KeyPair
import java.security.cert.Certificate
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import org.bouncycastle.openssl.PEMKeyPair
import org.bouncycastle.openssl.PEMParser
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
import org.bouncycastle.util.io.pem.PemReader
import org.matrix.TEESimulator.logging.SystemLogger
import org.matrix.TEESimulator.util.trimLines
/**
* A utility object for handling cryptographic certificates and keys. Provides functions for
* parsing, serialization, and conversion between different formats.
*/
object CertificateHelper {
// Lazy-initialized CertificateFactory for X.509 certificates.
private val certificateFactory: CertificateFactory by lazy {
CertificateFactory.getInstance("X.509")
}
/**
* Represents the result of an operation that can either succeed with data or fail with an
* error.
*
* @param T The type of the successful data.
*/
sealed class OperationResult<out T> {
data class Success<T>(val data: T) : OperationResult<T>()
data class Error(val message: String, val cause: Throwable? = null) :
OperationResult<Nothing>()
}
/**
* Parses a single X.509 certificate from a byte array.
*
* @param bytes The raw byte representation of the certificate.
* @return An [OperationResult.Success] containing the [X509Certificate], or an
* [OperationResult.Error] on failure.
*/
fun toCertificate(bytes: ByteArray): OperationResult<X509Certificate> {
return try {
val certificate =
certificateFactory.generateCertificate(ByteArrayInputStream(bytes))
as X509Certificate
OperationResult.Success(certificate)
} catch (e: CertificateException) {
SystemLogger.warning("Failed to parse X.509 certificate from byte array.", e)
OperationResult.Error("Failed to parse certificate", e)
}
}
/**
* Parses a collection of X.509 certificates from a byte array.
*
* @param bytes The raw byte representation of one or more concatenated certificates.
* @return A collection of [X509Certificate] objects. Returns an empty list on failure.
*/
@Suppress("UNCHECKED_CAST")
fun toCertificates(bytes: ByteArray?): Collection<X509Certificate> {
return bytes?.let {
try {
certificateFactory.generateCertificates(ByteArrayInputStream(it))
as Collection<X509Certificate>
} catch (e: CertificateException) {
SystemLogger.warning("Could not parse certificate collection from byte array.", e)
emptyList()
}
} ?: emptyList()
}
/**
* Serializes a collection of certificates into a single byte array by concatenating their
* encoded forms.
*
* @param certificates The collection of [Certificate] objects to serialize.
* @return A [ByteArray] containing the concatenated certificates, or `null` on failure.
*/
fun certificatesToByteArray(certificates: Collection<Certificate>): ByteArray? {
return runCatching {
ByteArrayOutputStream().use { stream ->
certificates.forEach { cert -> stream.write(cert.encoded) }
stream.toByteArray()
}
}
.onFailure {
SystemLogger.warning(
"Failed to serialize certificate collection to byte array.",
it,
)
}
.getOrNull()
}
/**
* Parses a PEM-encoded private key and converts it into a Java [KeyPair].
*
* @param pemContent The string containing the PEM-encoded key.
* @return An [OperationResult.Success] with the [KeyPair], or an [OperationResult.Error] on
* failure.
*/
fun parsePemKeyPair(pemContent: String): OperationResult<KeyPair> {
return try {
PEMParser(StringReader(pemContent.trimLines())).use { parser ->
when (val pemObject = parser.readObject()) {
is PEMKeyPair -> {
val keyPair = JcaPEMKeyConverter().getKeyPair(pemObject)
OperationResult.Success(keyPair)
}
else ->
OperationResult.Error(
"Invalid PEM format: Expected a key pair, but got ${pemObject?.javaClass?.simpleName}"
)
}
}
} catch (e: Exception) {
SystemLogger.error("Failed to parse PEM key pair.", e)
OperationResult.Error("Failed to parse PEM key pair", e)
}
}
/**
* Parses a PEM-encoded X.509 certificate.
*
* @param pemContent The string containing the PEM-encoded certificate.
* @return An [OperationResult.Success] with the [Certificate], or an [OperationResult.Error] on
* failure.
*/
fun parsePemCertificate(pemContent: String): OperationResult<Certificate> {
return try {
PemReader(StringReader(pemContent.trimLines())).use { reader ->
val pemObject = reader.readPemObject()
val certificate =
certificateFactory.generateCertificate(ByteArrayInputStream(pemObject.content))
OperationResult.Success(certificate)
}
} catch (e: Exception) {
SystemLogger.error("Failed to parse PEM certificate.", e)
OperationResult.Error("Failed to parse PEM certificate", e)
}
}
/**
* Extracts the full certificate chain from a KeyStore [KeyMetadata] object.
*
* @param metadata The metadata associated with a keystore key entry.
* @return An array of [Certificate] objects, with the leaf certificate at index 0, or `null`.
*/
fun getCertificateChain(metadata: KeyMetadata?): Array<Certificate>? {
metadata ?: return null
val leafCertBytes = metadata.certificate ?: return null
val leafCert =
(toCertificate(leafCertBytes) as? OperationResult.Success)?.data ?: return null
val chainBytes = metadata.certificateChain
return if (chainBytes == null) {
arrayOf(leafCert)
} else {
val additionalCerts = toCertificates(chainBytes)
(listOf(leafCert) + additionalCerts).toTypedArray()
}
}
/**
* Extracts the full certificate chain from a [KeyEntryResponse].
*
* @param response The response object from a keystore operation.
* @return An array of [Certificate] objects, or `null`.
*/
fun getCertificateChain(response: KeyEntryResponse?): Array<Certificate>? {
return response?.let { getCertificateChain(it.metadata) }
}
/**
* Updates the certificate chain within a [KeyMetadata] object.
*
* @param metadata The metadata object to modify.
* @param chain The new certificate chain to set. The leaf must be at index 0.
* @return A [Result] indicating success or failure.
*/
fun updateCertificateChain(metadata: KeyMetadata, chain: Array<Certificate>): Result<Unit> {
return runCatching {
require(chain.isNotEmpty()) { "Certificate chain cannot be empty." }
metadata.certificate = chain[0].encoded
metadata.certificateChain =
if (chain.size > 1) {
certificatesToByteArray(chain.drop(1))
} else {
null
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More