Low watch CVSS 9.8 chrome Race 📄 Reporter bug report 🔧 Commit mapped

Overview

Low
Severity
9.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactRace in Media
DescriptionRace in Media
ComponentMedia
Sub-componentMedia
Bug ClassRace
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker483109205
Fix commitcf230cf0cda3 (chromium/src) +2/-2
CWECWE-362 (Race condition)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedLuke Francis
Reported2026-02-10 (per vendor advisory)
Fix landed2026-02-11 (public git)
Disclosed2026-04-07
Patch gap55 days — fix public in source before the advisory

Background

NDK Video Encode Accelerator
Chrome’s Android hardware video encoder wrapper (NdkVideoEncodeAccelerator) that drives the platform NDK media encoder and produces encoded bitstream buffers.
Temporal scalability (SVC) parsing
Post-encode analysis of the output bitstream to extract temporal-layer metadata, performed here by svc_parser_->ParseChunk.
Shared-memory output buffer
The destination (output_dst) backed by memory shared with the (less-trusted) renderer, into which encoded data is copied for delivery.
Double fetch / TOCTOU
Reading the same data from shared memory twice (once to copy out, once to re-parse) so that a concurrent writer can change it between the two reads.

Root Cause Analysis

The output-handling path copied the encoder’s bitstream into the shared-memory destination output_dst and then re-read that same shared-memory region to parse temporal-scalability metadata, calling svc_parser_->ParseChunk(output_dst.first(mc_buffer_size), ...). Because output_dst is backed by memory the renderer can also map, a compromised renderer could modify the bytes after the copy but before (or during) the metadata parse, so the browser/GPU-process parser operated on data different from what was encoded — a classic time-of-check/time-of-use double-fetch on shared memory.

The fix changes the parse to read from out_buffer_data, a process-local buffer holding the encoder’s own copy of the data, instead of the renderer-writable output_dst. With the parser consuming trusted local memory, the renderer can no longer influence the metadata parse after the fact, closing the race window described in the commit message.

Key insight
The temporal-scalability parser re-read encoded data from renderer-shared memory rather than from a trusted local copy, allowing a concurrent renderer write to change it between copy and parse; the fix points ParseChunk at the process-local out_buffer_data buffer.

Attack Path

  1. Drive hardware encoding with SVC A renderer uses a media/WebCodecs encode path that reaches the Android NDK encoder with temporal scalability enabled, so each output buffer is parsed by svc_parser_->ParseChunk.
  2. Compromised renderer maps the output shared memory An already-compromised renderer retains write access to the shared-memory region backing output_dst.
  3. Race the parse After the browser/GPU process copies the bitstream into output_dst but before it parses the metadata, the renderer rewrites the shared bytes, so the parser consumes attacker-controlled, inconsistent data (the race window).
  4. Downstream effect (inferred) What the mismatched/malformed parsed metadata leads to (memory corruption or further misbehavior in the parser/consumer) is not shown in this diff and is (inferred).

Impact Assessment

An attacker with an already-compromised renderer could win a TOCTOU race and feed attacker-controlled data into the NDK VEA’s temporal-scalability metadata parser in the browser/GPU process on Android. Preconditions are significant: a compromised renderer, the Android NDK hardware encoder path, and temporal-scalability (SVC) encoding in use; the vendor advisory rates this low severity, and any memory-corruption or escalation beyond feeding bad data to the parser is (inferred) because the patch only shows the change of parse source.

Files Changed

  • media/gpu/android/ndk_video_encode_accelerator.cc

Audit Directions

  • Parse-from-shared-memory
    Audit GPU/browser-process code that both copies data into renderer-shared buffers and then re-reads those same buffers for validation or metadata parsing; parse from a local copy instead.
  • Double fetch on IPC/shared buffers
    Search for patterns where a size or structure is read once and the payload read again from the same untrusted mapping, especially in media/codec accelerators where large shared buffers are common.
  • Hardware-encoder metadata paths
    Review other VEA backends (V4L2, Media Foundation, VideoToolbox) for SVC/temporal-layer or header parsers that consume the shared output region rather than an internal buffer.
From cf230cf0cda34477afd4c14096ffc6692fa79994 Mon Sep 17 00:00:00 2001
From: Eugene Zemtsov <[email protected]>
Date: Wed, 11 Feb 2026 14:34:45 -0800
Subject: [PATCH] media: Fix race condition in NDK VEA's temporal scalability parsing

Previously, the encoder copied bitstream data to shared memory and then
read it back for metadata parsing. This created a race window where a
compromised renderer could modify the shared memory before the browser
parsed it.

Bug: 483109205
Change-Id: Ic6aec49b840c03fed1b01e2f9c2e553e44eec158
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7567414
Commit-Queue: Eugene Zemtsov <[email protected]>
Reviewed-by: Dale Curtis <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1583529}
---

diff --git a/media/gpu/android/ndk_video_encode_accelerator.cc b/media/gpu/android/ndk_video_encode_accelerator.cc
index 30bd5ca..1287329 100644
--- a/media/gpu/android/ndk_video_encode_accelerator.cc
+++ b/media/gpu/android/ndk_video_encode_accelerator.cc
@@ -1484,8 +1484,8 @@
     }
 
     TemporalScalabilityIdExtractor::BitstreamMetadata bits_md;
-    if (!svc_parser_->ParseChunk(output_dst.first(mc_buffer_size),
-                                 input_since_keyframe_count_, bits_md)) {
+    if (!svc_parser_->ParseChunk(out_buffer_data, input_since_keyframe_count_,
+                                 bits_md)) {
       NotifyErrorStatus({EncoderStatus::Codes::kEncoderHardwareDriverError,
                          "Parse bitstream failed"});
       return;
Loading diff…

Original Bug Report

reported by [email protected]

TOCTOU in NdkVideoEncodeAccelerator: shared memory re-read after write allows attacker-controlled bitstream parsing

VULNERABILITY DETAILS

The Android NDK Video Encode Accelerator (NdkVideoEncodeAccelerator) writes encoded bitstream data into an UnsafeSharedMemoryRegion, then re-reads from that same shared memory to parse temporal scalability metadata. The renderer retains write access to this region. A compromised renderer can mutate the buffer contents between the encoder’s write and the metadata parse, feeding attacker-controlled bitstream data to H.264, HEVC, VP9, and AV1 parsers running in the GPU process.

The vulnerable code is in media/gpu/android/ndk_video_encode_accelerator.cc:

  • Line 1460: Encoder output copied into shared memory via output_dst.copy_prefix_from
  • Line 1487: svc_parser_->ParseChunk() re-reads from the same shared memory span

Between these two lines, the renderer can overwrite the buffer with crafted NALUs/OBUs. The parsers (H264Parser, H265NaluParser, Vp9Parser, libgav1 ObuParser) were not designed to handle adversarial input on this code path. They expect trusted encoder output.

Other platform encoders are not affected. VAAPI derives metadata from encoder-internal state (vaapi_video_encode_accelerator.cc:524-556). V4L2 parses from its kernel mmap’d capture buffer before copying to shared memory (v4l2_video_encode_accelerator.cc:1467-1485). MediaFoundation parses from its MF COM buffer (media_foundation_video_encode_accelerator_win.cc:2398-2413). Only the NDK path parses from the shared memory span after writing to it.

The fix is to move the ParseChunk() call to operate on the MediaCodec output buffer (out_buffer_data) before copying to shared memory, matching the pattern used by V4L2 and MediaFoundation.

VERSION

Chrome Version: trunk (verified against current main branch) Operating System: Android (all versions using NDK MediaCodec encoder)

REPRODUCTION CASE

A standalone PoC demonstrates the race condition by simulating the GPU and renderer threads operating on the same UnsafeSharedMemoryRegion. Results: 1.9 million race wins out of 62 million iterations (3.06% hit rate) in 2 seconds.

The PoC is attached. It can also be built as a Chromium unit test.

The race window exists between ndk_video_encode_accelerator.cc lines 1460 and 1487. In real exploitation, the attacker substitutes arbitrary H.264/VP9/AV1 bitstream data, feeding crafted input to TemporalScalabilityIdExtractor::ParseChunk() (media/parsers/temporal_scalability_id_extractor.cc:56), which dispatches to:

  • ParseH264 (line 77): H264Parser::AdvanceToNextNALU on attacker data
  • ParseHEVC (line 108): H265NaluParser::AdvanceToNextNALU on attacker data
  • ParseVP9 (line 132): Vp9Parser::ParseNextFrame on attacker data, reads ref_frame_idx[] used to index into vp9_ref_buffer_[kVp9NumRefFrames=8]
  • ParseAV1 (line 175): libgav1::ObuParser::ParseOneFrame on attacker data, complex parser touching DecoderState and BufferPool

The VP9 and AV1 paths maintain persistent reference frame state across frames, so corrupted state cascades to subsequent frames.

FOR CRASHES, PLEASE INCLUDE THE FOLLOWING ADDITIONAL INFORMATION

Type of crash: GPU process. Attacker-controlled data reaches bitstream parsers. Impact depends on parser robustness to adversarial input. Potential outcomes include wrong metadata propagation, parser state corruption, and possible out-of-bounds reads in complex parsers (especially libgav1).

CREDIT INFORMATION

Reporter credit: Luke Francis

View on issue tracker