From d268ac52c7f50517a56f3ad7d9f3c6f118ba9adb Mon Sep 17 00:00:00 2001 From: Larry Gritz Date: Sun, 28 Jun 2026 13:26:07 -0400 Subject: [PATCH] fix(dds): reject corrupt bytes-per-pixel before it forces a huge allocation A DDS file with a garbage pixel-format bpp field (e.g. fmt.bpp ~2.5e9) yields a native bytes-per-pixel (m_Bpp) of hundreds of millions. internal_readimg() then does `new uint8_t[width * m_Bpp]`, a multi-GB allocation that OOMs under the fuzzer's RSS limit. The core resolution/imagesize limits do not catch this: they only see the small output ImageSpec, not the native pixel size, and the existing fmt.bpp check is gated on RGB/LUM/YUV/ALPHA flags that a corrupt header can simply omit. Validate m_Bpp for uncompressed formats right after it is computed in open(): the largest real uncompressed DDS/DXGI pixel is 128-bit (16 bytes), so anything outside [1,16] is a corrupt header and is rejected before any allocation. The check is confined to uncompressed formats because m_Bpp is neither set nor used for compressed (block) formats, which size their data via GetStorageRequirements. Add the fuzzer reproducer as testsuite/dds/src/crash-bpp.dds. crash-1635.dds now reports this error slightly earlier than the prior "bit depth" message; update the reference accordingly. Assisted-by: Claude Code / Claude Opus 4.8 Signed-off-by: Larry Gritz --- src/dds.imageio/ddsinput.cpp | 12 ++++++++++++ testsuite/dds/ref/out.txt | 5 ++++- testsuite/dds/run.py | 3 +++ testsuite/dds/src/crash-bpp.dds | Bin 0 -> 251 bytes 4 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 testsuite/dds/src/crash-bpp.dds diff --git a/src/dds.imageio/ddsinput.cpp b/src/dds.imageio/ddsinput.cpp index eadcd17b31..6f3cff4c80 100644 --- a/src/dds.imageio/ddsinput.cpp +++ b/src/dds.imageio/ddsinput.cpp @@ -618,6 +618,18 @@ DDSInput::open(const std::string& name, ImageSpec& newspec) m_nchans++; } + // Sanity-check the native bytes-per-pixel for uncompressed formats. The + // largest real uncompressed DDS/DXGI pixel format is 128-bit (16 bytes per + // pixel); a larger value comes from a corrupt bpp or format field and would + // otherwise drive a wildly oversized allocation in internal_readimg(). This + // is not caught by the core resolution/imagesize limits, which only see the + // (small) output ImageSpec, not the native pixel size. + if (m_compression == Compression::None && (m_Bpp < 1 || m_Bpp > 16)) { + errorfmt("Invalid DDS bytes-per-pixel ({}). Possible corrupt input?", + m_Bpp); + return false; + } + // fix depth, pitch and mipmaps for later use, if needed if (!(m_dds.fmt.flags & DDS_PF_FOURCC && m_dds.flags & DDS_PITCH)) m_dds.pitch = m_dds.width * m_Bpp; diff --git a/testsuite/dds/ref/out.txt b/testsuite/dds/ref/out.txt index 43c5a59564..3870f2b9df 100644 --- a/testsuite/dds/ref/out.txt +++ b/testsuite/dds/ref/out.txt @@ -266,7 +266,7 @@ src/crash-1634.dds : 16 x 8, 1 channel, uint8 dds channel list: Y textureformat: "Plain Texture" oiio:BitsPerSample: 16 -oiiotool ERROR: read : "src/crash-1635.dds": Unsupported DDS bit depth: 1048576 (maybe it's a corrupted file?) +oiiotool ERROR: read : "src/crash-1635.dds": Invalid DDS bytes-per-pixel (131072). Possible corrupt input? Full command line was: > oiiotool --info -v -a --hash src/crash-1635.dds Reading src/crash-3950.dds @@ -275,3 +275,6 @@ src/crash-3950.dds : 16 x 1, 4 channel, uint8 dds channel list: R, G, B, A compression: "DXT4" textureformat: "Plain Texture" +oiiotool ERROR: read : "src/crash-bpp.dds": Invalid DDS bytes-per-pixel (322122548). Possible corrupt input? +Full command line was: +> oiiotool --info -v -a --hash src/crash-bpp.dds diff --git a/testsuite/dds/run.py b/testsuite/dds/run.py index 3f58dbd502..86a56549e9 100755 --- a/testsuite/dds/run.py +++ b/testsuite/dds/run.py @@ -62,3 +62,6 @@ command += info_command ("src/crash-1634.dds", hash=True) command += info_command ("src/crash-1635.dds", hash=True) command += info_command ("src/crash-3950.dds", hash=True) +# crash-bpp.dds has a corrupt bytes-per-pixel that used to drive a multi-GB +# allocation (OOM) before the native bpp was sanity-checked. +command += info_command ("src/crash-bpp.dds", hash=True) diff --git a/testsuite/dds/src/crash-bpp.dds b/testsuite/dds/src/crash-bpp.dds new file mode 100644 index 0000000000000000000000000000000000000000..003257df46b8f85717df20171ff901ce3ff580d9 GIT binary patch literal 251 zcmZ>930A0KU|?VuxXZu}q#b~mfnfs!6C(qIyQgn}u8T`>3{ZrblYxQpHv`iZAo~zV x2?IkNgo2Tr3<^NzO!@;BptX)6o`wtze{cbi%NQ6q7#LcBluJQz3IjU_0{|jHY3=|3 literal 0 HcmV?d00001