CVE-2026-16402
Overview
Background
- JPEG XL (JXL)
- A raster image codec whose Firefox decoder lives in
nsJXLDecoder.cppwithin the ImageLib component. - `AllocateFrameBuffers`
- The
nsJXLDecodermethod that sizes and allocates per-frame pixel buffers before decoded samples are written into them. - `PixelFormat::Cmyk8`
- An 8-bit-per-channel CMYK pixel format that requires a separate
mKBufferto hold the K (black) channel. - Integer overflow
- An arithmetic result that exceeds the range of its operand type and wraps to a smaller value, here corrupting an allocation size.
Root Cause Analysis
In nsJXLDecoder::AllocateFrameBuffers, when the decoded image uses PixelFormat::Cmyk8, the K-channel buffer was sized by evaluating size.width * size.height and passing the result to mKBuffer.resize. Because size.width and size.height are narrow (32-bit) integer types, their product is computed in 32-bit arithmetic and can wrap around for attacker-controlled large dimensions, so resize allocates a buffer far smaller than width * height bytes. The invariant that the K-channel buffer must hold one byte per pixel was thus violated, setting up a subsequent heap buffer overflow when the full-resolution K channel is decoded into the undersized allocation.
The fix casts one operand to size_t (size_t(size.width) * size.height), forcing the multiplication into 64-bit (pointer-width) arithmetic so the product no longer overflows and resize receives the true pixel count.
size_t first so the entire product is computed at full width and the allocation size is correct.Attack Path
- Craft a malicious JXL
An attacker builds a JPEG XL image declaring the
Cmyk8pixel format and dimensions whosewidth * heightoverflows 32 bits. - Deliver to victim
The image is served on a web page or otherwise loaded so Firefox’s ImageLib routes it to
nsJXLDecoder. - Undersized allocation
AllocateFrameBufferscomputes the wrapped 32-bit product andmKBuffer.resizeallocates a buffer far smaller than the real pixel count. - Out-of-bounds write
As the K channel is decoded, per-pixel writes run past the end of the undersized
mKBuffer, corrupting adjacent heap memory.
Impact Assessment
Files Changed
image/decoders/nsJXLDecoder.cpp
Audit Directions
- Width-narrow size arithmeticSearch other decoders and buffer allocators for
width * height(or similar dimension products) fed toresize/malloc/Allocatewithout asize_tcast on at least one operand. - Per-format buffer pathsReview format-specific branches (e.g. CMYK, alpha, ancillary channels) that allocate auxiliary buffers, since these secondary sizing computations are easy to miss during the main-buffer audit.
- Attacker-controlled dimensionsTreat image header width/height as untrusted and confirm every allocation derived from them is bounded and computed in a type wide enough to hold the maximum product.
Patch
diff --git a/image/decoders/nsJXLDecoder.cpp b/image/decoders/nsJXLDecoder.cpp
index 3cfaf0fd1bb..ed687b6e782 100644
--- a/image/decoders/nsJXLDecoder.cpp
+++ b/image/decoders/nsJXLDecoder.cpp
@@ -418,7 +418,7 @@ nsresult nsJXLDecoder::AllocateFrameBuffers() {
}
if (mPixelFormat.value() == PixelFormat::Cmyk8 &&
- !mKBuffer.resize(size.width * size.height)) {
+ !mKBuffer.resize(size_t(size.width) * size.height)) {
return NS_ERROR_FAILURE;
}