bdcb10e3b7c0f672e339ba5ed49089c327ee7b41 [blink] Fix nullptr deref on VideoFrame::set_color_space
Triage note: Renderer null-deref in WebCodecs VideoFrame construction, reachable from JS in default config. Null-deref (DoS), not memory corruption. watch.
The bug at a glance
A genuine null-pointer dereference in the WebCodecs VideoFrame construction path, reachable from unprivileged web JavaScript. media::VideoFrame::WrapSharedImage can return nullptr on failure, and the old code immediately called frame->set_color_space() on the result without checking, dereferencing null. The impact is a renderer-process crash (denial of service). set_color_space writes a gfx::ColorSpace member through the null this pointer; the store lands at a small fixed offset from address 0, which under normal near-null page protections faults rather than corrupting useful memory, so this is a crash primitive, not a demonstrated controlled write. Web reachability (WebCodecs VideoFrame is constructible from script) and a browser-shipping default keep it above the floor; the bounded, crash-only impact and lack of a corruption/RCE primitive keep it out of high. Medium, matching the upstream banding. CVSS 6.5 for network/web reachability, low complexity, no privileges, availability impact on the renderer, no confirmed confidentiality/integrity impact.
WrapSharedImage is a factory that may legitimately fail and return nullptr (invalid or inconsistent frame parameters, unsupported format/size combination), yet the surrounding code treated its result as unconditionally valid. The CHECK just above guards only client_shared_image, giving a false sense of safety: the shared image being non-null does not imply the wrapped VideoFrame is non-null. The color space was read from the shared image before wrapping and then pushed onto the frame afterward, so the natural failure window is exactly between a successful CHECK and an unchecked frame use.
Root cause
In VideoFrame.cc (~line 822), the code obtains client_shared_image = sbi->GetSharedImage(), CHECKs it, reads its color space into a local (shared_image_cs), calls media::VideoFrame::WrapSharedImage(…) to produce frame, then unconditionally calls frame->set_color_space(shared_image_cs). If WrapSharedImage returns nullptr, that final call dereferences null. The fix removes the local and the immediate setter: it assigns the shared image color space to gfx_color_space (a variable declared earlier in the function and shared across construction branches), and deletes the frame->set_color_space(shared_image_cs) line. Per the commit message, gfx_color_space is applied to the frame later, in a code path that only runs when frame is valid. So the color space is captured up front and set on the frame only after a validity gate, eliminating the null dereference while preserving the intended color-space assignment on the success path.
Key code
Capture color space up front instead of dereferencing a possibly-null WrapSharedImage result
auto client_shared_image = sbi->GetSharedImage();
CHECK(client_shared_image);
- auto shared_image_cs = client_shared_image->color_space();
+ gfx_color_space = client_shared_image->color_space();
frame = media::VideoFrame::WrapSharedImage(
format, std::move(client_shared_image), sbi->GetSyncToken(),
std::move(release_cb), coded_size, parsed_init.visible_rect,
parsed_init.display_size, timestamp);
- frame->set_color_space(shared_image_cs);
Patch walkthrough
third_party/blink/renderer/modules/webcodecs/video_frame.cc— In the shared-image branch of VideoFrame construction, the color space read from the client shared image is now stored into the function-scoped gfx_color_space instead of a local, and the unconditional frame->set_color_space(shared_image_cs) call immediately following WrapSharedImage is removed. Because gfx_color_space is applied to the frame later only when the frame is valid, the code no longer dereferences a possibly-null frame. The CHECK(client_shared_image) is unchanged and does not (and cannot) guarantee WrapSharedImage succeeded, which was the root of the bug.
Background
WebCodecs VideoFrame — A script-constructible object (new VideoFrame(source, init)) in the modules/webcodecs Blink module. Construction from a CanvasImageSource / accelerated bitmap goes through the shared-image branch touched here, so the crash is reachable from ordinary web pages that use WebCodecs.
media::VideoFrame::WrapSharedImage — A media factory that wraps a GPU shared image into a VideoFrame. It returns a scoped_refptr that can be null when parameters are invalid or inconsistent (format/size/visible_rect/display_size mismatches). Callers must null-check the result before use.
set_color_space — A VideoFrame member setter that stores a gfx::ColorSpace on the frame. Invoked through a null frame pointer, it performs a member store at a small offset from null, which faults rather than yielding a useful write under near-null protections.
StaticBitmapImage / GetSharedImage (sbi) — The accelerated bitmap source providing the GPU shared image and sync token used to build the VideoFrame. Its shared image being present (CHECKed) does not imply the subsequent wrap succeeds.
gfx::ColorSpace / gfx_color_space — The color space value moved from the shared image to the frame. Refactored here into a function-scoped variable so it can be applied to the frame only after the frame is confirmed valid.
Vulnerability window
- Discovery — Bug 475607264 identified that WrapSharedImage may return nullptr while the following set_color_space call assumed a valid frame, a null-deref crash in the WebCodecs path.
- Fix — On 2026-01-14 Saifuddin Hitawala landed CL 7476763 (r1569330), reviewed by Dale Curtis, restructuring the code to capture the color space up front and apply it only to a valid frame.
Proof of concept
No test or reproducer shipped in this diff and no exploit is demonstrated. Honest recipe: drive the WebCodecs VideoFrame shared-image construction branch with parameters that cause WrapSharedImage to fail and return nullptr while GetSharedImage still yields a valid shared image (so the CHECK passes). In practice that means constructing a VideoFrame from an accelerated CanvasImageSource with an init whose format/coded_size/visible_rect/display_size are internally inconsistent enough to fail the wrap but not the earlier shared-image acquisition. Pre-patch, frame->set_color_space on the null frame crashes the renderer. Precisely which parameter combination forces the null return is not evidenced by the diff, so this is a directional recipe only; the outcome is a renderer crash, not a corruption exploit.
Exploitation
- Reach — Unprivileged JS constructs a WebCodecs VideoFrame from an accelerated bitmap source, entering the shared-image branch. This requires no special privileges and works from a normal web origin where WebCodecs is available.
- Trigger — Choose construction parameters that make WrapSharedImage return nullptr (invalid/inconsistent geometry or format) while GetSharedImage succeeds, so control reaches the unchecked frame->set_color_space.
- Impact — Null-pointer member store crashes the renderer process. Near-null offset store is not a controllable write under standard mitigations, so the realistic outcome is DoS, not code execution.
Detection & hunting
For defenders and detection engineers:
- Renderer crash signature — Watch for null-deref crashes in modules/webcodecs video_frame.cc at set_color_space with a null VideoFrame, on the WrapSharedImage construction path; correlate with bug 475607264.
- WebCodecs fuzzing — Fuzz VideoFrame construction from accelerated sources with adversarial init geometry/format to surface WrapSharedImage null-return paths.
Audit directions
- Unchecked VideoFrame factory results — Audit all callers of media::VideoFrame::WrapSharedImage and sibling factories (WrapExternalTextures, WrapMappableSharedImage, CreateFrame) in blink and media for missing null checks before dereference; this bug is a representative of that class.
- CHECK-vs-use mismatch — Flag patterns where a CHECK validates an input (client_shared_image) but subsequent code dereferences a different, unvalidated derived object (frame). Review nearby WebCodecs construction branches for the same anti-pattern.
- Color space / metadata application ordering — Verify other places that read metadata from a source and push it onto a factory result do so only after a validity gate, mirroring the gfx_color_space restructuring here.