From cee6952b9f0109a491545362365bc1a89256c493 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Sun, 17 May 2026 22:54:47 +0200 Subject: [PATCH] Track data and src on canvas_jpeg_error_mgr to free on libjpeg longjmp If libjpeg calls error_exit during jpeg_read_scanlines (corrupt or truncated JPEG that decoded the header but fails in the scan), it longjmps back to the setjmp point in loadJPEGFromBuffer. That bypasses the cleanup at the bottom of decodeJPEGIntoSurface, leaking both 'data' (width * height * 4) and 'src' (width * output_components). Store both pointers on the canvas_jpeg_error_mgr so the error handler can delete[] them before longjmp. Also reorder cleanup to destroy libjpeg state and free src before creating the cairo surface, so the success path is symmetric with the new failure path. --- src/Image.cc | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/Image.cc b/src/Image.cc index 06dd564ae..59b26dd7f 100644 --- a/src/Image.cc +++ b/src/Image.cc @@ -29,6 +29,8 @@ typedef struct { struct canvas_jpeg_error_mgr: jpeg_error_mgr { Image* image; + uint8_t* data = nullptr; + uint8_t* src = nullptr; jmp_buf setjmp_buffer; }; #endif @@ -830,6 +832,7 @@ cairo_status_t Image::decodeJPEGIntoSurface(jpeg_decompress_struct *args, Orientation orientation) { const int channels = 4; cairo_status_t status = CAIRO_STATUS_SUCCESS; + canvas_jpeg_error_mgr *err = static_cast(args->err); uint8_t *data = new uint8_t[naturalWidth * naturalHeight * channels]; if (!data) { @@ -838,15 +841,18 @@ Image::decodeJPEGIntoSurface(jpeg_decompress_struct *args, Orientation orientati this->errorInfo.set(NULL, "malloc", errno); return CAIRO_STATUS_NO_MEMORY; } + err->data = data; uint8_t *src = new uint8_t[naturalWidth * args->output_components]; if (!src) { + err->data = nullptr; free(data); jpeg_abort_decompress(args); jpeg_destroy_decompress(args); this->errorInfo.set(NULL, "malloc", errno); return CAIRO_STATUS_NO_MEMORY; } + err->src = src; // These are the three main cases to handle. libjpeg converts YCCK to CMYK // and YCbCr to RGB by default. @@ -880,6 +886,16 @@ Image::decodeJPEGIntoSurface(jpeg_decompress_struct *args, Orientation orientati updateDimensionsForOrientation(orientation); + if (status) { + jpeg_abort_decompress(args); + } else { + jpeg_finish_decompress(args); + } + jpeg_destroy_decompress(args); + + delete[] src; + err->src = nullptr; + if (!status) { _surface = cairo_image_surface_create_for_data( data @@ -887,22 +903,19 @@ Image::decodeJPEGIntoSurface(jpeg_decompress_struct *args, Orientation orientati , naturalWidth , naturalHeight , cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, naturalWidth)); + status = cairo_surface_status(_surface); } - jpeg_finish_decompress(args); - jpeg_destroy_decompress(args); - status = cairo_surface_status(_surface); - - rotatePixels(data, naturalWidth, naturalHeight, channels, orientation); - - delete[] src; - if (status) { delete[] data; + err->data = nullptr; return status; } + rotatePixels(data, naturalWidth, naturalHeight, channels, orientation); + _data = data; + err->data = nullptr; return CAIRO_STATUS_SUCCESS; } @@ -914,6 +927,10 @@ Image::decodeJPEGIntoSurface(jpeg_decompress_struct *args, Orientation orientati static void canvas_jpeg_error_exit(j_common_ptr cinfo) { canvas_jpeg_error_mgr *cjerr = static_cast(cinfo->err); cjerr->output_message(cinfo); + delete[] cjerr->data; + delete[] cjerr->src; + cjerr->data = nullptr; + cjerr->src = nullptr; // Return control to the setjmp point longjmp(cjerr->setjmp_buffer, 1); }