From 436584a4302b1c6da55080c0a0408a98e551ea7e Mon Sep 17 00:00:00 2001
From: "Arnold D. Robbins" <arnold@skeeve.com>
Date: Mon, 6 Apr 2026 10:56:38 +0300
Subject: [PATCH] Add overflow checking in do_sub for 32 bit systems.

CVE: CVE-2026-40469
Upstream-Status: Backport [https://cgit.git.savannah.gnu.org/cgit/gawk.git/commit/?id=aa7272a6e1184cdd21ab8f89200219abd8053eda]

(cherry picked from commit aa7272a6e1184cdd21ab8f89200219abd8053eda)
Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
---
 ChangeLog |  9 +++++++++
 builtin.c | 25 ++++++++++++++++++++-----
 2 files changed, 29 insertions(+), 5 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 8cdf90f6..c1b2e7f4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2026-04-06         Arnold D. Robbins     <arnold@skeeve.com>
+
+	* builtin.c (do_sub): Check for overflow in calculation of size
+	of result buffer by doing the math in 64 bits. This provides
+	a fatal message on 32 bit systems if overflow happens instead
+	of letting dynamic memory get corrupted and likely causing
+	a core dump. Thanks to Michał Majchrowicz <mmajchrowicz@afine.com>
+	for the report and fix.
+
 2026-04-04         Arnold D. Robbins     <arnold@skeeve.com>
 
 	* builtin.c (do_sub): Make `sofar' be size_t to avoid
diff --git a/builtin.c b/builtin.c
index d0e083da..3906d507 100644
--- a/builtin.c
+++ b/builtin.c
@@ -1820,11 +1820,13 @@ do_sub(int nargs, unsigned int flags)
 	bool lastmatchnonzero;
 	char *mb_indices = NULL;
 	int searchflags = RE_NEED_START;
+	const char *fname = NULL;	// for fatal message, below
 
 	if ((flags & GENSUB) != 0) {
 		double d;
 		NODE *glob_flag;
 
+		fname = "gensub";
 		check_exact_args(nargs, "gensub", 4);
 
 		tmp = PEEK(3);
@@ -1856,11 +1858,9 @@ do_sub(int nargs, unsigned int flags)
 		DEREF(glob_flag);
 		searchflags |= RE_NEED_SUB;
 	} else {
-		if ((flags & GSUB) != 0) {
-			check_exact_args(nargs, "gsub", 3);
-		} else {
-			check_exact_args(nargs, "sub", 3);
-		}
+		fname = ((flags & GSUB) != 0) ? "gsub" : "sub";
+
+		check_exact_args(nargs, fname, 3);
 
 		/* take care of regexp early, in case re_update is fatal */
 
@@ -1976,6 +1976,21 @@ do_sub(int nargs, unsigned int flags)
 		 * vary since ampersand is actual text of regexp match.
 		 */
 
+		// 4/2026: This overflow check simply provides a fatal
+		// message instead of letting realloc() die later after
+		// a buffer overrun.  It simply makes the user experience better,
+		// but does not prevent gawk from dying miserably.  I suppose
+		// it's worth the trouble, but just barely.
+
+		/* uint64_t so the product is 64-bit even on 32-bit ILP32 builds */
+		uint64_t repl_contribution =
+			(uint64_t)(unsigned int)ampersands
+			* (uint64_t)(uintptr_t)(matchend - matchstart);
+		if (repl_contribution > (uint64_t)SIZE_MAX
+		    || repl_contribution > (uint64_t)SIZE_MAX - (size_t)(matchend - text)
+						- repllen - 1)
+			fatal(_("%s: replacement expansion too large"), fname);
+
 		/*
 		 * add 1 to len to handle "empty" case where
 		 * matchend == matchstart and we force a match on a single
-- 
2.53.0

