CVE-2024-0044 in 60 seconds
Android keeps a compact file named packages.list so native tools can look up application identities.
During an ADB-driven install, CVE-2024-0044 allowed attacker-controlled installer metadata to contain
the separators used by that file. A crafted value could therefore create a forged-looking package
record. The privileged run-as utility later trusted the record and could be misled into assuming
another application’s identity.
The attack path was:
ADB shell access
-> install a package with a crafted installer name
-> inject record structure into packages.list
-> make run-as parse a forged application identity
-> cross another app's data boundary within the remaining UID and SELinux limits
This was a local elevation-of-privilege path, not a remote drive-by attack and not an automatic
route to Android’s system UID. It required the ability to reach the relevant ADB/shell installation
path. The exact result also depended on the device build, target application, and the controls that
run-as and SELinux still enforced.
What are packages.list and run-as?
Android’s Package Manager owns the rich package database. It also writes selected fields into
packages.list, a line- and space-delimited projection for native consumers. Each line is data, but
its separators are grammar: a newline starts another record and spaces divide identity fields.
run-as is a privileged debugging utility. For an eligible debuggable app, it uses package metadata
to enter that app’s execution context. It therefore needs an authoritative answer to “which UID,
data directory, SELinux inputs, and debuggability state belong to this package?” In the vulnerable
flow, part of that answer came from a text projection whose record structure an installer-controlled
value could change.
What happened?
An installer package name supplied through an ADB installation path was escaped in packages.xml
but not equivalently protected when written to packages.list. A value that looked like ordinary
metadata at the API boundary became record syntax at serialization time. run-as then interpreted
the resulting fields as identity data.
The first AOSP correction introduced a real package-name validator and applied it to
params.installerPackageName. A later patch applied the same validator to a separate
installerPackageName method argument before Android selected between the two. In other words, one
semantic value had two producers, but only one crossed the first new check. The follow-up closed that
producer gap and added the android.security.cts.CVE_2024_0044 regression test.
Who was actually affected?
Google’s October 2024 Android bulletin lists Android 12, 12L, 13, 14, and 15 and classifies the issue as a High-severity Framework elevation of privilege. Devices carrying the applicable platform fix at security patch level 2024-10-01 or later address this bulletin entry; OEM backports still need to be evaluated against the device’s actual branch and patch level.
Exposure did not mean that any website or ordinary app could immediately invoke the chain. A useful
assessment asks whether an attacker could obtain the required ADB/shell installation position, whether
the build contained the vulnerable producer path, and what identity the remaining run-as and SELinux
checks would permit.
What I verified
- The initial AOSP changes replace length-only handling with a package-name syntax check for
params.appPackageNameandparams.installerPackageName. - That first change still allows the selected installer identity to fall back to the separate
installerPackageNamemethod argument without applying the new validator to that argument. - The later AOSP change validates both inputs before choosing between them and names the regression
test
android.security.cts.CVE_2024_0044. - The Android October 2024 bulletin classifies CVE-2024-0044 as a High-severity Framework elevation of privilege and lists updated AOSP versions 12, 12L, 13, 14, and 15.
- The current CVE record describes the flaw in
createSessionInternal()as improper input validation that can allowrun-asto assume another app identity. - This is static reproduction of the data flow and patch logic. I verified neither exploitability on a specific OEM build nor in-the-wild exploitation.
Deep dive: the value crossed three different trust domains
The vulnerable path is easier to understand as a sequence of format and authority changes:
- an ADB shell caller supplies installer metadata during a package installation;
PackageInstallerServiceaccepts that value as a package name;- Package Manager serializes package metadata into
packages.list; run-asparses the record and uses its fields to make an application-identity decision.
The value begins as a Java string. It becomes syntax when written into a delimiter-based record. It becomes authority when a privileged consumer treats the parsed fields as the UID, debuggability, SELinux inputs, and data directory of an application.
Meta Red Team X documented the format mismatch precisely: special characters were escaped in
packages.xml, but not in packages.list. That difference matters because a defense in one
serialization format says nothing about another format with different delimiters and consumers.
run-as trusted a projection, not the package database
packages.list is a projection of selected package metadata. It exists so native consumers can
read a compact record without loading Package Manager’s richer internal state. That convenience
creates a trust question: which fields can the consumer safely accept from the projection, and
which should it derive independently?
The public research shows that run-as obtained the target package’s debuggability, UID-related
metadata, SELinux inputs, and data-directory path from this record. The tool did retain additional
checks: it would not assume non-application UIDs, and its SELinux transition did not simply copy a
privileged application’s original domain. Those controls limited the outcome, but they did not
restore the violated application-data boundary.
This distinction is important for severity analysis. “Run as any app” does not mean “become the
Android system UID with every original SELinux permission.” It means the forged record could make
the tool authorize an application context it should have rejected, exposing data and capabilities
available from that resulting context. The exact impact still depends on Android version, build,
target application, and the remaining run-as and SELinux controls.
The first patch fixed the visible input
The initial commits, 65bd134b0a82 and 954b2874b85b, are branch-specific forms of the same
security change. They add an isValidPackageName() helper that enforces the maximum length and uses
Android’s package parser to reject invalid name syntax. They then apply it to two values carried in
the session parameters:
if session.appName is invalid: clear it
if session.requestedInstallerName is invalid: clear it
selectedInstallerName =
session.requestedInstallerName ?? installerNameArgument
That is a meaningful improvement. A delimiter is not a valid package-name character, so the documented injection form no longer survives through the validated session parameter.
The control-flow shape nevertheless leaves an asymmetry. The fallback
installerNameArgument reaches selectedInstallerName without the same check. The variable names
make the values look interchangeable; the validation coverage shows they were not yet equivalent.
I treat “the first patch left a second path open” as a patch-diff conclusion, not as a claim that I reproduced an exploit against every March-patched build. The evidence is the unchanged fallback in the first diff, followed by a later same-CVE patch that validates that exact fallback and adds a CVE-specific regression test.
The October patch closes the producer gap
Commit 836750619a8b changes the comment on the existing check to distinguish the requested
installer package name. It then adds the same validity check for the separate method argument before
the null-coalescing selection occurs:
validate session.requestedInstallerName
validate installerNameArgument
selectedInstallerName = requested ?? argument
The important property is not that two if statements now appear together. It is that every value
that can become selectedInstallerName must satisfy the same package-name grammar first. The
security invariant now follows the semantic value across both producers.
The October bulletin places CVE-2024-0044 in the 2024-10-01 Framework group and lists Android 12, 12L, 13, 14, and 15 as updated AOSP versions. That current vendor scope is broader than the Android 12 and 13 attack scenario in the original March write-up. The two statements answer different questions: the research describes the versions on which its published chain was demonstrated; the current bulletin identifies platform branches receiving the applicable CVE correction. I use the vendor’s current 12–15 range for the frontmatter and do not extrapolate the original exploit chain to every listed version.
Safe static reproduction
The central claim can be checked without installing a package or constructing a delimiter payload. Use the public diffs as a bounded control-flow exercise:
- In either initial commit, identify the two inputs to the expression that selects the installer package name.
- Mark which input passes through
isValidPackageName()before that expression. - In the follow-up commit, repeat the marking exercise.
- Confirm that both producers are validated before the selection in the later version.
- Confirm that the later commit names a CVE-specific Android security regression test.
This reproduces the validation-coverage failure and its correction. It does not reproduce record
injection, an unauthorized run-as transition, or access to application data.
Negative controls
The negative controls are values and paths that should not be conflated with the vulnerable case:
- A syntactically valid installer package name remains accepted. If all installer attribution is cleared, the fix is broader than the published diff.
- An invalid name arriving through
params.installerPackageNameis rejected by the initial and follow-up versions. This distinguishes the original repaired path from the later producer gap. - An invalid name arriving through the separate
installerPackageNameargument is rejected only after the follow-up change. This is the discriminating control for patch coverage. - A value safely represented in
packages.xmldoes not prove it is safe inpackages.list; each serializer and downstream parser is a separate control surface. - A device reporting an October patch date is not automatically equivalent to an AOSP branch. OEM backports must be assessed through the device’s published bulletin or branch evidence.
Evidence matrix
| Claim | Direct evidence | What it does not prove | Confidence |
|---|---|---|---|
Installer metadata could alter packages.list record structure | Meta Red Team X traces unsanitized installer input to the newline- and space-delimited file | Exploitability on every OEM build | High |
run-as consumes security-relevant identity fields from that file | Original research links the parser to debuggability, UID, SELinux inputs, and data path | Unrestricted system UID or original privileged SELinux domain | High |
| The initial fix validates session-carried package names | AOSP commits 65bd134b0a82 and 954b2874b85b add and call isValidPackageName() | Equivalent validation of every producer | High |
| A separate fallback remained outside the first new check | Initial diffs select installerPackageName when the requested value is null | A weaponized bypass on every patched branch | High |
| The later fix validates both producers | AOSP commit 836750619a8b validates the argument before selection | Behavior of OEM code that diverges from AOSP | High |
| The applicable vendor patch level is October 2024 | Android bulletin maps the CVE to the 2024-10-01 Framework group and versions 12–15 | Whether a specific device actually received the required backport | High |
| Exploitation occurred in the wild | No primary source reviewed here confirms in-the-wild exploitation | Absence of private or undisclosed activity | Limited |
The durable fix is an invariant, not a filter location
The October change closes both producers visible at the selection point. That is the immediate patch property. The architecture still suggests two defense-in-depth questions.
First, should a delimiter-based security record ever accept raw strings from multiple upstream callers? A safe serializer should either reject record syntax centrally or encode every field in a format whose parser cannot reinterpret data as structure. Validating package-name grammar is strong here because the business object is already supposed to be a package name; generic escaping would need equally careful agreement between every writer and reader.
Second, should run-as trust reconstructible identity fields from a flat projection? The original
research notes that at least some data-path information can be derived from trusted identifiers. A
consumer that reconstructs or cross-checks security-critical fields reduces the authority of a
single serialized record. That is defense in depth, not a claim about changes present in the three
patches analyzed here.
The reusable review rule is simple:
When several inputs can become one trusted value, validate the value after convergence—or prove that every producer enforces the same invariant before it joins.
Defender and reviewer takeaways
- Use the Android security patch level, vendor bulletin, and OEM backport evidence together. For this CVE, 2024-10-01 or later is the applicable AOSP bulletin level.
- Do not use “Android 14 was not exploitable in the original public scenario” as a substitute for the current vendor patch scope. Defense-in-depth changes and variant paths can change that scope.
- In patch review, search both for the named field and for the destination variable that eventually carries it. The second search is what reveals alternate producers.
- Treat every human-readable metadata export consumed by privileged native code as a protocol. Define its grammar, escaping, versioning, and trust model accordingly.
- Keep public validation non-destructive. The two-diff control-flow comparison is sufficient to verify the patch lesson without touching app data or operational devices.
Research limits and credit
Tom Hebb of Meta Red Team X discovered and reported the original issue, documented the exploitation chain, and published the associated advisory. Google assigned CVE-2024-0044 and the Android team authored the public fixes. I claim neither discovery nor exploit reproduction.
My contribution is the independent comparison of the initial and follow-up AOSP changes, the producer-coverage model, and the evidence boundaries recorded in this casefile. Public source review cannot establish OEM-specific behavior, the contents of private Android bug records, or in-the-wild exploitation. Those remain outside the claim set.
My conclusion
CVE-2024-0044 is not only a newline-injection story. It is a warning about semantic convergence. Android had two values that could become the installer identity. The first fix enforced the right grammar on one. The later fix applied the same invariant to the other before either could reach the shared decision.
The package name was not dangerous because it was a string. It became dangerous when one component treated it as record syntax and another treated the resulting record as identity authority. The patch history is valuable because it shows exactly where a local input check ends—and where a trust boundary review has to begin.
Sources
- CVE.org: CVE-2024-0044 record
- Android Security Bulletin — October 2024
- AOSP initial fix, Android branch:
65bd134b0a82 - AOSP initial fix, later branch:
954b2874b85b - AOSP follow-up fix:
836750619a8b - Meta Red Team X: Android
run-asforgery research - Meta Red Team external disclosure: CVE-2024-0044
Evidence used for this analysis
Version and exploitation status can change; follow the linked vendor records.
Primary public records were checked. Environment-specific behavior remains outside the claim unless separately reproduced.
- cve.org · CVERecord ↗
- source.android.com · 2024-10-01 ↗
- android.googlesource.com · 65bd134b0a82…5cdd00ddc31792 ↗
- android.googlesource.com · 954b2874b85b…7cdf22e5dbd77b ↗
- android.googlesource.com · 836750619a8b…9990e294671563 ↗
- rtx.meta.security · Android-run-as-forgery ↗
- github.com · GHSA-m7fh-f3w4-r6v2 ↗
