Reject BMP RLE absolute runs that overrun the current scanline In src/common/imagbmp.cpp::LoadBMPData() the absolute-mode branches of both the BI_RLE4 (around line 763) and BI_RLE8 (around line 870) decode loops increment 'column' for each pixel without checking it against 'width'. The image buffer is sized width * height * 3 and 'poffset' is computed as line * width * 3 + column * 3, so an absolute escape with a count larger than (width - column) keeps writing through the rest of the row and into adjacent rows or past the end of the buffer entirely on the last decoded scanline. The neighbouring encoded-mode branches at lines 798 and 896 already use "&& column < width" to clamp runs to the row width, and the delta-mode branch at lines 758 and 863 rejects out-of-range row offsets, so the absolute branches are the only RLE paths left without bounds checking. Reject the file with return false when the absolute run would extend past the right edge of the current row, matching the existing "return false on malformed input" pattern in the same function. Add a regression test that loads a 4x4 8bpp RLE BMP with an absolute escape claiming 100 pixels on the first row and expects the loader to fail rather than write past the image buffer. Closes #26496.
Validate IFF BMHD fields to prevent pixel-buffer overflow
wxIFFDecoder::ReadIFF() in src/common/imagiff.cpp parses the BMHD
chunk without bounding the width, height or bitplane count. The
subsequent pixel buffer is allocated with
m_image->p = new byte[bmhd_width * bmhd_height * 3];
using signed-int multiplication. With bmhd_width = 21849 and
bmhd_height = 65535 (both legal 16-bit BMHD values) the product
4,295,622,645 overflows int and wraps down to 655,349, so only
~640 KiB is actually allocated. The BODY decode loop then writes
3 * bmhd_width bytes per row, so a BODY chunk supplying just 10
lineskips of zeros (lineskip = 2732 for this width, total 27,320
bytes) is enough to overrun the allocation. A bmhd_bitplanes or
bmhd_width of zero also makes lineskip * bmhd_bitplanes zero and
causes a divide-by-zero in the height computation a few lines
later.
Reject malformed BMHD chunks at parse time: require positive
width, height and bitplane count, cap the bitplane count at 32
(the largest format the decoder handles is 24-bit ILBM), and cap
bmhd_width * bmhd_height so that the product multiplied by 3
stays within INT_MAX. This makes the existing int-typed buffer
size computation safe and keeps the patch local to the BMHD
parsing branch.
A unit test in tests/image/image.cpp builds the malformed IFF
described above in memory, registers the IFF handler and asserts
that LoadFile() rejects it; without the fix the test triggers a
heap-buffer-overflow during BODY decoding.
Closes #26497.
Use wxDEPRECATED_BUT_USED_INTERNALLY_MSG() in wxPG headers It shouldn't be necessary to use this macro instead of the previously used wxDEPRECATED_MSG() here because none of the functions is actually used by wxWidgets, but MSVS 2026 somehow thinks they are and gives warnings about it, so avoid the warning to fix the GitHub Actions CI job using this compiler.
| ... | ... | @@ -210,7 +210,7 @@ public: |
| 210 | 210 | virtual int GetCustomColourIndex() const;
|
| 211 | 211 | |
| 212 | 212 | #if WXWIN_COMPATIBILITY_3_2
|
| 213 | - wxDEPRECATED_MSG("use ValueToString with 'flags' argument as wxPGPropValFormatFlags")
|
|
| 213 | + wxDEPRECATED_BUT_USED_INTERNALLY_MSG("use ValueToString with 'flags' argument as wxPGPropValFormatFlags")
|
|
| 214 | 214 | virtual wxString ValueToString(wxVariant& value, int flags) const override
|
| 215 | 215 | {
|
| 216 | 216 | m_oldValueToStringCalled = true;
|
| ... | ... | @@ -506,7 +506,7 @@ public: |
| 506 | 506 | |
| 507 | 507 | virtual void OnSetValue() override;
|
| 508 | 508 | #if WXWIN_COMPATIBILITY_3_2
|
| 509 | - wxDEPRECATED_MSG("use ValueToString with 'flags' argument as wxPGPropValFormatFlags")
|
|
| 509 | + wxDEPRECATED_BUT_USED_INTERNALLY_MSG("use ValueToString with 'flags' argument as wxPGPropValFormatFlags")
|
|
| 510 | 510 | virtual wxString ValueToString(wxVariant& value, int flags) const override
|
| 511 | 511 | {
|
| 512 | 512 | m_oldValueToStringCalled = true;
|
| ... | ... | @@ -515,7 +515,7 @@ public: |
| 515 | 515 | #endif // WXWIN_COMPATIBILITY_3_2
|
| 516 | 516 | virtual wxString ValueToString(wxVariant& value, wxPGPropValFormatFlags flags = wxPGPropValFormatFlags::Null) const override;
|
| 517 | 517 | #if WXWIN_COMPATIBILITY_3_2
|
| 518 | - wxDEPRECATED_MSG("use StringToValue with 'flags' argument as wxPGPropValFormatFlags")
|
|
| 518 | + wxDEPRECATED_BUT_USED_INTERNALLY_MSG("use StringToValue with 'flags' argument as wxPGPropValFormatFlags")
|
|
| 519 | 519 | virtual bool StringToValue(wxVariant& variant, const wxString& text,
|
| 520 | 520 | int flags) const override
|
| 521 | 521 | {
|
| ... | ... | @@ -531,7 +531,7 @@ public: |
| 531 | 531 | // If wxPGPropValFormatFlags::FullValue is not set in flags, then the value is interpreted
|
| 532 | 532 | // as index to choices list. Otherwise, it is actual value.
|
| 533 | 533 | #if WXWIN_COMPATIBILITY_3_2
|
| 534 | - wxDEPRECATED_MSG("use IntToValue with 'flags' argument as wxPGPropValFormatFlags")
|
|
| 534 | + wxDEPRECATED_BUT_USED_INTERNALLY_MSG("use IntToValue with 'flags' argument as wxPGPropValFormatFlags")
|
|
| 535 | 535 | virtual bool IntToValue(wxVariant& variant, int number, int flags) const override
|
| 536 | 536 | {
|
| 537 | 537 | m_oldIntToValueCalled = true;
|
| ... | ... | @@ -763,6 +763,13 @@ bool LoadBMPData(wxImage * image, const BMPDesc& desc, |
| 763 | 763 | {
|
| 764 | 764 | // absolute mode (pixels not runs)
|
| 765 | 765 | int absolute = aByte;
|
| 766 | + // RLE runs do not span scanlines; reject a
|
|
| 767 | + // file whose absolute run would advance past
|
|
| 768 | + // the right edge of the row and write into
|
|
| 769 | + // adjacent rows or past the end of the image
|
|
| 770 | + // buffer.
|
|
| 771 | + if ( column + absolute > width )
|
|
| 772 | + return false;
|
|
| 766 | 773 | wxUint8 nibble[2] ;
|
| 767 | 774 | int readBytes = 0 ;
|
| 768 | 775 | for (int k = 0; k < absolute; k++)
|
| ... | ... | @@ -868,6 +875,13 @@ bool LoadBMPData(wxImage * image, const BMPDesc& desc, |
| 868 | 875 | {
|
| 869 | 876 | // absolute mode (pixels not runs)
|
| 870 | 877 | int absolute = aByte;
|
| 878 | + // RLE runs do not span scanlines; reject a
|
|
| 879 | + // file whose absolute run would advance past
|
|
| 880 | + // the right edge of the row and write into
|
|
| 881 | + // adjacent rows or past the end of the image
|
|
| 882 | + // buffer.
|
|
| 883 | + if ( column + absolute > width )
|
|
| 884 | + return false;
|
|
| 871 | 885 | for (int k = 0; k < absolute; k++)
|
| 872 | 886 | {
|
| 873 | 887 | aByte = stream.GetC();
|
| ... | ... | @@ -29,6 +29,7 @@ |
| 29 | 29 | #include "wx/palette.h"
|
| 30 | 30 | #endif // wxUSE_PALETTE
|
| 31 | 31 | |
| 32 | +#include <limits.h> // for INT_MAX
|
|
| 32 | 33 | #include <stdlib.h>
|
| 33 | 34 | #include <string.h>
|
| 34 | 35 | |
| ... | ... | @@ -404,6 +405,23 @@ int wxIFFDecoder::ReadIFF() |
| 404 | 405 | // bmhd_masking = *(dataptr + 8 + 9); -- unused currently
|
| 405 | 406 | bmhd_compression = *(dataptr + 8 + 10); // get compression
|
| 406 | 407 | bmhd_transcol = iff_getword(dataptr + 8 + 12);
|
| 408 | + |
|
| 409 | + // Reject malformed BMHD chunks: zero width/height or bitplanes
|
|
| 410 | + // would later cause a divide-by-zero when computing the lineskip
|
|
| 411 | + // and row count for the BODY chunk; oversized dimensions would
|
|
| 412 | + // overflow the signed-int product used to size the pixel buffer
|
|
| 413 | + // (new byte[bmhd_width * bmhd_height * 3] below), leaving an
|
|
| 414 | + // undersized allocation behind for the BODY decode loop to
|
|
| 415 | + // overrun. Cap bmhd_width * bmhd_height so that the same product
|
|
| 416 | + // multiplied by 3 stays within INT_MAX.
|
|
| 417 | + if (bmhd_width <= 0 || bmhd_height <= 0
|
|
| 418 | + || bmhd_bitplanes <= 0 || bmhd_bitplanes > 32
|
|
| 419 | + || static_cast<wxUint64>(bmhd_width)
|
|
| 420 | + * static_cast<wxUint64>(bmhd_height)
|
|
| 421 | + > static_cast<wxUint64>(INT_MAX) / 3) {
|
|
| 422 | + break;
|
|
| 423 | + }
|
|
| 424 | + |
|
| 407 | 425 | BMHDok = true; // got BMHD
|
| 408 | 426 | dataptr += 8 + chunkLen; // to next chunk
|
| 409 | 427 | }
|
| ... | ... | @@ -1387,6 +1387,47 @@ TEST_CASE_METHOD(ImageHandlersInit, "wxImage::BadIFF", "[image][iff][error]") |
| 1387 | 1387 | REQUIRE( !img.LoadFile(mis, wxBITMAP_TYPE_IFF) );
|
| 1388 | 1388 | }
|
| 1389 | 1389 | |
| 1390 | +TEST_CASE_METHOD(ImageHandlersInit, "wxImage::BadIFFBmhdOverflow",
|
|
| 1391 | + "[image][iff][error]")
|
|
| 1392 | +{
|
|
| 1393 | + // BMHD width = 21849, height = 65535 gives bmhd_width * bmhd_height * 3
|
|
| 1394 | + // = 4 295 622 645, which overflows the 32-bit signed int product used by
|
|
| 1395 | + // wxIFFDecoder::ReadIFF() to size the pixel buffer and wraps down to
|
|
| 1396 | + // 655 349. The subsequent BODY decode loop writes 3 * bmhd_width bytes
|
|
| 1397 | + // per scanline, so a BODY chunk containing 10 lineskips' worth of zeros
|
|
| 1398 | + // (lineskip = 2732 for this width, total 27320 bytes) is enough to
|
|
| 1399 | + // overrun the undersized heap allocation. The fix rejects the file at
|
|
| 1400 | + // BMHD validation time.
|
|
| 1401 | + wxImage::AddHandler(new wxIFFHandler);
|
|
| 1402 | + |
|
| 1403 | + static const unsigned char header[] =
|
|
| 1404 | + {
|
|
| 1405 | + 0x46,0x4f,0x52,0x4d, // "FORM"
|
|
| 1406 | + 0x00,0x00,0x6a,0xe0, // FORM length = 27360
|
|
| 1407 | + 0x49,0x4c,0x42,0x4d, // "ILBM"
|
|
| 1408 | + 0x42,0x4d,0x48,0x44, // "BMHD"
|
|
| 1409 | + 0x00,0x00,0x00,0x14, // BMHD chunk length = 20
|
|
| 1410 | + 0x55,0x59, // width = 21849
|
|
| 1411 | + 0xff,0xff, // height = 65535
|
|
| 1412 | + 0x00,0x00,0x00,0x00, // x, y
|
|
| 1413 | + 0x01, // nPlanes
|
|
| 1414 | + 0x00, // masking
|
|
| 1415 | + 0x00, // compression (BI_RGB, uncompressed)
|
|
| 1416 | + 0x00, // pad
|
|
| 1417 | + 0x00,0x00, // transparentColor
|
|
| 1418 | + 0x00,0x00, // x/y aspect
|
|
| 1419 | + 0x00,0x00,0x00,0x00, // page width / height
|
|
| 1420 | + 0x42,0x4f,0x44,0x59, // "BODY"
|
|
| 1421 | + 0x00,0x00,0x6a,0xb8, // BODY chunk length = 27320
|
|
| 1422 | + };
|
|
| 1423 | + std::vector<unsigned char> data(header, header + WXSIZEOF(header));
|
|
| 1424 | + data.resize(data.size() + 27320, 0);
|
|
| 1425 | + |
|
| 1426 | + wxMemoryInputStream mis(data.data(), data.size());
|
|
| 1427 | + wxImage img;
|
|
| 1428 | + REQUIRE( !img.LoadFile(mis, wxBITMAP_TYPE_IFF) );
|
|
| 1429 | +}
|
|
| 1430 | + |
|
| 1390 | 1431 | #endif // wxUSE_IFF
|
| 1391 | 1432 | |
| 1392 | 1433 | TEST_CASE_METHOD(ImageHandlersInit, "wxImage::DibPadding", "[image]")
|
—
View it on GitLab.
You're receiving this email because of your account on gitlab.com. Manage all notifications · Help