home intel cve-2025-48635-taskfragment-activity-token-leak-privilege-escalation
CVE Analysis 2026-03-02 · 9 min read

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.

#activity-token-leak#privilege-escalation#logic-error#android-framework#local-exploit
Technical mode — for security professionals
▶ Vulnerability overview — CVE-2025-48635 · Vulnerability
ATTACKERCross-platformVULNERABILITYCVE-2025-48635HIGHSYSTEM COMPROMISEDNo confirmed exploits

Vulnerability Overview

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.

Affected Component

  • File: frameworks/base/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
  • 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.
TASKFRAGMENTINFO PARCEL LAYOUT (wire format, little-endian):

Offset  Size   Field
------  ----   -----
0x0000   4     fragmentToken (IBinder handle)
0x0004   4     windowContainerToken (IBinder handle)
0x0008  var    Configuration (Parcelable)
  ...
0x00XX   4     activityTokens.size()  = N
0x00XX   4     activityTokens[0]      <-- victim IBinder handle (LEAKED)
0x00XX   4     activityTokens[1]      <-- additional victim tokens
  ...
0x00YY   4     isVisible (boolean)
0x00YY   4     hasDirectActivity (boolean)

Patch Analysis

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.
CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →