diff --git a/Libraries/LibGfx/Bitmap.cpp b/Libraries/LibGfx/Bitmap.cpp index e1e469368d..77e687756d 100644 --- a/Libraries/LibGfx/Bitmap.cpp +++ b/Libraries/LibGfx/Bitmap.cpp @@ -138,6 +138,9 @@ ErrorOr> Bitmap::create_with_anonymous_buffer(BitmapFormat if (size_would_overflow(format, size)) return Error::from_string_literal("Gfx::Bitmap::create_with_anonymous_buffer size overflow"); + if (buffer.size() < size_in_bytes(minimum_pitch(size.width(), format), size.height())) + return Error::from_string_literal("Gfx::Bitmap::create_with_anonymous_buffer buffer too small for size"); + return adopt_nonnull_ref_or_enomem(new (nothrow) Bitmap(format, alpha_type, move(buffer), size)); } @@ -146,6 +149,9 @@ ErrorOr> Bitmap::create_with_raw_data(BitmapFormat format, if (size_would_overflow(format, size)) return Error::from_string_literal("Gfx::Bitmap::create_with_raw_data size overflow"); + if (raw_data.size() < size_in_bytes(minimum_pitch(size.width(), format), size.height())) + return Error::from_string_literal("Gfx::Bitmap::create_with_raw_data data too small for size"); + auto backing_store = TRY(Bitmap::allocate_backing_store(format, size, InitializeBackingStore::No)); raw_data.copy_to(Bytes { backing_store.data, backing_store.size_in_bytes }); return AK::adopt_nonnull_ref_or_enomem(new (nothrow) Bitmap(format, alpha_type, size, backing_store)); diff --git a/Tests/LibGfx/CMakeLists.txt b/Tests/LibGfx/CMakeLists.txt index c6528d21fd..9e49c348e7 100644 --- a/Tests/LibGfx/CMakeLists.txt +++ b/Tests/LibGfx/CMakeLists.txt @@ -18,5 +18,6 @@ target_link_libraries(BenchmarkJPEGLoader PRIVATE LibImageDecoders) target_link_libraries(TestImageDecoder PRIVATE LibImageDecoders) target_link_libraries(TestImageWriter PRIVATE LibImageDecoders) +ladybird_test(TestBitmapSequence.cpp LibGfx LIBS LibGfx LibIPC) ladybird_test(TestShareableBitmap.cpp LibGfx LIBS LibGfx LibIPC) ladybird_test(TestYUVData.cpp LibGfx LIBS LibGfx skia) diff --git a/Tests/LibGfx/TestBitmapSequence.cpp b/Tests/LibGfx/TestBitmapSequence.cpp new file mode 100644 index 0000000000..948261713a --- /dev/null +++ b/Tests/LibGfx/TestBitmapSequence.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2026, Ladybird contributors + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TEST_CASE(ipc_decode_rejects_undersized_single_frame_backing) +{ + // Issue #10036: A single-frame BitmapSequence whose metadata describes a 50000x10 (2,000,000-byte) bitmap but ships + // only a 4096-byte backing buffer. Decoding must fail rather than adopt the undersized buffer (and write OOB). + constexpr size_t backing_size = 4096; + auto buffer = MUST(Core::AnonymousBuffer::create_with_size(backing_size)); + + Gfx::BitmapMetadata metadata { + .format = Gfx::BitmapFormat::BGRA8888, + .alpha_type = Gfx::AlphaType::Premultiplied, + .size = Gfx::IntSize { 50000, 10 }, + .size_in_bytes = backing_size, + }; + + Vector> metadata_list; + metadata_list.append(metadata); + + // Hand-encode the wire fields in the order IPC::encode(BitmapSequence) uses. The message is internally consistent, + // yet inconsistent with the claimed bitmap geometry — which is exactly what the decoder must catch. + IPC::MessageBuffer message_buffer; + IPC::Encoder encoder { message_buffer }; + MUST(encoder.encode(metadata_list)); + MUST(encoder.encode(static_cast(backing_size))); + MUST(encoder.encode(buffer)); + + auto data = message_buffer.take_data(); + FixedMemoryStream stream { data.span() }; + + Queue attachments; + for (auto& attachment : message_buffer.take_attachments()) + attachments.enqueue(move(attachment)); + + IPC::Decoder decoder { stream, attachments }; + auto result = IPC::decode(decoder); + EXPECT(result.is_error()); +} + +TEST_CASE(create_with_anonymous_buffer_rejects_undersized_buffer) +{ + auto buffer = MUST(Core::AnonymousBuffer::create_with_size(4096)); + auto result = Gfx::Bitmap::create_with_anonymous_buffer(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, move(buffer), Gfx::IntSize { 50000, 10 }); + EXPECT(result.is_error()); +} + +TEST_CASE(create_with_anonymous_buffer_accepts_correctly_sized_buffer) +{ + Gfx::IntSize size { 64, 64 }; + auto required = Gfx::Bitmap::size_in_bytes(Gfx::Bitmap::minimum_pitch(size.width(), Gfx::BitmapFormat::BGRA8888), size.height()); + auto buffer = MUST(Core::AnonymousBuffer::create_with_size(required)); + auto bitmap = Gfx::Bitmap::create_with_anonymous_buffer(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, move(buffer), size); + EXPECT(!bitmap.is_error()); +} + +TEST_CASE(create_with_raw_data_rejects_undersized_data) +{ + Array tiny {}; + auto result = Gfx::Bitmap::create_with_raw_data(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, tiny, Gfx::IntSize { 50000, 10 }); + EXPECT(result.is_error()); +}