From b305cb4e4de77fb37807aa89e02643c4b0240d8f Mon Sep 17 00:00:00 2001 From: MarkLee131 Date: Mon, 11 May 2026 01:54:54 +0800 Subject: [PATCH] Avoid out-of-bounds palette read in 8bpp BMP decoder The non-RLE 8bpp branch at imagbmp.cpp:903, plus the RLE absolute and RLE encoded branches a few lines above, all index cmap[aByte] without checking aByte against the palette colour count. A BMP that pairs a small palette with a colour-index byte >= ncolors reads past the palette and the value flows into the decoded pixel. Reject the file (return false) at each site, matching the surrounding "return false on malformed input" pattern. Closes #26438. Closes #26439. --- src/common/imagbmp.cpp | 6 ++++++ tests/image/image.cpp | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/common/imagbmp.cpp b/src/common/imagbmp.cpp index 0ae3dfdde2..df3b52c0cd 100644 --- a/src/common/imagbmp.cpp +++ b/src/common/imagbmp.cpp @@ -873,6 +873,8 @@ bool LoadBMPData(wxImage * image, const BMPDesc& desc, aByte = stream.GetC(); if ( !stream.IsOk() ) return false; + if ( aByte >= ncolors ) + return false; ptr[poffset ] = cmap[aByte].r; ptr[poffset + 1] = cmap[aByte].g; ptr[poffset + 2] = cmap[aByte].b; @@ -889,6 +891,8 @@ bool LoadBMPData(wxImage * image, const BMPDesc& desc, else { // encoded mode (repeat aByte first times) + if ( aByte >= ncolors ) + return false; for ( int l = 0; l < first && column < width; l++ ) { ptr[poffset ] = cmap[aByte].r; @@ -900,6 +904,8 @@ bool LoadBMPData(wxImage * image, const BMPDesc& desc, } else { + if ( aByte >= ncolors ) + return false; ptr[poffset ] = cmap[aByte].r; ptr[poffset + 1] = cmap[aByte].g; ptr[poffset + 2] = cmap[aByte].b; diff --git a/tests/image/image.cpp b/tests/image/image.cpp index 50336c838d..1b6cb4f66b 100644 --- a/tests/image/image.cpp +++ b/tests/image/image.cpp @@ -1678,6 +1678,26 @@ TEST_CASE_METHOD(ImageHandlersInit, "wxImage::BMP", "[image][bmp]") LoadMalformedImage("image/badrle4.bmp", wxBITMAP_TYPE_BMP); LoadMalformedImage("image/width-times-height-overflow.bmp", wxBITMAP_TYPE_BMP); } + SECTION("8bpp colour index past end of palette") + { + // An 8bpp BMP whose pixel stream contains a colour index larger + // than the palette colour count. Used to read past the palette + // buffer in the decode loops. + static const unsigned char data[] = + { + 0x42,0x4d,0x3a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x09,0x00, + 0x00,0x00,0x28,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x01,0x00, + 0x00,0x00,0x01,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x42,0x4d,0x3a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2b,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05, + 0x00,0x00,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x01,0x00,0x00, + }; + wxMemoryInputStream mis(data, WXSIZEOF(data)); + wxImage img; + REQUIRE( !img.LoadFile(mis, wxBITMAP_TYPE_BMP) ); + } wxImage image; SECTION("32bpp alpha") {