ladybird/Libraries/LibGfx/ImageFormats/JPEGLoader.cpp
Timothy Flynn 063a3afe02 ImageDecoder: Fix memory leak of partially decoded invalid JPEG images
We have to be careful to always destroy the jpeglib decompression struct
before returning from JPEGLoadingContext::decode. We were doing this in
jpeglib error handlers, but we have a couple of paths that bail from the
decoder via TRY. These paths were neither cleaning up memory nor setting
the image decoder to an error state.

So this patch sets up a scope guard to ensure we free the decompressor
upon exit from the function. And it delegates the responsibility of
setting the decoder state to the caller (of which there is only one),
to ensure all error paths result in an error state.
2025-02-10 16:05:43 +00:00

227 lines
7.2 KiB
C++

/*
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/CMYKBitmap.h>
#include <LibGfx/ImageFormats/JPEGLoader.h>
#include <jpeglib.h>
#include <setjmp.h>
namespace Gfx {
struct JPEGLoadingContext {
enum class State {
NotDecoded,
Error,
Decoded,
};
State state { State::NotDecoded };
RefPtr<Gfx::Bitmap> rgb_bitmap;
RefPtr<Gfx::CMYKBitmap> cmyk_bitmap;
ReadonlyBytes data;
Vector<u8> icc_data;
JPEGLoadingContext(ReadonlyBytes data)
: data(data)
{
}
ErrorOr<void> decode();
};
struct JPEGErrorManager : jpeg_error_mgr {
jmp_buf setjmp_buffer {};
};
ErrorOr<void> JPEGLoadingContext::decode()
{
struct jpeg_decompress_struct cinfo;
ScopeGuard guard { [&]() { jpeg_destroy_decompress(&cinfo); } };
struct JPEGErrorManager jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_source_mgr source_manager {};
if (setjmp(jerr.setjmp_buffer))
return Error::from_string_literal("Failed to decode JPEG");
jerr.error_exit = [](j_common_ptr cinfo) {
char buffer[JMSG_LENGTH_MAX];
(*cinfo->err->format_message)(cinfo, buffer);
dbgln("JPEG error: {}", buffer);
longjmp(static_cast<JPEGErrorManager*>(cinfo->err)->setjmp_buffer, 1);
};
jpeg_create_decompress(&cinfo);
source_manager.next_input_byte = data.data();
source_manager.bytes_in_buffer = data.size();
source_manager.init_source = [](j_decompress_ptr) { };
source_manager.fill_input_buffer = [](j_decompress_ptr) -> boolean { return false; };
source_manager.skip_input_data = [](j_decompress_ptr context, long num_bytes) {
if (num_bytes > static_cast<long>(context->src->bytes_in_buffer)) {
context->src->bytes_in_buffer = 0;
return;
}
context->src->next_input_byte += num_bytes;
context->src->bytes_in_buffer -= num_bytes;
};
source_manager.resync_to_restart = jpeg_resync_to_restart;
source_manager.term_source = [](j_decompress_ptr) { };
cinfo.src = &source_manager;
jpeg_save_markers(&cinfo, JPEG_APP0 + 2, 0xFFFF);
if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
return Error::from_string_literal("Failed to read JPEG header");
if (cinfo.jpeg_color_space == JCS_CMYK || cinfo.jpeg_color_space == JCS_YCCK) {
cinfo.out_color_space = JCS_CMYK;
} else {
cinfo.out_color_space = JCS_EXT_BGRX;
}
jpeg_start_decompress(&cinfo);
bool could_read_all_scanlines = true;
if (cinfo.out_color_space == JCS_EXT_BGRX) {
rgb_bitmap = TRY(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, { static_cast<int>(cinfo.output_width), static_cast<int>(cinfo.output_height) }));
while (cinfo.output_scanline < cinfo.output_height) {
auto* row_ptr = (u8*)rgb_bitmap->scanline(cinfo.output_scanline);
auto out_size = jpeg_read_scanlines(&cinfo, &row_ptr, 1);
if (cinfo.output_scanline < cinfo.output_height && out_size == 0) {
dbgln("JPEG Warning: Decoding produced no more scanlines in scanline {}/{}.", cinfo.output_scanline, cinfo.output_height);
could_read_all_scanlines = false;
break;
}
}
} else {
cmyk_bitmap = TRY(CMYKBitmap::create_with_size({ static_cast<int>(cinfo.output_width), static_cast<int>(cinfo.output_height) }));
while (cinfo.output_scanline < cinfo.output_height) {
auto* row_ptr = (u8*)cmyk_bitmap->scanline(cinfo.output_scanline);
auto out_size = jpeg_read_scanlines(&cinfo, &row_ptr, 1);
if (cinfo.output_scanline < cinfo.output_height && out_size == 0) {
dbgln("JPEG Warning: Decoding produced no more scanlines in scanline {}/{}.", cinfo.output_scanline, cinfo.output_height);
could_read_all_scanlines = false;
break;
}
}
}
JOCTET* icc_data_ptr = nullptr;
unsigned int icc_data_length = 0;
if (jpeg_read_icc_profile(&cinfo, &icc_data_ptr, &icc_data_length)) {
icc_data.resize(icc_data_length);
memcpy(icc_data.data(), icc_data_ptr, icc_data_length);
free(icc_data_ptr);
}
if (could_read_all_scanlines)
jpeg_finish_decompress(&cinfo);
else
jpeg_abort_decompress(&cinfo);
if (cmyk_bitmap && !rgb_bitmap)
rgb_bitmap = TRY(cmyk_bitmap->to_low_quality_rgb());
return {};
}
JPEGImageDecoderPlugin::JPEGImageDecoderPlugin(NonnullOwnPtr<JPEGLoadingContext> context)
: m_context(move(context))
{
}
JPEGImageDecoderPlugin::~JPEGImageDecoderPlugin() = default;
IntSize JPEGImageDecoderPlugin::size()
{
if (m_context->state == JPEGLoadingContext::State::NotDecoded)
(void)frame(0);
if (m_context->state == JPEGLoadingContext::State::Error)
return {};
if (m_context->rgb_bitmap)
return m_context->rgb_bitmap->size();
if (m_context->cmyk_bitmap)
return m_context->cmyk_bitmap->size();
return {};
}
bool JPEGImageDecoderPlugin::sniff(ReadonlyBytes data)
{
return data.size() > 3
&& data.data()[0] == 0xFF
&& data.data()[1] == 0xD8
&& data.data()[2] == 0xFF;
}
ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> JPEGImageDecoderPlugin::create(ReadonlyBytes data)
{
return adopt_own(*new JPEGImageDecoderPlugin(make<JPEGLoadingContext>(data)));
}
ErrorOr<ImageFrameDescriptor> JPEGImageDecoderPlugin::frame(size_t index, Optional<IntSize>)
{
if (index > 0)
return Error::from_string_literal("JPEGImageDecoderPlugin: Invalid frame index");
if (m_context->state == JPEGLoadingContext::State::Error)
return Error::from_string_literal("JPEGImageDecoderPlugin: Decoding failed");
if (m_context->state < JPEGLoadingContext::State::Decoded) {
if (auto result = m_context->decode(); result.is_error()) {
m_context->state = JPEGLoadingContext::State::Error;
return result.release_error();
}
m_context->state = JPEGLoadingContext::State::Decoded;
}
return ImageFrameDescriptor { m_context->rgb_bitmap, 0 };
}
Optional<Metadata const&> JPEGImageDecoderPlugin::metadata()
{
return OptionalNone {};
}
ErrorOr<Optional<ReadonlyBytes>> JPEGImageDecoderPlugin::icc_data()
{
if (m_context->state == JPEGLoadingContext::State::NotDecoded)
(void)frame(0);
if (!m_context->icc_data.is_empty())
return m_context->icc_data;
return OptionalNone {};
}
NaturalFrameFormat JPEGImageDecoderPlugin::natural_frame_format() const
{
if (m_context->state == JPEGLoadingContext::State::NotDecoded)
(void)const_cast<JPEGImageDecoderPlugin&>(*this).frame(0);
if (m_context->cmyk_bitmap)
return NaturalFrameFormat::CMYK;
return NaturalFrameFormat::RGB;
}
ErrorOr<NonnullRefPtr<CMYKBitmap>> JPEGImageDecoderPlugin::cmyk_frame()
{
if (m_context->state == JPEGLoadingContext::State::NotDecoded)
(void)frame(0);
if (m_context->state == JPEGLoadingContext::State::Error)
return Error::from_string_literal("JPEGImageDecoderPlugin: Decoding failed");
if (!m_context->cmyk_bitmap)
return Error::from_string_literal("JPEGImageDecoderPlugin: No CMYK data available");
return *m_context->cmyk_bitmap;
}
}