CVE-2025-48597 AI-generated analysis
platform/frameworks/base, WindowManager (services/core RootWindowContainer) and WM Shell PiP (libs/WindowManager/Shell PipTransition)
Moves the grant of the TRUSTED_OVERLAY surface flag for a PiP task out of WindowManager (where it was set as soon as the activity was marked pinned) into WM Shell's PipTransition.onFinishResize, so it is only set once Shell finishes the enter-PiP transition with a valid leash, while WM keeps only the revocation when the task leaves pinned mode.
Root cause
In RootWindowContainer.notifyActivityPipModeChanged, system_server applied mWmService.mTransactionFactory.get().setTrustedOverlay(task.getSurfaceControl(), inPip).apply() on every PiP mode change. When an activity entered PiP this marked the task surface as a trusted overlay immediately, at notification time, regardless of whether WM Shell actually received and completed the entering-PiP transition (the commit message says the flag must only be granted after WMShell successfully handled the transition). On Android 14 the call passed task.getSurfaceControl() without a null check (15/16 already had one). Surfaces flagged TRUSTED_OVERLAY are not counted as obscuring windows in input touch-obscured checks (general platform knowledge).
How the fix works
- libs/WindowManager/Shell/.../pip/PipTransition.java:onFinishResize: the leash/hasValidLeash computation (mPipOrganizer.getSurfaceControl(), non-null and isValid()) is moved to the top of the method; inside the existing `if (enteringPip)` block (after mPipTransitionState.setTransitionState(ENTERED_PIP)) the fix adds `if (hasValidLeash) tx.setTrustedOverlay(leash, true)` plus a ProtoLog WM_SHELL_PICTURE_IN_PICTURE debug line. The grant therefore happens only for an in-PiP direction with a valid PiP leash, and is written to the Shell transaction `tx` passed to onFinishResize.
- services/core/java/com/android/server/wm/RootWindowContainer.java:notifyActivityPipModeChanged: the unconditional setTrustedOverlay(task.getSurfaceControl(), inPip) after setPipVisibilityLw is removed. In its place, only in the `else` branch (r == null, activity unpinned) after notifyActivityUnpinned(), WM applies setTrustedOverlay(task.getSurfaceControl(), false), guarded by task.getSurfaceControl() != null, as a blanket revocation.
- Net effect: the grant moves to Shell and is gated on transition completion; the revoke stays in WM and runs on every unpin. Both files must change together: with only the WM change nothing grants the flag, and with only the Shell change WM still grants it early.
Completeness
- The grant is now only emitted from PipTransition.onFinishResize when isInPipDirection(direction) and the PiP leash is valid; within the changed code, a pin that does not reach this Shell callback with a valid leash does not get TRUSTED_OVERLAY (fail-closed).
- Revocation in RootWindowContainer.notifyActivityPipModeChanged runs on every unpin notification whether or not the grant happened, and is skipped only when task.getSurfaceControl() is null.
- The diff adds no separate apply for `tx` in the enter direction; how `tx` gets applied for entering PiP is not shown in the material (the context only shows mFinishTransaction.merge(tx) in the out-PiP branch, inside a conditional block).
- The fix does not change who may enter PiP or when; it only changes when the flag is set, and which process sets it (Shell instead of WM).
Behavior changes
- TRUSTED_OVERLAY is set on the PiP task leash later than before: at Shell onFinishResize for the enter direction, not at WM's pinned notification.
- If the Shell has no valid leash at onFinishResize, no TRUSTED_OVERLAY is granted for that PiP session.
- The flag is now set through the WM Shell transaction instead of system_server's transaction factory.
- On unpin, WM always clears TRUSTED_OVERLAY on the task surface (if non-null) and applies the transaction immediately.
- New ProtoLog debug message 'Set TRUSTED_OVERLAY for Task#%d' in the WM_SHELL_PICTURE_IN_PICTURE group.
- Android 14 only: the WM call site gains a null check on task.getSurfaceControl() that 15/16 already had.
Per major
- Android 14: same fix: platform/frameworks/base 3972f55646f4 in android-security-14.0.0_r24 (cherry-pick of b90e634cc244, Change-Id Id849d93bc92209fc296ee91ef70b0ffe2a09550e). Removes the unconditional setTrustedOverlay(inPip) (which on 14 had no null check) and adds the Shell grant + WM revoke. 8/8 patched lines present at android-security-14.0.0_r29.
- Android 15: same fix: cfcef3f38c2f in android-security-15.0.0_r12. Identical Shell hunk; the WM hunk replaces an already null-checked unconditional setTrustedOverlay(inPip) block. 8/8 lines present at android-security-15.0.0_r17.
- Android 16: same fix, trivially adapted: 68170bad5225 in android-security-16.0.0_r3. onFinishResize there has an extra leashOffset parameter and its finish condition includes mMoveToBackTransition; the moved leash/hasValidLeash lines are removed from that block. WM hunk identical to 15. 8/8 lines present at android-security-16.0.0_r8.
- Android 17: not affected per the bulletin; the patched region (8/8 lines) is already present at android-security-17.0.0_r1.
Unknowns
- The concrete scenario in which the early WM grant was abusable (e.g. aborted or merged enter transitions, or a task holding the flag while not in a completed PiP state) is not shown; only the commit message's statement that the grant must follow successful Shell handling.
- How the Shell transaction `tx` is applied in the enter-PiP direction is not shown in the material.
- Whether any non-Shell-transitions (legacy) PiP path exists on Android 14 that previously relied on the WM grant; the material only shows PipTransition.
- Whether PinnedStackTests changes referenced in the Test: line were part of the same change.