Talos Vulnerability Report

TALOS-2026-2364

LibRaw deflate_dng_load_raw integer overflow vulnerability

April 7, 2026
CVE Number

CVE-2026-20884

SUMMARY

An integer overflow vulnerability exists in the deflate_dng_load_raw functionality of LibRaw Commit 8dc68e2. A specially crafted malicious file can lead to a heap buffer overflow. An attacker can provide a malicious file to trigger this vulnerability.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

LibRaw Commit 8dc68e2

PRODUCT URLS

LibRaw - https://github.com/LibRaw/LibRaw.git

CVSSv3 SCORE

8.1 - CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

CWE

CWE-190 - Integer Overflow or Wraparound

DETAILS

LibRaw is an open-source C/C++ library for reading, decoding, and processing RAW image files from multiple camera manufacturers.

LibRaw is a widely-used library for reading RAW image files from digital cameras. The library includes support for deflate-compressed floating-point DNG files. The decoder routine is vulnerable to a heap buffer overflow because the size calculation used for the memory limit check and the buffer allocation uses 32-bit arithmetic that can overflow when processing attacker-controlled tile dimension values, despite the result being assigned to a 64-bit variable.

Note: This vulnerability is not exploitable with the default max_raw_memory_mb. A separate pre-decoder check in unpack.cpp uses correct 64-bit arithmetic and blocks large images before they reach the vulnerable code. See memory limit requirements below.

The vulnerable function deflate_dng_load_raw() in src/decoders/fp_dng.cpp:

void LibRaw::deflate_dng_load_raw()
{
    [...]
    tile_stripe_data_t tiles;
    tiles.init(ifd, imgdata.sizes, libraw_internal_data.unpacker_data, 
               libraw_internal_data.unpacker_data.order,
               libraw_internal_data.internal_data.input);

    if (ifd->sample_format == 3)
    {
[1]     INT64 raw_bytes = tiles.tileCnt * tiles.tileWidth * 
                          tiles.tileHeight * ifd->samples * sizeof(float);
[2]     if (raw_bytes > INT64(imgdata.rawparams.max_raw_memory_mb) * INT64(1024 * 1024))
            throw LIBRAW_EXCEPTION_TOOBIG;
[3]     float_raw_image = (float *)calloc(tiles.tileCnt * tiles.tileWidth * 
                                          tiles.tileHeight * ifd->samples, 
                                          sizeof(float));
    }
    [...]

    for (size_t y = 0, t = 0; y < imgdata.sizes.raw_height; y += tiles.tileHeight)
    {
        for (size_t x = 0; x < imgdata.sizes.raw_width; x += tiles.tileWidth, ++t)
        {
            [...]
            for (size_t row = 0; row < rowsInTile; ++row)
            {
                [...]
[4]             unsigned char *dst2 = (unsigned char *)&float_raw_image
                    [((y + row) * imgdata.sizes.raw_width + x) * ifd->samples];
[5]             memmove(dst2, dst, colsInTile * ifd->samples * sizeof(float));
            }
        }
    }
}

The tiles.tileWidth and tiles.tileHeight values come from the TileWidth and TileLength TIFF tags. The tiles.tileCnt is calculated as ceil(ImageWidth/TileWidth) * ceil(ImageLength/TileHeight). The ifd->samples comes from SamplesPerPixel. All of these are attacker-controlled through the DNG file.

At [1], the code calculates the required buffer size. Although the result is assigned to INT64, the operands are not cast to INT64:

INT64 raw_bytes = tiles.tileCnt * tiles.tileWidth * tiles.tileHeight * ifd->samples * sizeof(float);

The types involved are: - tiles.tileCnt - int (32-bit) - tiles.tileWidth - unsigned (32-bit) - tiles.tileHeight - unsigned (32-bit) - ifd->samples - int (32-bit) - sizeof(float) - size_t (64-bit)

The first four operands are multiplied left-to-right in 32-bit arithmetic. Only after the 32-bit multiplication completes is the result promoted to 64-bit for the final sizeof(float) multiplication. If the 32-bit product overflows, the corrupted value is what gets promoted.

At [2], the corrupted raw_bytes value is compared against the memory limit. Because the check uses the overflowed value, it passes when it should fail.

At [3], the allocation also uses 32-bit arithmetic (same pattern), resulting in an undersized float_raw_image buffer.

At [4], the code calculates dst2 as a pointer into float_raw_image using the original (non-overflowed) tile dimensions. The index ((y + row) * raw_width + x) * samples is computed using the correct image dimensions.

At [5], memmove() writes tile data to dst2. When the tile processing reaches positions beyond the undersized allocation, this write overflows the heap buffer.

The in-function check at [1]-[2] can be bypassed via overflow, but exploitation is blocked by separate pre-decoder checks in LibRaw::unpack() (src/decoders/unpack.cpp) that use correct 64-bit arithmetic. For example, the check at lines 345-349:

if (INT64(rwidth) * INT64(rheight + 8) *
        INT64(sizeof(imgdata.rawdata.raw_image[0])) * 4 
        + INT64(libraw_internal_data.unpacker_data.meta_length) >
        INT64(imgdata.rawparams.max_raw_memory_mb) * INT64(1024 * 1024))
    throw LIBRAW_EXCEPTION_TOOBIG;

These pre-decoder checks happen BEFORE deflate_dng_load_raw() is called and correctly reject large images with the default limit:

  1. Pre-decoder checks use correct 64-bit arithmetic throughout
  2. Default max_raw_memory_mb is 2048 MB (2 GB)
  3. For 38000×38000 image: ~10.8 GB required → rejected by default
  4. Required: max_raw_memory_mb >= 11'017 MB (~11 GB) to reach vulnerable code

Once max_raw_memory_mb is elevated to pass the pre-decoder check, the flawed in-function check at [1]-[2] is reached and bypassed via overflow. Applications that increase this limit become vulnerable.

Example: Using a tiled DNG with ImageWidth=38000, ImageLength=38000, TileWidth=9500, TileHeight=9500, SamplesPerPixel=3:

The tile dimensions create 16 tiles (4×4 layout). The 32-bit multiplication at [1]:

tileCnt * tileWidth * tileHeight * samples = 16 * 9500 * 9500 * 3 = 4'332'000'000

This exceeds UINT32_MAX (4’294’967’295) and wraps to 37'032'704. Multiplied by sizeof(float):

37'032'704 * 4 = 148'130'816 bytes (~141 MB)

At [2], the check sees ~141 MB which passes any reasonable memory limit. At [3], the allocation requests only ~141 MB. However, the true size needed is:

16 * 9500 * 9500 * 3 * 4 = 17'328'000'000 bytes (~16.1 GB)

At [4]-[5], the processing loops iterate using the original tile dimensions. When y and row advance beyond the ~141 MB buffer, memmove() writes to memory beyond the allocated region.

Any application that calls unpack() on untrusted DNG files with elevated max_raw_memory_mb is vulnerable to this heap buffer overflow, which can result in heap corruption and potential code execution.

Crash Information

AddressSanitizer:DEADLYSIGNAL
=================================================================
==69447==ERROR: AddressSanitizer: SEGV on unknown address 0x000110bb2240 (pc 0x0001017cfc28 bp 0x00016eeb6550 sp 0x00016eeb5d00 T0)
==69447==The signal is caused by a WRITE memory access.
    #0 0x0001017cfc28 in __sanitizer_internal_memmove+0x84 (libclang_rt.asan_osx_dynamic.dylib:arm64e+0x53c28)
    #1 0x000100f16c1c in LibRaw::deflate_dng_load_raw() fp_dng.cpp:419
    #2 0x000100fcba84 in LibRaw::unpack() unpack.cpp:447
    #3 0x000100e8d400 in main poc_deflate_dng_overflow.cpp:75
    #4 0x00018f671d50  (<unknown module>)

==69447==Register values:
 x[0] = 0x0000000110bb2240   x[1] = 0x00000005b2e89e90   x[2] = 0x000000000001bd50   x[3] = 0x00000005b0b574a4
 x[4] = 0x00000005b2ea5bdc   x[5] = 0x0000000000000000   x[6] = 0x00000005b2ea5bdf   x[7] = 0x0000000000000000
 x[8] = 0x000000000001bd40   x[9] = 0x00000005b2e89ef0  x[10] = 0x0000000110bb2260  x[11] = 0x000000000001bd40
x[12] = 0x0000007022199bf0  x[13] = 0x00000000000006f5  x[14] = 0x00000000000006f0  x[15] = 0x00000005b0b6534c
x[16] = 0x0000000302803ea8  x[17] = 0x00000005b0b6c2a0  x[18] = 0x0000000000000000  x[19] = 0x000000000001bd50
x[20] = 0x00000005b2e89e90  x[21] = 0x0000000110bb2240  x[22] = 0x0000000000000514  x[23] = 0x000000010220a368
x[24] = 0x00000001022095e8  x[25] = 0x000000000001bd0f  x[26] = 0x0000007000020000  x[27] = 0x000000000001bd50
x[28] = 0x000000002dde41ac     fp = 0x000000016eeb6550     lr = 0x0000000101801598     sp = 0x000000016eeb5d00
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV fp_dng.cpp:419 in LibRaw::deflate_dng_load_raw()
==69447==ABORTING
TIMELINE

2026-02-12 - Initial Vendor Contact
2026-02-12 - Vendor Disclosure
2026-04-06 - Vendor Patch Release
2026-04-07 - Public Release

Credit

Discovered by Francesco Benvenuto of Cisco Talos.