From 9a606ec4e9f6979c4828815865d8fed6ae87e9d4 Mon Sep 17 00:00:00 2001 From: Kirill Osipov Date: Thu, 16 Jul 2026 19:03:34 +0200 Subject: [PATCH] Cap PixelBuffer.from_bytes dimensions at 64k x 64k Following review feedback, the reader now rejects a header claiming a width or height beyond 65536, guarding against malformed byte streams that would otherwise trigger an unreasonable allocation. The writer stays full 32-bit ('unlimited'), and native little-endian order and the transparency mask are kept as-is. Adds Ruby and Python tests for the oversized-header rejection. Ref: https://github.com/KLayout/klayout/pull/2387#issuecomment-4984941409 --- src/gsi/gsi/gsiDeclTlPixelBuffer.cc | 9 ++++++++- testdata/python/layPixelBuffer.py | 4 ++++ testdata/ruby/layPixelBuffer.rb | 9 +++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/gsi/gsi/gsiDeclTlPixelBuffer.cc b/src/gsi/gsi/gsiDeclTlPixelBuffer.cc index a02a98eeb..56b45a655 100644 --- a/src/gsi/gsi/gsiDeclTlPixelBuffer.cc +++ b/src/gsi/gsi/gsiDeclTlPixelBuffer.cc @@ -146,6 +146,12 @@ static tl::PixelBuffer pixel_buffer_from_bytes (const std::vector &data) memcpy (&w, data.data (), 4); memcpy (&h, data.data () + 4, 4); } + // sanity guard against malformed headers: reader caps dimensions at 64k x 64k + // (generous for finely-resolved drawings), writer stays full 32-bit + const uint32_t max_dim = 65536; + if (w > max_dim || h > max_dim) { + throw tl::Exception (tl::to_string (tr ("Invalid PixelBuffer byte stream: width or height exceeds the 65536 x 65536 limit"))); + } if (data.size () != 8 + (size_t) w * (size_t) h * 4) { throw tl::Exception (tl::to_string (tr ("Invalid PixelBuffer byte stream: length does not match the width and height in the header"))); } @@ -229,7 +235,8 @@ Class decl_PixelBuffer ("lay", "PixelBuffer", gsi::method ("from_bytes", &pixel_buffer_from_bytes, gsi::arg ("data"), "@brief Reconstructs a pixel buffer from a byte stream produced by \\to_bytes\n" "\n" - "The width and height are taken from the header and the stream length is checked against them.\n" + "The width and height are taken from the header and the stream length is checked against them. " + "The dimensions are capped at 65536 x 65536; a header exceeding this limit raises an error.\n" "\n" "This method has been added in version 0.30.10." ) + diff --git a/testdata/python/layPixelBuffer.py b/testdata/python/layPixelBuffer.py index 77e24183d..f04ebfde1 100644 --- a/testdata/python/layPixelBuffer.py +++ b/testdata/python/layPixelBuffer.py @@ -78,6 +78,10 @@ class LAYPixelBufferTests(unittest.TestCase): with self.assertRaises(Exception): pya.PixelBuffer.from_bytes(data[:-4]) + # a header beyond the 64k x 64k limit is rejected + with self.assertRaises(Exception): + pya.PixelBuffer.from_bytes(struct.pack("=II", 65537, 1)) + # run unit tests if __name__ == '__main__': diff --git a/testdata/ruby/layPixelBuffer.rb b/testdata/ruby/layPixelBuffer.rb index e8c0db63f..d6054c453 100644 --- a/testdata/ruby/layPixelBuffer.rb +++ b/testdata/ruby/layPixelBuffer.rb @@ -176,6 +176,15 @@ class LAYPixelBuffer_TestClass < TestBase end assert_equal(error, true) + # a header beyond the 64k x 64k limit is rejected + error = false + begin + RBA::PixelBuffer.from_bytes([ 65537, 1 ].pack("VV")) + rescue + error = true + end + assert_equal(error, true) + end def test_11