CVE-2025-48634 AI-generated analysis
platform/frameworks/base, WindowManagerService (services/core/java/com/android/server/wm), window relayout
Adds a permission-based sanitizer (sanitizePrivateFlags) to WindowManagerService relayout so that a client cannot newly set PRIVATE_FLAG_TRUSTED_OVERLAY / PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY without INTERNAL_SYSTEM_WINDOW, or PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP without MANAGE_ACTIVITY_TASKS.
Root cause
In WindowManagerService's relayout path (the block that calls displayPolicy.adjustWindowParamsLw(win, attrs) and then sanitizeFlagSlippery / sanitizeSpyWindow), the caller-supplied attrs.privateFlags were taken as-is, while attrs.flags (FLAG_SLIPPERY), attrs.inputFeatures (spy window) and the systemUiVisibility disable bits (hasStatusBarPermission) were checked against caller permissions. Therefore a client that already owns a window could, on a subsequent relayout, add the private flags PRIVATE_FLAG_TRUSTED_OVERLAY, PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY or PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP to its window without holding INTERNAL_SYSTEM_WINDOW or MANAGE_ACTIVITY_TASKS. The bulletin rates this High / EoP.
How the fix works
- services/core/java/com/android/server/wm/WindowManagerService.java, relayout block (`if (attrs != null)` after displayPolicy.adjustWindowParamsLw): new line `attrs.privateFlags = sanitizePrivateFlags(attrs.privateFlags, win.mAttrs.privateFlags, win.getName(), uid, pid);` inserted before the existing sanitizeFlagSlippery call, using the Binder calling uid/pid captured at the top of the method.
- WindowManagerService.java: new private helper sanitizePrivateFlags(newPrivateFlags, oldPrivateFlags, windowName, callingUid, callingPid). It computes addedPrivateFlags = ~oldPrivateFlags & newPrivateFlags (only flags being newly turned on). If PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY or PRIVATE_FLAG_TRUSTED_OVERLAY is being added and the caller lacks INTERNAL_SYSTEM_WINDOW, both bits are cleared from the result and a Slog.w is emitted. If PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP is being added and the caller lacks MANAGE_ACTIVITY_TASKS, that bit is cleared with a Slog.w. The sanitized value is returned and written back to attrs.privateFlags.
- WindowManagerService.java: new helpers hasFlags(flags, mask) and hasPermission(permission, callingPid, callingUid), the latter wrapping mContext.checkPermission(...) == PackageManager.PERMISSION_GRANTED.
- services/tests/wmtests/.../WindowManagerServiceTests.java: testRelayoutFlagChanges gains internalSystemWindowGranted / manageActivityTasksGranted parameters that stub mWm.mContext.checkPermission for the two permissions (with anyInt() for pid/uid), plus six tests (permissionDenied/permissionGranted for each of the three flags) asserting the flag is stripped or kept and that mDwpcHelper.keepActivityOnWindowFlagsChanged is not invoked when nothing changed.
Completeness
- Covers only the relayout path and only when attrs != null; the window-add path is not in the material, so whether these flags are checked at addWindow is unknown from this diff.
- Only three private flags are gated (TRUSTED_OVERLAY, IS_ROUNDED_CORNERS_OVERLAY, INTERCEPT_GLOBAL_DRAG_AND_DROP); all other privateFlags bits remain unchecked by this sanitizer at relayout.
- Only newly added bits are evaluated (~old & new); bits already present in win.mAttrs.privateFlags are never re-validated.
- Argument-order observation from the diff: hasPermission is declared as (permission, callingPid, callingUid) and forwards positionally to mContext.checkPermission(permission, callingPid, callingUid), but both call sites in sanitizePrivateFlags invoke it as hasPermission(permission, callingUid, callingPid). As written, the value passed as the caller uid reaches the pid parameter of Context.checkPermission and vice versa. Context.checkPermission(String, int pid, int uid) evaluates the permission against the uid argument (general platform knowledge), so the check as written is evaluated against the caller's pid number rather than its uid. The unit tests stub checkPermission with anyInt(), anyInt() and therefore cannot detect this. The real-world effect is not shown in the material and is listed under unknowns.
- The two overlay bits are cleared together: a caller lacking INTERNAL_SYSTEM_WINDOW that adds only one of them loses both, including one that may have been present before.
Behavior changes
- During relayout, unprivileged attempts to turn on PRIVATE_FLAG_TRUSTED_OVERLAY / PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY / PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP are silently dropped (no exception), with a Slog.w line naming the window and the missing permission.
- Per the new tests, when a flag is stripped and nothing else changes on a non-first relayout, mDwpcHelper.keepActivityOnWindowFlagsChanged is not called.
- Additional mContext.checkPermission calls can occur per relayout when the relevant bits are newly requested.
Per major
- Android 14: Same fix: commit c88804906997 in android-security-14.0.0_r24 (cherry-pick of ec6a92be6c93). Context line after the insertion is sanitizeSpyWindow(attrs.inputFeatures, ...). 8/8 patched lines present at android-security-14.0.0_r29.
- Android 15: Same fix: commit a54ba90ae134 in android-security-15.0.0_r12; identical sanitizePrivateFlags body. Only the surrounding context differs (sanitizeInputFeatures(..., win.isTrustedOverlay()) instead of sanitizeSpyWindow). 8/8 lines present at android-security-15.0.0_r17.
- Android 16: Same fix: commit d550a457e65c in android-security-16.0.0_r3; identical body and 15-style context. 8/8 lines present at android-security-16.0.0_r8.
- Android 17: Not listed as affected by the bulletin. At android-security-17.0.0_r1, 7/8 lines of the patched region are present; which line differs, and whether the sanitizer is fully present, is not shown in the material.
Unknowns
- Whether the add-window path (not in the material) already validates these private flags, or whether relayout was the only unguarded entry.
- The runtime consequence of the swapped uid/pid arguments to mContext.checkPermission for real callers (both unprivileged apps and legitimate system callers), and whether the one differing line on Android 17 relates to it.
- Where privateFlagChanges is computed relative to the new sanitizer call (that code is not in the material).
- The concrete privilege gained by an unprivileged window carrying each of the three flags; the bulletin only states High / EoP.