Graphics protocol: Fix memory leak when loading corrupt PNG with ICC profile
Some checks are pending
CI / Linux (python=3.14 cc=clang sanitize=1) (push) Waiting to run
CI / Linux (python=3.12 cc=gcc sanitize=0) (push) Waiting to run
CI / Linux (python=3.13 cc=gcc sanitize=1) (push) Waiting to run
CI / Linux package (push) Waiting to run
CI / Bundle test (macos-latest) (push) Waiting to run
CI / Bundle test (ubuntu-latest) (push) Waiting to run
CI / macOS Brew (push) Waiting to run
CI / Test ./dev.sh and benchmark (push) Waiting to run
CodeQL / CodeQL-Build (actions, ubuntu-latest) (push) Waiting to run
CodeQL / CodeQL-Build (c, macos-latest) (push) Waiting to run
CodeQL / CodeQL-Build (c, ubuntu-latest) (push) Waiting to run
CodeQL / CodeQL-Build (go, ubuntu-latest) (push) Waiting to run
CodeQL / CodeQL-Build (python, ubuntu-latest) (push) Waiting to run
Depscan / Scan dependencies for vulnerabilities (push) Waiting to run

This commit is contained in:
Kovid Goyal 2026-09-24 19:00:13 +05:30
parent c02f932641
commit 73f5102392
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
3 changed files with 20 additions and 4 deletions

View file

@ -205,6 +205,9 @@ Detailed list of changes
- Fix an out-of-bounds memory access when drawing a multi-line text sized
character with the cursor below the bottom margin of the scroll region
- Fix a memory leak when loading a corrupt PNG image that has an embedded ICC
color profile
0.49.1 [2026-09-24]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -64,6 +64,10 @@ inflate_png_inner(png_read_data *d, const uint8_t *buf, size_t bufsz, int max_im
png_structp png = NULL;
png_infop info = NULL;
struct custom_error_handler eh = {.d = d};
// volatile because these are modified after setjmp() and read after a
// longjmp() in the error path, so they must not be cached in registers
cmsHPROFILE volatile input_profile = NULL;
cmsHTRANSFORM volatile colorspace_transform = NULL;
png = png_create_read_struct(PNG_LIBPNG_VER_STRING, &eh, read_png_error_handler, read_png_warn_handler);
if (!png) ABRT(ENOMEM, "Failed to create PNG read structure");
info = png_create_info_struct(png);
@ -82,8 +86,6 @@ inflate_png_inner(png_read_data *d, const uint8_t *buf, size_t bufsz, int max_im
bit_depth = png_get_bit_depth(png, info);
double image_gamma;
int intent;
cmsHPROFILE input_profile = NULL;
cmsHTRANSFORM colorspace_transform = NULL;
if (png_get_sRGB(png, info, &intent)) {
// do nothing since we output sRGB
} else if (png_get_gAMA(png, info, &image_gamma)) {
@ -132,12 +134,12 @@ inflate_png_inner(png_read_data *d, const uint8_t *buf, size_t bufsz, int max_im
if (colorspace_transform) {
for (int i = 0; i < d->height; i++) { cmsDoTransform(colorspace_transform, d->row_pointers[i], d->row_pointers[i], d->width); }
cmsDeleteTransform(colorspace_transform);
}
if (input_profile) cmsCloseProfile(input_profile);
d->ok = true;
err:
if (colorspace_transform) cmsDeleteTransform(colorspace_transform);
if (input_profile) cmsCloseProfile(input_profile);
if (png) png_destroy_read_struct(&png, info ? &info : NULL, NULL);
return;
}

View file

@ -697,6 +697,17 @@ class TestGraphics(BaseTest):
# while libpng still expects more compressed data.
truncated = full[:-16]
self.assertRaisesRegex(ValueError, '[EBADPNG]', load_png_data, truncated)
# Same, but with an embedded ICC profile so that lcms2 handles are
# allocated before libpng longjmps out, they must be freed in the
# error path (detected by LeakSanitizer in ASAN builds).
try:
from PIL import ImageCms
except ImportError:
return
icc = ImageCms.ImageCmsProfile(ImageCms.createProfile('sRGB')).tobytes()
buf = BytesIO()
Image.frombytes('RGBA', (w, h), byte_block(w * h * 4)).save(buf, 'PNG', icc_profile=icc)
self.assertRaisesRegex(ValueError, '[EBADPNG]', load_png_data, buf.getvalue()[:-16])
def test_gr_operations_with_numbers(self):
s = self.create_screen()