← All reports

[5] Unbounded coded dimensions in WebCodecs encoder configuration

MediumWebCodecs VideoEncoderIntegerOverflow

WebCodecs accepted a 4-billion-pixel frame width and passed it along.

e410d5c

Medium — the validation gap itself is unambiguous and script-reachable, but the memory-safety consequence lands inside third-party encoder code outside WebKit's tree. The commit title attributes a heap buffer overflow in the WebRTC VP9 encoder; that attribution, not a demonstrated write, is what sets the ceiling here.

A missing upper bound on an attacker-supplied size value can let that value reach an allocation or geometry computation in code that never expected it. WebCodecs is the low-level web API that hands raw encode and decode control to JavaScript, and its encoder half takes a VideoEncoderConfig dictionary — codec string, bitrate, and coded frame dimensions — straight from script. WebCore validates that configuration, translates it into an internal VideoEncoder::Config, and passes it to a platform encoder backend, so WebCore's validation is the only boundary between script-chosen numbers and third-party encoder code.

The angle: a page can configure a WebCodecs video encoder with coded dimensions anywhere up to 2^32-1 and have them reach the platform encoder with no upper bound applied anywhere in WebCore.

The IDL that establishes the input range:

Source/WebCore/Modules/webcodecs/WebCodecsVideoEncoderConfig.idl

[EnforceRange] unsigned long width;
[EnforceRange] unsigned long height;

And the type they are copied into:

Source/WebCore/platform/VideoEncoder.h

uint64_t width;
uint64_t height;

isSupportedEncoderCodec is re-signatured from (const String& codec, const SettingsValues&) to (const WebCodecsVideoEncoderConfig&, const SettingsValues&) and gains an early bail-out at the top of its body — constexpr size_t maxFrameDimension = 32767; followed by a rejection when either coded dimension exceeds it — before any codec-string matching runs. Both call sites, the instance configure() and the static isConfigSupported(), now pass the whole config rather than just the codec string. The accompanying regression test pins the boundary exactly: 32767 is accepted, 32768 is rejected.

The placement is load-bearing rather than incidental. Because the bound sits inside the codec-support predicate rather than in isValidEncoderConfig, an oversized configuration does not throw synchronously out of configure() — it surfaces through the encoder's error callback, which is the path the regression test exercises.

Attacker-supplied size validated for a lower bound only, with the upper bound left implicitly to a downstream consumer in third-party code.

Where this lives. Source/WebCore/Modules/webcodecs/WebCodecsVideoEncoder.cpp is the WebCore-side configuration and control-message layer for the WebCodecs encoding API. For VP8, VP9, and AV1 under USE(LIBWEBRTC), the backend it hands configuration to is LibWebRTCVPXVideoEncoder, a thin WebCore wrapper over the third-party libwebrtc and libvpx encoders.

[EnforceRange] in Web IDL. This extended attribute makes the bindings layer throw a TypeError for values outside the declared type's range instead of silently wrapping them. On unsigned long that means the bindings guarantee a value in [0, 2^32-1] — and guarantee nothing narrower. [EnforceRange] is a type-range enforcement, not an application-level bound; a reader who sees it on a dimension field may reasonably but wrongly assume the value has been sanity-checked.

The pre-fix validation chain. configure ran isValidEncoderConfig, which rejects zero-valued width and height, and then isSupportedEncoderCodec(config.codec, ...), a predicate whose entire body inspected the codec string. createVideoEncoderConfig then copied the dimensions verbatim into VideoEncoder::Config before VideoEncoder::create handed the config to the backend.

The missing invariant is that a coded frame dimension surviving WebCodecs configuration validation must be within the range the platform encoder can represent and allocate for. WebCore enforced only "non-zero"; the upper end was left entirely to the backend.

  Before:                              After:
  script config {w,h}                  script config {w,h}
    └─► [EnforceRange] u32 ─ 0..2^32-1   └─► [EnforceRange] u32
          └─► isValidEncoderConfig             └─► isValidEncoderConfig
                (rejects zero only)                  (rejects zero)
                └─► isSupportedEncoderCodec          └─► isSupportedEncoderCodec(config)
                      (codec string only)                  ├─ bail if w|h > 32767
                      └─► VideoEncoder::Config             └─ then codec-string match
                            (uint64_t w/h)                       └─► VideoEncoder::Config
                            └─► platform backend                       (bounded)
                                                                       └─► platform backend

The validation gap and its closure are direct: pre-fix, attacker-chosen dimensions up to 2^32-1 flowed from JavaScript through the zero-check, through a codec-string-only support predicate, into VideoEncoder::Config, and on to the backend with nothing testing an upper bound.

The shape of the downstream failure is inferred rather than read off code, and the chosen constant is the reason. The patch selected 32767 — INT16_MAX, not UINT16_MAX and not a power-of-two area limit — and the regression test insists 32768 be rejected while 32767 is accepted. That points at a signed 16-bit representation limit somewhere below WebCore rather than at an area or byte-count bound. Under that reading, a dimension above INT16_MAX would be narrowed when stored into the encoder's internal geometry field, so buffers sized from the narrowed value would disagree with the geometry of a frame submitted through a later encode() call.

The commit title attributes the downstream consequence to a "Heap Buffer Overflow in WebRTC VP9 Encoder". WebKit's side of the boundary ends at LibWebRTCVPXVideoEncoder; the allocation site and the out-of-bounds write are inside libwebrtc/libvpx, so the overflow is the commit title's attribution rather than something this change demonstrates.

This vulnerability weakens the boundary between script-supplied media parameters and third-party codec code that WebCore embeds, on a surface any page can reach without a user gesture.

Note: The signed-16-bit narrowing is projected from the choice of INT16_MAX as the bound and the test's 32767/32768 boundary; the narrowing field itself is inside libwebrtc/libvpx and is not identified here.