CVE-2025-48635: Activity Token Leak in TaskFragmentOrganizerController
A logic error in TaskFragmentOrganizerController.java leaks activity IBinder tokens to unprivileged apps, enabling local privilege escalation without user interaction on Android.
# Android's Hidden Security Flaw Could Let Hackers Take Over Your Phone
Deep inside Android's code sits a flaw that works like leaving your house key visible in a window. An app installed on your phone could spy on special security tokens—think of them as digital permission slips—that Android uses to manage which apps can do what.
The problem is in how Android handles something called "task fragments," which are basically the building blocks of how apps organize themselves on your screen. In certain situations, these permission slips get exposed to any app running on your device, not just the ones that should see them. An attacker with this information can trick Android into giving them powers they shouldn't have.
Here's why it matters: Right now, Android has a system where apps need your permission before they can access your contacts, location, or camera. This vulnerability could let a malicious app bypass those protections entirely by stealing these digital credentials. It's like someone making a fake copy of your house key after spotting the real one.
The risk is highest for people who download apps from unknown sources or who install lots of apps from third-party stores. Anyone with multiple apps on their phone is potentially exposed, but people who use only official app stores face lower risk since Google screens apps before they're published.
What you can do: First, stick to the official Google Play Store when downloading apps—it's not perfect, but it filters out the worst offenders. Second, regularly check which apps have access to sensitive information in your settings and remove permissions from apps you don't trust. Finally, keep your phone updated; Google will likely patch this soon, so installing updates quickly is your best defense.
Want the full technical analysis? Click "Technical" above.
CVE-2025-48635 is a logic error in TaskFragmentOrganizerController.java, a core component of Android's WindowManager subsystem responsible for managing task fragment lifecycle events. The flaw causes activity IBinder tokens — opaque handles that represent running activities and gate privileged IPC calls — to be returned to organizer callbacks without adequate validation of the calling process's entitlement to those tokens.
CVSS 7.7 (HIGH). No additional execution privileges required. No user interaction required. Exploitation is entirely local, requiring only that the attacker controls a foreground or background application process.
Root cause: Multiple dispatch functions in TaskFragmentOrganizerController return activity IBinder tokens in TaskFragmentInfo parcels to registered organizer callbacks without verifying that the calling UID owns or is otherwise authorized to observe those activity tokens, allowing a malicious organizer to enumerate foreign activity tokens and invoke privileged window operations on behalf of victim activities.
Subsystem: WindowManager / Task Fragment API (android.window)
Android versions: Affected builds prior to the March 2026 Android Security Bulletin patch level (2026-03-01)
Privilege boundary crossed: App (UID N) → System Server token namespace
Root Cause Analysis
The Task Fragment Organizer API lets privileged embedding hosts (e.g., Jetpack WindowManager) register an ITaskFragmentOrganizer callback. The system server dispatches events — onTaskFragmentAppeared, onTaskFragmentInfoChanged, onActivityReparentToTask — by packaging a TaskFragmentInfo containing the running activities' raw IBinder tokens.
The bug manifests in the dispatch helpers. Rather than gate-checking the organizer's UID against each activity token before including it in the info parcel, the controller unconditionally iterates all activities in the fragment and stuffs their tokens into the callback payload:
// TaskFragmentOrganizerController.java — simplified decompiled pseudocode
// Represents the JVM bytecode logic as equivalent C pseudocode for analysis
TaskFragmentInfo makeTaskFragmentInfo(TaskFragment tf, IBinder orgToken) {
List activityTokens = new ArrayList<>();
for (ActivityRecord ar : tf.getActivities()) {
// BUG: no UID / organizer ownership check before adding token
activityTokens.add(ar.appToken); // leaks foreign IBinder to caller
}
return new TaskFragmentInfo(
tf.getFragmentToken(),
tf.getWindowContainerToken(),
tf.getConfiguration(),
activityTokens, // <-- attacker-controlled organizer receives this
/* isVisible= */ tf.isVisible(),
/* hasDirectActivity= */ tf.hasDirectActivity()
);
}
void dispatchOnTaskFragmentInfoChanged(ITaskFragmentOrganizer org,
TaskFragment tf) {
TaskFragmentInfo info = makeTaskFragmentInfo(tf, org.asBinder());
// BUG: dispatched unconditionally; organizer may not own activities in tf
org.onTaskFragmentInfoChanged(info);
}
The same pattern repeats in onTaskFragmentAppeared and onActivityReparentToTask. A second logic error exists in onActivityReparentToTask: the reparent event is dispatched to all registered organizers rather than only the organizer that owns the destination fragment, broadcasting the reparented activity's token system-wide to every registered callback.
// onActivityReparentToTask — vulnerable broadcast logic
void dispatchOnActivityReparentToTask(Task task, ActivityRecord ar) {
for (ITaskFragmentOrganizer org : mOrganizers) {
// BUG: dispatched to ALL organizers, not only owner of destination fragment
// ar.appToken is sent to every registered organizer regardless of ownership
org.onActivityReparentToTask(task.taskId, ar.intent, ar.appToken);
}
}
Exploitation Mechanics
EXPLOIT CHAIN:
1. Attacker app registers a malicious ITaskFragmentOrganizer via
WindowOrganizer.registerTaskFragmentOrganizer(). No special permission
beyond normal app execution is required.
2. Attacker creates a TaskFragment embedding a controlled activity (decoy).
This legitimizes the organizer registration and causes the system server
to add attacker to mOrganizers list.
3. On any device activity reparent event (e.g., picture-in-picture transition,
split-screen drag, back-stack pop), dispatchOnActivityReparentToTask()
fires and broadcasts the victim activity's raw IBinder appToken to ALL
registered organizers — including the attacker's.
4. Attacker's onActivityReparentToTask() callback receives victim IBinder
token (e.g., token for a banking app or Settings privileged activity).
5. Attacker uses the leaked token in subsequent WindowManager IPC calls:
- ActivityTaskManager.moveActivityToFront(victimToken)
- WindowManager.setWindowAttributes(victimToken, ...)
- startActivity(intent, /* resultTo= */ victimToken)
These calls bypass normal UID validation because possession of a valid
activity IBinder token is treated as proof of ownership.
6. Result: attacker can manipulate victim activity stack, inject result
intents (startActivityForResult abuse), or force victim activity into
a state exposing sensitive UI — constituting local privilege escalation.
The attack surface is entirely in-process IPC via the Binder interface. No memory corruption is required. Token possession is the primitive; the escalation is semantic.
Memory Layout
While this is not a memory corruption vulnerability, the IBinder token leak has a concrete representation in the Binder kernel driver's object table. Understanding it clarifies why token possession is dangerous:
BINDER OBJECT TABLE (kernel/drivers/android/binder.c — binder_node):
Victim activity's appToken (JavaBinder in system_server):
binder_node @ 0xffffff80_1a3c4000
+0x00 cookie = 0xb4000075_deadbeef // ActivityRecord* in JVM heap
+0x08 ptr = 0xb4000075_cafef00d // JavaBBinder*
+0x10 refs = 4 // system_server holds 4 strong refs
+0x18 proc = system_server (pid 1234)
AFTER LEAK — attacker app receives handle:
binder_ref @ 0xffffff80_1b2d8000 (in attacker's proc binder_proc)
+0x00 desc = 0x0000001f // handle number in attacker's handle table
+0x08 node = 0xffffff80_1a3c4000 // points to victim's binder_node
+0x10 strong = 1 // attacker holds strong ref
CONSEQUENCE:
Attacker passes handle 0x1f in Parcel to any WindowManager Binder call
that accepts an IBinder activity token. Kernel resolves handle → node →
system_server validates token exists (it does) → operation proceeds on
victim activity without UID check at token resolution time.
The March 2026 Android Security Bulletin patches this by introducing ownership checks before token inclusion in dispatch paths. The fix adds a callerOwnsActivity() guard in makeTaskFragmentInfo and replaces the broadcast loop in dispatchOnActivityReparentToTask with a targeted dispatch to the owning organizer only.
// BEFORE (vulnerable) — makeTaskFragmentInfo:
for (ActivityRecord ar : tf.getActivities()) {
activityTokens.add(ar.appToken); // unconditional
}
// AFTER (patched — 2026-03-01 bulletin):
for (ActivityRecord ar : tf.getActivities()) {
// Only include token if organizer's UID matches activity's UID,
// or organizer is the explicit embedding host for this activity.
if (isActivityVisibleToOrganizer(ar, organizerUid)) {
activityTokens.add(ar.appToken);
}
// Foreign activities: include a null sentinel to preserve index alignment
// without leaking the token.
}
// BEFORE (vulnerable) — dispatchOnActivityReparentToTask:
void dispatchOnActivityReparentToTask(Task task, ActivityRecord ar) {
for (ITaskFragmentOrganizer org : mOrganizers) {
org.onActivityReparentToTask(task.taskId, ar.intent, ar.appToken);
}
}
// AFTER (patched):
void dispatchOnActivityReparentToTask(Task task, ActivityRecord ar) {
ITaskFragmentOrganizer owningOrg = getOrganizerOwningTask(task);
if (owningOrg == null) return;
// Dispatch ONLY to the organizer that owns the destination task.
// ar.appToken is only sent if the organizer's UID owns the activity,
// otherwise an opaque non-functional placeholder token is sent.
IBinder tokenToSend = organizerOwnsActivity(owningOrg, ar)
? ar.appToken
: ar.mActivityToken; // non-privileged placeholder
owningOrg.onActivityReparentToTask(task.taskId, ar.intent, tokenToSend);
}
Detection and Indicators
There is no reliable on-device indicator of active exploitation since the attack operates entirely through legitimate Binder IPC. The following heuristics may surface suspicious behavior:
Logcat / adb: Watch for repeated onTaskFragmentInfoChanged or onActivityReparentToTask calls from an organizer whose UID does not match the embedding host package (e.g., Jetpack library or OEM launcher). Tag: TaskFragmentOrganizer.
Binder transaction logs: An app with no embedding use-case holding unexpected strong references to foreign ActivityRecord Binder nodes in /sys/kernel/debug/binder/proc/<pid>.
WindowManager dumpsys:adb shell dumpsys window — look for task fragments owned by suspicious UIDs containing activities from a different UID.
Exploit prerequisite: Target device must have an app registered as a TaskFragmentOrganizer. This is detectable via adb shell dumpsys activity inspecting registered organizers.
// Suspicious logcat pattern indicating organizer receiving foreign tokens:
W TaskFragmentOrganizerController: onActivityReparentToTask uid=10234
task.uid=10087 mismatch — pre-patch this proceeds silently
Remediation
Apply the March 2026 security patch level (2026-03-01) immediately. This resolves all affected dispatch paths in TaskFragmentOrganizerController.java.
OEM integrations that extend or override TaskFragmentOrganizerController should audit any override of makeTaskFragmentInfo, dispatchOnTaskFragmentInfoChanged, dispatchOnTaskFragmentAppeared, and dispatchOnActivityReparentToTask for equivalent UID ownership checks.
Enterprise environments should restrict sideloaded or unknown app installation to minimize the attacker foothold required (any installed app process).
Until patched: consider disabling or restricting the Task Fragment Organizer API at the policy level if embedding functionality is not required for production deployment.