CVE-2026-87430
Overview
Background
- WebRTC H.264 encoder wrapper
- The
webrtc::H264EncoderImplclass that adapts raw I420 video frames for an underlying H.264 codec, including creating downscaled buffers for simulcast layers. - I420 planar format
- A YUV pixel layout where a full-resolution luma (
Y) plane is accompanied by two half-resolution chroma (U/V) planes, each subsampled by 2 in both dimensions. - Stride
- The number of bytes/samples per row in an image plane, which for chroma planes must cover
ceil(width/2)samples so odd resolutions retain their final odd chroma pixel. - `I420Buffer::Create`
- A WebRTC factory that allocates an I420 frame buffer, optionally taking explicit
Y/U/Vstrides or, when omitted, computing correct default strides.
Root Cause Analysis
In H264EncoderImpl::SetRates (the code creating downscaled_buffers_), I420Buffer::Create was invoked with explicit stride arguments where the U and V strides were computed as configurations_[i].width / 2, an integer division that rounds down. The correct chroma stride for I420 is ceil(width/2) (i.e., (width + 1) / 2), which rounds up so that odd-width frames retain the extra chroma sample for their last odd pixel column. For any layer whose configured width is odd, the explicit stride under-allocated each chroma row by one sample, violating the invariant that a plane’s allocation must be large enough to hold every sample the encoder subsequently writes.
The fix removes the incorrect explicit stride arguments entirely and passes only width and height, letting I420Buffer::Create compute the correct default (rounded-up) chroma strides. This works because the default-stride path already implements the correct ceil rounding, eliminating the under-allocation.
width / 2) instead of up (ceil(width/2)), which under-sizes the buffer for odd resolutions; the fix deletes the hand-computed strides so I420Buffer::Create supplies its already-correct default strides.Attack Path
- Establish a media session A remote peer negotiates a WebRTC connection that causes the victim to encode H.264 video with the affected wrapper.
- Force an odd resolution
The attacker steers simulcast/downscale configuration so at least one layer’s
configurations_[i].widthis odd, makingwidth / 2differ from the requiredceil(width/2). - Allocate the undersized buffer
Rate/resolution setup calls
I420Buffer::Createwith the too-smallU/Vstride, producing adownscaled_buffers_entry lacking one chroma sample per row. - Trigger the overflow Encoding writes the full (rounded-up) chroma plane into the undersized allocation, writing out of bounds past the buffer.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifmodules/video_coding/codecs/h264/h264_encoder_impl.cc |
modified |
Files Changed
modules/video_coding/codecs/h264/h264_encoder_impl.cc
Audit Directions
- Chroma stride roundingSearch for chroma/subsampled dimensions computed with
width / 2or/ 2integer division and confirm they useceil/(x + 1) / 2wherever odd resolutions are possible. - Explicit-stride buffer allocationAudit callers that pass hand-computed strides to
I420Buffer::Create(or similar allocators) and prefer the default-stride overload unless a custom stride is genuinely required. - Odd-resolution handlingReview video scaling and simulcast paths for assumptions that widths/heights are even, since odd dimensions expose off-by-one under-allocation and overflow.
Patch
From 437408bd428c915b11f44df4f35cf1834acd61c9 Mon Sep 17 00:00:00 2001
From: Ilya Nikolaevskiy <ilnik@webrtc.org>
Date: Wed, 05 Aug 2026 10:51:13 +0200
Subject: [PATCH] Use default strides in allocated buffer in h264 encoder wrapper
The explicit values were incorrect: u/v strides were half-width rounded
down, but it has to be rounded up: for odd resolutions there are supposed to be extra u/v samples for last odd pixels.
Bug: chromium:542449805
Change-Id: I06f85d4cba737f69699f056057ebf039094bbffe
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/493940
Reviewed-by: Sergey Silkin <ssilkin@webrtc.org>
Auto-Submit: Ilya Nikolaevskiy <ilnik@webrtc.org>
Commit-Queue: Sergey Silkin <ssilkin@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#48298}
---
diff --git a/modules/video_coding/codecs/h264/h264_encoder_impl.cc b/modules/video_coding/codecs/h264/h264_encoder_impl.cc
index 69db6ff..0056291 100644
--- a/modules/video_coding/codecs/h264/h264_encoder_impl.cc
+++ b/modules/video_coding/codecs/h264/h264_encoder_impl.cc
@@ -335,9 +335,7 @@
// Create downscaled image buffers.
if (i > 0) {
downscaled_buffers_[i - 1] = I420Buffer::Create(
- configurations_[i].width, configurations_[i].height,
- configurations_[i].width, configurations_[i].width / 2,
- configurations_[i].width / 2);
+ configurations_[i].width, configurations_[i].height);
}
// Codec_settings uses kbits/second; encoder uses bits/second.