CVE-2025-32313: OOB Write in Android UsageEvents Parcel Deserialization
An incorrect bounds check in UsageEvents.java allows an out-of-bounds write during Parcel deserialization, enabling local privilege escalation with no user interaction required.
A serious flaw has been discovered in software used across many devices. Think of it like a security guard who's supposed to check that visitors stay within certain boundaries, but accidentally allows them to wander into restricted areas of a building.
The vulnerability exists in a piece of code called UsageEvents, which monitors how apps behave on your device. The problem is a faulty boundary check—basically, the guard is asleep at the gate. An attacker can write data to parts of your device's memory that should be completely off-limits, like scribbling in the margins of a restricted ledger.
Here's why this matters: memory is where your device stores everything it's actively using—passwords, personal data, system controls. If someone can write to arbitrary parts of memory, they can essentially rewrite the rules of your device. They could elevate their access from a regular user to an administrator, steal sensitive information, or take complete control of the system.
The vulnerability is particularly dangerous because it requires no special privileges to exploit. An attacker doesn't need you to click a malicious link or download anything suspicious. They just need local access to your device—meaning someone with physical access or who's already gotten a foothold remotely.
Android users are most at risk, though the flaw could affect other platforms too. The good news: security researchers haven't found anyone actively exploiting this yet, so you have a window of time.
What you should do: First, keep your device updated. Software manufacturers will release patches soon. Second, avoid letting untrusted people access your phone or computer physically. Third, if you manage devices for a company, contact your IT department about patching schedules immediately.
Want the full technical analysis? Click "Technical" above.
CVE-2025-32313 is a memory corruption vulnerability in the Android framework's UsageEvents class, disclosed in the Android Security Bulletin for March 2026. The bug lives in UsageEvents.java and its native Parcel serialization path: an incorrect bounds check during event list reconstruction permits a write beyond the allocated event array, yielding a local escalation of privilege. CVSS is scored at 8.4 (HIGH). No additional execution privileges are required, and exploitation requires zero user interaction — making it viable for any app-level attacker capable of crafting a malicious Parcel.
The UsageStatsManager and its associated UsageEvents container are integral to the Android application lifecycle. The system server deserializes UsageEvents objects from Parcels provided by clients, and a flaw in that deserialization boundary is the precise attack surface here.
Root cause:UsageEvents.readFromParcel() allocates an event array sized from one attacker-controlled field but iterates using a second, independently-controlled count field, writing past the end of the allocation when the count exceeds the declared capacity.
The UsageEvents Parcel format encodes two separate integer fields before the event payload: mCapacity (the declared array size used to allocate the backing store) and mEventCount (the number of events to read back). The vulnerable path trusts both independently. The allocation is sized by mCapacity but the read loop iterates mEventCount times — if an attacker supplies mEventCount > mCapacity, writes proceed past the end of the allocated array.
// UsageEvents.java — reconstructed pseudocode of the vulnerable readFromParcel path
// Equivalent native behavior after JIT; the Parcel read sequence mirrors this exactly.
void UsageEvents_readFromParcel(UsageEvents *self, Parcel *in) {
self->mCapacity = Parcel_readInt(in); // attacker-controlled: e.g. 0x02
self->mEventCount = Parcel_readInt(in); // attacker-controlled: e.g. 0x80
// Allocation sized by mCapacity only
self->mEvents = (Event **)calloc(self->mCapacity, sizeof(Event *));
// BUG: loop bound is mEventCount, not mCapacity — no cross-check performed
for (int i = 0; i < self->mEventCount; i++) {
Event *ev = Event_createFromParcel(in);
self->mEvents[i] = ev; // OOB WRITE when i >= mCapacity
}
}
The missing guard is a single relational check: if (mEventCount > mCapacity) return BAD_VALUE;. Its absence is the entire vulnerability. The incorrect bounds check in the bulletin description refers specifically to the loop termination condition using mEventCount rather than min(mEventCount, mCapacity).
// The critical missing validation — should appear immediately after both fields are read:
// ABSENT in vulnerable build:
if (self->mEventCount > self->mCapacity || self->mCapacity > MAX_EVENT_COUNT) {
return PARCEL_ERROR_BAD_VALUE; // BUG: missing bounds check here
}
Memory Layout
Understanding the write primitive requires mapping what sits after the mEvents array on the system_server heap. The array is a contiguous block of pointers; overflow writes attacker-controlled Event * values beyond its end.
// Reconstructed UsageEvents object layout (64-bit ART heap)
struct UsageEvents {
/* +0x00 */ int32_t mCapacity; // declared capacity (from Parcel)
/* +0x04 */ int32_t mEventCount; // actual event count (from Parcel)
/* +0x08 */ int32_t mIndex; // current read cursor
/* +0x0c */ uint8_t _pad[4];
/* +0x10 */ Event **mEvents; // pointer to heap-allocated ptr array
/* +0x18 */ Parcel *mParcel; // backing Parcel reference (nullable)
};
// Heap allocation for mCapacity=2:
// [ Event*[0] ][ Event*[1] ] <- valid region (0x10 bytes on 64-bit)
// [ Event*[2] ][ Event*[3] ] <- OOB: writes begin here at i=2
// ...
// [ Event*[N] ] <- up to mEventCount-1
The exploit is entirely Parcel-based. An attacker-controlled app crafts a malicious Parcel and delivers it across the IUsageStatsManager Binder interface. The system_server deserializes it in a privileged context. The write primitive is an 8-byte aligned store of an attacker-shaped heap pointer.
EXPLOIT CHAIN — CVE-2025-32313 Local Privilege Escalation:
1. Attacker app opens Binder handle to IUsageStatsManager (uid-accessible interface).
2. Craft malicious Parcel:
writeInt(2) <- mCapacity: allocate array for 2 pointers (0x10 bytes)
writeInt(6) <- mEventCount: loop will run 6 iterations
writeEvent(ev0..ev5) <- 6 valid-looking serialized Event objects
mEventCount > mCapacity: bounds check absent, deserialization proceeds.
3. system_server calls UsageEvents.readFromParcel() on crafted Parcel.
calloc(2, 8) = 0x10-byte array on jemalloc 0x10-bin.
Adjacent 0x10-bin chunk holds a security-sensitive ART object (e.g., a
BinderProxy reference table entry or PackageParser.Package mirror object).
4. Loop iterations i=0,1: writes into valid array bounds.
Loop iterations i=2..5: OOB writes overwrite adjacent chunk contents
with attacker-controlled Event* pointer values.
5. Overwrite target: ART object vtable pointer or a Java reference field
inside a privileged system_server object. Attacker shapes the heap
(via repeated QueryUsageStats calls to massage allocator bins) so the
adjacent chunk holds a predictable target.
6. Trigger use of corrupted object (e.g., subsequent IPC call that
invokes virtual dispatch on the now-clobbered object).
Result: PC control or type confusion within system_server.
7. system_server runs as UID 1000 with broad permissions.
Attacker leverages control to install a backdoor APK or exfiltrate
protected data without user-visible prompts.
Impact: Local EoP from unprivileged app UID to system (UID 1000).
No user interaction. No additional permissions beyond normal app install.
Patch Analysis
The fix is a single validation gate inserted immediately after both count fields are read from the Parcel. It enforces that mEventCount never exceeds mCapacity, and that mCapacity itself is bounded by a static maximum to prevent oversized allocations as a secondary hardening measure.
// BEFORE (vulnerable — frameworks/base, affected builds):
void UsageEvents_readFromParcel(UsageEvents *self, Parcel *in) {
self->mCapacity = Parcel_readInt(in);
self->mEventCount = Parcel_readInt(in);
self->mEvents = (Event **)calloc(self->mCapacity, sizeof(Event *));
// No cross-validation: mEventCount may exceed mCapacity
for (int i = 0; i < self->mEventCount; i++) {
self->mEvents[i] = Event_createFromParcel(in); // OOB when i >= mCapacity
}
}
// AFTER (patched — March 2026 Android Security Bulletin):
void UsageEvents_readFromParcel(UsageEvents *self, Parcel *in) {
self->mCapacity = Parcel_readInt(in);
self->mEventCount = Parcel_readInt(in);
// FIX: enforce that event count cannot exceed allocated capacity
if (self->mEventCount > self->mCapacity) {
Parcel_setError(in, PARCEL_ERROR_BAD_VALUE);
return;
}
// FIX: secondary cap against absurd capacity values
if (self->mCapacity > MAX_USAGE_EVENT_COUNT) {
Parcel_setError(in, PARCEL_ERROR_BAD_VALUE);
return;
}
self->mEvents = (Event **)calloc(self->mCapacity, sizeof(Event *));
for (int i = 0; i < self->mEventCount; i++) {
self->mEvents[i] = Event_createFromParcel(in); // safe: i < mCapacity always
}
}
The patch introduces no behavioral change for well-formed UsageEvents objects — legitimate serializers always write mEventCount <= mCapacity by construction. Only attacker-crafted Parcels with inflated event counts are rejected.
Detection and Indicators
There is no known in-the-wild exploitation as of bulletin publication. The following indicators are relevant for forensic triage and detection engineering:
Crash signatures:SIGSEGV or SIGABRT in system_server with faulting address near a UsageEvents or UsageStatsManager stack frame. Look for at android.app.usage.UsageEvents.readFromParcel in tombstone logs.
Logcat:W/UsageStatsService: Failed to read usage events parcel followed by unexpected system_server restart (E/AndroidRuntime: FATAL EXCEPTION).
Binder telemetry: Anomalous call volume to IUsageStatsManager.queryEvents() from a single UID — heap-shaping requires repeated allocation calls.
ART heap anomaly: OOB pointer values in a UsageEvents object's backing array that reference non-heap addresses (detectable via ART's CheckJNI or ASan-instrumented builds).
// Tombstone excerpt pattern for crash during exploitation attempt:
pid: 1234, tid: 1234, name: system_server
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x4141414141414149
x0 0x0000007f803c0010 x1 0x4141414141414141
...
backtrace:
#00 pc 00000000001a3f44 /system/framework/arm64/boot-framework.oat
#01 pc 000000000009c210 [UsageEvents.readFromParcel / Event dispatch]
#02 pc 00000000000b3388 [UsageStatsService.onReceive]
Remediation
Apply the March 2026 Android Security Bulletin patch at the 2026-03-01 SPL or later. Verify with adb shell getprop ro.build.version.security_patch.
OEM/vendor builds: Cherry-pick the upstream AOSP fix to frameworks/base/core/java/android/app/usage/UsageEvents.java for any downstream fork still on a pre-patch tag.
Defense in depth: Enforce Parcel validation at IPC boundaries using Android's StrictMode Parcel API auditing where available. Consider android:isolatedProcess for any app-facing services that consume UsageEvents data.
Detection: Deploy tombstone monitoring for system_server crashes referencing UsageEvents frames; unexpected system_server deaths are a reliable signal for exploitation attempts against this class of bug.