From cf155de014fc6c5cb199dd681dd5c8fb70429072 Mon Sep 17 00:00:00 2001
From: Cosmin Truta <ctruta@gmail.com>
Date: Sat, 10 Jan 2026 15:20:18 +0200
Subject: [PATCH] fix: Remove incorrect truncation casts from
 `png_write_image_*`

The type of the row stride (`display->row_bytes`) is ptrdiff_t. Casting
to png_uint_16 before division will truncate large strides, causing
incorrect pointer arithmetic for images exceeding 65535 bytes per row.
For bottom-up images (negative stride), the truncation also corrupts
the sign, advancing the row pointer forward instead of backward.

Remove the erroneous casts and let the compiler handle the pointer
arithmetic correctly. Also replace `sizeof (png_uint_16)` with 2.

Add regression test via `pngstest --stride-extra N` where N > 32767
triggers the affected code paths.

A NOTE ABOUT HISTORY:
The original code in libpng 1.5.6 (2011) had no such casts. They were
introduced in libpng 1.6.26 (2016), likely to silence compiler warnings
on 16-bit systems where the cast would be a no-op. On 32/64-bit systems
the cast truncates the strides above 65535 and corrupts the negative
strides.

CVE: CVE-2026-22801
Upstream-Status: Backport [https://github.com/pnggroup/libpng/commit/cf155de014fc6c5cb199dd681dd5c8fb70429072]
Signed-off-by: Peter Marko <peter.marko@siemens.com>
---
 CMakeLists.txt              |  9 ++++++++-
 contrib/libtests/pngstest.c | 29 ++++++++++++++++++++++++++++-
 pngwrite.c                  | 10 +++++-----
 tests/pngstest-large-stride |  8 ++++++++
 4 files changed, 49 insertions(+), 7 deletions(-)
 create mode 100755 tests/pngstest-large-stride

diff --git a/CMakeLists.txt b/CMakeLists.txt
index a8cd82402..a595ed91d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,7 @@
 
 # CMakeLists.txt - CMake lists for libpng
 #
-# Copyright (c) 2018-2024 Cosmin Truta.
+# Copyright (c) 2018-2026 Cosmin Truta
 # Copyright (c) 2007-2018 Glenn Randers-Pehrson.
 # Originally written by Christian Ehrlicher, 2007.
 #
@@ -859,6 +859,13 @@ if(PNG_TESTS AND PNG_SHARED)
     endforeach()
   endforeach()
 
+  # Regression test:
+  # Use stride_extra > 32767 to trigger row_bytes > 65535 for linear images.
+  png_add_test(NAME pngstest-large-stride
+               COMMAND pngstest
+               OPTIONS --stride-extra 33000 --tmpfile "large-stride-" --log
+               FILES "${CMAKE_CURRENT_SOURCE_DIR}/contrib/testpngs/rgb-alpha-16-linear.png")
+
   add_executable(pngunknown ${pngunknown_sources})
   target_link_libraries(pngunknown PRIVATE png_shared)
 
diff --git a/contrib/libtests/pngstest.c b/contrib/libtests/pngstest.c
index ff4c2b24a..2f29afee2 100644
--- a/contrib/libtests/pngstest.c
+++ b/contrib/libtests/pngstest.c
@@ -1,7 +1,7 @@
 
 /* pngstest.c
  *
- * Copyright (c) 2021 Cosmin Truta
+ * Copyright (c) 2021-2026 Cosmin Truta
  * Copyright (c) 2013-2017 John Cunningham Bowler
  *
  * This code is released under the libpng license.
@@ -3571,6 +3571,33 @@ main(int argc, char **argv)
          opts |= NO_RESEED;
       else if (strcmp(arg, "--fault-gbg-warning") == 0)
          opts |= GBG_ERROR;
+      else if (strcmp(arg, "--stride-extra") == 0)
+      {
+         if (c+1 < argc)
+         {
+            char *ep;
+            unsigned long val = strtoul(argv[++c], &ep, 0);
+
+            if (ep > argv[c] && *ep == 0 && val <= 65535)
+               stride_extra = (int)val;
+
+            else
+            {
+               fflush(stdout);
+               fprintf(stderr, "%s: bad argument for --stride-extra: %s\n",
+                  argv[0], argv[c]);
+               exit(99);
+            }
+         }
+
+         else
+         {
+            fflush(stdout);
+            fprintf(stderr, "%s: missing argument for --stride-extra\n",
+               argv[0]);
+            exit(99);
+         }
+      }
       else if (strcmp(arg, "--tmpfile") == 0)
       {
          if (c+1 < argc)
diff --git a/pngwrite.c b/pngwrite.c
index 08066bcc4..a95b846c8 100644
--- a/pngwrite.c
+++ b/pngwrite.c
@@ -1,7 +1,7 @@
 
 /* pngwrite.c - general routines to write a PNG file
  *
- * Copyright (c) 2018-2024 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.
@@ -1645,7 +1645,7 @@ png_write_image_16bit(png_voidp argument)
       }
 
       png_write_row(png_ptr, png_voidcast(png_const_bytep, display->local_row));
-      input_row += (png_uint_16)display->row_bytes/(sizeof (png_uint_16));
+      input_row += display->row_bytes / 2;
    }
 
    return 1;
@@ -1771,7 +1771,7 @@ png_write_image_8bit(png_voidp argument)
 
          png_write_row(png_ptr, png_voidcast(png_const_bytep,
              display->local_row));
-         input_row += (png_uint_16)display->row_bytes/(sizeof (png_uint_16));
+         input_row += display->row_bytes / 2;
       } /* while y */
    }
 
@@ -1796,7 +1796,7 @@ png_write_image_8bit(png_voidp argument)
          }
 
          png_write_row(png_ptr, output_row);
-         input_row += (png_uint_16)display->row_bytes/(sizeof (png_uint_16));
+         input_row += display->row_bytes / 2;
       }
    }
 
@@ -2115,7 +2115,7 @@ png_image_write_main(png_voidp argument)
       ptrdiff_t row_bytes = display->row_stride;
 
       if (linear != 0)
-         row_bytes *= (sizeof (png_uint_16));
+         row_bytes *= 2;
 
       if (row_bytes < 0)
          row += (image->height-1) * (-row_bytes);
diff --git a/tests/pngstest-large-stride b/tests/pngstest-large-stride
new file mode 100755
index 000000000..7958c5b42
--- /dev/null
+++ b/tests/pngstest-large-stride
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# Regression test:
+# Use stride_extra > 32767 to trigger row_bytes > 65535 for linear images.
+exec ./pngstest \
+     --stride-extra 33000 \
+     --tmpfile "large-stride-" \
+     --log "${srcdir}/contrib/testpngs/rgb-alpha-16-linear.png"
