CVE-2026-16408
Overview
Background
- `WebMDemuxer`
- the Gecko component that parses a WebM container and extracts track metadata such as frame and crop dimensions.
- `IsValidVideoRegion`
- a validation helper that checks a frame size, picture rectangle, and display size are within safe bounds before video-frame creation.
- `CheckedInt`
- a Mozilla integer wrapper type that detects overflow on arithmetic and exposes validity via
isValid(). - Crop values
- container-supplied
crop_left,crop_right,crop_top, andcrop_bottomoffsets that shrink the coded frame into the displayed picture region.
Root Cause Analysis
In WebMDemuxer::ReadMetadata, the horizontal and vertical crop totals were computed as static_cast<uint64_t>(params.crop_left) + params.crop_right and the vertical equivalent, then compared against params.width/params.height; while the addition itself was widened, the resulting pictureRect was populated from params.width - cropH without robustly rejecting hostile crop combinations, so an attacker-controlled WebM could drive the picture rectangle to values inconsistent with the frame. The invariant that the picture region must lie fully inside the frame (aPicture.x >= 0, aPicture.XMost() <= aFrame.width, and the vertical equivalents) was not enforced, letting a picture rect escape the frame bounds and feed later aPicture.width * aPicture.height arithmetic that could overflow int32_t.
The fix converts the crop sums to CheckedInt<uint32_t> and continues (dropping the track) whenever the sum overflows or meets/exceeds the frame dimension, and constructs pictureRect from validated croppedWidth/croppedHeight. IsValidVideoRegion is simultaneously hardened to reject negative aPicture.x/aPicture.y and to require XMost() <= aFrame.width and YMost() <= aFrame.height, so the picture rectangle is proven to sit inside the frame before any multiplication.
CheckedInt to reject overflowing crop sums and adds explicit lower-bound and XMost/YMost containment checks so no out-of-range picture region reaches size-computing code.Attack Path
- Craft malicious WebM
An attacker authors a WebM file with a video track whose
crop_left/crop_rightorcrop_top/crop_bottomfields are chosen to overflow or exceed the frame dimensions. - Deliver to victim
The file is served over the web or embedded so the victim’s browser demuxes it via
WebMDemuxer::ReadMetadata. - Bypass weak crop check
The old crop-sum comparison and unbounded
pictureRectpopulation let a picture rectangle be built that is inconsistent with or outside the coded frame. - Reach overflowing arithmetic
The malformed picture rect flows into
aPicture.width * aPicture.heightand related size math, whereint32_tmultiplication can overflow.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifdom/media/webm/WebMDemuxer.cpp |
modified |
Files Changed
dom/media/VideoUtils.cppdom/media/webm/WebMDemuxer.cpp
Audit Directions
- Container-derived dimensionsAudit every demuxer path (MP4, Ogg, etc.) that computes crop, offset, or picture regions from untrusted metadata for the same missing bounds and overflow checks.
- Unchecked integer arithmeticSearch for
+/*on parsed media fields not wrapped inCheckedInt, especially where the result feeds allocation orint32_tsizes. - Region containment invariantsVerify that any picture-rect-versus-frame validation enforces both lower bounds and
XMost/YMostcontainment, not just upperMAX_DIMENSIONlimits.
Patch
diff --git a/dom/media/VideoUtils.cpp b/dom/media/VideoUtils.cpp
index bfab6e2d663..4e45a9be4b8 100644
--- a/dom/media/VideoUtils.cpp
+++ b/dom/media/VideoUtils.cpp
@@ -228,13 +228,14 @@ bool IsValidVideoRegion(const gfx::IntSize& aFrame,
aFrame.height <= PlanarYCbCrImage::MAX_DIMENSION &&
aFrame.width * aFrame.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
aPicture.width > 0 &&
- aPicture.width <= PlanarYCbCrImage::MAX_DIMENSION &&
+ aPicture.width <= PlanarYCbCrImage::MAX_DIMENSION && aPicture.x >= 0 &&
aPicture.x < PlanarYCbCrImage::MAX_DIMENSION &&
aPicture.x + aPicture.width < PlanarYCbCrImage::MAX_DIMENSION &&
- aPicture.height > 0 &&
+ aPicture.XMost() <= aFrame.width && aPicture.height > 0 &&
aPicture.height <= PlanarYCbCrImage::MAX_DIMENSION &&
- aPicture.y < PlanarYCbCrImage::MAX_DIMENSION &&
+ aPicture.y >= 0 && aPicture.y < PlanarYCbCrImage::MAX_DIMENSION &&
aPicture.y + aPicture.height < PlanarYCbCrImage::MAX_DIMENSION &&
+ aPicture.YMost() <= aFrame.height &&
aPicture.width * aPicture.height <=
MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
aDisplay.width > 0 &&
diff --git a/dom/media/webm/WebMDemuxer.cpp b/dom/media/webm/WebMDemuxer.cpp
index dfa3a39a28f..df0aa9090ef 100644
--- a/dom/media/webm/WebMDemuxer.cpp
+++ b/dom/media/webm/WebMDemuxer.cpp
@@ -20,6 +20,7 @@
#include "XiphExtradata.h"
#include "gfx2DGlue.h"
#include "gfxUtils.h"
+#include "mozilla/CheckedInt.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/Maybe.h"
#include "mozilla/SharedThreadPool.h"
@@ -526,26 +527,28 @@ nsresult WebMDemuxer::ReadMetadata() {
mInfo.mVideo.mHDRMetadata = ParseWebMMasteringMetadata(params);
- // Picture region, taking into account cropping, before scaling
- // to the display size. Default to the full frame and apply cropping only
- // when it leaves a non-empty region within the frame.
- gfx::IntRect pictureRect(0, 0, AssertedCast<int32_t>(params.width),
- AssertedCast<int32_t>(params.height));
- uint64_t cropH =
- static_cast<uint64_t>(params.crop_left) + params.crop_right;
- uint64_t cropV =
- static_cast<uint64_t>(params.crop_top) + params.crop_bottom;
- if (cropH < params.width && cropV < params.height) {
- pictureRect.x = AssertedCast<int32_t>(params.crop_left);
- pictureRect.y = AssertedCast<int32_t>(params.crop_top);
- pictureRect.width = AssertedCast<int32_t>(params.width - cropH);
- pictureRect.height = AssertedCast<int32_t>(params.height - cropV);
+ CheckedInt<uint32_t> cropH =
+ CheckedInt<uint32_t>(params.crop_left) + params.crop_right;
+ CheckedInt<uint32_t> cropV =
+ CheckedInt<uint32_t>(params.crop_top) + params.crop_bottom;
+ if (!cropH.isValid() || !cropV.isValid() ||
+ cropH.value() >= params.width || cropV.value() >= params.height) {
+ WEBM_DEBUG("Invalid crop values left: {} right: {} top: {} bottom: {}",
+ params.crop_left, params.crop_right, params.crop_top,
+ params.crop_bottom);
+ continue;
}
// Validate the container-reported frame and pictureRect sizes. This
// ensures that our video frame creation code doesn't overflow.
gfx::IntSize displaySize(params.display_width, params.display_height);
gfx::IntSize frameSize(params.width, params.height);
+ const uint32_t croppedWidth = params.width - cropH.value();
+ const uint32_t croppedHeight = params.height - cropV.value();
+ gfx::IntRect pictureRect(AssertedCast<int32_t>(params.crop_left),
+ AssertedCast<int32_t>(params.crop_top),
+ AssertedCast<int32_t>(croppedWidth),
+ AssertedCast<int32_t>(croppedHeight));
if (!IsValidVideoRegion(frameSize, pictureRect, displaySize)) {
// Video track's frame sizes will overflow. Ignore the video track.
continue;