A logic error in KeyguardViewMediator.java allows local privilege escalation by bypassing the Android lockscreen entirely. No additional privileges or user interaction required.
Your Android phone's lockscreen is supposed to be the ultimate security barrier—like a vault that keeps your most sensitive data safe even if someone steals your device. A newly discovered flaw breaks that promise.
A logic error in Android's core lockscreen system lets an attacker bypass your PIN, fingerprint, or face recognition entirely. Think of it like a building's security door that, under certain conditions, simply fails to lock—not because someone broke in, but because the mechanism itself is flawed. Once unlocked, the attacker gains full access to everything: your photos, messages, banking apps, and personal files.
What makes this especially dangerous is that it requires no special hacking skills and no user interaction. An attacker with physical access to your phone can exploit this without you even knowing. They don't need to install software first or trick you into clicking something—they can just unlock it directly.
The good news: there's no evidence this is being actively exploited in the wild yet, giving device makers time to issue fixes.
Who's most at risk? Anyone carrying an Android phone, but especially people who might have their devices stolen: travelers, delivery workers, journalists in sensitive regions, and activists.
What you can do right now:
Install security updates immediately when they arrive—don't ignore that notification asking you to update. Check your phone's settings under Security or System Updates and do it manually if needed.
Enable additional protections like two-factor authentication on your most important apps, especially banking and email. That way, even if someone unlocks your phone, they can't access your accounts.
Consider using a biometric lock combined with a strong PIN rather than relying on just one method.
Want the full technical analysis? Click "Technical" above.
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."
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.