Knowledge Center / Mobile runtime attacks / mobile runtime attacks · 2026·01
Run-as trust model — debuggable targets, SELinux scope, and the run-as defect class
Android's `run-as` elevation is part of the debug-bridge tooling and is scoped, by design, to debuggable target packages and to processes Android trusts to invoke it. The attack pattern this article describes is not a single CVE but a class: what becomes possible when a debuggable helper or developer tool sits on the same device as the target app, and what the substrate signs while it does.
run-as elevation vulnerability affected Android 12–13 (API 31–33). API 34+ tightens the scoping. Estates with unpatched devices are still exposed.
1. What run-as does, and where it is scoped
run-as is part of Android’s debug bridge (adb) tooling [1]. The
binary lives in the platform image and lets a caller execute
commands inside a target package’s UID and data directory — but
only when two conditions hold: the target package is built with
android:debuggable="true", and the caller is in a context the
platform trusts to issue the request (historically, the adb
shell). A release-build app cannot be elevated into via run-as
at all.
The attack-pattern question is not “is run-as broken” — it is
“what does the trust model look like when a debuggable helper is
already present on the device.” A side-loaded developer tool, a
debuggable utility installed alongside the target, or a build of
the host app shipped with debuggable=true left on by accident
each create that condition.
2. Where in the runtime it lives
Three platform components carry the trust:
- The debug bridge.
adbdand therun-asbinary, signed by the OEM and shipped in the platform image. - The zygote process model. App processes are forked from zygote; the elevated context re-establishes UID and SELinux domain for the forked process.
- SELinux policy. The platform distinguishes
untrusted_app,isolated_app, the various developer-mode domains, and theshelldomain. The trust boundary lives in the domain transitions between them.
A defect in any of these — a permission check that omits a case, a SELinux rule that allows a transition it should not, an adbd path that accepts a caller it should reject — collapses the elevation scope. Patch levels and security bulletins exist precisely because these defects are found and fixed over the fleet’s lifetime; the canonical published example is CVE-2024-0044 (Android Security Bulletin, March 2024) — a run-as scope defect on Android 12L / 13 that allowed elevation into another app’s data directory under specific configurations. The Android Security Bulletin [3] is the canonical record for this defect class.
3. What checkpoints see, and don’t see
- Play Integrity device-integrity verdict. A device on a
build that predates a given run-as fix still receives
MEETS_DEVICE_INTEGRITYif it is Play-certified. The device-integrity verdict does not encode “is this build’s patch level past fix X?” - Play Integrity
MEETS_STRONG_INTEGRITY. This verdict requires a recent security patch level and a hardware-backed verdict. A device that has applied the run-as fix and reports a current patch level will receiveMEETS_STRONG_INTEGRITY; one that has not, will not. Operators that gate sensitive flows onMEETS_STRONG_INTEGRITYinherit the patch-level freshness check. - Android Key Attestation. The attestation chain validates cryptographically and the key-bound chain is unaffected by this attack class — the trust collapse described here is in the platform’s process model, not the keystore.
4. What the substrate observes
code.integrity and runtime.environment. The Trusted Runtime
Primitive observes the calling process’s UID, parent UID, and
SELinux context, alongside the device’s reported security patch
level (Build.VERSION.SECURITY_PATCH). A process whose parent
domain is a privileged shell or developer-mode context, when the
expected parent is the zygote, is the signal. Combined with the
SDK level and patch-level freshness, the operator’s verifier can
decide.
5. Evidence Token shape when observed
The following example is illustrative; field names, type values, and schema are defined in YEI-001 §4 (available through the spec-access process).
{
"ev": [{
"ts": "2026-06-15T10:23:14Z",
"class": "code.integrity",
"type": "process.unexpected_parent",
"data": {
"host_pid": 9421,
"host_uid": 10341,
"parent_selinux": "u:r:shell:s0",
"expected_parent": "u:r:zygote:s0",
"android_sdk": 32,
"security_patch": "2025-09-05",
"patch_age_days": 283
}
}]
}
The pairing of a non-zygote parent SELinux context with a stale patch level is what the substrate records. The operator decides what to do with the pair — block, step up, or annotate the record for later forensic review.
6. Cross-references
- Sibling articles:
debugger-attachment,magisk-zygisk-modules - Theme 2:
play-integrity - Architecture:
/architecture/threat-model
7. External references
[1] Android Developers. Android Debug Bridge (adb) — run-as. developer.android.com/tools/adb. Cited 2026-01-17.
[2] Android Developers. Android 14 — Behavior changes that affect all apps. developer.android.com/about/versions/14/behavior-changes-all. Cited 2026-01-17.
[3] Google. Android Security Bulletin. source.android.com/docs/security/bulletin. Cited 2026-01-17.