Some WebP files advertise ICC metadata in their VP8X feature flags even
though the ICCP chunk is absent or malformed. We accepted the bitstream
header and could decode the pixels, but the follow-up mux metadata
lookup failed during header decoding and made sniffing reject the image
entirely.
Treat mux and ICC metadata extraction as best-effort after libwebp
accepts the header. Images without readable optional ICC metadata now
decode normally and simply report no ICC profile.
Problem: A BMP V5 image whose embedded ICC profile offset points past
the end of the file triggers an OOB read.
Cause: The bounds check summed the profile offset, the file-header size,
and the profile size in 32-bit arithmetic. So, a large offset (e.g.
0xfffffff0) wraps the sum back into range and passes the check. The
decoder then returns a span pointing far past the end of the file.
Fix: Compute the sum in 64 bits — so an out-of-bounds offset can no
longer wrap, and the profile’s rejected.
Fixes: https://github.com/LadybirdBrowser/ladybird/issues/9967
Problem: Decoding a BMP whose height is INT_MIN triggered a UBSan error.
A top-down BMP legitimately uses a negative height — and unlike width,
it’s not rejected, so it can be INT_MIN.
Cause: decode_bmp_pixel_data() took the magnitude of the i32 width and
height with abs(). Negating INT_MIN is undefined behavior.
Fix: Widen to i64 before taking the absolute value — so the magnitude of
INT_MIN is representable. The resulting out-of-range dimension is still
rejected by Bitmap::create — so only the undefined behavior changes.
Fixes: https://github.com/LadybirdBrowser/ladybird/issues/9994
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
Replace the homegrown GIF parser and LZW decompressor with the wuffs
GIF decoder, which is memory-safe by construction and already used in
other engines via Skia.
One behavior change is that `loop_count()` now reports the correct
value, since the raw value stored in the file does not include the
first frane and should be incremented by 1 to be compatible with what
callers expect.
Problem: Decoding a BI_RLE24 BMP binds a u32 reference to a misaligned
address, gets flagged by UBSan while decompressing the run-length data.
Cause: The decompressed RLE24 buffer holds 24-bit pixels at a 3-byte
stride (decode_bmp_pixel_data reads it back with LE read_u24) — but each
pixel was getting stored with a 4-byte write at 3-byte-strided offsets.
Fix: Store the 24-bit value as three LE bytes — so the write
matches the stride and is always aligned. Size the buffer to the
real 3-bytes-per-pixel total, and bound-check the 3-byte write.
Fixes https://github.com/LadybirdBrowser/ladybird/issues/9958
Remove the TinyVG decoder now that the Qt chrome no longer depends on
TVG resources. Drop the decoder registration, MIME and supported image
type entries, fuzzer target, decoder tests, and TinyVG test inputs.
When choosing the best image from an ICO file, prefer the entry with the
largest pixel area first, and only use bits-per-pixel as a tie-breaker.
This had regressed in commit b10fe7c136.
This fixes ICO files like Discord's favicon, where 16x16, 32x32, 48x48,
and 256x256 entries all advertise the same bpp. We were previously just
choosing whichever we saw first, which happened to be the 16x16 icon.
Problem: Runs of the Linux Sanitizers intermittently fail with
LeakSanitizer leaks of ~45 KB across 21 allocations from
PNGLoadingContext::read_frames() — triggered when the test suite
decodes malformed PNGs.
Cause: When libpng hits a corrupted IDAT chunk inside png_read_image(),
it longjmps back to the setjmp landing pad in
PNGLoadingContext::read_all_frames(). longjmp unwinds the stack without
running C++ destructors — so the stack-locals in read_frames
(Vector<u8*> row_pointers, the in-flight Bitmap inside decode_frame, and
the APNG branch’s output buffer and Painter) leak their heap storage.
Fix: Promote those stack-locals to members of PNGLoadingContext (which
is heap-allocated and outlives the setjmp scope) — so their storage is
reachable by RAII when the context is destroyed. Clear them in the
setjmp error handler too — so memory is released promptly on the error
path, rather than waiting until ~PNGLoadingContext().
This matches Firefox's behavior which allows up to 65535 for both width
and height. The total area is still constrained by the pitch overflow
check. It was changed to int32_t, so now it limits the maximum area to
~23kx23k, which is still less than the 65535x65535 area that Firefox
allows, but we would eat too much memory if we allowed that.
We should invert CMYK data only if color space is JCS_CMYK and either
there is no Adobe marker, or the Adobe transform is 0. Transform 2
indicates YCCK data, which we should not invert.
The decoder was requiring GIF files to be at least 32 bytes, but the
actual minimum for a valid GIF is only 26 bytes:
- 6 bytes for the header
- 7 bytes for the Logical Screen Descriptor
- 10 bytes for the Image Descriptor
- 2 bytes for the LZW minimum code size and block terminator
- 1 byte for the GIF trailer
This change allows us to load minimal 1x1 GIFs with empty LZW data.
They are commonly used on the web as transparent placeholders with
minimal file size.
Gfx::Bitmap only supports a bit depth of 8, therefore we refused to
load AVIF images which didn't have this bit depth.
However, we can tell the libavif decoder to reduce the output depth by
setting avifRGBImage.depth to 8. This allows us to support any input
depth.
Makes images load on https://www.ikea.com/ which uses Cloudflare Images
to re-encode their images to 16-bit AVIF.
In the previous fix, we were still drawing IDAT data to the reference
frame even when no fcTL was present. This would cause rendering issues
when subsequent frames use APNG_BLEND_OP_OVER blending mode, as they
would composite over the incorrect reference frame. This commit adds a
simple check to properly skip any frame without an fcTL chunk.
We now properly handle OS/2 format BMPs that use 3 bytes per color
entry instead of 4. While OS/2 2.x officially specified 4 bytes per
color, some tools still produce files with 3-byte entries. We can
identify such files by checking the available color table space.
This extends ICO loader to support Windows cursor files. There is no
point in creating a separate loader for this, as the ICO format is
very similar to the CUR format. The only differences are bytes used to
identify the file and a presence of a hotspot in the CUR header.
Color masks should only be used when the compression type is either
BITFIELDS or ALPHABITFIELDS. They were always read before and produced
corrupted images when there was random data in the mask fields.
Other browsers don't think that BMP files with more than 1024 colors are
invalid. They clamp the palette instead, and now we do the same. This
allows us to load more BMPs.
Before this change, IDAT data was mistakenly always included in the
animation. Now we only include frames with explicit fcTL chunks.
As per the PNG spec (third edition):
"The static image may be included as the first frame of the animation
by the presence of a single fcTL chunk before IDAT. Otherwise, the
static image is not part of the animation."
We also fall back to the IDAT data when APNG has acTL but no fcTL
chunks. Test image is 062.png from fDAT-inherits-cICP.html from WPT.
GIF loader was completely failing when encountering errors with
frame descriptors or individual frames, even when some frames were
successfully loaded. Now we attempt to decode at least some frames
and fail only when no frames can be decoded at all.
Fixes wpt/png/exif-chunk.html.
At some point there should probably be some mechanism to handle this
outside of the individual decoder plugins. The TIFF decoder seems to
have its own version of this, and as far as I can tell, the JPEG decoder
doesn't handle this at all, even though that's probably the most common
use case for Exif orientations. :^)
It currently doesn't support animated image.
Note that Gfx::Bitmap has no support for get_pixel when the format is
RGBA8888. This is why it has been removed from the tests.
This matches libwebp (see ZeroFillCanvas() call in
libwebp/src/demux/anim_decode.c:355 and ZeroFillFrameRect() call
in line 435, but in WebPAnimDecoderGetNext()) and makes files
written e.g. by asesprite look correct -- even though the old
behavior is also spec-compliant and arguably makes more sense.
Now nothing looks at the background color stored in the file.
See PR for an example image where it makes a visible difference.
Cherry-picked from serenityos master
276a904d20ffe260b5544a9ace9841d083e0243
Bilevel images are not required to have a BitsPerSample or a
SamplesPerPixel tag, while this is unusual these images are still valid.
The test case has been generated by first making a copy of
ccitt3_1d_fill.tiff and then, using `tiffset` to remove both tags:
tiffset -u 258 ccitt3_no_tags.tiff
tiffset -u 277 ccitt3_no_tags.tiff
...and add a test case that shows why it's incorrect.
If one dimension is 2^n + 1 and the other side is just 1, then the
topmost node will have 2^n x 1 and 1 x 1 children. The first child will
have n levels of children. The 1 x 1 child could end immediately, or it
could require that it also has n levels of (all 1 x 1) children. The
spec isn't clear on which of the two alternatives should happen. We
currently have n levels of 1 x 1 blocks.
This test case shows that a VERIFY we had was incorrect, so remove it.
The alternative implementation is to keep the VERIFY and to add a
if (x_count == 1 && y_count == 1)
level = 0;
to the top of TagTreeNode::create(). Then we don't have multiple levels
of 1 x 1 nodes, and we need to read fewer bits.
The images in the spec suggest that all nodes should have the same
number of levels, so go with that interpretation for now. Once we can
actually decode images, we'll hopefully see which of the two
interpretations is correct.
(The removed VERIFY() is hit when decoding
Tests/LibGfx/test-inputs/jpeg2000/buggie-gray.jpf in a local branch that
has some image decoding implemented. That file contains a packet with
1x3 code-blocks, which hits this case.)
This tests reading JPEG2000 codestreams that aren't embedded in
the ISOBMFF wrapper. It's also useful for debugging bitstream
internals, since the spec lists expected output for many internal
intermediate results.
A tag tree is a data structure used for deserializing JPEG2000
packet headers.
We don't use them for anything yet, except from tests.
The implementation feels a bit awkward to me, but we can always polish
it later.
The spec thankfully includes two concrete examples. The code is
correct enough to pass those -- I added them as test.
The implementation is very similar to #23831.
I created the test exactly like in #23713, except that I replaced the
last four lines in the ini file with:
```
-txt -Param -rATX1 10
-txt -Param -rATY1 -1
-txt -Param -rATX2 4
-txt -Param -rATY2 15
```