YinkoShield

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 exploit — SDK 31–33 vulnerability window ] API 31–33 · vulnerable API 30 A11 API 31 A12 API 32 A12L API 33 A13 API 34 A14 patch API 35 A15 // the abuse pattern run-as <debuggable_pkg> → shell elevates into another package's UID context a debuggable companion app is enough to reach a non-debuggable target on affected versions platform mitigations API 34+: tighter run-as scoping, debug-bridge process restrictions, SELinux changes applies to estates that updated; tail of unpatched devices remains
The 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. adbd and the run-as binary, 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 the shell domain. 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_INTEGRITY if 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 receive MEETS_STRONG_INTEGRITY; one that has not, will not. Operators that gate sensitive flows on MEETS_STRONG_INTEGRITY inherit 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

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.