From fd9be73eb0f1bd4fb0ac7c2272334262fa311815 Mon Sep 17 00:00:00 2001 From: Michal Hlavinka Date: Mon, 25 May 2026 11:35:55 +0200 Subject: [PATCH] fix CVE-2026-33416: use-after-free via pointer aliasing in png_set_tRNS and png_set_PLTE (RHEL-161457) Resolves: RHEL-161457 --- libpng-1.6-CVE-2026-33416_p1of5.patch | 77 ++++++++++++++++++++++ libpng-1.6-CVE-2026-33416_p2of5.patch | 25 +++++++ libpng-1.6-CVE-2026-33416_p3of5.patch | 94 +++++++++++++++++++++++++++ libpng-1.6-CVE-2026-33416_p4of5.patch | 24 +++++++ libpng-1.6-CVE-2026-33416_p5of5.patch | 30 +++++++++ libpng15.spec | 24 ++++++- 6 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 libpng-1.6-CVE-2026-33416_p1of5.patch create mode 100644 libpng-1.6-CVE-2026-33416_p2of5.patch create mode 100644 libpng-1.6-CVE-2026-33416_p3of5.patch create mode 100644 libpng-1.6-CVE-2026-33416_p4of5.patch create mode 100644 libpng-1.6-CVE-2026-33416_p5of5.patch diff --git a/libpng-1.6-CVE-2026-33416_p1of5.patch b/libpng-1.6-CVE-2026-33416_p1of5.patch new file mode 100644 index 0000000..47ebc32 --- /dev/null +++ b/libpng-1.6-CVE-2026-33416_p1of5.patch @@ -0,0 +1,77 @@ +--- libpng-1.5.30/pngread.c 2017-09-28 21:10:04.000000000 +0200 ++++ libpng-1.5.30/pngread.c 2026-05-21 21:17:19.540923384 +0200 +@@ -1056,9 +1056,11 @@ png_read_destroy(png_structp png_ptr, pn + + #if defined(PNG_tRNS_SUPPORTED) || \ + defined(PNG_READ_EXPAND_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) +- if (png_ptr->free_me & PNG_FREE_TRNS) +- png_free(png_ptr, png_ptr->trans_alpha); +- png_ptr->free_me &= ~PNG_FREE_TRNS; ++ /* png_ptr->trans_alpha is always independently allocated (not aliased ++ * with info_ptr->trans_alpha), so free it unconditionally. ++ */ ++ png_free(png_ptr, png_ptr->trans_alpha); ++ png_ptr->trans_alpha = NULL; + #endif + + #ifdef PNG_READ_hIST_SUPPORTED +--- libpng-1.5.30/pngset.c 2026-05-21 21:15:27.664454789 +0200 ++++ libpng-1.5.30/pngset.c 2026-05-21 21:18:03.395444070 +0200 +@@ -1008,19 +1008,33 @@ png_set_tRNS(png_structp png_ptr, png_in + + if (trans_alpha != NULL) + { +- /* It may not actually be necessary to set png_ptr->trans_alpha here; +- * we do it for backward compatibility with the way the png_handle_tRNS +- * function used to do the allocation. +- */ +- + png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, 0); + +- /* Changed from num_trans to PNG_MAX_PALETTE_LENGTH in version 1.2.1 */ +- png_ptr->trans_alpha = info_ptr->trans_alpha = +- (png_bytep)png_malloc(png_ptr, (png_size_t)PNG_MAX_PALETTE_LENGTH); +- + if (num_trans > 0 && num_trans <= PNG_MAX_PALETTE_LENGTH) ++ { ++ /* Allocate info_ptr's copy of the transparency data. */ ++ info_ptr->trans_alpha = ++ (png_bytep)png_malloc(png_ptr, (png_size_t)PNG_MAX_PALETTE_LENGTH); + png_memcpy(info_ptr->trans_alpha, trans_alpha, (png_size_t)num_trans); ++ ++ /* Allocate an independent copy for png_struct, so that the ++ * lifetime of png_ptr->trans_alpha is decoupled from the ++ * lifetime of info_ptr->trans_alpha. Previously these two ++ * pointers were aliased, which caused a use-after-free if ++ * png_free_data freed info_ptr->trans_alpha while ++ * png_ptr->trans_alpha was still in use by the row transform ++ * functions (e.g. png_do_expand_palette). ++ */ ++ png_free(png_ptr, png_ptr->trans_alpha); ++ png_ptr->trans_alpha = ++ (png_bytep)png_malloc(png_ptr, (png_size_t)PNG_MAX_PALETTE_LENGTH); ++ png_memcpy(png_ptr->trans_alpha, trans_alpha, (png_size_t)num_trans); ++ } ++ else ++ { ++ png_free(png_ptr, png_ptr->trans_alpha); ++ png_ptr->trans_alpha = NULL; ++ } + } + + if (trans_color != NULL) +--- libpng-1.5.30/pngwrite.c 2017-09-28 21:10:04.000000000 +0200 ++++ libpng-1.5.30/pngwrite.c 2026-05-21 21:18:14.420323571 +0200 +@@ -977,6 +977,12 @@ png_write_destroy(png_structp png_ptr) + png_free(png_ptr, png_ptr->paeth_row); + #endif + ++#if defined(PNG_tRNS_SUPPORTED) ++ /* Free the independent copy of trans_alpha owned by png_struct. */ ++ png_free(png_ptr, png_ptr->trans_alpha); ++ png_ptr->trans_alpha = NULL; ++#endif ++ + #ifdef PNG_SETJMP_SUPPORTED + /* Reset structure */ + png_memcpy(tmp_jmp, png_ptr->longjmp_buffer, png_sizeof(jmp_buf)); diff --git a/libpng-1.6-CVE-2026-33416_p2of5.patch b/libpng-1.6-CVE-2026-33416_p2of5.patch new file mode 100644 index 0000000..816d4ec --- /dev/null +++ b/libpng-1.6-CVE-2026-33416_p2of5.patch @@ -0,0 +1,25 @@ +--- libpng-1.5.30/pngset.c 2026-05-21 21:18:03.395444070 +0200 ++++ libpng-1.5.30/pngset.c 2026-05-21 21:24:35.399291945 +0200 +@@ -1012,9 +1012,13 @@ png_set_tRNS(png_structp png_ptr, png_in + + if (num_trans > 0 && num_trans <= PNG_MAX_PALETTE_LENGTH) + { +- /* Allocate info_ptr's copy of the transparency data. */ ++ /* Allocate info_ptr's copy of the transparency data. ++ * Initialize all entries to fully opaque (0xff), then overwrite ++ * the first num_trans entries with the actual values. ++ */ + info_ptr->trans_alpha = + (png_bytep)png_malloc(png_ptr, (png_size_t)PNG_MAX_PALETTE_LENGTH); ++ png_memset(info_ptr->trans_alpha, 0xff, PNG_MAX_PALETTE_LENGTH); + png_memcpy(info_ptr->trans_alpha, trans_alpha, (png_size_t)num_trans); + + /* Allocate an independent copy for png_struct, so that the +@@ -1028,6 +1032,7 @@ png_set_tRNS(png_structp png_ptr, png_in + png_free(png_ptr, png_ptr->trans_alpha); + png_ptr->trans_alpha = + (png_bytep)png_malloc(png_ptr, (png_size_t)PNG_MAX_PALETTE_LENGTH); ++ png_memset(png_ptr->trans_alpha, 0xff, PNG_MAX_PALETTE_LENGTH); + png_memcpy(png_ptr->trans_alpha, trans_alpha, (png_size_t)num_trans); + } + else diff --git a/libpng-1.6-CVE-2026-33416_p3of5.patch b/libpng-1.6-CVE-2026-33416_p3of5.patch new file mode 100644 index 0000000..8834c17 --- /dev/null +++ b/libpng-1.6-CVE-2026-33416_p3of5.patch @@ -0,0 +1,94 @@ +--- libpng-1.5.30/pngread.c 2026-05-21 21:17:19.540923384 +0200 ++++ libpng-1.5.30/pngread.c 2026-05-21 21:32:04.901633911 +0200 +@@ -1050,9 +1050,11 @@ png_read_destroy(png_structp png_ptr, pn + png_free(png_ptr, png_ptr->quantize_index); + #endif + +- if (png_ptr->free_me & PNG_FREE_PLTE) +- png_zfree(png_ptr, png_ptr->palette); +- png_ptr->free_me &= ~PNG_FREE_PLTE; ++ /* png_ptr->palette is always independently allocated (not aliased ++ * with info_ptr->palette), so free it unconditionally. ++ */ ++ png_free(png_ptr, png_ptr->palette); ++ png_ptr->palette = NULL; + + #if defined(PNG_tRNS_SUPPORTED) || \ + defined(PNG_READ_EXPAND_SUPPORTED) || defined(PNG_READ_BACKGROUND_SUPPORTED) +--- libpng-1.5.30/pngrtran.c 2026-05-21 21:15:27.667146120 +0200 ++++ libpng-1.5.30/pngrtran.c 2026-05-21 21:51:26.488584634 +0200 +@@ -698,7 +698,13 @@ png_set_quantize(png_structp png_ptr, pn + } + 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)); ++ png_memcpy(png_ptr->palette, palette, (unsigned int)num_palette * ++ png_sizeof(png_color)); + } + png_ptr->num_palette = (png_uint_16)num_palette; + +--- libpng-1.5.30/pngset.c 2026-05-21 21:24:35.399291945 +0200 ++++ libpng-1.5.30/pngset.c 2026-05-21 21:51:26.572583763 +0200 +@@ -607,22 +607,37 @@ png_set_PLTE(png_structp png_ptr, png_in + return; + } + +- /* 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. +- */ + png_free_data(png_ptr, info_ptr, PNG_FREE_PLTE, 0); + + /* Changed in libpng-1.2.1 to allocate PNG_MAX_PALETTE_LENGTH instead + * of num_palette entries, in case of an invalid PNG file or incorrect + * call to png_set_PLTE() with too-large sample values. ++ * ++ * Allocate independent buffers for info_ptr and png_ptr so that the ++ * lifetime of png_ptr->palette is decoupled from the lifetime of ++ * info_ptr->palette. Previously, these two pointers were aliased, ++ * which caused a use-after-free vulnerability if png_free_data freed ++ * info_ptr->palette while png_ptr->palette was still in use by the ++ * row transform functions (e.g. png_do_expand_palette). ++ * ++ * Both buffers are allocated with png_calloc to zero-fill, because ++ * the ARM NEON palette riffle reads all 256 entries unconditionally, ++ * regardless of num_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)); ++ info_ptr->palette = (png_colorp)png_calloc(png_ptr, ++ PNG_MAX_PALETTE_LENGTH * png_sizeof(png_color)); ++ png_ptr->num_palette = info_ptr->num_palette = (png_uint_16)num_palette; + +- png_memcpy(png_ptr->palette, palette, num_palette * png_sizeof(png_color)); +- info_ptr->palette = png_ptr->palette; +- info_ptr->num_palette = png_ptr->num_palette = (png_uint_16)num_palette; ++ if (num_palette > 0) ++ { ++ png_memcpy(info_ptr->palette, palette, (unsigned int)num_palette * ++ png_sizeof(png_color)); ++ png_memcpy(png_ptr->palette, palette, (unsigned int)num_palette * ++ png_sizeof(png_color)); ++ } + + info_ptr->free_me |= PNG_FREE_PLTE; + +--- libpng-1.5.30/pngwrite.c 2026-05-21 21:18:14.420323571 +0200 ++++ libpng-1.5.30/pngwrite.c 2026-05-21 21:51:26.656582891 +0200 +@@ -983,6 +983,10 @@ png_write_destroy(png_structp png_ptr) + png_ptr->trans_alpha = NULL; + #endif + ++ /* Free the independent copy of the palette owned by png_struct. */ ++ png_free(png_ptr, png_ptr->palette); ++ png_ptr->palette = NULL; ++ + #ifdef PNG_SETJMP_SUPPORTED + /* Reset structure */ + png_memcpy(tmp_jmp, png_ptr->longjmp_buffer, png_sizeof(jmp_buf)); diff --git a/libpng-1.6-CVE-2026-33416_p4of5.patch b/libpng-1.6-CVE-2026-33416_p4of5.patch new file mode 100644 index 0000000..8dfd920 --- /dev/null +++ b/libpng-1.6-CVE-2026-33416_p4of5.patch @@ -0,0 +1,24 @@ +--- libpng-1.5.30/pngrtran.c 2026-05-21 21:51:26.488584634 +0200 ++++ libpng-1.5.30/pngrtran.c 2026-05-21 22:17:17.648501809 +0200 +@@ -1895,6 +1895,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) + { diff --git a/libpng-1.6-CVE-2026-33416_p5of5.patch b/libpng-1.6-CVE-2026-33416_p5of5.patch new file mode 100644 index 0000000..cdbf8fb --- /dev/null +++ b/libpng-1.6-CVE-2026-33416_p5of5.patch @@ -0,0 +1,30 @@ +--- libpng-1.5.30/pngrtran.c 2026-05-21 22:17:17.648501809 +0200 ++++ libpng-1.5.30/pngrtran.c 2026-05-21 22:18:11.383978646 +0200 +@@ -1895,19 +1895,15 @@ 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) + { +- 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)); +- } ++ /* Sync info_ptr->palette with png_ptr->palette, which may ++ * have been modified by png_init_read_transformations ++ * (e.g. for gamma correction or background compositing). ++ */ ++ png_memcpy(info_ptr->palette, png_ptr->palette, ++ PNG_MAX_PALETTE_LENGTH * png_sizeof(png_color)); + } + + #ifdef PNG_READ_EXPAND_SUPPORTED diff --git a/libpng15.spec b/libpng15.spec index af583ce..c4a189d 100644 --- a/libpng15.spec +++ b/libpng15.spec @@ -1,7 +1,7 @@ Summary: Old version of libpng, needed to run old binaries Name: libpng15 Version: 1.5.30 -Release: 15%{?dist} +Release: 16%{?dist} License: zlib URL: http://www.libpng.org/pub/png/ @@ -16,6 +16,20 @@ Patch1: libpng15-CVE-2018-13785.patch # from upstream, for <= 1.6.54, RHEL-148412 # https://github.com/pnggroup/libpng/commit/01d03b8453eb30ade759cd45c707e5a1c7277d88 Patch2: libpng-1.6-cve-2026-25646.patch +# from upstream, for <1.6.56 (fix), for <1.6.58 (regression fix), RHEL-161457 +# https://github.com/pnggroup/libpng/commit/23019269764e35ed8458e517f1897bd3c54820eb +Patch12: libpng-1.6-CVE-2026-33416_p1of5.patch +# https://github.com/pnggroup/libpng/commit/a3a21443ed12bfa1ef46fa0d4fb2b74a0fa34a25 +Patch13: libpng-1.6-CVE-2026-33416_p2of5.patch +# https://github.com/pnggroup/libpng/commit/7ea9eea884a2328cc7fdcb3c0c00246a50d90667 +Patch14: libpng-1.6-CVE-2026-33416_p3of5.patch +# https://github.com/pnggroup/libpng/commit/c1b0318b393c90679e6fa5bc1d329fd5d5012ec1 +Patch15: libpng-1.6-CVE-2026-33416_p4of5.patch +# regression fix for 7ea9eea8 (part 3) +# https://github.com/pnggroup/libpng/commit/d4c4e49eb5c8981075ec2cd946428758c0cda6ac +Patch16: libpng-1.6-CVE-2026-33416_p5of5.patch + + BuildRequires: gcc BuildRequires: zlib-devel @@ -33,6 +47,11 @@ version of libpng. %patch -P 0 -p1 %patch -P 1 -p1 %patch -P 2 -p1 -b .cve-2026-25646 +%patch -P 12 -p1 -b .CVE-2026-33416_p1of4 +%patch -P 13 -p1 -b .CVE-2026-33416_p2of4 +%patch -P 14 -p1 -b .CVE-2026-33416_p3of5 +%patch -P 15 -p1 -b .CVE-2026-33416_p4of5 +%patch -P 16 -p1 -b .CVE-2026-33416_p5of5 # Provide pngusr.dfa for build. cp -p %{SOURCE1} . @@ -58,6 +77,9 @@ rm -rf $RPM_BUILD_ROOT%{_bindir}/* %{_libdir}/libpng15.so.* %changelog +* Tue Jun 02 2026 Michal Hlavinka - 1.5.30-16 +- fix CVE-2026-33416: use-after-free via pointer aliasing in png_set_tRNS and png_set_PLTE (RHEL-161457) + * Fri Feb 20 2026 Michal Hlavinka - 1.5.30-15 - fix CVE-2026-25646: heap buffer overflow in png_set_quantize (RHEL-148412)