CVE-2025-54957: Integer Wraparound OOB Write in Dolby UDC DD+ Decoder
An integer wraparound in evo_priv.c's length calculation allows a malformed DD+ bitstream to trigger an out-of-bounds heap write in Dolby UDC 4.5–4.13, exploitable 0-click via Android audio transcription.
A flaw has been discovered in Dolby's audio processing software that could let attackers crash devices or potentially take control of them by sending a specially crafted audio file. Think of it like a lock that miscalculates how much space it needs, leaving a gap that lets something slip through.
Here's what's happening: Dolby UDC is software that decodes DD+ audio — the high-quality sound format used in streaming services, smart TVs, and home theater systems. When the software receives certain types of corrupted audio files, it makes a math mistake that causes it to allocate less memory than it actually needs.
This is similar to ordering a storage box based on a miscalculation, then trying to stuff more items into it than there's room for. In this case, excess data spills into neighboring memory areas, potentially corrupting other programs or letting attackers inject malicious code.
Who should worry? Anyone using streaming devices, smart TVs, or sound systems that process DD+ audio — which includes most modern streaming platforms. The vulnerability affects Dolby's software versions 4.5 through 4.13, which are widely used but are now known to be vulnerable.
The good news is security researchers haven't seen this being actively exploited in the wild yet, giving companies time to patch things. But the window to fix it is narrow.
What you should do: Check if your streaming device or TV has pending software updates and install them immediately. If you use devices made by major manufacturers, they'll likely release patches soon — keep automatic updates enabled. Finally, be cautious about downloading audio files from untrusted sources, since that's the most likely attack vector. For now, the risk is real but manageable if you stay on top of updates.
Want the full technical analysis? Click "Technical" above.
CVE-2025-54957 is a critical (CVSS 9.8) integer wraparound leading to an out-of-bounds heap write in the Dolby Unified Decoder Component (UDC), versions 4.5 through 4.13. The bug lives in evo_priv.c, the file responsible for parsing Evolution (EVO) supplementary data embedded in Dolby Digital Plus (DD+/EAC-3) bitstreams. Because Android's Google Messages automatically decodes incoming audio attachments for transcription with no user interaction, this vulnerability sits squarely in the 0-click attack surface of the vast majority of Android devices in active use.
The vulnerability was reported to Google by Natalie Silvanovich and Ivan Fratric of Project Zero. It was fixed as of January 5, 2026, and was used as the first stage of a full 0-click exploit chain against the Pixel 9, achieving arbitrary code execution in the mediacodec context. The exploit chain details appear across three Project Zero blog posts; this analysis focuses on the root cause and exploitation mechanics of the memory corruption primitive itself.
Root cause: The EVO data length calculation in evo_priv.c uses an integer type narrow enough to wrap around on attacker-controlled input, producing a smaller-than-required heap allocation whose subsequent bounds check is defeated by the same wrapped value, allowing an unbounded out-of-bounds write.
Affected Component
The Dolby Unified Decoder (UDC) is a shared library distributed by Dolby and integrated by OEMs into the Android media stack. It provides hardware-agnostic software decoding for DD (AC-3) and DD+ (EAC-3) audio. On Pixel 9 it runs under the mediacodec process, which operates inside a restricted seccomp/SELinux sandbox. The EVO (Evolution data) subsystem, implemented in evo_priv.c, parses auxiliary data blocks appended to standard DD+ frames — things like object audio metadata and spatial cues — and stages them into internal buffers for downstream processing.
Affected versions: Dolby UDC 4.5 – 4.13. Any platform shipping these library versions is affected; this includes the majority of Android OEM devices in the field.
Root Cause Analysis
The EVO parser reads a caller-controlled length field from the bitstream and uses it to compute how many bytes to write into a staging buffer. The calculation is performed in a type that is too narrow, so sufficiently large attacker-supplied values wrap to a small positive number. The allocation is then sized by the wrapped value. The subsequent bounds check before the write uses the same wrapped (small) value, so even though the write is "within bounds" according to the check, the actual write target extends far past the end of the allocation.
/* evo_priv.c — simplified decompiled pseudocode, Dolby UDC 4.x */
typedef struct {
uint8_t *evo_buf; // staging buffer for EVO payload
uint32_t evo_buf_size; // allocated size of evo_buf
uint32_t evo_written; // bytes already written
} evo_state_t;
/*
* evo_write_data — copies EVO payload from the bitstream into evo_buf.
* `elem_count` : number of EVO elements, read directly from bitstream (attacker-controlled)
* `elem_size` : per-element size in bytes, also bitstream-derived (attacker-controlled)
*/
int evo_write_data(evo_state_t *state, uint16_t elem_count, uint16_t elem_size,
const uint8_t *src)
{
/* BUG: both operands are uint16_t; product silently wraps mod 2^16
* e.g. elem_count=0x0101, elem_size=0x0100 → 0x0101*0x0100 = 0x10100
* truncated to uint16_t → 0x0100 (256 bytes)
* actual data to copy: 0x10100 bytes
*/
uint16_t total_len = elem_count * elem_size; // BUG: integer wraparound here
/* Allocation is sized by the *wrapped* total_len — too small */
if (state->evo_buf == NULL) {
state->evo_buf = malloc(total_len); // allocates only 256 bytes
state->evo_buf_size = total_len; // stores the wrapped size
}
/* Bounds check uses the same wrapped value — check passes trivially
* because evo_written (0) + total_len (0x100) <= evo_buf_size (0x100)
* but the real copy is elem_count * elem_size = 0x10100 bytes */
if (state->evo_written + total_len > state->evo_buf_size) {
return EVO_ERR_OVERFLOW; // never reached — check defeated by wraparound
}
/* Out-of-bounds write: copies 0x10100 bytes into a 0x100-byte buffer */
memcpy(state->evo_buf + state->evo_written, src, elem_count * elem_size); // BUG: OOB write
state->evo_written += total_len;
return EVO_OK;
}
The critical observation is that total_len is used for both the allocation size and the bounds check ceiling, but the memcpy length argument is re-derived as elem_count * elem_size without the truncation. This means the allocation and guard are coherent with each other but both disagree with the actual copy length.
Memory Layout
The following illustrates the heap state on a Pixel 9 (jemalloc-based allocator in mediacodec) when the bug triggers with elem_count=0x0101, elem_size=0x0100.
/* Internal EVO state struct — relevant fields */
struct evo_state_t {
/* +0x00 */ uint8_t *evo_buf; // ptr to staging allocation
/* +0x08 */ uint32_t evo_buf_size; // 0x100 (wrapped, too small)
/* +0x0C */ uint32_t evo_written; // bytes committed so far
/* +0x10 */ uint32_t flags;
/* +0x14 */ uint32_t reserved[3];
/* +0x20 */ /* ... decoder pipeline state ... */
};
HEAP STATE BEFORE OVERFLOW:
[chunk @ 0xb4006a2000] evo_buf size=0x100 ← target allocation
[chunk @ 0xb4006a2100] next_obj size=0x200 ← adjacent object (e.g. codec context)
[chunk @ 0xb4006a2300] ...
HEAP STATE DURING memcpy (0x10100 bytes written into 0x100-byte buf):
[chunk @ 0xb4006a2000] evo_buf bytes 0x000–0x0ff ← legitimate write
[chunk @ 0xb4006a2100] CORRUPTED bytes 0x100–0x2ff ← next_obj header smashed
[chunk @ 0xb4006a2300] CORRUPTED bytes 0x300–... ← continues ~65 KB further
Overflow magnitude: 0x10100 - 0x100 = 0x10000 bytes (65,535 bytes past end of alloc)
CORRUPTED next_obj (example — codec context header at +0x100):
+0x00 vtable ptr = 0x4141414141414141 ← attacker-controlled DD+ payload bytes
+0x08 ref_count = 0x4141414141414141
+0x10 internal_buf = 0x4141414141414141
Exploitation Mechanics
Project Zero developed a full 0-click exploit for the Pixel 9 using this primitive. The delivery vector is a malformed EAC-3 audio attachment sent via RCS/SMS, decoded automatically by Google Messages before the user opens or interacts with the message.
EXPLOIT CHAIN — CVE-2025-54957, mediacodec RCE stage:
1. DELIVER: Attacker sends crafted EAC-3 audio attachment via RCS to target device.
No user interaction required; Google Messages triggers automatic transcription decode.
2. PARSE: Dolby UDC opens the bitstream. DD+ frame parser reaches EVO data block.
evo_priv.c reads attacker-controlled elem_count=0x0101, elem_size=0x0100 from bitstream.
3. WRAP: total_len = 0x0101 * 0x0100 = 0x10100 → truncated to uint16_t → 0x0100.
malloc(0x100) allocates 256-byte evo_buf on the jemalloc heap.
4. GROOM: Exploit bitstream is constructed to trigger decode operations that place
a high-value target object (e.g. codec pipeline vtable-bearing object) immediately
adjacent to evo_buf in the jemalloc slab. Heap layout is deterministic enough
within mediacodec's allocator state to make this reliable.
5. OVERFLOW: memcpy(evo_buf, src, 0x10100) runs. 65,535 bytes of attacker-controlled
EVO payload bytes spill past evo_buf, corrupting the adjacent object's vtable pointer.
6. HIJACK: Decoder continues processing. It invokes a virtual method on the now-corrupted
adjacent object. PC is redirected to attacker-supplied address embedded in the payload.
7. ROP/JOP: Attacker constructs a ROP chain using gadgets from libudcDecoder.so and
libdolby_audio_decoder.so (both non-PIE in affected builds, or with known base via
info leak from a separate step). Chain pivots to shellcode or calls mprotect+exec.
8. RESULT: Arbitrary code execution in the mediacodec process context.
Continues to CVE-2025-36934 for kernel privilege escalation (Part 2).
Patch Analysis
The correct fix is twofold: (1) perform the multiplication in a wider type to detect the overflow before it truncates, and (2) reject or cap the length rather than proceeding with a mismatched allocation/copy pair. The patched version in Dolby UDC 4.14+ does the following:
// BEFORE (vulnerable — Dolby UDC 4.5–4.13, evo_priv.c):
uint16_t total_len = elem_count * elem_size; // truncates silently
if (state->evo_buf == NULL) {
state->evo_buf = malloc(total_len);
state->evo_buf_size = total_len;
}
if (state->evo_written + total_len > state->evo_buf_size) {
return EVO_ERR_OVERFLOW;
}
memcpy(state->evo_buf + state->evo_written, src, elem_count * elem_size);
state->evo_written += total_len;
// AFTER (patched — Dolby UDC 4.14+, evo_priv.c):
uint32_t total_len = (uint32_t)elem_count * (uint32_t)elem_size; // FIX: widen before multiply
if (total_len > EVO_BUF_MAX_SIZE) { // FIX: explicit upper-bound sanity check
return EVO_ERR_INVALID_LENGTH;
}
if (state->evo_buf == NULL) {
state->evo_buf = malloc(total_len);
state->evo_buf_size = total_len;
}
// FIX: bounds check now uses same type and value as the memcpy length argument
if (state->evo_written + total_len > state->evo_buf_size) {
return EVO_ERR_OVERFLOW;
}
memcpy(state->evo_buf + state->evo_written, src, total_len); // FIX: single source of truth
state->evo_written += total_len;
Three distinct changes: the multiplication operands are widened to uint32_t before the operation; an explicit ceiling constant EVO_BUF_MAX_SIZE is checked against the result; and the memcpy length argument is replaced with the single computed total_len variable rather than re-deriving it, eliminating the type-mismatch between the guard and the actual copy.
Detection and Indicators
During active exploitation, the mediacodec process crashes or produces anomalous output. The following are observable indicators:
Crash signal:SIGSEGV or SIGABRT in mediacodec with PC in an unresolvable region, or in memcpy called from evo_write_data. Tombstone will show evo_priv.c in the backtrace on debug builds.
Tombstone backtrace pattern:#00 pc ... memcpy ... #01 pc ... evo_write_data ... #02 pc ... evo_parse_block
Anomalous EAC-3 files: Valid EAC-3 container with EVO data block where elem_count × elem_size overflows uint16_t — easily identified with a bitstream parser checking for products exceeding 0xFFFF.
Network indicator: Inbound RCS audio attachments with MIME type audio/eac3 or audio/vnd.dolby.dd-raw from unknown senders, particularly short files with large EVO metadata blocks disproportionate to audio frame count.
A simple fuzzer harness targeting evo_priv.c with elem_count and elem_size values that produce wraparound products will reliably reproduce the crash within seconds on affected library versions. The AddressSanitizer heap-buffer-overflow report from such a harness will point directly to the memcpy call inside evo_write_data.
Remediation
Update Dolby UDC to version 4.14 or later. OEMs must integrate the updated library and ship an OTA. The January 5, 2026 Android security bulletin contains the patch.
Apply Android security patches dated January 2026 or later. Google's patch also includes mitigations for the mediacodec sandbox (CVE-2025-36934).
OEMs not yet patched should consider disabling automatic audio transcription in Google Messages until the library update is deployed, though this is a product-level workaround and not a security boundary.
Compiler-level: Enable -fsanitize=integer (UBSan integer overflow) in CI fuzzing builds targeting audio codec libraries. This class of bug is trivially caught at compile time if integer sanitizers are applied to codec parsing code paths.
Architectural: Audio decoders processing 0-click content should operate in a tighter sandbox than general mediacodec; the lesson from this chain is that mediacodec compromise is a viable stepping stone to kernel access (see Part 2, CVE-2025-36934).