Skip to content

integer overflow in PNM size check bypasses memory guard (_load_pnm) #478

@JorgeBarredo14

Description

@JorgeBarredo14

The size check in _load_pnm compares the declared pixel count against the file size, but the multiplication is done in unsigned int and wraps at
2^32. Oversized images go through, then assign() uses 64-bit arithmetic and tries to allocate the full huge size.

Current code (CImg.h around line 57390 and 57428 in _load_pnm):

unsigned int ppm_type, W, H, D = 1, colormax = 255;
// ... W, H, D parsed from the ASCII header ...
const cimg_int64 siz = cimg::fsize(filename);
if (W*H*D > siz)                   // unsigned int overflow here
    throw CImgIOException(...);
assign(W, H, D, channels);         // safe_size is 64-bit, sees the real size

With W = 65536, H = 65537, D = 1:

W*H*D as unsigned int = 65536 * 65537 = 0x100010000 -> wraps to 65536
65536 > siz -> false for any file >= 64KB, so check passes
assign(65536, 65537, 1, 1) allocates 4,295,032,832 bytes -> OOM

CVE-2020-25693 was the integer overflow in _load_pnm fixed in 2.9.3. The fix there added 64-bit checks inside assign/safe_size, but this earlier pre-assign guard was left in 32-bit. So the guard is still bypassable on current master (commit 3de5134, version 3.7.5-pre).

To Reproduce

Attached PoC: poc_pnm_intoverflow.pgm (65,556 bytes).

Header: P5\n65536 65537\n255\n followed by 65537 bytes of pixel data. Load with CImg().load_pnm(path).

ASan output:

ERROR: libFuzzer: out-of-memory (malloc(4295032832))
    #8  in cimg_library::CImg<unsigned char>::assign() CImg.h:13579
    #9  in cimg_library::CImg<unsigned char>::_load_pnm() (via assign)

Expected behavior

Reject the image, same as the internal safe_size does when it is reached directly.

Fix idea

--- a/CImg.h
+++ b/CImg.h
@@ -57425,7 +57425,7 @@
     const cimg_int64 siz = cimg::fsize(filename);
-    if (W*H*D > siz)
+    if ((cimg_int64)W*H*D > siz)
         throw CImgIOException(_cimg_instance
                               "load_pnm(): File size (%lld) inconsistent with "
                               "image dimensions (%u,%u,%u).",
                               cimg_instance,
                               (long long)siz,W,H,D);

Same pattern (unsigned int multiplication against a 64-bit size) is worth checking in the other ASCII/binary loaders (pandore, inr, etc.).

poc_pnm_intoverflow.zip

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