From e4f7ad4ea2a471776c81dda4846b7691925d9786 Mon Sep 17 00:00:00 2001
From: Cosmin Truta <ctruta@gmail.com>
Date: Fri, 9 Jan 2026 20:51:53 +0200
Subject: [PATCH] Fix a heap buffer over-read in `png_image_read_direct_scaled`

Fix a regression from commit 218612ddd6b17944e21eda56caf8b4bf7779d1ea.

The function `png_image_read_direct_scaled`, introduced by the fix for
CVE-2025-65018, copies transformed row data from an intermediate buffer
(`local_row`) to the user's output buffer. The copy incorrectly used
`row_bytes` (the caller's stride) as the size parameter to memcpy, even
though `local_row` is only `png_get_rowbytes()` bytes long.

This causes a heap buffer over-read when:

1. The caller provides a padded stride (e.g., for memory alignment):
   memcpy reads past the end of `local_row` by `stride - row_width`
   bytes.

2. The caller provides a negative stride (for bottom-up layouts):
   casting ptrdiff_t to size_t produces ~2^64, causing memcpy to
   attempt reading exabytes, resulting in an immediate crash.

The fix consists in using the size of the row buffer for the copy and
using the stride for pointer advancement only.

Reported-by: Petr Simecek <simecek@users.noreply.github.com>
Analyzed-by: Stanislav Fort
Analyzed-by: Pavel Kohout
Co-authored-by: Petr Simecek <simecek@users.noreply.github.com>
Signed-off-by: Cosmin Truta <ctruta@gmail.com>

CVE: CVE-2026-22695
Upstream-Status: Backport [https://github.com/pnggroup/libpng/commit/e4f7ad4ea2a471776c81dda4846b7691925d9786]
Signed-off-by: Peter Marko <peter.marko@siemens.com>
---
 AUTHORS   | 1 +
 pngread.c | 4 +++-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/AUTHORS b/AUTHORS
index 26b7bb50f..b9c0fffcf 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -23,6 +23,7 @@ Authors, for copyright and licensing purposes.
  * Mike Klein
  * Pascal Massimino
  * Paul Schmidt
+ * Petr Simecek
  * Philippe Antoine
  * Qiang Zhou
  * Sam Bushell
diff --git a/pngread.c b/pngread.c
index e3426292b..9d86b01dc 100644
--- a/pngread.c
+++ b/pngread.c
@@ -3270,9 +3270,11 @@ png_image_read_direct_scaled(png_voidp argument)
        argument);
    png_imagep image = display->image;
    png_structrp png_ptr = image->opaque->png_ptr;
+   png_inforp info_ptr = image->opaque->info_ptr;
    png_bytep local_row = png_voidcast(png_bytep, display->local_row);
    png_bytep first_row = png_voidcast(png_bytep, display->first_row);
    ptrdiff_t row_bytes = display->row_bytes;
+   size_t copy_bytes = png_get_rowbytes(png_ptr, info_ptr);
    int passes;
 
    /* Handle interlacing. */
@@ -3302,7 +3304,7 @@ png_image_read_direct_scaled(png_voidp argument)
          png_read_row(png_ptr, local_row, NULL);
 
          /* Copy from local_row to user buffer. */
-         memcpy(output_row, local_row, (size_t)row_bytes);
+         memcpy(output_row, local_row, copy_bytes);
          output_row += row_bytes;
       }
    }
