VYPR
Unrated severityNVD Advisory· Published May 27, 2026

CVE-2025-70103

CVE-2025-70103

Description

Heap buffer overflow vulnerability in libjxl 0.12.0 via crafted PBM images to the jxl::extras::DecodeImagePNM function in file lib/extras/dec/pnm.cc.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Heap buffer overflow in libjxl 0.12.0 when decoding crafted PBM images via jxl::extras::DecodeImagePNM.

Vulnerability

A heap buffer overflow vulnerability exists in libjxl version 0.12.0 within the jxl::extras::DecodeImagePNM function in lib/extras/dec/pnm.cc. Crafted PBM images cause the decoder to write beyond the allocated buffer, as memcpy attempts to write 24 bytes into a 16-byte buffer due to incorrect size calculations during PNM frame initialization ([1]).

Exploitation

An attacker must provide a crafted PBM image to the libjxl encoder (cjxl) via file input. No prior authentication or special privileges are required; the vulnerability is triggered by opening the malicious image, as demonstrated by running ./cjxl ./2_PBM_lib_extras_dec_pnm_cc_554 --disable_output ([1]). The overflow occurs during pixel data copying at pnm.cc:554.

Impact

Successful exploitation results in a heap buffer overflow, potentially leading to memory corruption. This could allow an attacker to cause denial of service or, in some cases, achieve arbitrary code execution depending on memory layout and mitigations. The overflow occurs when processing PBM images, affecting the integrity of the process.

Mitigation

The fix was merged in pull request #4338 ([3]), which added unified buffer size checks and additional channel buffer validation. Updating to a version of libjxl containing this commit (e.g., after August 7, 2025) resolves the issue. There is no known workaround for unpatched versions; users should avoid processing untrusted PBM images until the patch is applied.

AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2
  • Libjxl/Libjxlreferences2 versions
    (expand)+ 1 more
    • (no CPE)
    • (no CPE)range: =0.12.0

Patches

1
6377ff837357

Merge 5ef063e7c815069e5c453f1f76d4f0d2c6d93076 into 1a9611d3162b44ec2207d825808e234119b6e88b

https://github.com/libjxl/libjxlAlexanderAug 7, 2025via nvd-ref
1 file changed · +22 0
  • lib/extras/dec/pnm.cc+22 0 modified
    @@ -547,6 +547,13 @@ Status DecodeImagePNM(const Span<const uint8_t> bytes,
       }
       if (ec_out.empty()) {
         const bool flipped_y = header.bits_per_sample == 32;  // PFMs are flipped
    +    
    +    // Validate output buffer size before processing rows
    +    const size_t required_bytes = frame->color.stride * header.ysize;
    +    if (frame->color.pixels_size < required_bytes) {
    +      return JXL_FAILURE("Output buffer too small for PNM image");
    +    }
    +
         for (size_t y = 0; y < header.ysize; ++y) {
           size_t y_in = flipped_y ? header.ysize - 1 - y : y;
           const uint8_t* row_in = &pos[y_in * frame->color.stride];
    @@ -556,6 +563,21 @@ Status DecodeImagePNM(const Span<const uint8_t> bytes,
       } else {
         JXL_RETURN_IF_ERROR(PackedImage::ValidateDataType(data_type));
         size_t pwidth = PackedImage::BitsPerChannel(data_type) / 8;
    +    
    +    // Validate output buffer size before processing pixels
    +    const size_t required_pixels = header.xsize * header.ysize * 
    +                                   frame->color.pixel_stride();
    +    if (frame->color.pixels_size < required_pixels) {
    +      return JXL_FAILURE("Pixel buffer too small for PNM image");
    +    }
    +    
    +    // Validate extra channels buffers
    +    for (const auto& ec : frame->extra_channels) {
    +      if (ec.pixels_size < required_pixels) {
    +        return JXL_FAILURE("Extra channel buffer too small");
    +      }
    +    }
    +
         for (size_t y = 0; y < header.ysize; ++y) {
           for (size_t x = 0; x < header.xsize; ++x) {
             memcpy(out, pos, frame->color.pixel_stride());
    

Vulnerability mechanics

Root cause

"Missing buffer-size validation before memcpy in DecodeImagePNM allows writing beyond allocated output buffer."

Attack vector

An attacker crafts a malformed PBM image that causes the PNM decoder to allocate an undersized output buffer (e.g., 16 bytes) during frame initialization at line 526 of pnm.cc [ref_id=1]. When the decoder later copies pixel data via `memcpy` at line 554, it writes beyond the allocated buffer (e.g., 24 bytes into a 16-byte region), resulting in a heap-buffer-overflow [ref_id=1]. The attack is triggered by feeding the crafted image to the `cjxl` encoder tool (or any code path calling `DecodeBytes` -> `DecodeImagePNM`) without any special privileges beyond file access [ref_id=1].

Affected code

The vulnerability resides in `lib/extras/dec/pnm.cc` within the `jxl::extras::DecodeImagePNM` function. The heap-buffer-overflow occurs at line 554 during a `memcpy` operation that writes pixel data into an undersized buffer allocated at line 526 [ref_id=1]. The patch adds buffer-size validation checks before processing rows (color.stride * header.ysize) and before processing pixels (xsize * ysize * pixel_stride), as well as validation for extra channel buffers [patch_id=2691380].

What the fix does

The patch adds three buffer-size validation checks before any pixel data is copied [patch_id=2691380]. First, it verifies that `frame->color.pixels_size` is at least `frame->color.stride * header.ysize` for the row-based code path. Second, it checks that `frame->color.pixels_size` is at least `header.xsize * header.ysize * frame->color.pixel_stride()` for the pixel-based code path. Third, it validates that every extra channel buffer also meets the required pixel count. If any check fails, the function returns `JXL_FAILURE` instead of performing the out-of-bounds write [patch_id=2691380].

Preconditions

  • inputAttacker must supply a crafted PBM image that triggers incorrect buffer size calculation during frame initialization
  • networkThe vulnerable code path is reached via DecodeBytes -> DecodeImagePNM, e.g., when running cjxl on the crafted file

Reproduction

Run the `cjxl` encoder with the crafted PBM sample as input and `--disable_output` flag: `./cjxl ./2_PBM_lib_extras_dec_pnm_cc_554 --disable_output` [ref_id=1]. Under AddressSanitizer, this reproduces a heap-buffer-overflow write of size 24 at address 0x5020000000a0, which is 0 bytes after a 16-byte region [ref_id=1].

Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.