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

CVE-2025-48605: Android KeyguardViewMediator Logic Error Enables Lockscreen Bypass

A logic error in KeyguardViewMediator.java allows local privilege escalation by bypassing the Android lockscreen entirely. No additional privileges or user interaction required.

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

Vulnerability Overview

CVE-2025-48605 is a logic error in Android's KeyguardViewMediator that permits a local attacker to bypass the device lockscreen and escalate privileges. The CVSS score of 8.4 (HIGH) reflects the no-interaction, no-extra-privileges requirement: any process running on the device can trigger the bypass without user awareness. The vulnerability exists in multiple functions across KeyguardViewMediator.java, indicating the flawed assumption is structural rather than isolated to a single call site.

The Android lockscreen mediator (KeyguardViewMediator) is the central state machine responsible for coordinating lock/unlock transitions. It arbitrates between hardware events (fingerprint, face unlock, trusted devices), telephony state, and window manager policy. A state transition that can be forced into an inconsistent condition — where the mediator believes the keyguard is dismissed while the authentication gate was never passed — constitutes a full lockscreen bypass at the system service level.

Root cause: Multiple functions in KeyguardViewMediator evaluate keyguard dismiss conditions without atomically verifying authentication state, allowing a race or forged callback to transition the mediator into mHiding=true / mOccluded=true while mShowing=true persists — a contradiction the downstream window policy interprets as "authenticated."

Affected Component

File: frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java

Interacts with: IKeyguardService, KeyguardUpdateMonitor, WindowManagerPolicy, TrustManager, BiometricUnlockController. The vulnerability class affects all Android versions sharing this code path prior to the June 2025 security patch.

Root Cause Analysis

The mediator maintains several boolean flags to track keyguard state. The logic error manifests when handleHide() or handleStartKeyguardExiting() is called without a preceding successful authentication event being atomically committed. The guard checks are non-atomic and evaluated in separate code paths, creating a TOCTOU window exploitable via synthetic Message injection or crafted trust agent callbacks.


// KeyguardViewMediator.java — reconstructed pseudocode (Java -> C analogue)
// Simplified to expose the state machine logic

typedef struct KeyguardState {
    bool mShowing;            // keyguard is currently shown
    bool mOccluded;           // keyguard is occluded by an activity
    bool mHiding;             // hide animation in progress
    bool mWakeAndUnlocking;   // biometric wake+unlock flow active
    bool mKeyguardDonePending;// setKeyguardDone() was called, awaiting confirm
    bool mKeyguardDone;       // final done flag — gate to dismiss
    int  mAuthFlags;          // bitmask of completed auth methods
} KeyguardState;

// Handler message: MSG_KEYGUARD_DONE (sent by TrustAgent or BiometricController)
void handleKeyguardDone(KeyguardViewMediator *self) {
    // BUG: mKeyguardDonePending checked without verifying mAuthFlags
    // A synthetic MSG_KEYGUARD_DONE message bypasses auth entirely
    if (self->state.mKeyguardDonePending) {
        self->state.mKeyguardDone = true;
        handleHide(self);       // proceeds directly to hide
    }
}

// handleHide — begins keyguard dismissal sequence
void handleHide(KeyguardViewMediator *self) {
    if (self->state.mShowing) {
        self->state.mHiding = true;
        // BUG: no re-validation of authentication state here
        // mAuthFlags is never checked before calling notifyFinishedGoingAway()
        self->keyguardView->hide(animationCallback);
        WindowManagerPolicy_notifyKeyguardDismissAnimationFinished();
    }
}

// setKeyguardDone — called by IKeyguardService (accessible to system apps)
void setKeyguardDone(KeyguardViewMediator *self, bool authenticated) {
    // BUG: 'authenticated' parameter is trusted without re-verification
    // caller can pass authenticated=true without completing auth challenge
    self->state.mKeyguardDonePending = true;
    // MSG_KEYGUARD_DONE posted to handler — async, decoupled from auth result
    Handler_sendMessage(self->handler, MSG_KEYGUARD_DONE);
    // At this point mAuthFlags may still be 0x0 (no auth completed)
}

The core issue: mKeyguardDonePending is set by setKeyguardDone() based on an unverified boolean argument, and handleKeyguardDone() checks only mKeyguardDonePending — not whether any authentication method actually succeeded. The mAuthFlags field is populated by KeyguardUpdateMonitor callbacks and is on a separate code path entirely, never consulted at the moment of dismissal.


// KeyguardUpdateMonitor.java — auth flag population (separate path)
void onBiometricAuthenticated(int userId, BiometricType type) {
    self->mUserAuthFlags[userId] |= AUTH_FLAG_BIOMETRIC;
    // This callback is NOT called in the bypass scenario —
    // the mediator never waits for it before acting on MSG_KEYGUARD_DONE
}

// IKeyguardService.aidl — exposed binder interface
// android.service.keyguard.IKeyguardService
interface IKeyguardService {
    void setKeyguardEnabled(boolean enabled);
    void onDreamingStarted();
    void onDreamingStopped();
    void keyguardDone(boolean authenticated);   // <-- vector
    void setOccluded(boolean isOccluded, ...);  // <-- secondary vector
}

Exploitation Mechanics


EXPLOIT CHAIN — CVE-2025-48605 Lockscreen Bypass:

1. Attacker obtains a system-level context or abuses an app with
   BIND_KEYGUARD permission (or exploits a secondary bug to reach
   IKeyguardService — e.g., a companion app with system UID).

2. While device is locked, call IKeyguardService.keyguardDone(true)
   via binder directly — passing authenticated=true without any
   biometric or PIN challenge having completed.

3. KeyguardViewMediator.setKeyguardDone() receives the call:
     mKeyguardDonePending = true
     Handler.sendMessage(MSG_KEYGUARD_DONE)
   mAuthFlags[userId] remains 0x0 — no auth was performed.

4. Handler dispatches MSG_KEYGUARD_DONE on the main thread:
     handleKeyguardDone() checks mKeyguardDonePending == true  ✓
     mKeyguardDone = true
     handleHide() called immediately.

5. handleHide() sets mHiding = true, triggers hide animation,
   calls WindowManagerPolicy.notifyKeyguardDismissAnimationFinished().

6. WindowManager receives dismissal notification, removes lockscreen
   surface. Device is now at the home screen / last active app.
   mShowing transitions to false.

7. mAuthFlags was never set; no authentication event was logged.
   Lock screen is dismissed. Full UI access granted.

SECONDARY VECTOR (setOccluded path):
1. Call setOccluded(true) while mShowing=true and mHiding=false.
2. Race condition: mOccluded=true causes keyguardGoingAway to be
   called by WindowManagerPolicy before auth state is committed.
3. Same outcome: lockscreen surface removed without auth.

Memory Layout

This is a logic error rather than a memory corruption bug; the "layout" of concern is the state machine flag layout inside the mediator object and the handler message queue ordering.


KeyguardViewMediator object — relevant field layout (inferred from AOSP):

  /* +0x000 */  Context          mContext
  /* +0x008 */  Handler          mHandler
  /* +0x010 */  KeyguardUpdateMonitor  mUpdateMonitor
  /* +0x018 */  KeyguardDisplayManager mKeyguardDisplayManager
  /* +0x020 */  boolean          mSystemReady
  /* +0x021 */  boolean          mShowing          // LOCK STATE A
  /* +0x022 */  boolean          mOccluded         // LOCK STATE B
  /* +0x023 */  boolean          mHiding           // LOCK STATE C
  /* +0x024 */  boolean          mWakeAndUnlocking
  /* +0x025 */  boolean          mKeyguardDonePending  // SET BY ATTACKER
  /* +0x026 */  boolean          mKeyguardDone         // FLIPPED BY BUG
  /* +0x028 */  int              mAuthFlags            // NEVER CHECKED

NORMAL AUTHENTICATED UNLOCK — state sequence:
  mShowing=T  mHiding=F  mKeyguardDonePending=F  mAuthFlags=0x0
       │
  [biometric callback] → mAuthFlags |= AUTH_FLAG_BIOMETRIC
       │
  [MSG_KEYGUARD_DONE posted by BiometricUnlockController]
       │
  handleKeyguardDone(): mKeyguardDonePending=T → mKeyguardDone=T
       │
  handleHide(): mHiding=T → surface dismissed → mShowing=F

BYPASS — attacker-induced state sequence:
  mShowing=T  mHiding=F  mKeyguardDonePending=F  mAuthFlags=0x0
       │
  [IKeyguardService.keyguardDone(true)] ← ATTACKER INPUT
       │
  setKeyguardDone(): mKeyguardDonePending=T  ← no auth check
       │
  handleKeyguardDone(): mKeyguardDonePending=T ✓ → mKeyguardDone=T
       │                mAuthFlags=0x0         ← NEVER VERIFIED
  handleHide(): mHiding=T → surface dismissed → mShowing=F
                                              ^^^^^^^^^^^^
                                              LOCKSCREEN BYPASSED

Patch Analysis

The correct fix requires re-validating authentication state at the point of dismissal, not trusting the boolean parameter or the pending flag alone. The patch must also cover the setOccluded path.


// BEFORE (vulnerable) — handleKeyguardDone():
void handleKeyguardDone(KeyguardViewMediator *self) {
    if (self->state.mKeyguardDonePending) {
        self->state.mKeyguardDone = true;
        handleHide(self);
        // BUG: mAuthFlags never consulted; authenticated= param trusted blindly
    }
}

// BEFORE (vulnerable) — setKeyguardDone():
void setKeyguardDone(KeyguardViewMediator *self, bool authenticated) {
    self->state.mKeyguardDonePending = true;
    // BUG: 'authenticated' is ignored; pending flag set unconditionally
    Handler_sendMessage(self->handler, MSG_KEYGUARD_DONE);
}

// -----------------------------------------------------------------------

// AFTER (patched):
void handleKeyguardDone(KeyguardViewMediator *self) {
    if (self->state.mKeyguardDonePending) {
        // FIX: re-query KeyguardUpdateMonitor for current auth state
        // do not trust the stale mKeyguardDonePending flag alone
        bool authValid = KeyguardUpdateMonitor_isUserUnlocked(
                             self->updateMonitor,
                             KeyguardUpdateMonitor_getCurrentUser()
                         )
                         || self->state.mAuthFlags != 0x0;
        if (!authValid) {
            // FIX: abort dismissal if no authentication has been recorded
            self->state.mKeyguardDonePending = false;
            return;
        }
        self->state.mKeyguardDone = true;
        handleHide(self);
    }
}

// AFTER (patched) — setKeyguardDone():
void setKeyguardDone(KeyguardViewMediator *self, bool authenticated) {
    // FIX: only set pending flag when caller supplies a verified credential
    // and cross-check with KeyguardUpdateMonitor's authoritative state
    if (!authenticated ||
        !KeyguardUpdateMonitor_isUserUnlocked(self->updateMonitor,
                                              getCurrentUser())) {
        return;  // FIX: reject unauthenticated done signals
    }
    self->state.mKeyguardDonePending = true;
    Handler_sendMessage(self->handler, MSG_KEYGUARD_DONE);
}

Additionally, the patch should add a permission gate on IKeyguardService.keyguardDone() at the binder level, restricting callers to the BiometricUnlockController and TrustAgent system components by UID rather than relying on permission declarations alone.

Detection and Indicators

Detecting active exploitation is non-trivial because the bypass produces no authentication failure events. Look for absence of expected events:


LOGCAT INDICATORS (anomalous patterns):

# Dismissal with no preceding auth event:
KeyguardViewMediator: handleKeyguardDone
KeyguardViewMediator: handleHide
  -- MISSING: KeyguardUpdateMonitor: onBiometricAuthenticated
  -- MISSING: KeyguardUpdateMonitor: onTrustChanged / onUnlockWithTrustAttempted
  -- MISSING: KeyguardViewMediator: notifyKeyguardDonePending (from legit source)

# Unexpected binder call origin:
Binder: IKeyguardService.keyguardDone called from UID != system_server UID
  (check /proc//status for Uid line of binder transaction caller)

# setOccluded race:
KeyguardViewMediator: setOccluded(true) while mShowing=true, mHiding=false
  followed immediately by:
KeyguardViewMediator: handleStartKeyguardExiting (no auth event between these)

AUDIT COMMAND:
  adb logcat -d | grep -E \
    "KeyguardViewMediator|KeyguardUpdateMonitor|BiometricUnlock" \
    | grep -B5 -A5 "handleHide\|handleKeyguardDone"

On a non-rooted production device, triggering this requires either a malicious system app or a secondary privilege escalation vulnerability to reach the binder interface. Monitor for apps declaring android.permission.CONTROL_KEYGUARD or binding to android.service.keyguard.KeyguardService.

Remediation

  • Apply the June 2025 Android Security Bulletin patch immediately on all affected devices. OEMs shipping modified KeyguardViewMediator forks must audit their changes against the upstream fix.
  • Verify patch application: Check Settings → Security → Android Security Patch Level is 2025-06-01 or later.
  • Enterprise MDM: Enforce minimum patch level via DevicePolicyManager.setMinimumRequiredWifiSecurityLevel() or equivalent MDM policy; flag non-compliant devices.
  • Defense in depth: Restrict binder-level access to IKeyguardService via SELinux policy. Verify that keyguard_service type is only accessible from system_server domain in device-specific SELinux policy files.
  • Temporary workaround (no patch available): Disable Smart Lock / Trust Agents if not required — these are the most accessible callers of the vulnerable code path from less-privileged contexts.
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 →