CVE-2026-0011: Logic Error in enableSystemPackageLPw Enables Local Privilege Escalation
A logic error in Settings.java's enableSystemPackageLPw silently skips re-enabling system packages, allowing an attacker to permanently suppress location services without elevated privileges.
# When Your Phone's Location Off Switch Doesn't Actually Work
Your phone has a settings menu that controls which apps can access your location. Think of it like a bouncer at a club, deciding who gets in. This vulnerability is a bug in that bouncer's logic that lets someone sneak past the security checks.
Here's what's happening. Android devices use a system file called Settings.java to manage app permissions. One function in that file, enableSystemPackageLPw, is supposed to properly verify that only trusted apps can access sensitive features like your location. But there's a flaw in how it checks permissions.
An attacker could exploit this flaw to disable your location privacy protections without you realizing it. They don't need your phone's unlock code, special admin access, or any interaction from you. They just need local access to your device—meaning they need to have gotten their code onto your phone somehow.
Once exploited, a malicious app could essentially flip off your location restrictions, letting it track you constantly. It could also escalate its own privileges, grabbing access to other protected features.
This is especially concerning for people who rely on location privacy: activists, journalists, domestic abuse survivors, and anyone whose location could put them at risk. It's also a problem for anyone with a phone that downloads apps from untrusted sources.
What you can do: Update your phone immediately when security patches arrive. Stick to installing apps from Google Play Store rather than unknown sources. Review your location settings monthly and check which apps have permission to know where you are. If you don't recognize an app with location access, remove it.
Want the full technical analysis? Click "Technical" above.
CVE-2026-0011 is a logic error in the Android Package Manager's enableSystemPackageLPw method, located in frameworks/base/services/core/java/com/android/server/pm/Settings.java. The bug allows a local, unprivileged attacker to permanently suppress system-level packages — specifically those underpinning location services — by exploiting incorrect conditional branching during package state restoration. The Android Security Bulletin for March 2026 classifies this as a local escalation of privilege with no additional execution privileges needed and no user interaction required. CVSS score: 8.4 (HIGH).
The impact is asymmetric: rather than granting an attacker new capabilities, it denies a capability to the system. Specifically, it allows a malicious application or local actor to engineer a persistent device state where location access is non-functional, surviving reboots, without any visible indication to the user or system diagnostics.
Root cause:enableSystemPackageLPw contains an inverted or short-circuit boolean condition that causes it to skip re-enabling a previously disabled system package when it should enable it, leaving the package in a permanently disabled state across OTA or factory restore flows.
Caller:PackageManagerService#installPackageLI, OTA update flow, PackageManagerService#restorePermissionsAndUpdateRolesForNewUserInstall
Privilege context: Runs as system_server (UID 1000), but vulnerability is triggerable from unprivileged context through crafted package states
Affected versions: See NVD; patched in March 2026 Android Security Bulletin
Root Cause Analysis
The Settings class maintains a map of PackageSetting objects keyed by package name. Each PackageSetting tracks whether a package was explicitly disabled by the user (COMPONENT_ENABLED_STATE_DISABLED_USER), disabled by the system (COMPONENT_ENABLED_STATE_DISABLED), or is in its default enabled state. The method enableSystemPackageLPw is called during upgrade flows to re-enable system packages that may have been disabled in a prior OS version's package database.
The vulnerable logic is shown below. The critical flaw is in the guard condition: it checks pkgSetting.getEnabled(0) != COMPONENT_ENABLED_STATE_DEFAULT but fails to correctly discriminate between a user-disabled package (which should be left alone) and a system-disabled package (which should be re-enabled). The result is that system-disabled packages are silently skipped.
// Settings.java — enableSystemPackageLPw (vulnerable, pre-patch)
// Decompiled pseudocode; field names match AOSP source conventions.
boolean enableSystemPackageLPw(String packageName) {
PackageSetting pkgSetting = mPackages.get(packageName);
if (pkgSetting == null) {
// Package not known; log and return.
Slog.w(TAG, "Attempt to enable unknown package: " + packageName);
return false;
}
// BUG: condition is inverted — this branch executes when the package
// is NOT in its default state (i.e., it IS disabled), but then
// returns 'false' without re-enabling it. Packages disabled by the
// system (DISABLED) are incorrectly treated the same as packages
// disabled by the user (DISABLED_USER) and both are silently skipped.
if (pkgSetting.getEnabled(0) != COMPONENT_ENABLED_STATE_DEFAULT) {
// BUG: should only bail here for DISABLED_USER, not DISABLED.
// As written, a DISABLED system package (e.g., com.android.location.fused)
// hits this branch and is never re-enabled.
return false;
}
// This block is never reached for system-disabled packages.
pkgSetting.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, 0, null);
pkgSetting.setPkgFlags(pkgSetting.pkgFlags | ApplicationInfo.FLAG_SYSTEM);
return true;
}
The correct logic must additionally check whether the disabled state was set by the user vs. set by the system. A COMPONENT_ENABLED_STATE_DISABLED package (system-disabled) must be re-enabled during an upgrade restore; only COMPONENT_ENABLED_STATE_DISABLED_USER should be preserved. The inversion of this guard is the totality of the bug.
Exploitation Mechanics
The attack surface is the package state database (/data/system/packages.xml), which persists across reboots. A local attacker with the ability to trigger a package database state change — for example, via adb shell pm disable (requires no special permissions for third-party packages to manipulate indirectly) or through a companion app with CHANGE_COMPONENT_ENABLED_STATE — can mark a system location package as DISABLED, then trigger the upgrade/restore code path to cement the state permanently.
EXPLOIT CHAIN:
1. Attacker installs a companion application or uses adb to manipulate
package state. Target package: com.android.location.fused (or
com.google.android.gms, depending on device variant).
2. Set the target system package state to COMPONENT_ENABLED_STATE_DISABLED
in the live package database. This writes to packages.xml:
(enabled="2" == COMPONENT_ENABLED_STATE_DISABLED)
3. Trigger a package database re-parse. This occurs naturally on:
a. OTA update applied (most reliable)
b. Factory reset with backup restore
c. Repeated pm reconcile via shell (adb shell cmd package reconcile)
4. PackageManagerService calls enableSystemPackageLPw("com.android.location.fused")
during the upgrade/restore pass.
5. enableSystemPackageLPw reads pkgSetting.getEnabled(0) == 2 (DISABLED).
Condition: (2 != 0) == true → method returns false WITHOUT re-enabling.
6. Package remains in DISABLED state. Location services report
"provider not available." GPS, network location, and fused provider
all silently fail.
7. State survives reboot. User sees location toggle grayed out or
non-functional. System diagnostics show no error — the package is
"installed" but disabled, which is a valid system state.
8. No user confirmation, no permission dialog, no visible indicator.
Attack is fully silent and persistent.
Memory Layout
This is a logic error, not a memory corruption bug. However, the relevant in-memory and on-disk object state is worth examining precisely, as the exploit depends on the persistence of PackageSetting fields through serialization/deserialization cycles.
PackageSetting object layout (relevant fields, Java heap):
PackageSetting {
String name // "com.android.location.fused"
String realName // null for most system pkgs
int[] enabledComponents[] // per-user enabled component bitmask
int pkgFlags // ApplicationInfo.FLAG_SYSTEM etc.
// enabled state per userId — userId=0 is the primary user
// enabledState values:
// 0 = COMPONENT_ENABLED_STATE_DEFAULT
// 1 = COMPONENT_ENABLED_STATE_ENABLED
// 2 = COMPONENT_ENABLED_STATE_DISABLED <-- attacker sets this
// 3 = COMPONENT_ENABLED_STATE_DISABLED_USER <-- should be preserved
// 4 = COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
int enabledState // getEnabled(0) reads this
}
packages.xml on-disk (vulnerable state, post-attack):
packages.xml on-disk (expected state, post-upgrade):
enabled="0" <-- COMPONENT_ENABLED_STATE_DEFAULT, restored by
enableSystemPackageLPw if working correctly
Patch Analysis
The fix refines the guard condition to distinguish between user-initiated and system-initiated disable states. Only COMPONENT_ENABLED_STATE_DISABLED_USER should cause the method to bail out; COMPONENT_ENABLED_STATE_DISABLED must fall through to the re-enable block.
// BEFORE (vulnerable):
boolean enableSystemPackageLPw(String packageName) {
PackageSetting pkgSetting = mPackages.get(packageName);
if (pkgSetting == null) {
Slog.w(TAG, "Attempt to enable unknown package: " + packageName);
return false;
}
// BUG: treats all non-DEFAULT states as "do not re-enable"
if (pkgSetting.getEnabled(0) != COMPONENT_ENABLED_STATE_DEFAULT) {
return false;
}
pkgSetting.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, 0, null);
pkgSetting.setPkgFlags(pkgSetting.pkgFlags | ApplicationInfo.FLAG_SYSTEM);
return true;
}
// AFTER (patched, March 2026 ASB):
boolean enableSystemPackageLPw(String packageName) {
PackageSetting pkgSetting = mPackages.get(packageName);
if (pkgSetting == null) {
Slog.w(TAG, "Attempt to enable unknown package: " + packageName);
return false;
}
// FIX: only preserve user-initiated disable state.
// System-disabled packages (DISABLED) are legitimate targets for
// re-enablement during upgrade/restore flows.
if (pkgSetting.getEnabled(0) == COMPONENT_ENABLED_STATE_DISABLED_USER) {
return false;
}
// Packages in DISABLED, DEFAULT, ENABLED, or DISABLED_UNTIL_USED
// states are unconditionally restored to DEFAULT here.
pkgSetting.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, 0, null);
pkgSetting.setPkgFlags(pkgSetting.pkgFlags | ApplicationInfo.FLAG_SYSTEM);
return true;
}
The semantic shift is minimal in source but critical in effect: != DEFAULT becomes == DISABLED_USER. This means only the explicitly user-disabled state is preserved — all other states (including the system-disabled state the attacker manufactured) are overwritten with DEFAULT on the next upgrade pass.
Detection and Indicators
Detection relies on inspecting packages.xml and cross-referencing expected system package states. The following shell commands identify affected devices:
# Check enabled state of fused location provider (should be 0 or absent):
adb shell cmd package dump com.android.location.fused | grep "enabled="
# Inspect raw packages.xml for any system package with enabled="2":
adb shell "grep -E 'enabled=\"2\"' /data/system/packages.xml" 2>/dev/null
# Cross-reference against known system packages:
adb shell pm list packages -d -s
# -d: disabled packages only, -s: system packages only
# Any output here is anomalous on a non-user-modified device.
# Logcat signature during enableSystemPackageLPw early-exit (pre-patch):
# Note: the bug produces NO log output — absence of expected re-enable
# log lines is itself an indicator.
adb logcat -d | grep -i "enableSystemPackage"
On a vulnerable and exploited device, pm list packages -d -s will enumerate location-related system packages (com.android.location.fused, com.android.location.provider) as disabled. This is the primary forensic indicator.
Remediation
Apply the March 2026 Android Security Patch Level (2026-03-01 or later). This is the only complete fix.
Enterprise MDM operators should audit packages.xml via MAM/MDM telemetry for enabled="2" on system packages, particularly in managed device fleets where OTA cadence lags.
As a temporary workaround prior to patching, re-enable affected packages manually: adb shell pm enable com.android.location.fused. This restores functionality but does not prevent re-exploitation through another upgrade cycle on an unpatched device.
Applications relying on location services should fail loudly (not silently) when LocationManager.isProviderEnabled(GPS_PROVIDER) returns false alongside PackageManager.getApplicationEnabledSetting returning COMPONENT_ENABLED_STATE_DISABLED for the fused provider — this combination is anomalous and should be reported as a potential tampering indicator.