LibGfx: Fix heap overflow applying Exif transpose to non-square images

Problem: Decoding a non-square image whose Exif orientation is 5
(transpose) writes one pixel past the end of the destination bitmap —
an ASan heap-buffer-overflow in ExifOrientedBitmap::set_pixel.

Cause: oriented_position() mapped orientation 5 by composing the
“flip-horizontally” and “rotate-90-clockwise” helpers. Each helper
mirrors using the source width. But after the rotate, the point is
already in the transposed coordinate space — where the relevant
dimension is the source height. For a non-square image, that composition
produces x-coordinates past the destination width. Only square images
happened to stay in bounds. The destination bitmap is the transposed
size — so the out-of-range column wrote past its allocation.

Fix: Map orientation 5 directly as a transpose across the main diagonal:
source (x, y) to destination (y, x).

Fixes https://github.com/LadybirdBrowser/ladybird/issues/10102
This commit is contained in:
sideshowbarker 2026-06-15 08:43:02 +09:00 committed by Jelle Raaijmakers
parent 69654a3a7c
commit 20b1129352
3 changed files with 17 additions and 1 deletions

View file

@ -92,7 +92,7 @@ private:
case Orientation::FlipVertically:
return IntPoint(point.x(), m_height - point.y() - 1);
case Orientation::Rotate90ClockwiseThenFlipHorizontally:
return flip_horizontally(rotate_90_clockwise(point));
return IntPoint(point.y(), point.x());
case Orientation::Rotate90Clockwise:
return rotate_90_clockwise(point);
case Orientation::FlipHorizontallyThenRotate90Clockwise:

View file

@ -546,6 +546,22 @@ TEST_CASE(test_exif)
EXPECT_EQ(frame.image->get_pixel(190, 10), Gfx::Color(255, 0, 0));
}
TEST_CASE(test_exif_orientation_transpose_non_square)
{
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("png/exif-orientation-5.png"sv)));
EXPECT(Gfx::PNGImageDecoderPlugin::sniff(file->bytes()));
auto plugin_decoder = TRY_OR_FAIL(Gfx::PNGImageDecoderPlugin::create(file->bytes()));
auto frame = TRY_OR_FAIL(expect_single_frame_of_size(*plugin_decoder, { 1, 3 }));
EXPECT(plugin_decoder->metadata().has_value());
auto const& exif_metadata = static_cast<Gfx::ExifMetadata const&>(plugin_decoder->metadata().value());
EXPECT_EQ(*exif_metadata.orientation(), Gfx::TIFF::Orientation::Rotate90ClockwiseThenFlipHorizontally);
EXPECT_EQ(frame.image->get_pixel(0, 0), Gfx::Color(255, 0, 0));
EXPECT_EQ(frame.image->get_pixel(0, 1), Gfx::Color(0, 255, 0));
EXPECT_EQ(frame.image->get_pixel(0, 2), Gfx::Color(0, 0, 255));
}
TEST_CASE(test_png_malformed_frame)
{
Array test_inputs = {

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 B