fix: Prevent double-freeing `png_struct` members on allocation failure Clear the pointers inside `png_struct` after `png_free`, following the existing idiom in `png_read_buffer`, to ensure that a subsequent free will be a no-op. Several read-side functions free a `png_struct` member and allocate a replacement without clearing the pointer in between. When that allocation fails, `png_malloc` calls `png_error`, which longjmps out before the assignment, leaving the member pointing at freed memory. The application's cleanup path (`png_destroy_read_struct`, then `png_read_destroy`) then frees it a second time. The same defect occurs at five members across four functions: - `big_row_buf` and `big_prev_row` in `png_read_start_row`; - `palette` in `png_set_PLTE`; - `trans_alpha` in `png_set_tRNS`; - `quantize_index` in `png_set_quantize`. This is robustness hardening, not a fix for untrusted input. Arming the double-free needs a prior successful pass through the same site, and PNG content alone cannot deliver one: duplicate PLTE and tRNS chunks are rejected before their setters run, and, although APNG re-runs `png_read_start_row` on every frame, fcTL validation caps each frame to the first frame's dimensions, so the row-buffer reallocation guard cannot re-fire within one decode. The trigger is an application that causes a setter to run twice on one `png_struct` or reuses the struct across decodes, and then meets an allocation failure. Co-authored-by: Cosmin Truta <ctruta@gmail.com> Signed-off-by: Anthony Hurtado <amhurtado@pm.me> Signed-off-by: Cosmin Truta <ctruta@gmail.com>
diff --git a/AUTHORS.md b/AUTHORS.md index 291a0b7..be1c9b6 100644 --- a/AUTHORS.md +++ b/AUTHORS.md
@@ -7,6 +7,7 @@ * Adam Richter * Alexander Smorkalov * Andreas Dilger + * Anthony Hurtado * Chris Blume * Cosmin Truta * Daisuke Nishikawa
diff --git a/pngrtran.c b/pngrtran.c index 25a333f..38a9fb9 100644 --- a/pngrtran.c +++ b/pngrtran.c
@@ -495,6 +495,7 @@ * this function more than once per png_struct. */ png_free(png_ptr, png_ptr->quantize_index); + png_ptr->quantize_index = NULL; png_ptr->quantize_index = (png_byte *)png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH); for (i = 0; i < PNG_MAX_PALETTE_LENGTH; i++)
diff --git a/pngrutil.c b/pngrutil.c index 6d937c8..d426075 100644 --- a/pngrutil.c +++ b/pngrutil.c
@@ -4790,6 +4790,7 @@ { png_free(png_ptr, png_ptr->big_row_buf); png_free(png_ptr, png_ptr->big_prev_row); + png_ptr->big_row_buf = png_ptr->big_prev_row = NULL; if (png_ptr->interlaced != 0) png_ptr->big_row_buf = (png_byte *)png_calloc(png_ptr,
diff --git a/pngset.c b/pngset.c index 773a2f8..10eea62 100644 --- a/pngset.c +++ b/pngset.c
@@ -809,6 +809,7 @@ * regardless of num_palette. */ png_free(png_ptr, png_ptr->palette); + png_ptr->palette = NULL; png_ptr->palette = png_voidcast(png_color *, png_calloc(png_ptr, PNG_MAX_PALETTE_LENGTH * (sizeof (png_color)))); info_ptr->palette = png_voidcast(png_colorp, png_calloc(png_ptr, @@ -1221,6 +1222,7 @@ * functions (e.g. png_do_expand_palette). */ png_free(png_ptr, png_ptr->trans_alpha); + png_ptr->trans_alpha = NULL; png_ptr->trans_alpha = png_voidcast(png_bytep, png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH)); memset(png_ptr->trans_alpha, 0xff, PNG_MAX_PALETTE_LENGTH);