Merge tag 'v1.6.58' into branch 'libpng18' into develop
diff --git a/.gitignore b/.gitignore index 3768c51..3d08cb3 100644 --- a/.gitignore +++ b/.gitignore
@@ -162,6 +162,7 @@ /png-fix-itxt /pngcp /pngfix +/pnggetset /pngimage /pngstest /pngtest
diff --git a/CHANGES b/CHANGES index 7a6156e..50bd394 100644 --- a/CHANGES +++ b/CHANGES
@@ -6379,6 +6379,12 @@ Fixed integer overflow in rowbytes computation in read transforms. (Contributed by Mohammad Seet.) +Version 1.6.58 [April 15, 2026] + Fixed a regression introduced in version 1.6.56 that caused `png_get_PLTE` + to return stale palette data after applying gamma and background transforms + in-place. + (Reported by ralfjunker <ralfjunker@users.noreply.github.com>.) + Version 2.0.0 [TODO] Send comments/corrections/commendations to png-mng-implement at lists.sf.net.
diff --git a/contrib/libtests/pnggetset.c b/contrib/libtests/pnggetset.c index 6ae43dc..e2c1ca5 100644 --- a/contrib/libtests/pnggetset.c +++ b/contrib/libtests/pnggetset.c
@@ -6,12 +6,7 @@ * 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. + * Test getter and setter correctness. */ #include <stdio.h> @@ -99,9 +94,9 @@ } 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)) + 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); @@ -569,6 +564,215 @@ } #endif /* PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED */ +/* Memory buffer for PNG I/O without temp files. */ +#define MEM_BUF_SIZE 4096 + +typedef struct +{ + png_byte data[MEM_BUF_SIZE]; + size_t len; + size_t pos; +} mem_buf; + +static void PNGCBAPI +mem_write(png_structp png_ptr, png_bytep buf, png_size_t length) +{ + mem_buf *mb = (mem_buf *)png_get_io_ptr(png_ptr); + + if (mb->len + length > MEM_BUF_SIZE) + png_error(png_ptr, "pnggetset: write overflow"); + + memcpy(mb->data + mb->len, buf, length); + mb->len += length; +} + +static void PNGCBAPI +mem_flush(png_structp png_ptr) +{ + (void)png_ptr; +} + +static void PNGCBAPI +mem_read(png_structp png_ptr, png_bytep buf, png_size_t length) +{ + mem_buf *mb = (mem_buf *)png_get_io_ptr(png_ptr); + + if (mb->pos + length > mb->len) + png_error(png_ptr, "pnggetset: read overflow"); + + memcpy(buf, mb->data + mb->pos, length); + mb->pos += length; +} + +/* Palette sync after gamma correction. + * + * When info_ptr->palette and png_ptr->palette are separate buffers, + * in-place gamma correction of png_ptr->palette must be synced back + * to info_ptr->palette so that png_get_PLTE returns the corrected + * values. + */ +#define PLTE_SYNC_NPALETTE 4 + +static const png_color plte_sync_original[PLTE_SYNC_NPALETTE] = +{ + { 64, 96, 128 }, + { 128, 160, 192 }, + { 192, 224, 240 }, + { 32, 48, 64 } +}; + +static int +test_plte_palette_sync(void) +{ + mem_buf buf; + png_structp png_ptr; + png_infop info_ptr; + png_colorp got_palette; + int num_palette; + double file_gamma; + png_byte row[1]; + int i; + int changed; + + /* Write a 1x1 palette PNG with gAMA = 1.0 (linear). */ + buf.len = 0; + buf.pos = 0; + + 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_palette_sync" + " (write)\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + return 1; + } + + png_set_write_fn(png_ptr, &buf, mem_write, mem_flush); + 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, + (png_colorp)plte_sync_original, PLTE_SYNC_NPALETTE); + png_set_gAMA(png_ptr, info_ptr, 1.0); + png_write_info(png_ptr, info_ptr); + + row[0] = 0; + png_write_row(png_ptr, row); + png_write_end(png_ptr, info_ptr); + png_destroy_write_struct(&png_ptr, &info_ptr); + + /* Read back with gamma correction as the sole transform. */ + buf.pos = 0; + + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + if (png_ptr == NULL) + { + fprintf(stderr, "pnggetset: png_create_read_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_read_struct(&png_ptr, NULL, NULL); + return 1; + } + + if (setjmp(png_jmpbuf(png_ptr))) + { + fprintf(stderr, "pnggetset: libpng error in test_plte_palette_sync" + " (read)\n"); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return 1; + } + + png_set_read_fn(png_ptr, &buf, mem_read); + png_read_info(png_ptr, info_ptr); + + if (png_get_gAMA(png_ptr, info_ptr, &file_gamma) == 0) + { + fprintf(stderr, "pnggetset: gAMA chunk not found\n"); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return 1; + } + + png_set_gamma(png_ptr, 2.2, file_gamma); + png_read_update_info(png_ptr, info_ptr); + + if (png_get_PLTE(png_ptr, info_ptr, &got_palette, &num_palette) == 0) + { + fprintf(stderr, "pnggetset: png_get_PLTE failed after update\n"); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return 1; + } + + if (num_palette != PLTE_SYNC_NPALETTE) + { + fprintf(stderr, "pnggetset: palette size %d, expected %d\n", + num_palette, PLTE_SYNC_NPALETTE); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return 1; + } + + /* Every entry must differ from the original after gamma correction + * (file_gamma=1.0, screen_gamma=2.2). If the sync was skipped, + * info_ptr->palette still holds the stale pre-correction values. + */ + changed = 0; + + for (i = 0; i < PLTE_SYNC_NPALETTE; i++) + { + if ((got_palette[i].red != plte_sync_original[i].red) + || (got_palette[i].green != plte_sync_original[i].green) + || (got_palette[i].blue != plte_sync_original[i].blue)) + { + changed++; + } + else + { + fprintf(stderr, + "pnggetset: palette entry %d NOT gamma-corrected: " + "got {%u, %u, %u}, same as original\n", + i, + (unsigned)got_palette[i].red, + (unsigned)got_palette[i].green, + (unsigned)got_palette[i].blue); + } + } + + png_read_row(png_ptr, row, NULL); + png_read_end(png_ptr, NULL); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + + if (changed != PLTE_SYNC_NPALETTE) + { + fprintf(stderr, + "pnggetset: only %d of %d palette entries were " + "gamma-corrected (palette sync failed)\n", + changed, PLTE_SYNC_NPALETTE); + return 1; + } + + return 0; +} + int main(void) { @@ -644,5 +848,15 @@ printf("PASS\n"); #endif + printf("Testing PLTE sync after gamma correction... "); + fflush(stdout); + if (test_plte_palette_sync() != 0) + { + printf("FAIL\n"); + result = 1; + } + else + printf("PASS\n"); + return result; }
diff --git a/manuals/libpng-manual.txt b/manuals/libpng-manual.txt index da40b1e..258ad25 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.57 - April 2026 + libpng version 1.6.36, December 2018, through 1.6.58 - 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 512a6d1..52e10cc 100644 --- a/manuals/libpng.3 +++ b/manuals/libpng.3
@@ -1,4 +1,4 @@ -.TH LIBPNG 3 "April 8, 2026" +.TH LIBPNG 3 "April 15, 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.57 - April 2026 + libpng version 1.6.36, December 2018, through 1.6.58 - 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 df25110..803f8f6 100644 --- a/manuals/png.5 +++ b/manuals/png.5
@@ -1,4 +1,4 @@ -.TH PNG 5 "April 8, 2026" +.TH PNG 5 "April 15, 2026" .SH NAME png \- Portable Network Graphics (PNG) format
diff --git a/png.h b/png.h index 6dc5c4c..6c15518 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.57, April 2026: + * libpng versions 1.6.36, December 2018, through 1.6.58, April 2026: * Cosmin Truta * See also "Contributing Authors", below. */
diff --git a/pngpread.c b/pngpread.c index 340c636..98717ec 100644 --- a/pngpread.c +++ b/pngpread.c
@@ -1,6 +1,6 @@ /* pngpread.c - read a png file in push mode * - * Copyright (c) 2018-2025 Cosmin Truta + * Copyright (c) 2018-2026 Cosmin Truta * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson * Copyright (c) 1996-1997 Andreas Dilger * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. @@ -216,19 +216,12 @@ return; } + png_crc_finish(png_ptr, png_ptr->push_length); png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER; return; } else if (chunk_name == png_fdAT) { - if (png_ptr->buffer_size < 4) - { - png_push_save_buffer(png_ptr); - return; - } - - png_ensure_sequence_number(png_ptr, 4); - if (!(png_ptr->mode & PNG_HAVE_fcTL)) { /* Discard trailing fdATs for frames other than the first. */ @@ -241,6 +234,8 @@ return; } + png_ensure_sequence_number(png_ptr, png_ptr->push_length); + png_crc_finish(png_ptr, png_ptr->push_length - 4); png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER; return; } @@ -248,6 +243,13 @@ else { /* Frame data follows. */ + if (png_ptr->buffer_size < 4) + { + png_push_save_buffer(png_ptr); + return; + } + + png_ensure_sequence_number(png_ptr, png_ptr->push_length); png_ptr->idat_size = png_ptr->push_length - 4; png_ptr->mode |= PNG_HAVE_IDAT; png_ptr->process_mode = PNG_READ_IDAT_MODE; @@ -291,6 +293,7 @@ return; } png_warning(png_ptr, "Ignoring unexpected chunk in APNG sequence"); + png_crc_finish(png_ptr, png_ptr->push_length); png_ptr->mode &= ~PNG_HAVE_CHUNK_HEADER; return; }
diff --git a/pngrtran.c b/pngrtran.c index 85af06b..25a333f 100644 --- a/pngrtran.c +++ b/pngrtran.c
@@ -2052,19 +2052,15 @@ { 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). - */ - memcpy(info_ptr->palette, png_ptr->palette, - PNG_MAX_PALETTE_LENGTH * (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). + */ + memcpy(info_ptr->palette, png_ptr->palette, + PNG_MAX_PALETTE_LENGTH * (sizeof (png_color))); } #ifdef PNG_READ_EXPAND_SUPPORTED
diff --git a/pngtest.c b/pngtest.c index 00fc0aa..57b38d1 100644 --- a/pngtest.c +++ b/pngtest.c
@@ -50,9 +50,6 @@ */ #define STDERR stdout -/* Generate a compiler error if there is an old png.h in the search path. */ -typedef png_libpng_version_2_0_0_git Your_png_h_is_not_version_2_0_0_git; - /* Ensure that all version numbers in png.h are consistent with one another. */ #if (PNG_LIBPNG_VER != PNG_LIBPNG_VER_MAJOR * 10000 + \ PNG_LIBPNG_VER_MINOR * 100 + \