Skip to content

OOM on crafted BMP via unbounded nb_colors field (_load_bmp) #477

@JorgeBarredo14

Description

@JorgeBarredo14

_load_bmp reads nb_colors from bytes 0x2E-0x31 of the BITMAPINFOHEADER as a signed int and passes it straight to colormap.assign(nb_colors). The only check is for nb_colors == 0, any non-zero value goes through. With nb_colors = 0x3FFFFFFF and bpp = 8 the call becomes colormap.assign(0x3FFFFFFF) and tries to allocate ~4GB. It stays under the internal cimg_max_buf_size (16GB on 64-bit) so CImg does not reject it,
and the process runs out of memory.

Current code (CImg.h around line 56645):

int nb_colors = header[0x2E] + (header[0x2F]<<8) + (header[0x30]<<16) + (header[0x31]<<24);
// ...
if (bpp<16) { if (!nb_colors) nb_colors = 1<<bpp; } else nb_colors = 0;
if (nb_colors) { colormap.assign(nb_colors); cimg::fread(colormap._data,nb_colors,nfile); }

The check on nb_colors only handles the zero case. Negative values from the signed int cast, or values larger than 1<<bpp, are never clamped.

CVE-2022-1325 fixed the dx/dy allocation in 3.1.0 by adding cimg_max_buf_size, but that limit is 16GB so it does not catch a 4GB colormap. The nb_colors path was not covered by that fix.

To Reproduce

Attached PoC: poc_nbcolors_oom.bmp (55 bytes).

Reproduces on current master (commit 3de5134, version 3.7.5-pre) with a minimal harness calling CImg().load_bmp(path).

ASan output:

ERROR: libFuzzer: out-of-memory (malloc(4294967292))
    #8  in cimg_library::CImg<int>::assign() CImg.h:13579
    #9  in cimg_library::CImg<unsigned char>::_load_bmp() CImg.h:56649

Expected behavior

Reject the file or clamp nb_colors to the valid range for the bpp.

Fix idea

--- a/CImg.h
+++ b/CImg.h
@@ -56645,7 +56645,11 @@
     int nb_colors = header[0x2E] + (header[0x2F]<<8) + (header[0x30]<<16) + (header[0x31]<<24);
     // ...
-    if (bpp<16) { if (!nb_colors) nb_colors = 1<<bpp; } else nb_colors = 0;
+    if (bpp<16) {
+      const int max_colors = 1 << bpp;
+      if (nb_colors <= 0 || nb_colors > max_colors) nb_colors = max_colors;
+    } else nb_colors = 0;
     if (nb_colors) { colormap.assign(nb_colors); cimg::fread(colormap._data,nb_colors,nfile); }

Same pattern (signed int read from file, passed to assign without bounds)

poc_nbcolors_oom.bmp

probably exists in other loaders too, worth a grep.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions