Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package libpng12 for openSUSE:Factory checked in at 2026-04-28 13:22:31 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/libpng12 (Old) and /work/SRC/openSUSE:Factory/.libpng12.new.11940 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "libpng12" Tue Apr 28 13:22:31 2026 rev:43 rq:1349750 version:1.2.59 Changes: -------- --- /work/SRC/openSUSE:Factory/libpng12/libpng12.changes 2025-12-04 11:27:30.000091370 +0100 +++ /work/SRC/openSUSE:Factory/.libpng12.new.11940/libpng12.changes 2026-04-28 13:22:33.155156360 +0200 @@ -1,0 +2,9 @@ +Tue Apr 28 08:05:20 UTC 2026 - Petr Gajdos <[email protected]> + +- added patches + CVE-2026-33416: use-after-free via pointer aliasing in `png_set_tRNS` and `png_set_PLTE` can lead to arbitrary code execution [bsc#1260754] + * libpng12-CVE-2026-33416.patch + CVE-2026-34757: Information disclosure and data corruption via use-after-free vulnerability [bsc#1261957] + * libpng12-CVE-2026-34757.patch + +------------------------------------------------------------------- New: ---- libpng12-CVE-2026-33416.patch libpng12-CVE-2026-34757.patch ----------(New B)---------- New: CVE-2026-33416: use-after-free via pointer aliasing in `png_set_tRNS` and `png_set_PLTE` can lead to arbitrary code execution [bsc#1260754] * libpng12-CVE-2026-33416.patch CVE-2026-34757: Information disclosure and data corruption via use-after-free vulnerability [bsc#1261957] New: CVE-2026-34757: Information disclosure and data corruption via use-after-free vulnerability [bsc#1261957] * libpng12-CVE-2026-34757.patch ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ libpng12.spec ++++++ --- /var/tmp/diff_new_pack.QU0piQ/_old 2026-04-28 13:22:33.935188378 +0200 +++ /var/tmp/diff_new_pack.QU0piQ/_new 2026-04-28 13:22:33.939188543 +0200 @@ -1,7 +1,7 @@ # # spec file for package libpng12 # -# Copyright (c) 2022 SUSE LLC +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -36,6 +36,10 @@ Patch1: libpng-1.2.51-CVE-2013-7354.patch # CVE-2025-64505 [bsc#1254157], heap buffer over-read in `png_do_quantize` via malformed palette index Patch2: libpng12-CVE-2025-64505.patch +# CVE-2026-33416: use-after-free via pointer aliasing in `png_set_tRNS` and `png_set_PLTE` can lead to arbitrary code execution [bsc#1260754] +Patch3: libpng12-CVE-2026-33416.patch +# CVE-2026-34757: Information disclosure and data corruption via use-after-free vulnerability [bsc#1261957] +Patch4: libpng12-CVE-2026-34757.patch BuildRequires: libtool BuildRequires: pkg-config BuildRequires: zlib-devel @@ -105,7 +109,7 @@ # PNG_SAFE_LIMITS_SUPPORTED: http://www.openwall.com/lists/oss-security/2015/01/10/1 export CFLAGS="%optflags -O3 -DPNG_SAFE_LIMITS_SUPPORTED -DPNG_SKIP_SETJMP_CHECK $(getconf LFS_CFLAGS)" export LDFLAGS="-Wl,-z,relro,-z,now" - +autoreconf -fi %configure \ --disable-static \ --with-libpng-compat=no ++++++ libpng12-CVE-2026-33416.patch ++++++ Index: libpng-1.2.59/Makefile.am =================================================================== --- libpng-1.2.59.orig/Makefile.am +++ libpng-1.2.59/Makefile.am @@ -14,10 +14,12 @@ PNGLIB_BASENAME= libpng@PNGLIB_MAJOR@@PN AUTOMAKE_OPTIONS = foreign # test programs - run on make check, make distcheck -check_PROGRAMS= pngtest +check_PROGRAMS= pngtest cve-2026-33416 pngtest_SOURCES = pngtest.c pngtest_LDADD = libpng12.la -TESTS = test-pngtest.sh +cve_2026_33416_SOURCES = cve-2026-33416.c +cve_2026_33416_LDADD = libpng12.la +TESTS = test-pngtest.sh cve-2026-33416 TESTS_ENVIRONMENT= srcdir=$(srcdir) # man pages @@ -89,6 +91,7 @@ EXTRA_DIST= \ ${srcdir}/contrib/pngsuite/* \ ${srcdir}/contrib/visupng/* \ $(TESTS) \ + cve-2026-33416.c \ example.c libpng-1.2.59.txt pnggccrd.c pngvcrd.c CLEANFILES= pngout.png libpng12.pc libpng12-config libpng.vers \ Index: libpng-1.2.59/pngread.c =================================================================== --- libpng-1.2.59.orig/pngread.c +++ libpng-1.2.59/pngread.c @@ -1233,35 +1233,30 @@ png_read_destroy(png_structp png_ptr, pn png_free(png_ptr, png_ptr->gamma_from_1); png_free(png_ptr, png_ptr->gamma_to_1); #endif + png_zfree(png_ptr, png_ptr->palette); + png_ptr->palette = NULL; #ifdef PNG_FREE_ME_SUPPORTED - if (png_ptr->free_me & PNG_FREE_PLTE) - png_zfree(png_ptr, png_ptr->palette); png_ptr->free_me &= ~PNG_FREE_PLTE; #else - if (png_ptr->flags & PNG_FLAG_FREE_PLTE) - png_zfree(png_ptr, png_ptr->palette); png_ptr->flags &= ~PNG_FLAG_FREE_PLTE; #endif + #if defined(PNG_tRNS_SUPPORTED) || \ defined(PNG_READ_EXPAND_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) + png_free(png_ptr, png_ptr->trans); + png_ptr->trans = NULL; #ifdef PNG_FREE_ME_SUPPORTED - if (png_ptr->free_me & PNG_FREE_TRNS) - png_free(png_ptr, png_ptr->trans); png_ptr->free_me &= ~PNG_FREE_TRNS; #else - if (png_ptr->flags & PNG_FLAG_FREE_TRNS) - png_free(png_ptr, png_ptr->trans); png_ptr->flags &= ~PNG_FLAG_FREE_TRNS; #endif #endif #ifdef PNG_READ_hIST_SUPPORTED + png_free(png_ptr, png_ptr->hist); + png_ptr->hist = NULL; #ifdef PNG_FREE_ME_SUPPORTED - if (png_ptr->free_me & PNG_FREE_HIST) - png_free(png_ptr, png_ptr->hist); png_ptr->free_me &= ~PNG_FREE_HIST; #else - if (png_ptr->flags & PNG_FLAG_FREE_HIST) - png_free(png_ptr, png_ptr->hist); png_ptr->flags &= ~PNG_FLAG_FREE_HIST; #endif #endif Index: libpng-1.2.59/pngrtran.c =================================================================== --- libpng-1.2.59.orig/pngrtran.c +++ libpng-1.2.59/pngrtran.c @@ -471,7 +471,13 @@ png_set_dither(png_structp png_ptr, png_ } if (png_ptr->palette == NULL) { - png_ptr->palette = palette; + /* Allocate an owned copy rather than aliasing the caller's pointer, + * so that png_read_destroy can free png_ptr->palette unconditionally. + */ + png_ptr->palette = (png_colorp)png_calloc(png_ptr, + PNG_MAX_PALETTE_LENGTH * png_sizeof(png_color)); + memcpy(png_ptr->palette, palette, (unsigned int)num_palette * + (sizeof (png_color))); } png_ptr->num_palette = (png_uint_16)num_palette; @@ -1208,6 +1214,21 @@ png_read_transform_info(png_structp png_ { png_debug(1, "in png_read_transform_info"); + if (png_ptr->transformations != 0) + { + if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE && + info_ptr->palette != NULL && png_ptr->palette != NULL) + { + /* Sync info_ptr->palette with png_ptr->palette. + * The function png_init_read_transformations may have modified + * png_ptr->palette in place (e.g. for gamma correction or for + * background compositing). + */ + png_memcpy(info_ptr->palette, png_ptr->palette, + PNG_MAX_PALETTE_LENGTH * (png_sizeof(png_color))); + } + } + #ifdef PNG_READ_EXPAND_SUPPORTED if (png_ptr->transformations & PNG_EXPAND) { Index: libpng-1.2.59/pngset.c =================================================================== --- libpng-1.2.59.orig/pngset.c +++ libpng-1.2.59/pngset.c @@ -200,9 +200,11 @@ png_set_hIST(png_structp png_ptr, png_in #ifdef PNG_FREE_ME_SUPPORTED png_free_data(png_ptr, info_ptr, PNG_FREE_HIST, 0); #endif + /* Changed from info->num_palette to PNG_MAX_PALETTE_LENGTH in * version 1.2.1 */ + png_free(png_ptr, png_ptr->hist); png_ptr->hist = (png_uint_16p)png_malloc_warn(png_ptr, (png_uint_32)(PNG_MAX_PALETTE_LENGTH * png_sizeof(png_uint_16))); if (png_ptr->hist == NULL) @@ -211,11 +213,19 @@ png_set_hIST(png_structp png_ptr, png_in return; } + info_ptr->hist = (png_uint_16p)png_malloc_warn(png_ptr, + (png_uint_32)(PNG_MAX_PALETTE_LENGTH * png_sizeof(png_uint_16))); + if (info_ptr->hist == NULL) + { + png_free(png_ptr, png_ptr->hist); + png_ptr->hist = NULL; + png_warning(png_ptr, "Insufficient memory for hIST chunk data."); + return; + } + for (i = 0; i < info_ptr->num_palette; i++) - png_ptr->hist[i] = hist[i]; - info_ptr->hist = png_ptr->hist; + png_ptr->hist[i] = info_ptr->hist[i] = hist[i]; info_ptr->valid |= PNG_INFO_hIST; - #ifdef PNG_FREE_ME_SUPPORTED info_ptr->free_me |= PNG_FREE_HIST; #else @@ -224,6 +234,7 @@ png_set_hIST(png_structp png_ptr, png_in } #endif + void PNGAPI png_set_IHDR(png_structp png_ptr, png_infop info_ptr, png_uint_32 width, png_uint_32 height, int bit_depth, @@ -483,10 +494,17 @@ png_set_PLTE(png_structp png_ptr, png_in * of num_palette entries, in case of an invalid PNG file or incorrect * call to png_set_PLTE() with too-large sample values. */ + + /* Always separate png_ptr->palette from info_ptr->palette */ + png_free(png_ptr, png_ptr->palette); png_ptr->palette = (png_colorp)png_calloc(png_ptr, PNG_MAX_PALETTE_LENGTH * png_sizeof(png_color)); png_memcpy(png_ptr->palette, palette, num_palette * png_sizeof(png_color)); - info_ptr->palette = png_ptr->palette; + + info_ptr->palette = (png_colorp)png_calloc(png_ptr, + PNG_MAX_PALETTE_LENGTH * png_sizeof(png_color)); + png_memcpy(info_ptr->palette, palette, num_palette * png_sizeof(png_color)); + info_ptr->num_palette = png_ptr->num_palette = (png_uint_16)num_palette; #ifdef PNG_FREE_ME_SUPPORTED @@ -896,10 +914,20 @@ png_set_tRNS(png_structp png_ptr, png_in #endif /* Changed from num_trans to PNG_MAX_PALETTE_LENGTH in version 1.2.1 */ - png_ptr->trans = info_ptr->trans = (png_bytep)png_malloc(png_ptr, + png_free(png_ptr, png_ptr->trans); + png_ptr->trans = (png_bytep)png_malloc(png_ptr, + (png_uint_32)PNG_MAX_PALETTE_LENGTH); + info_ptr->trans = (png_bytep)png_malloc(png_ptr, (png_uint_32)PNG_MAX_PALETTE_LENGTH); + + png_memset(png_ptr->trans, 0xff, PNG_MAX_PALETTE_LENGTH); + png_memset(info_ptr->trans, 0xff, PNG_MAX_PALETTE_LENGTH); + if (num_trans > 0 && num_trans <= PNG_MAX_PALETTE_LENGTH) + { + png_memcpy(png_ptr->trans, trans, (png_size_t)num_trans); png_memcpy(info_ptr->trans, trans, (png_size_t)num_trans); + } } if (trans_values != NULL) Index: libpng-1.2.59/pngwrite.c =================================================================== --- libpng-1.2.59.orig/pngwrite.c +++ libpng-1.2.59/pngwrite.c @@ -1129,6 +1129,16 @@ png_write_destroy(png_structp png_ptr) /* Free our memory. png_free checks NULL for us. */ png_free(png_ptr, png_ptr->zbuf); png_free(png_ptr, png_ptr->row_buf); + png_zfree(png_ptr, png_ptr->palette); + png_ptr->palette = NULL; +#ifdef PNG_WRITE_tRNS_SUPPORTED + png_free(png_ptr, png_ptr->trans); + png_ptr->trans = NULL; +#endif +#ifdef PNG_WRITE_hIST_SUPPORTED + png_free(png_ptr, png_ptr->hist); + png_ptr->hist = NULL; +#endif #ifdef PNG_WRITE_FILTER_SUPPORTED png_free(png_ptr, png_ptr->prev_row); png_free(png_ptr, png_ptr->sub_row); Index: libpng-1.2.59/cve-2026-33416.c =================================================================== --- /dev/null +++ libpng-1.2.59/cve-2026-33416.c @@ -0,0 +1,196 @@ +#include "png.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* + * Test case for CVE-2026-33416: Use-after-free in png_set_PLTE, png_set_tRNS, png_set_hIST. + * This test verifies that info_ptr and png_ptr buffers are decoupled and + * that calling setter functions multiple times does not lead to leaks or UAF. + */ + +static int test_PLTE(void) { + printf("Testing PLTE decoupling...\n"); + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) return 1; + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, NULL, NULL); + return 1; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return 1; + } + + png_color palette[1]; + palette[0].red = 100; palette[0].green = 150; palette[0].blue = 200; + + // First set + png_set_PLTE(png_ptr, info_ptr, palette, 1); + if (info_ptr->palette == png_ptr->palette) { + fprintf(stderr, "FAIL: PLTE pointers are aliased after first set\n"); + goto fail; + } + + // Second set (should free old png_ptr->palette and info_ptr->palette) + palette[0].red = 255; + png_set_PLTE(png_ptr, info_ptr, palette, 1); + if (info_ptr->palette == png_ptr->palette) { + fprintf(stderr, "FAIL: PLTE pointers are aliased after second set\n"); + goto fail; + } + + // Verify independent freeing + png_free_data(png_ptr, info_ptr, PNG_FREE_PLTE, -1); + if (info_ptr->palette != NULL) { + fprintf(stderr, "FAIL: info_ptr->palette not NULL after free\n"); + goto fail; + } + if (png_ptr->palette == NULL) { + fprintf(stderr, "FAIL: png_ptr->palette incorrectly NULL after info_ptr free\n"); + goto fail; + } + if (png_ptr->palette[0].red != 255) { + fprintf(stderr, "FAIL: png_ptr->palette data corrupted after info_ptr free\n"); + goto fail; + } + + printf("PASS: PLTE\n"); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return 0; + +fail: + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return 1; +} + +static int test_tRNS(void) { + printf("Testing tRNS decoupling...\n"); + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) return 1; + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, NULL, NULL); + return 1; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return 1; + } + + png_byte trans[1] = {123}; + + // First set + png_set_tRNS(png_ptr, info_ptr, trans, 1, NULL); + if (info_ptr->trans == png_ptr->trans) { + fprintf(stderr, "FAIL: tRNS pointers are aliased after first set\n"); + goto fail; + } + + // Second set + trans[0] = 200; + png_set_tRNS(png_ptr, info_ptr, trans, 1, NULL); + if (info_ptr->trans == png_ptr->trans) { + fprintf(stderr, "FAIL: tRNS pointers are aliased after second set\n"); + goto fail; + } + + // Verify independent freeing + png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, -1); + if (info_ptr->trans != NULL) { + fprintf(stderr, "FAIL: info_ptr->trans not NULL after free\n"); + goto fail; + } + if (png_ptr->trans == NULL) { + fprintf(stderr, "FAIL: png_ptr->trans incorrectly NULL after info_ptr free\n"); + goto fail; + } + if (png_ptr->trans[0] != 200) { + fprintf(stderr, "FAIL: png_ptr->trans data corrupted after info_ptr free\n"); + goto fail; + } + + printf("PASS: tRNS\n"); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return 0; + +fail: + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return 1; +} + +static int test_hIST(void) { + printf("Testing hIST decoupling...\n"); + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) return 1; + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, NULL, NULL); + return 1; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return 1; + } + + // Must set palette before hIST in libpng-1.2 + png_color palette[1] = {{0,0,0}}; + png_set_PLTE(png_ptr, info_ptr, palette, 1); + + png_uint_16 hist[1] = {500}; + + // First set + png_set_hIST(png_ptr, info_ptr, hist); + if (info_ptr->hist == png_ptr->hist) { + fprintf(stderr, "FAIL: hIST pointers are aliased after first set\n"); + goto fail; + } + + // Second set + hist[0] = 1000; + png_set_hIST(png_ptr, info_ptr, hist); + if (info_ptr->hist == png_ptr->hist) { + fprintf(stderr, "FAIL: hIST pointers are aliased after second set\n"); + goto fail; + } + + // Verify independent freeing + png_free_data(png_ptr, info_ptr, PNG_FREE_HIST, -1); + if (info_ptr->hist != NULL) { + fprintf(stderr, "FAIL: info_ptr->hist not NULL after free\n"); + goto fail; + } + if (png_ptr->hist == NULL) { + fprintf(stderr, "FAIL: png_ptr->hist incorrectly NULL after info_ptr free\n"); + goto fail; + } + if (png_ptr->hist[0] != 1000) { + fprintf(stderr, "FAIL: png_ptr->hist data corrupted after info_ptr free\n"); + goto fail; + } + + printf("PASS: hIST\n"); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return 0; + +fail: + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return 1; +} + +int main(void) { + int count = 0; + count += test_PLTE(); + count += test_tRNS(); + count += test_hIST(); + if (count == 0) { + printf("ALL TESTS PASSED\n"); + return 0; + } + printf("%d TESTS FAILED\n", count); + return 1; +} ++++++ libpng12-CVE-2026-34757.patch ++++++ Index: libpng-1.2.59/Makefile.am =================================================================== --- libpng-1.2.59.orig/Makefile.am +++ libpng-1.2.59/Makefile.am @@ -14,12 +14,14 @@ PNGLIB_BASENAME= libpng@PNGLIB_MAJOR@@PN AUTOMAKE_OPTIONS = foreign # test programs - run on make check, make distcheck -check_PROGRAMS= pngtest cve-2026-33416 +check_PROGRAMS= pngtest cve-2026-33416 cve-2026-34757 pngtest_SOURCES = pngtest.c pngtest_LDADD = libpng12.la cve_2026_33416_SOURCES = cve-2026-33416.c cve_2026_33416_LDADD = libpng12.la -TESTS = test-pngtest.sh cve-2026-33416 +cve_2026_34757_SOURCES = cve-2026-34757.c +cve_2026_34757_LDADD = libpng12.la +TESTS = test-pngtest.sh cve-2026-33416 cve-2026-34757 TESTS_ENVIRONMENT= srcdir=$(srcdir) # man pages @@ -92,6 +94,7 @@ EXTRA_DIST= \ ${srcdir}/contrib/visupng/* \ $(TESTS) \ cve-2026-33416.c \ + cve-2026-34757.c \ example.c libpng-1.2.59.txt pnggccrd.c pngvcrd.c CLEANFILES= pngout.png libpng12.pc libpng12-config libpng.vers \ Index: libpng-1.2.59/pngset.c =================================================================== --- libpng-1.2.59.orig/pngset.c +++ libpng-1.2.59/pngset.c @@ -182,6 +182,7 @@ png_set_gAMA_fixed(png_structp png_ptr, void PNGAPI png_set_hIST(png_structp png_ptr, png_infop info_ptr, png_uint_16p hist) { + png_uint_16 safe_hist[PNG_MAX_PALETTE_LENGTH]; int i; png_debug1(1, "in %s storage function", "hIST"); @@ -197,6 +198,13 @@ png_set_hIST(png_structp png_ptr, png_in return; } + /* Snapshot the caller's hist before freeing, in case it points to + * info_ptr->hist (getter-to-setter aliasing). + */ + png_memcpy(safe_hist, hist, (unsigned int)info_ptr->num_palette * + (png_sizeof(png_uint_16))); + hist = safe_hist; + #ifdef PNG_FREE_ME_SUPPORTED png_free_data(png_ptr, info_ptr, PNG_FREE_HIST, 0); #endif @@ -460,7 +468,7 @@ void PNGAPI png_set_PLTE(png_structp png_ptr, png_infop info_ptr, png_colorp palette, int num_palette) { - + png_color safe_palette[PNG_MAX_PALETTE_LENGTH]; png_uint_32 max_palette_length; png_debug1(1, "in %s storage function", "PLTE"); @@ -482,6 +490,15 @@ png_set_PLTE(png_structp png_ptr, png_in } } + /* Snapshot the caller's palette before freeing, in case it points to + * info_ptr->palette (getter-to-setter aliasing). + */ + if (num_palette > 0) + png_memcpy(safe_palette, palette, (unsigned int)num_palette * + (png_sizeof(png_color))); + + palette = safe_palette; + /* It may not actually be necessary to set png_ptr->palette here; * we do it for backward compatibility with the way the png_handle_tRNS * function used to do the allocation. @@ -680,6 +697,7 @@ png_set_text_2(png_structp png_ptr, png_ int num_text) { int i; + png_textp old_text = NULL; png_debug1(1, "in %s storage function", ((png_ptr == NULL || png_ptr->chunk_name[0] == '\0') ? @@ -709,8 +727,6 @@ png_set_text_2(png_structp png_ptr, png_ if (info_ptr->text != NULL) { - png_textp old_text; - info_ptr->max_text = info_ptr->num_text + num_text + 8; old_text = info_ptr->text; @@ -725,7 +741,6 @@ png_set_text_2(png_structp png_ptr, png_ } png_memcpy(info_ptr->text, old_text, (png_size_t)(old_max_text * png_sizeof(png_text))); - png_free(png_ptr, old_text); } else { @@ -807,7 +822,10 @@ png_set_text_2(png_structp png_ptr, png_ (png_uint_32) (key_len + text_length + lang_len + lang_key_len + 4)); if (textp->key == NULL) + { + png_free(png_ptr, old_text); return(1); + } png_debug2(2, "Allocated %lu bytes at %p in png_set_text", (png_uint_32) (key_len + lang_len + lang_key_len + text_length + 4), @@ -858,6 +876,7 @@ png_set_text_2(png_structp png_ptr, png_ info_ptr->num_text++; png_debug1(3, "transferred text chunk %d", info_ptr->num_text); } + png_free(png_ptr, old_text); return(0); } #endif @@ -904,6 +923,16 @@ png_set_tRNS(png_structp png_ptr, png_in if (trans != NULL) { + /* Snapshot the caller's trans before freeing, in case it + * points to info_ptr->trans (getter-to-setter aliasing). + */ + png_byte safe_trans[PNG_MAX_PALETTE_LENGTH]; + + if (num_trans > 0 && num_trans <= PNG_MAX_PALETTE_LENGTH) + png_memcpy(safe_trans, trans, (png_size_t)num_trans); + + trans = safe_trans; + /* It may not actually be necessary to set png_ptr->trans here; * we do it for backward compatibility with the way the png_handle_tRNS * function used to do the allocation. @@ -973,6 +1002,7 @@ png_set_sPLT(png_structp png_ptr, */ { png_sPLT_tp np; + png_sPLT_tp old_spalettes; int i; if (png_ptr == NULL || info_ptr == NULL) @@ -999,7 +1029,11 @@ png_set_sPLT(png_structp png_ptr, png_memcpy(np, info_ptr->splt_palettes, info_ptr->splt_palettes_num * png_sizeof(png_sPLT_t)); - png_free(png_ptr, info_ptr->splt_palettes); + + /* Defer freeing the old array until after the copy loop below, + * in case entries aliases info_ptr->splt_palettes (getter-to-setter). + */ + old_spalettes = info_ptr->splt_palettes; info_ptr->splt_palettes=NULL; for (i = 0; i < nentries; i++) @@ -1035,6 +1069,7 @@ png_set_sPLT(png_structp png_ptr, info_ptr->splt_palettes = np; info_ptr->splt_palettes_num += nentries; + png_free(png_ptr, old_spalettes); info_ptr->valid |= PNG_INFO_sPLT; #ifdef PNG_FREE_ME_SUPPORTED info_ptr->free_me |= PNG_FREE_SPLT; @@ -1048,6 +1083,7 @@ png_set_unknown_chunks(png_structp png_p png_infop info_ptr, png_unknown_chunkp unknowns, int num_unknowns) { png_unknown_chunkp np; + png_unknown_chunkp old_unknowns; int i; if (png_ptr == NULL || info_ptr == NULL || num_unknowns == 0) @@ -1073,7 +1109,11 @@ png_set_unknown_chunks(png_structp png_p png_memcpy(np, info_ptr->unknown_chunks, info_ptr->unknown_chunks_num * png_sizeof(png_unknown_chunk)); - png_free(png_ptr, info_ptr->unknown_chunks); + + /* Defer freeing the old array until after the copy loop below, + * in case unknowns aliases info_ptr->unknown_chunks (getter-to-setter). + */ + old_unknowns = info_ptr->unknown_chunks; info_ptr->unknown_chunks = NULL; for (i = 0; i < num_unknowns; i++) @@ -1107,6 +1147,7 @@ png_set_unknown_chunks(png_structp png_p info_ptr->unknown_chunks = np; info_ptr->unknown_chunks_num += num_unknowns; + png_free(png_ptr, old_unknowns); #ifdef PNG_FREE_ME_SUPPORTED info_ptr->free_me |= PNG_FREE_UNKN; #endif Index: libpng-1.2.59/cve-2026-34757.c =================================================================== --- /dev/null +++ libpng-1.2.59/cve-2026-34757.c @@ -0,0 +1,372 @@ +/* cve-2026-34757.c + * + * Copyright (c) 2026 Cosmin Truta + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * Test the get-then-set roundtrip pattern for various chunks. + * + * Passing the internal pointer returned by a getter back into the + * corresponding setter is a natural API usage pattern. A previous + * version had a use-after-free on this path because the setter freed + * the internal buffer before copying from the caller-supplied pointer. + */ + +#include "png.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static int test_PLTE_aliasing(void) { + int i; + printf("Testing PLTE aliasing... "); + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) return 1; + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_write_struct(&png_ptr, NULL); + return 1; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + + png_color palette[4]; + for (i = 0; i < 4; i++) { + palette[i].red = (png_byte)(i * 10); + palette[i].green = (png_byte)(i * 20); + palette[i].blue = (png_byte)(i * 30); + } + + png_set_IHDR(png_ptr, info_ptr, 1, 1, 8, PNG_COLOR_TYPE_PALETTE, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + png_set_PLTE(png_ptr, info_ptr, palette, 4); + + png_colorp got_palette; + int num_palette; + png_get_PLTE(png_ptr, info_ptr, &got_palette, &num_palette); + + /* Critical call: got_palette aliases internal info_ptr->palette */ + png_set_PLTE(png_ptr, info_ptr, got_palette, num_palette); + + /* Verify data */ + png_get_PLTE(png_ptr, info_ptr, &got_palette, &num_palette); + if (num_palette != 4) { + fprintf(stderr, "FAIL: PLTE count mismatch\n"); + goto fail; + } + for (i = 0; i < 4; i++) { + if (got_palette[i].red != (png_byte)(i * 10) || + got_palette[i].green != (png_byte)(i * 20) || + got_palette[i].blue != (png_byte)(i * 30)) { + fprintf(stderr, "FAIL: PLTE data corruption at %d\n", i); + goto fail; + } + } + + printf("PASS\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 0; + +fail: + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; +} + +#ifdef PNG_hIST_SUPPORTED +static int test_hIST_aliasing(void) { + int i; + printf("Testing hIST aliasing... "); + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) return 1; + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_write_struct(&png_ptr, NULL); + return 1; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + + png_color palette[4] = {{0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}}; + png_set_IHDR(png_ptr, info_ptr, 1, 1, 8, PNG_COLOR_TYPE_PALETTE, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + png_set_PLTE(png_ptr, info_ptr, palette, 4); + + png_uint_16 hist[4] = {100, 200, 300, 400}; + png_set_hIST(png_ptr, info_ptr, hist); + + png_uint_16p got_hist; + png_get_hIST(png_ptr, info_ptr, &got_hist); + + /* Critical call: got_hist aliases internal info_ptr->hist */ + png_set_hIST(png_ptr, info_ptr, got_hist); + + /* Verify data */ + png_get_hIST(png_ptr, info_ptr, &got_hist); + for (i = 0; i < 4; i++) { + if (got_hist[i] != (png_uint_16)((i + 1) * 100)) { + fprintf(stderr, "FAIL: hIST data corruption at %d\n", i); + goto fail; + } + } + + printf("PASS\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 0; + +fail: + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; +} +#endif + +#ifdef PNG_tRNS_SUPPORTED +static int test_tRNS_aliasing(void) { + printf("Testing tRNS aliasing... "); + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) return 1; + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_write_struct(&png_ptr, NULL); + return 1; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + + png_color palette[4] = {{0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}}; + png_set_IHDR(png_ptr, info_ptr, 1, 1, 8, PNG_COLOR_TYPE_PALETTE, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + png_set_PLTE(png_ptr, info_ptr, palette, 4); + + png_byte trans[4] = {255, 128, 64, 0}; + png_set_tRNS(png_ptr, info_ptr, trans, 4, NULL); + + png_bytep got_trans; + int num_trans; + png_color_16p trans_values; + png_get_tRNS(png_ptr, info_ptr, &got_trans, &num_trans, &trans_values); + + /* Critical call: got_trans aliases internal info_ptr->trans */ + png_set_tRNS(png_ptr, info_ptr, got_trans, num_trans, trans_values); + + /* Verify data */ + png_get_tRNS(png_ptr, info_ptr, &got_trans, &num_trans, &trans_values); + if (num_trans != 4) { + fprintf(stderr, "FAIL: tRNS count mismatch\n"); + goto fail; + } + if (got_trans[1] != 128 || got_trans[2] != 64 || got_trans[3] != 0) { + fprintf(stderr, "FAIL: tRNS data corruption\n"); + goto fail; + } + + printf("PASS\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 0; + +fail: + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; +} +#endif + +#ifdef PNG_TEXT_SUPPORTED +static int test_text_aliasing(void) { + printf("Testing text aliasing... "); + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) return 1; + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_write_struct(&png_ptr, NULL); + return 1; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + + png_text text[2]; + text[0].compression = PNG_TEXT_COMPRESSION_NONE; + text[0].key = (png_charp)"Key1"; + text[0].text = (png_charp)"Value1"; + text[1].compression = PNG_TEXT_COMPRESSION_NONE; + text[1].key = (png_charp)"Key2"; + text[1].text = (png_charp)"Value2"; + + png_set_text(png_ptr, info_ptr, text, 2); + + png_textp got_text; + int num_text; + png_get_text(png_ptr, info_ptr, &got_text, &num_text); + + /* Trigger reallocation by adding enough entries to exceed max_text (initially 0+2+8=10, so let's add 10 more) */ + /* This will use the deferred free path in png_set_text_2 */ + { + int i; + for (i = 0; i < 5; i++) { + png_set_text(png_ptr, info_ptr, got_text, num_text); + } + } + + /* Verify data */ + png_get_text(png_ptr, info_ptr, &got_text, &num_text); + if (num_text != 12) { + fprintf(stderr, "FAIL: text count mismatch: %d\n", num_text); + goto fail; + } + if (strcmp(got_text[0].key, "Key1") != 0 || strcmp(got_text[11].text, "Value2") != 0) { + fprintf(stderr, "FAIL: text data corruption\n"); + goto fail; + } + + printf("PASS\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 0; + +fail: + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; +} +#endif + +#ifdef PNG_sPLT_SUPPORTED +static int test_sPLT_aliasing(void) { + printf("Testing sPLT aliasing... "); + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) return 1; + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_write_struct(&png_ptr, NULL); + return 1; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + + png_sPLT_t splt; + png_sPLT_entry entries[1]; + entries[0].red = 100; entries[0].green = 200; entries[0].blue = 300; entries[0].alpha = 400; entries[0].frequency = 500; + splt.name = (png_charp)"Test sPLT"; + splt.depth = 8; + splt.entries = entries; + splt.nentries = 1; + + png_set_sPLT(png_ptr, info_ptr, &splt, 1); + + png_sPLT_tp got_splt; + int num_splt = (int)png_get_sPLT(png_ptr, info_ptr, &got_splt); + + /* Critical call: got_splt aliases internal info_ptr->splt_palettes */ + png_set_sPLT(png_ptr, info_ptr, got_splt, num_splt); + + /* Verify data */ + num_splt = (int)png_get_sPLT(png_ptr, info_ptr, &got_splt); + if (num_splt != 2) { + fprintf(stderr, "FAIL: sPLT count mismatch: %d\n", num_splt); + goto fail; + } + if (strcmp(got_splt[0].name, "Test sPLT") != 0 || got_splt[1].entries[0].red != 100) { + fprintf(stderr, "FAIL: sPLT data corruption\n"); + goto fail; + } + + printf("PASS\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 0; + +fail: + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; +} +#endif + +#ifdef PNG_UNKNOWN_CHUNKS_SUPPORTED +static int test_unknown_aliasing(void) { + printf("Testing unknown chunks aliasing... "); + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) return 1; + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_write_struct(&png_ptr, NULL); + return 1; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + + png_unknown_chunk unknowns[1]; + memcpy(unknowns[0].name, "teSt", 5); + unknowns[0].data = (png_bytep)"TestData"; + unknowns[0].size = 8; + unknowns[0].location = PNG_HAVE_IHDR; + + png_set_unknown_chunks(png_ptr, info_ptr, unknowns, 1); + + png_unknown_chunkp got_unknowns; + int num_unknowns = (int)png_get_unknown_chunks(png_ptr, info_ptr, &got_unknowns); + + /* Critical call: got_unknowns aliases internal info_ptr->unknown_chunks */ + png_set_unknown_chunks(png_ptr, info_ptr, got_unknowns, num_unknowns); + + /* Verify data */ + num_unknowns = (int)png_get_unknown_chunks(png_ptr, info_ptr, &got_unknowns); + if (num_unknowns != 2) { + fprintf(stderr, "FAIL: unknown chunks count mismatch: %d\n", num_unknowns); + goto fail; + } + if (memcmp(got_unknowns[0].name, "teSt", 4) != 0 || got_unknowns[1].size != 8) { + fprintf(stderr, "FAIL: unknown chunks data corruption\n"); + goto fail; + } + + printf("PASS\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 0; + +fail: + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; +} +#endif + +int main(void) { + int count = 0; + count += test_PLTE_aliasing(); +#ifdef PNG_hIST_SUPPORTED + count += test_hIST_aliasing(); +#endif +#ifdef PNG_tRNS_SUPPORTED + count += test_tRNS_aliasing(); +#endif +#ifdef PNG_TEXT_SUPPORTED + count += test_text_aliasing(); +#endif +#ifdef PNG_sPLT_SUPPORTED + count += test_sPLT_aliasing(); +#endif +#ifdef PNG_UNKNOWN_CHUNKS_SUPPORTED + count += test_unknown_aliasing(); +#endif + + if (count == 0) { + printf("ALL ALIASING TESTS PASSED\n"); + return 0; + } + printf("%d ALIASING TESTS FAILED\n", count); + return 1; +}
