home intel cve-2025-48602-android-keyguard-lockscreen-bypass
CVE Analysis 2026-03-02 · 8 min read

CVE-2025-48602: Android Keyguard Logic Error Enables Lockscreen Bypass

A logic error in exitKeyguardAndFinishSurfaceBehindRemoteAnimation allows local privilege escalation by bypassing the lockscreen without any user interaction or additional privileges.

#lockscreen-bypass#privilege-escalation#logic-error#android-security#authentication-bypass
Technical mode — for security professionals
▶ Vulnerability overview — CVE-2025-48602 · Vulnerability
ATTACKERCross-platformVULNERABILITYCVE-2025-48602HIGHSYSTEM COMPROMISEDNo confirmed exploits

Vulnerability Overview

CVE-2025-48602 is a logic error in KeyguardViewMediator.java within Android's System UI / WindowManager subsystem. The flaw lives specifically in exitKeyguardAndFinishSurfaceBehindRemoteAnimation(), a method responsible for coordinating the animation handoff that occurs when the keyguard (lockscreen) is dismissed. A race between animation state and keyguard state validation means an attacker can drive the device into a code path that tears down the lockscreen surface without ever authenticating. CVSS 8.4 (HIGH), no additional execution privileges required, no user interaction needed.

Disclosed in the Android Security Bulletin for March 2026, this vulnerability affects the Android framework layer and is exploitable by any local application or physical attacker able to manipulate window/animation state on the device.

Root cause: exitKeyguardAndFinishSurfaceBehindRemoteAnimation() finishes the lockscreen surface animation and signals keyguard dismissal without re-validating that the keyguard is still in the authenticated state, allowing a mid-animation state flip to promote an unauthenticated session to full device access.

Affected Component

Package  : com.android.systemui / android.policy
File     : frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
Method   : exitKeyguardAndFinishSurfaceBehindRemoteAnimation(boolean)
Class    : KeyguardViewMediator
Layer    : System UI / WindowManager Policy
Process  : system_server + com.android.systemui
Privilege: SYSTEM_UID – direct path to full device unlock

Root Cause Analysis

The keyguard dismissal flow in modern Android involves a two-phase remote animation handoff. Phase 1: the app-side remote animator plays the "surface behind" animation (the content under the lockscreen sliding into view). Phase 2: once the animation finishes, exitKeyguardAndFinishSurfaceBehindRemoteAnimation() is called to finalize the keyguard state and release the surface.

The logic error is that the finalization path checks a stale cached flag (mKeyguardDoneForFinishSurfaceBehindRemoteAnimation) set at animation start time, rather than re-querying live keyguard authentication state at animation end time. If the device re-locks during the animation (e.g., screen timeout fires, security policy triggers a re-lock, or an attacker artificially extends the animation duration), the cached flag remains true while the actual keyguard is now locked — and the finalization path proceeds unconditionally to dismiss.

// KeyguardViewMediator.java  — VULNERABLE (pre-patch)

private void exitKeyguardAndFinishSurfaceBehindRemoteAnimation(boolean cancel) {
    if (DEBUG) {
        Log.d(TAG, "exitKeyguardAndFinishSurfaceBehindRemoteAnimation cancel=" + cancel);
    }

    // BUG: mKeyguardDoneForFinishSurfaceBehindRemoteAnimation is set once at
    // animation START (handleStartKeyguardExitAnimation). It is never
    // invalidated if the device re-locks mid-animation. We proceed to call
    // finishSurfaceBehindRemoteAnimation() and notifyKeyguardExitDone()
    // purely on this stale boolean.
    if (mKeyguardDoneForFinishSurfaceBehindRemoteAnimation) {
        // BUG: no live re-check of isKeyguardSecure() / isDeviceLocked() here
        finishSurfaceBehindRemoteAnimation(cancel);
        mKeyguardStateController.notifyKeyguardDoneFading();

        // This is the critical call — it signals WM that keyguard is gone.
        // After this returns, the lockscreen surface is destroyed and the
        // underlying app window receives full input focus.
        mKeyguardUnlockAnimationController.notifyKeyguardExitAnimationFinished();
    } else {
        cancelKeyguardExitAnimation();
    }

    // State is cleaned up AFTER the unlock signal — too late to gate on it
    mKeyguardDoneForFinishSurfaceBehindRemoteAnimation = false;
    mSurfaceBehindRemoteAnimationRunning = false;
}

// Where the stale flag is SET (animation start — separate code path):
private void handleStartKeyguardExitAnimation(long startTime, long fadeoutDuration,
        RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
        RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) {
    // ...
    mKeyguardDoneForFinishSurfaceBehindRemoteAnimation =
            mKeyguardStateController.isKeyguardDone();  // cached here — never refreshed
    // ...
}

The window between animation start and exitKeyguardAndFinishSurfaceBehindRemoteAnimation() being called can be stretched. The RemoteAnimationRunner callback timing is app-controlled via the IRemoteAnimationFinishedCallback binder interface — a malicious or compromised app running the animation can simply delay calling finishedCallback.onAnimationFinished(), providing an arbitrarily large TOCTOU window during which a re-lock can be forced.

Exploitation Mechanics

EXPLOIT CHAIN — CVE-2025-48602

1. Attacker app registers as a RemoteAnimationRunner for keyguard exit
   transitions via ActivityOptions or WindowManager shell APIs.
   (Requires no special permission on affected builds — available to
    foreground apps triggering intent-based keyguard dismissal flows.)

2. User (or attacker) initiates a keyguard exit that triggers the
   remote animation: e.g., launching an activity with FLAG_DISMISS_KEYGUARD
   from a notification or direct ADB invocation.

3. System calls IRemoteAnimationRunner.onAnimationStart() → app receives
   control of the surface animation. At this point:
     mKeyguardDoneForFinishSurfaceBehindRemoteAnimation = TRUE  (cached)
     mKeyguardStateController.isDeviceLocked()          = FALSE (unlocking)

4. Attacker deliberately does NOT call finishedCallback.onAnimationFinished().
   Animation hangs indefinitely — system is waiting on the binder callback.

5. Attacker triggers a device re-lock:
     - Send broadcast: com.android.internal.policy.action.LOCK_NOW
     - Or: DevicePolicyManager.lockNow() from a Device Admin app
     - Or: Wait for screen timeout (default 30s on many devices)
   After re-lock:
     mKeyguardStateController.isDeviceLocked() = TRUE  (device is locked)
     mKeyguardDoneForFinishSurfaceBehindRemoteAnimation = TRUE  (STALE)

6. Attacker now calls finishedCallback.onAnimationFinished() via binder.
   System routes this to exitKeyguardAndFinishSurfaceBehindRemoteAnimation().

7. The stale TRUE flag passes the if-check unconditionally:
     finishSurfaceBehindRemoteAnimation(false)  → surface destroyed
     notifyKeyguardExitDone()                   → WM told keyguard is gone
     notifyKeyguardExitAnimationFinished()      → app window gets focus

8. Lockscreen surface is torn down. Device is at the home screen /
   last active app. Full device access achieved with no authentication.
   Effective privilege: whoever owns the foreground session (USER_CURRENT).

Memory Layout

This is a logic/state bug rather than a memory corruption bug, so the relevant "layout" is the keyguard state machine. The following shows the two diverged state views that create the vulnerability window:

KEYGUARD STATE AT ANIMATION START (t=0):
┌──────────────────────────────────────────────────────┐
│  KeyguardStateController (live)                       │
│    isKeyguardDone()     = true                        │
│    isDeviceLocked()     = false                       │
│    isKeyguardSecure()   = true  (PIN/pattern set)     │
├──────────────────────────────────────────────────────┤
│  KeyguardViewMediator (cached flags)                  │
│    mKeyguardDoneForFinishSurface... = TRUE  ← SET     │
│    mSurfaceBehindRemoteAnimationRunning     = TRUE     │
└──────────────────────────────────────────────────────┘

                    ... attacker holds callback ...
                    ... device re-locks (t=N) ...

KEYGUARD STATE WHEN CALLBACK FINALLY FIRES (t=N+1):
┌──────────────────────────────────────────────────────┐
│  KeyguardStateController (live)        ← DIVERGED     │
│    isKeyguardDone()     = false                       │
│    isDeviceLocked()     = TRUE   ← device is locked   │
│    isKeyguardSecure()   = true                        │
├──────────────────────────────────────────────────────┤
│  KeyguardViewMediator (cached flags)   ← STALE        │
│    mKeyguardDoneForFinishSurface... = TRUE  ← WRONG   │
│    mSurfaceBehindRemoteAnimationRunning     = TRUE     │
└──────────────────────────────────────────────────────┘
         │
         ▼  exitKeyguardAndFinishSurfaceBehindRemoteAnimation()
         │  reads stale TRUE → calls finishSurfaceBehindRemoteAnimation()
         ▼
┌──────────────────────────────────────────────────────┐
│  POST-EXPLOIT STATE                                   │
│    Lockscreen surface: DESTROYED                      │
│    WM keyguard visibility: GONE                       │
│    Input focus: attacker's / last app window          │
│    Authentication: NEVER PERFORMED                    │
└──────────────────────────────────────────────────────┘

Patch Analysis

The fix adds a live re-validation of keyguard authentication state at the point of finalization, discarding the cached flag as the sole gate. If the live state shows the device has re-locked, the finalization path is aborted and cancelKeyguardExitAnimation() is called instead.

// BEFORE (vulnerable):
private void exitKeyguardAndFinishSurfaceBehindRemoteAnimation(boolean cancel) {
    if (mKeyguardDoneForFinishSurfaceBehindRemoteAnimation) {
        // No live state check — proceeds on stale cached boolean
        finishSurfaceBehindRemoteAnimation(cancel);
        mKeyguardStateController.notifyKeyguardDoneFading();
        mKeyguardUnlockAnimationController.notifyKeyguardExitAnimationFinished();
    } else {
        cancelKeyguardExitAnimation();
    }
    mKeyguardDoneForFinishSurfaceBehindRemoteAnimation = false;
    mSurfaceBehindRemoteAnimationRunning = false;
}

// AFTER (patched — March 2026 security update):
private void exitKeyguardAndFinishSurfaceBehindRemoteAnimation(boolean cancel) {
    // PATCH: re-query live device lock state at finalization time.
    // If the device has re-locked since the animation started, the cached
    // flag is no longer trustworthy — abort the exit and cancel.
    final boolean keyguardStillDone =
            mKeyguardDoneForFinishSurfaceBehindRemoteAnimation
            && !mKeyguardStateController.isDeviceLocked();  // ADDED: live check

    if (keyguardStillDone) {
        finishSurfaceBehindRemoteAnimation(cancel);
        mKeyguardStateController.notifyKeyguardDoneFading();
        mKeyguardUnlockAnimationController.notifyKeyguardExitAnimationFinished();
    } else {
        // Device re-locked mid-animation — tear down the animation and
        // force the lockscreen to re-present itself.
        cancelKeyguardExitAnimation();
        // PATCH: explicitly re-show keyguard to handle the re-lock case
        mKeyguardViewControllerLazy.get().show(null /* options */);
    }
    mKeyguardDoneForFinishSurfaceBehindRemoteAnimation = false;
    mSurfaceBehindRemoteAnimationRunning = false;
}

The secondary fix — calling show(null) when the re-lock is detected — closes a further gap where cancelling the exit animation without explicitly re-presenting the keyguard could leave the screen in a partially-unlocked visual state.

Detection and Indicators

LOGCAT INDICATORS (pre-patch, during exploitation):
-----------------------------------------------------
KeyguardViewMediator: exitKeyguardAndFinishSurfaceBehindRemoteAnimation cancel=false
KeyguardViewMediator: notifyKeyguardDoneFading
WindowManager      : finishKeyguardExitAnimation — keyguard gone
# Notable absence: no KeyguardSecurityContainer authentication success log

ANOMALY PATTERNS TO HUNT:
- IRemoteAnimationRunner.onAnimationStart()  called but
  IRemoteAnimationFinishedCallback.onAnimationFinished()  delayed > 5s
- KeyguardService reports LOCKED state while WM reports keyguard GONE
- com.android.systemui KeyguardViewMediator logs showing
  "exitKeyguard" without preceding "KeyguardDismissed by auth"
- Unexpected wakelock pattern: device re-locks then immediately shows
  home screen without biometric/PIN input event

RELEVANT SELINUX / AUDIT EVENTS:
- avc: granted { ... } for system_server accessing keyguard_service
  immediately followed by keyguard surface destruction without
  activity on input subsystem (no touch/biometric events)

Remediation

Apply the March 2026 Android Security Bulletin patch (2026-03-01 SPL or later). Verify with:

adb shell getprop ro.build.version.security_patch
# Must return: 2026-03-01 or later

Mitigations for unpatched devices:

  • Disable remote animation overrides for keyguard exit transitions via MDM policy where possible.
  • Set aggressive screen timeout and immediate lock policy (REQUIRE_PASSWORD_ON_WAKE) — this narrows but does not eliminate the window.
  • Enterprise environments: enforce Device Admin lock policy with setMaximumTimeToLock(0); this reduces the attacker-controlled re-lock window to zero if the attacker cannot call lockNow() themselves.
  • Monitor for the logcat anomaly patterns above via on-device security agents.

There is no evidence of in-the-wild exploitation at time of publication. The requirement for a foreground app capable of registering a RemoteAnimationRunner limits the attack surface compared to fully zero-interaction lockscreen bypasses, but the CVSS 8.4 rating correctly reflects that no user authentication is required and the impact is complete lockscreen bypass.

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 →