Merge tag 'v1.6.57' into branch 'libpng18' into develop
diff --git a/AUTHORS.md b/AUTHORS.md index 3c924f9..291a0b7 100644 --- a/AUTHORS.md +++ b/AUTHORS.md
@@ -29,6 +29,7 @@ * Mans Rullgard * Matt Sarett * Mike Klein + * Mohammad Seet * Pascal Massimino * Paul Schmidt * Petr Simecek
diff --git a/CHANGES b/CHANGES index cdcc35c..7a6156e 100644 --- a/CHANGES +++ b/CHANGES
@@ -6368,6 +6368,17 @@ (Contributed by Bob Friesenhahn and Philippe Antoine.) Performed various refactorings and cleanups. +Version 1.6.57 [April 8, 2026] + Fixed CVE-2026-34757 (medium severity): + Use-after-free in `png_set_PLTE`, `png_set_tRNS` and `png_set_hIST` + leading to corrupted chunk data and potential heap information disclosure. + Also hardened the append-style setters (`png_set_text`, `png_set_sPLT`, + `png_set_unknown_chunks`) against a theoretical variant of the same + aliasing pattern. + (Reported by Iv4n <Iv4n550@users.noreply.github.com>.) + Fixed integer overflow in rowbytes computation in read transforms. + (Contributed by Mohammad Seet.) + Version 2.0.0 [TODO] Send comments/corrections/commendations to png-mng-implement at lists.sf.net.
diff --git a/CMakeLists.txt b/CMakeLists.txt index 1890e8c..8e92b6f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -387,6 +387,9 @@ set(pngstest_sources contrib/libtests/pngstest.c ) +set(pnggetset_sources + contrib/libtests/pnggetset.c +) set(pngunknown_sources contrib/libtests/pngunknown.c ) @@ -549,6 +552,15 @@ COMMAND pngtest FILES "${TEST_PNG3_PNGS}") + # pnggetset test: + # Getter-to-setter roundtrips for various chunk types. + add_executable(pnggetset ${pnggetset_sources}) + target_link_libraries(pnggetset + PRIVATE png_shared) + + png_add_test(NAME pnggetset + COMMAND pnggetset) + # pngvalid tests: # Internal validation of standard and progressive reading, # transforms, and gamma handling.
diff --git a/Makefile.am b/Makefile.am index 0126e20..5cd0381 100644 --- a/Makefile.am +++ b/Makefile.am
@@ -13,7 +13,7 @@ # test programs - run on make check, make distcheck if ENABLE_TESTS -check_PROGRAMS= pngtest pngunknown pngstest pngvalid pngimage pngcp +check_PROGRAMS= pngtest pnggetset pngunknown pngstest pngvalid pngimage pngcp if HAVE_CLOCK_GETTIME check_PROGRAMS += timepng endif @@ -42,6 +42,9 @@ pngtest_SOURCES = pngtest.c pngtest_LDADD = libpng@PNGLIB_MAJOR@.la +pnggetset_SOURCES = contrib/libtests/pnggetset.c +pnggetset_LDADD = libpng@PNGLIB_MAJOR@@PNGLIB_MINOR@.la + pngvalid_SOURCES = contrib/libtests/pngvalid.c pngvalid_LDADD = libpng@PNGLIB_MAJOR@.la @@ -73,6 +76,7 @@ if ENABLE_TESTS TESTS =\ tests/pngtest-all\ + tests/pnggetset\ tests/pngvalid-gamma-16-to-8\ tests/pngvalid-gamma-alpha-mode\ tests/pngvalid-gamma-background\ @@ -254,9 +258,10 @@ pngtest.o: pnglibconf.h contrib/libtests/makepng.o: pnglibconf.h +contrib/libtests/pnggetset.o: pnglibconf.h +contrib/libtests/pngimage.o: pnglibconf.h contrib/libtests/pngstest.o: pnglibconf.h contrib/libtests/pngunknown.o: pnglibconf.h -contrib/libtests/pngimage.o: pnglibconf.h contrib/libtests/pngvalid.o: pnglibconf.h contrib/libtests/readpng.o: pnglibconf.h contrib/libtests/tarith.o: pnglibconf.h
diff --git a/arm/arm_init.c b/arm/arm_init.c index 4a25f41..664d05d 100644 --- a/arm/arm_init.c +++ b/arm/arm_init.c
@@ -186,7 +186,7 @@ /* Finally update row_info to reflect the expanded output: */ row_info->bit_depth = 8; row_info->pixel_depth = 32; - row_info->rowbytes = row_width * 4; + row_info->rowbytes = (size_t)row_width * 4; row_info->color_type = 6; row_info->channels = 4; return 1; @@ -194,7 +194,7 @@ else { /* No tRNS chunk (num_trans == 0), expand to RGB not RGBA. */ - png_byte *dp = row + (3/*RGB*/*row_width - 1); + png_byte *dp = row + (3/*RGB*/ * (size_t)row_width - 1); png_uint_32 i = png_target_do_expand_palette_rgb8_neon(palette, row_info->width, &sp, &dp); @@ -213,7 +213,7 @@ row_info->bit_depth = 8; row_info->pixel_depth = 24; - row_info->rowbytes = row_width * 3; + row_info->rowbytes = (size_t)row_width * 3; row_info->color_type = 2; row_info->channels = 3; return 1;
diff --git a/contrib/libtests/pnggetset.c b/contrib/libtests/pnggetset.c new file mode 100644 index 0000000..6ae43dc --- /dev/null +++ b/contrib/libtests/pnggetset.c
@@ -0,0 +1,648 @@ +/* pnggetset.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 for chunk types whose getters return + * a pointer to internal storage. + * + * Passing such a pointer back into the corresponding setter must not + * cause a use-after-free. A previous version freed the internal buffer + * before copying from the caller-supplied pointer. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#if defined(HAVE_CONFIG_H) && !defined(PNG_NO_CONFIG_H) +# include <config.h> +#endif + +#ifdef PNG_FREESTANDING_TESTS +# include <png.h> +#else +# include "../../png.h" +#endif + +/* Test: get the PLTE, pass it straight back to set, verify roundtrip. */ +static int +test_plte_roundtrip(void) +{ + png_structp png_ptr; + png_infop info_ptr; + png_color palette[4]; + png_colorp got_palette = NULL; + int num_palette = 0; + int i; + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + if (png_ptr == NULL) + { + fprintf(stderr, "pnggetset: png_create_write_struct failed\n"); + return 1; + } + + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) + { + fprintf(stderr, "pnggetset: png_create_info_struct failed\n"); + png_destroy_write_struct(&png_ptr, NULL); + return 1; + } + + if (setjmp(png_jmpbuf(png_ptr))) + { + fprintf(stderr, "pnggetset: libpng error in test_plte_roundtrip\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + + /* Set up a palette-color image header. */ + 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); + + /* Populate with recognizable values. */ + 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_PLTE(png_ptr, info_ptr, palette, 4); + + /* Get the internal pointer and feed it straight back. */ + png_get_PLTE(png_ptr, info_ptr, &got_palette, &num_palette); + if (got_palette == NULL || num_palette != 4) + { + fprintf(stderr, "pnggetset: png_get_PLTE returned unexpected values\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + + /* This is the critical call: the pointer aliases info_ptr->palette. */ + png_set_PLTE(png_ptr, info_ptr, got_palette, num_palette); + + /* Verify the data survived the roundtrip. */ + got_palette = NULL; + num_palette = 0; + png_get_PLTE(png_ptr, info_ptr, &got_palette, &num_palette); + if (got_palette == NULL || num_palette != 4) + { + fprintf(stderr, "pnggetset: PLTE lost after roundtrip\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + 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, + "pnggetset: PLTE entry %d corrupted after roundtrip\n", i); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + } + + png_destroy_write_struct(&png_ptr, &info_ptr); + return 0; +} + +#ifdef PNG_hIST_SUPPORTED +/* Test: get the hIST, pass it straight back to set, verify roundtrip. */ +static int +test_hist_roundtrip(void) +{ + png_structp png_ptr; + png_infop info_ptr; + png_color palette[4]; + png_uint_16 hist[4]; + png_uint_16p got_hist = NULL; + int i; + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + if (png_ptr == NULL) + { + fprintf(stderr, "pnggetset: png_create_write_struct failed\n"); + return 1; + } + + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) + { + fprintf(stderr, "pnggetset: png_create_info_struct failed\n"); + png_destroy_write_struct(&png_ptr, NULL); + return 1; + } + + if (setjmp(png_jmpbuf(png_ptr))) + { + fprintf(stderr, "pnggetset: libpng error in test_hist_roundtrip\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + + /* Set up a palette-color image header. */ + memset(palette, 0, sizeof palette); + 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); + + /* Populate with recognizable values. */ + for (i = 0; i < 4; i++) + hist[i] = (png_uint_16)(i * 100 + 42); + + png_set_hIST(png_ptr, info_ptr, hist); + + /* Get the internal pointer and feed it straight back. */ + if (png_get_hIST(png_ptr, info_ptr, &got_hist) == 0 || got_hist == NULL) + { + fprintf(stderr, "pnggetset: png_get_hIST returned unexpected values\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + + /* This is the critical call: the pointer aliases info_ptr->hist. */ + png_set_hIST(png_ptr, info_ptr, got_hist); + + /* Verify the data survived the roundtrip. */ + got_hist = NULL; + if (png_get_hIST(png_ptr, info_ptr, &got_hist) == 0 || got_hist == NULL) + { + fprintf(stderr, "pnggetset: hIST lost after roundtrip\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + for (i = 0; i < 4; i++) + { + if (got_hist[i] != (png_uint_16)(i * 100 + 42)) + { + fprintf(stderr, + "pnggetset: hIST entry %d corrupted after roundtrip\n", i); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + } + + png_destroy_write_struct(&png_ptr, &info_ptr); + return 0; +} +#endif /* PNG_hIST_SUPPORTED */ + +#ifdef PNG_tRNS_SUPPORTED +/* Test: get the tRNS, pass it straight back to set, verify roundtrip. */ +static int +test_trns_roundtrip(void) +{ + png_structp png_ptr; + png_infop info_ptr; + png_color palette[4]; + png_byte trans_alpha[4]; + png_color_16 trans_color; + png_bytep got_alpha = NULL; + png_color_16p got_color = NULL; + int num_trans = 0; + int i; + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + if (png_ptr == NULL) + { + fprintf(stderr, "pnggetset: png_create_write_struct failed\n"); + return 1; + } + + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) + { + fprintf(stderr, "pnggetset: png_create_info_struct failed\n"); + png_destroy_write_struct(&png_ptr, NULL); + return 1; + } + + if (setjmp(png_jmpbuf(png_ptr))) + { + fprintf(stderr, "pnggetset: libpng error in test_trns_roundtrip\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + + /* Set up a palette-color image. */ + memset(palette, 0, sizeof palette); + 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); + + /* Populate tRNS with recognizable values. */ + for (i = 0; i < 4; i++) + trans_alpha[i] = (png_byte)(0xff - i * 0x11); + memset(&trans_color, 0, sizeof trans_color); + + png_set_tRNS(png_ptr, info_ptr, trans_alpha, 4, &trans_color); + + /* Get the internal pointer and feed it straight back. */ + png_get_tRNS(png_ptr, info_ptr, &got_alpha, &num_trans, &got_color); + if (got_alpha == NULL || num_trans != 4) + { + fprintf(stderr, "pnggetset: png_get_tRNS returned unexpected values\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + + /* This is the critical call: the pointer aliases info_ptr->trans_alpha. */ + png_set_tRNS(png_ptr, info_ptr, got_alpha, num_trans, got_color); + + /* Verify the data survived the roundtrip. */ + got_alpha = NULL; + num_trans = 0; + png_get_tRNS(png_ptr, info_ptr, &got_alpha, &num_trans, &got_color); + if (got_alpha == NULL || num_trans != 4) + { + fprintf(stderr, "pnggetset: tRNS lost after roundtrip\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + for (i = 0; i < 4; i++) + { + if (got_alpha[i] != (png_byte)(0xff - i * 0x11)) + { + fprintf(stderr, + "pnggetset: tRNS entry %d corrupted after roundtrip\n", i); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + } + + png_destroy_write_struct(&png_ptr, &info_ptr); + return 0; +} +#endif /* PNG_tRNS_SUPPORTED */ + +#ifdef PNG_TEXT_SUPPORTED +/* Test: get the text array, pass it straight back to set, verify data. */ +#define TEXT_COUNT 6 /* enough to trigger reallocation on the second set */ +static int +test_text_roundtrip(void) +{ + png_structp png_ptr; + png_infop info_ptr; + png_text text_entries[TEXT_COUNT]; + png_textp got_text = NULL; + int got_num_text = 0; + int i; + + /* Recognizable keys and values. */ + static const char *keys[TEXT_COUNT] = { + "Title", "Author", "Desc", "Copyright", "Source", "Comment" + }; + static const char *vals[TEXT_COUNT] = { + "t0", "t1", "t2", "t3", "t4", "t5" + }; + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + if (png_ptr == NULL) + { + fprintf(stderr, "pnggetset: png_create_write_struct failed\n"); + return 1; + } + + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) + { + fprintf(stderr, "pnggetset: png_create_info_struct failed\n"); + png_destroy_write_struct(&png_ptr, NULL); + return 1; + } + + if (setjmp(png_jmpbuf(png_ptr))) + { + fprintf(stderr, "pnggetset: libpng error in test_text_roundtrip\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + + /* Populate the text entries. */ + memset(text_entries, 0, sizeof text_entries); + for (i = 0; i < TEXT_COUNT; i++) + { + text_entries[i].compression = PNG_TEXT_COMPRESSION_NONE; + text_entries[i].key = (png_charp)keys[i]; + text_entries[i].text = (png_charp)vals[i]; + } + png_set_text(png_ptr, info_ptr, text_entries, TEXT_COUNT); + + /* Get the internal pointer and feed it straight back (append). */ + png_get_text(png_ptr, info_ptr, &got_text, &got_num_text); + if (got_text == NULL || got_num_text != TEXT_COUNT) + { + fprintf(stderr, "pnggetset: png_get_text returned unexpected values\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + + /* This is the critical call: got_text aliases info_ptr->text. */ + png_set_text(png_ptr, info_ptr, got_text, got_num_text); + + /* Verify the original entries survived. */ + got_text = NULL; + got_num_text = 0; + png_get_text(png_ptr, info_ptr, &got_text, &got_num_text); + if (got_text == NULL || got_num_text != TEXT_COUNT * 2) + { + fprintf(stderr, "pnggetset: text count %d, expected %d after roundtrip\n", + got_num_text, TEXT_COUNT * 2); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + for (i = 0; i < TEXT_COUNT; i++) + { + if (got_text[i].key == NULL || + strcmp(got_text[i].key, keys[i]) != 0 || + got_text[i].text == NULL || + strcmp(got_text[i].text, vals[i]) != 0) + { + fprintf(stderr, + "pnggetset: text entry %d corrupted after roundtrip\n", i); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + } + + png_destroy_write_struct(&png_ptr, &info_ptr); + return 0; +} +#undef TEXT_COUNT +#endif /* PNG_TEXT_SUPPORTED */ + +#ifdef PNG_sPLT_SUPPORTED +/* Test: get the sPLT array, pass it straight back to set, verify data. */ +static int +test_splt_roundtrip(void) +{ + png_structp png_ptr; + png_infop info_ptr; + png_sPLT_t splt; + png_sPLT_entry splt_entries[4]; + png_sPLT_tp got_spalettes = NULL; + int got_num, i; + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + if (png_ptr == NULL) + { + fprintf(stderr, "pnggetset: png_create_write_struct failed\n"); + return 1; + } + + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) + { + fprintf(stderr, "pnggetset: png_create_info_struct failed\n"); + png_destroy_write_struct(&png_ptr, NULL); + return 1; + } + + if (setjmp(png_jmpbuf(png_ptr))) + { + fprintf(stderr, "pnggetset: libpng error in test_splt_roundtrip\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + + /* Populate with recognizable values. */ + memset(splt_entries, 0, sizeof splt_entries); + for (i = 0; i < 4; i++) + { + splt_entries[i].red = (png_uint_16)(i * 1000); + splt_entries[i].green = (png_uint_16)(i * 2000); + splt_entries[i].blue = (png_uint_16)(i * 3000); + splt_entries[i].alpha = 0xffffU; + splt_entries[i].frequency = (png_uint_16)(i + 1); + } + memset(&splt, 0, sizeof splt); + splt.name = (png_charp)"test_sPLT"; + splt.depth = 16; + splt.entries = splt_entries; + splt.nentries = 4; + + png_set_sPLT(png_ptr, info_ptr, &splt, 1); + + /* Get the internal pointer and feed it straight back (append). */ + got_num = png_get_sPLT(png_ptr, info_ptr, &got_spalettes); + if (got_spalettes == NULL || got_num != 1) + { + fprintf(stderr, "pnggetset: png_get_sPLT returned unexpected values\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + + /* This is the critical call: got_spalettes aliases internal storage. */ + png_set_sPLT(png_ptr, info_ptr, got_spalettes, got_num); + + /* Verify the original entry survived. */ + got_spalettes = NULL; + got_num = png_get_sPLT(png_ptr, info_ptr, &got_spalettes); + if (got_spalettes == NULL || got_num != 2) + { + fprintf(stderr, "pnggetset: sPLT count %d, expected 2 after roundtrip\n", + got_num); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + if (strcmp(got_spalettes[0].name, "test_sPLT") != 0 || + got_spalettes[0].nentries != 4 || + got_spalettes[0].depth != 16) + { + fprintf(stderr, + "pnggetset: sPLT entry 0 corrupted after roundtrip\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + for (i = 0; i < 4; i++) + { + if (got_spalettes[0].entries[i].red != (png_uint_16)(i * 1000) || + got_spalettes[0].entries[i].green != (png_uint_16)(i * 2000) || + got_spalettes[0].entries[i].blue != (png_uint_16)(i * 3000)) + { + fprintf(stderr, + "pnggetset: sPLT[0] entry %d corrupted after roundtrip\n", i); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + } + + png_destroy_write_struct(&png_ptr, &info_ptr); + return 0; +} +#endif /* PNG_sPLT_SUPPORTED */ + +#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED +/* Test: get unknown chunks, pass them straight back to set, verify data. */ +static int +test_unknown_roundtrip(void) +{ + png_structp png_ptr; + png_infop info_ptr; + png_unknown_chunk unk; + png_unknown_chunkp got_unknowns = NULL; + int got_num; + static const png_byte test_data[] = {0xde, 0xad, 0xbe, 0xef}; + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + if (png_ptr == NULL) + { + fprintf(stderr, "pnggetset: png_create_write_struct failed\n"); + return 1; + } + + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) + { + fprintf(stderr, "pnggetset: png_create_info_struct failed\n"); + png_destroy_write_struct(&png_ptr, NULL); + return 1; + } + + if (setjmp(png_jmpbuf(png_ptr))) + { + fprintf(stderr, + "pnggetset: libpng error in test_unknown_roundtrip\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + + /* Set up an unknown chunk with recognizable data. */ + memset(&unk, 0, sizeof unk); + memcpy(unk.name, "teSt", 5); + unk.data = (png_bytep)test_data; + unk.size = sizeof test_data; + unk.location = PNG_HAVE_IHDR; + + png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, NULL, 0); + png_set_unknown_chunks(png_ptr, info_ptr, &unk, 1); + + /* Get the internal pointer and feed it straight back (append). */ + got_num = png_get_unknown_chunks(png_ptr, info_ptr, &got_unknowns); + if (got_unknowns == NULL || got_num != 1) + { + fprintf(stderr, + "pnggetset: png_get_unknown_chunks returned unexpected values\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + + /* This is the critical call: got_unknowns aliases internal storage. */ + png_set_unknown_chunks(png_ptr, info_ptr, got_unknowns, got_num); + + /* Verify the original entry survived. */ + got_unknowns = NULL; + got_num = png_get_unknown_chunks(png_ptr, info_ptr, &got_unknowns); + if (got_unknowns == NULL || got_num != 2) + { + fprintf(stderr, + "pnggetset: unknown_chunks count %d, expected 2 after roundtrip\n", + got_num); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + if (memcmp(got_unknowns[0].name, "teSt", 4) != 0 || + got_unknowns[0].size != sizeof test_data || + memcmp(got_unknowns[0].data, test_data, sizeof test_data) != 0) + { + fprintf(stderr, + "pnggetset: unknown chunk 0 corrupted after roundtrip\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + + png_destroy_write_struct(&png_ptr, &info_ptr); + return 0; +} +#endif /* PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED */ + +int +main(void) +{ + int result = 0; + + printf("Testing PLTE get-then-set roundtrip... "); + fflush(stdout); + if (test_plte_roundtrip() != 0) + { + printf("FAIL\n"); + result = 1; + } + else + printf("PASS\n"); + +#ifdef PNG_hIST_SUPPORTED + printf("Testing hIST get-then-set roundtrip... "); + fflush(stdout); + if (test_hist_roundtrip() != 0) + { + printf("FAIL\n"); + result = 1; + } + else + printf("PASS\n"); +#endif + +#ifdef PNG_tRNS_SUPPORTED + printf("Testing tRNS get-then-set roundtrip... "); + fflush(stdout); + if (test_trns_roundtrip() != 0) + { + printf("FAIL\n"); + result = 1; + } + else + printf("PASS\n"); +#endif + +#ifdef PNG_TEXT_SUPPORTED + printf("Testing tEXt get-then-set roundtrip... "); + fflush(stdout); + if (test_text_roundtrip() != 0) + { + printf("FAIL\n"); + result = 1; + } + else + printf("PASS\n"); +#endif + +#ifdef PNG_sPLT_SUPPORTED + printf("Testing sPLT get-then-set roundtrip... "); + fflush(stdout); + if (test_splt_roundtrip() != 0) + { + printf("FAIL\n"); + result = 1; + } + else + printf("PASS\n"); +#endif + +#ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED + printf("Testing unknown chunks get-then-set roundtrip... "); + fflush(stdout); + if (test_unknown_roundtrip() != 0) + { + printf("FAIL\n"); + result = 1; + } + else + printf("PASS\n"); +#endif + + return result; +}
diff --git a/manuals/libpng-manual.txt b/manuals/libpng-manual.txt index 53af621..da40b1e 100644 --- a/manuals/libpng-manual.txt +++ b/manuals/libpng-manual.txt
@@ -9,7 +9,7 @@ Based on: - libpng version 1.6.36, December 2018, through 1.6.56 - March 2026 + libpng version 1.6.36, December 2018, through 1.6.57 - April 2026 Updated and distributed by Cosmin Truta Copyright (c) 2018-2026 Cosmin Truta
diff --git a/manuals/libpng.3 b/manuals/libpng.3 index 730e99d..512a6d1 100644 --- a/manuals/libpng.3 +++ b/manuals/libpng.3
@@ -1,4 +1,4 @@ -.TH LIBPNG 3 "March 25, 2026" +.TH LIBPNG 3 "April 8, 2026" .SH NAME libpng \- Portable Network Graphics (PNG) Reference Library 1.8.0.git @@ -516,7 +516,7 @@ Based on: - libpng version 1.6.36, December 2018, through 1.6.56 - March 2026 + libpng version 1.6.36, December 2018, through 1.6.57 - April 2026 Updated and distributed by Cosmin Truta Copyright (c) 2018-2026 Cosmin Truta
diff --git a/manuals/png.5 b/manuals/png.5 index 7859b7e..df25110 100644 --- a/manuals/png.5 +++ b/manuals/png.5
@@ -1,4 +1,4 @@ -.TH PNG 5 "March 25, 2026" +.TH PNG 5 "April 8, 2026" .SH NAME png \- Portable Network Graphics (PNG) format
diff --git a/png.h b/png.h index a7e6582..6dc5c4c 100644 --- a/png.h +++ b/png.h
@@ -14,7 +14,7 @@ * libpng versions 0.89, June 1996, through 0.96, May 1997: Andreas Dilger * libpng versions 0.97, January 1998, through 1.6.35, July 2018: * Glenn Randers-Pehrson - * libpng versions 1.6.36, December 2018, through 1.6.56, March 2026: + * libpng versions 1.6.36, December 2018, through 1.6.57, April 2026: * Cosmin Truta * See also "Contributing Authors", below. */
diff --git a/pngrtran.c b/pngrtran.c index 911dc16..85af06b 100644 --- a/pngrtran.c +++ b/pngrtran.c
@@ -2361,7 +2361,7 @@ } row_info->bit_depth = 8; row_info->pixel_depth = (png_byte)(8 * row_info->channels); - row_info->rowbytes = row_width * row_info->channels; + row_info->rowbytes = (size_t)row_width * row_info->channels; } } #endif @@ -2563,7 +2563,7 @@ row_info->bit_depth = 8; row_info->pixel_depth = (png_byte)(8 * row_info->channels); - row_info->rowbytes = row_info->width * row_info->channels; + row_info->rowbytes = (size_t)row_info->width * row_info->channels; } } #endif @@ -2591,7 +2591,7 @@ row_info->bit_depth = 8; row_info->pixel_depth = (png_byte)(8 * row_info->channels); - row_info->rowbytes = row_info->width * row_info->channels; + row_info->rowbytes = (size_t)row_info->width * row_info->channels; } } #endif @@ -2827,7 +2827,7 @@ *(--dp) = lo_filler; row_info->channels = 2; row_info->pixel_depth = 16; - row_info->rowbytes = row_width * 2; + row_info->rowbytes = (size_t)row_width * 2; } else @@ -2842,7 +2842,7 @@ } row_info->channels = 2; row_info->pixel_depth = 16; - row_info->rowbytes = row_width * 2; + row_info->rowbytes = (size_t)row_width * 2; } } @@ -2865,7 +2865,7 @@ *(--dp) = hi_filler; row_info->channels = 2; row_info->pixel_depth = 32; - row_info->rowbytes = row_width * 4; + row_info->rowbytes = (size_t)row_width * 4; } else @@ -2882,7 +2882,7 @@ } row_info->channels = 2; row_info->pixel_depth = 32; - row_info->rowbytes = row_width * 4; + row_info->rowbytes = (size_t)row_width * 4; } } #endif @@ -2906,7 +2906,7 @@ *(--dp) = lo_filler; row_info->channels = 4; row_info->pixel_depth = 32; - row_info->rowbytes = row_width * 4; + row_info->rowbytes = (size_t)row_width * 4; } else @@ -2923,7 +2923,7 @@ } row_info->channels = 4; row_info->pixel_depth = 32; - row_info->rowbytes = row_width * 4; + row_info->rowbytes = (size_t)row_width * 4; } } @@ -2950,7 +2950,7 @@ *(--dp) = hi_filler; row_info->channels = 4; row_info->pixel_depth = 64; - row_info->rowbytes = row_width * 8; + row_info->rowbytes = (size_t)row_width * 8; } else @@ -2972,7 +2972,7 @@ row_info->channels = 4; row_info->pixel_depth = 64; - row_info->rowbytes = row_width * 8; + row_info->rowbytes = (size_t)row_width * 8; } } #endif @@ -4451,7 +4451,7 @@ } row_info->bit_depth = 8; row_info->pixel_depth = 32; - row_info->rowbytes = row_width * 4; + row_info->rowbytes = (size_t)row_width * 4; row_info->color_type = 6; row_info->channels = 4; } @@ -4459,7 +4459,7 @@ else { sp = row + (size_t)row_width - 1; - dp = row + (size_t)(row_width * 3) - 1; + dp = row + (size_t)row_width * 3 - 1; for (i = 0; i < row_width; i++) { *dp-- = palette[*sp].blue; @@ -4470,7 +4470,7 @@ row_info->bit_depth = 8; row_info->pixel_depth = 24; - row_info->rowbytes = row_width * 3; + row_info->rowbytes = (size_t)row_width * 3; row_info->color_type = 2; row_info->channels = 3; }
diff --git a/pngset.c b/pngset.c index 23a18bb..773a2f8 100644 --- a/pngset.c +++ b/pngset.c
@@ -376,6 +376,7 @@ png_set_hIST(const png_struct *png_ptr, png_info *info_ptr, const png_uint_16 *hist) { + png_uint_16 safe_hist[PNG_MAX_PALETTE_LENGTH]; int i; png_debug1(1, "in %s storage function", "hIST"); @@ -392,6 +393,13 @@ return; } + /* Snapshot the caller's hist before freeing, in case it points to + * info_ptr->hist (getter-to-setter aliasing). + */ + memcpy(safe_hist, hist, (unsigned int)info_ptr->num_palette * + (sizeof (png_uint_16))); + hist = safe_hist; + png_free_data(png_ptr, info_ptr, PNG_FREE_HIST, 0); /* Changed from info->num_palette to PNG_MAX_PALETTE_LENGTH in @@ -740,7 +748,7 @@ png_set_PLTE(png_struct *png_ptr, png_info *info_ptr, const png_color *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"); @@ -774,6 +782,15 @@ png_error(png_ptr, "Invalid palette"); } + /* Snapshot the caller's palette before freeing, in case it points to + * info_ptr->palette (getter-to-setter aliasing). + */ + if (num_palette > 0) + memcpy(safe_palette, palette, (unsigned int)num_palette * + (sizeof (png_color))); + + palette = safe_palette; + png_free_data(png_ptr, info_ptr, PNG_FREE_PLTE, 0); /* Changed in libpng-1.2.1 to allocate PNG_MAX_PALETTE_LENGTH instead @@ -935,6 +952,7 @@ const png_text *text_ptr, int num_text) { int i; + png_textp old_text = NULL; png_debug1(1, "in text storage function, chunk typeid = 0x%lx", png_ptr == NULL ? 0xabadca11UL : (unsigned long)png_ptr->chunk_name); @@ -982,7 +1000,10 @@ return 1; } - png_free(png_ptr, info_ptr->text); + /* Defer freeing the old array until after the copy loop below, + * in case text_ptr aliases info_ptr->text (getter-to-setter). + */ + old_text = info_ptr->text; info_ptr->text = new_text; info_ptr->free_me |= PNG_FREE_TEXT; @@ -1067,6 +1088,7 @@ { png_chunk_report(png_ptr, "text chunk: out of memory", PNG_CHUNK_WRITE_ERROR); + png_free(png_ptr, old_text); return 1; } @@ -1120,6 +1142,8 @@ png_debug1(3, "transferred text chunk %d", info_ptr->num_text); } + png_free(png_ptr, old_text); + return 0; } #endif @@ -1163,6 +1187,16 @@ if (trans_alpha != NULL) { + /* Snapshot the caller's trans_alpha before freeing, in case it + * points to info_ptr->trans_alpha (getter-to-setter aliasing). + */ + png_byte safe_trans[PNG_MAX_PALETTE_LENGTH]; + + if (num_trans > 0 && num_trans <= PNG_MAX_PALETTE_LENGTH) + memcpy(safe_trans, trans_alpha, (size_t)num_trans); + + trans_alpha = safe_trans; + png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, 0); if (num_trans > 0 && num_trans <= PNG_MAX_PALETTE_LENGTH) @@ -1247,6 +1281,7 @@ */ { png_sPLT_t *np; + png_sPLT_t *old_spalettes; png_debug1(1, "in %s storage function", "sPLT"); @@ -1267,7 +1302,10 @@ return; } - 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 = np; info_ptr->free_me |= PNG_FREE_SPLT; @@ -1331,6 +1369,8 @@ } while (--nentries); + png_free(png_ptr, old_spalettes); + if (nentries > 0) png_chunk_report(png_ptr, "sPLT out of memory", PNG_CHUNK_WRITE_ERROR); } @@ -1517,6 +1557,7 @@ png_info *info_ptr, const png_unknown_chunk *unknowns, int num_unknowns) { png_unknown_chunk *np; + png_unknown_chunk *old_unknowns; if (png_ptr == NULL || info_ptr == NULL || num_unknowns <= 0 || unknowns == NULL) @@ -1563,7 +1604,10 @@ return; } - 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 = np; /* safe because it is initialized */ info_ptr->free_me |= PNG_FREE_UNKN; @@ -1609,6 +1653,8 @@ ++np; ++(info_ptr->unknown_chunks_num); } + + png_free(png_ptr, old_unknowns); } void
diff --git a/tests/pnggetset b/tests/pnggetset new file mode 100755 index 0000000..57ef731 --- /dev/null +++ b/tests/pnggetset
@@ -0,0 +1,5 @@ +#!/bin/sh + +# pnggetset test: +# Getter-to-setter roundtrips for various chunk types. +exec ./pnggetset