From: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Date: Tue, 11 Aug 2026 18:10:00 +0500
Subject: [PATCH] Fix PGTYPESdate_fmt_asc overflow when a year does not fit "yyyy"

PGTYPESdate_fmt_asc() replaces fixed-width tokens in place.  The token
"yyyy" is four characters and means a four-digit year.  When the year
needs more digits, snprintf() with "%04u" produces a longer string and
memcpy() writes past the token.  With the usual outbuf size of
strlen(fmtstring)+1 that is a heap buffer overflow.

Reject a replacement longer than the token and set errno to
PGTYPES_DATE_BAD_DATE.  A dt_test case for 10000-01-01 with "yyyy" is
included.

Author: Andrey Rachitskiy <pl0h0yp1@gmail.com>
---
diff --git a/src/interfaces/ecpg/pgtypeslib/datetime.c b/src/interfaces/ecpg/pgtypeslib/datetime.c
index f43343b4594..91d2c30615c 100644
--- a/src/interfaces/ecpg/pgtypeslib/datetime.c
+++ b/src/interfaces/ecpg/pgtypeslib/datetime.c
@@ -263,38 +263,42 @@ PGTYPESdate_fmt_asc(date dDate, const char *fmtstring, char *outbuf)
 						free(replace_val.str_val);
 					break;
 				case PGTYPES_TYPE_UINT:
-					{
-						char	   *t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS);
-
-						if (!t)
-							return -1;
-						snprintf(t, PGTYPES_DATE_NUM_MAX_DIGITS,
-								 "%u", replace_val.uint_val);
-						memcpy(start_pattern, t, strlen(t));
-						free(t);
-					}
-					break;
 				case PGTYPES_TYPE_UINT_2_LZ:
-					{
-						char	   *t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS);
-
-						if (!t)
-							return -1;
-						snprintf(t, PGTYPES_DATE_NUM_MAX_DIGITS,
-								 "%02u", replace_val.uint_val);
-						memcpy(start_pattern, t, strlen(t));
-						free(t);
-					}
-					break;
 				case PGTYPES_TYPE_UINT_4_LZ:
 					{
 						char	   *t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS);
+						size_t		pat_len;
+						size_t		t_len;
+						const char *fmt;
 
 						if (!t)
 							return -1;
+
+						if (replace_type == PGTYPES_TYPE_UINT)
+							fmt = "%u";
+						else if (replace_type == PGTYPES_TYPE_UINT_2_LZ)
+							fmt = "%02u";
+						else
+							fmt = "%04u";
+
 						snprintf(t, PGTYPES_DATE_NUM_MAX_DIGITS,
-								 "%04u", replace_val.uint_val);
-						memcpy(start_pattern, t, strlen(t));
+								 fmt, replace_val.uint_val);
+						t_len = strlen(t);
+						pat_len = strlen(mapping[i].format);
+
+						/*
+						 * In-place replace over a fixed-width token
+						 * ("yyyy" is four digits).  A longer value
+						 * would overrun typical outbufs.
+						 */
+						if (t_len > pat_len)
+						{
+							free(t);
+							errno = PGTYPES_DATE_BAD_DATE;
+							return -1;
+						}
+
+						memcpy(start_pattern, t, t_len);
 						free(t);
 					}
 					break;
diff --git a/src/interfaces/ecpg/test/expected/pgtypeslib-dt_test.c b/src/interfaces/ecpg/test/expected/pgtypeslib-dt_test.c
index 1f1d341a4ac..b8aa0fc88ac 100644
--- a/src/interfaces/ecpg/test/expected/pgtypeslib-dt_test.c
+++ b/src/interfaces/ecpg/test/expected/pgtypeslib-dt_test.c
@@ -150,6 +150,22 @@ if (sqlca.sqlcode < 0) sqlprint ( );}
 	PGTYPESchar_free(text);
 	free(out);
 
+	/* year 10000 does not fit the four-digit "yyyy" token */
+	{
+		date		big_year_date;
+		char	   *big_out;
+		int			fmt_rc;
+
+		errno = 0;
+		big_year_date = PGTYPESdate_from_asc("10000-01-01", NULL);
+		fmt = "yyyy";
+		big_out = (char *) malloc(strlen(fmt) + 1);
+		fmt_rc = PGTYPESdate_fmt_asc(big_year_date, fmt, big_out);
+		printf("date_fmt_asc yyyy year>=10000: rc=%d errno=%d\n",
+			   fmt_rc, errno);
+		free(big_out);
+	}
+
 	out = (char*) malloc(48);
 	i = PGTYPEStimestamp_fmt_asc(&ts1, out, 47, "Which is day number %j in %Y.");
 	printf("%s\n", out);
@@ -465,16 +481,16 @@ if (sqlca.sqlcode < 0) sqlprint ( );}
 	free(out);
 
 	{ ECPGtrans(__LINE__, NULL, "rollback");
-#line 393 "dt_test.pgc"
+#line 409 "dt_test.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint ( );}
-#line 393 "dt_test.pgc"
+#line 409 "dt_test.pgc"
 
         { ECPGdisconnect(__LINE__, "CURRENT");
-#line 394 "dt_test.pgc"
+#line 410 "dt_test.pgc"
 
 if (sqlca.sqlcode < 0) sqlprint ( );}
-#line 394 "dt_test.pgc"
+#line 410 "dt_test.pgc"
 
 
 	return 0;
diff --git a/src/interfaces/ecpg/test/expected/pgtypeslib-dt_test.stderr b/src/interfaces/ecpg/test/expected/pgtypeslib-dt_test.stderr
index 2a109ee7fa1..92a4695bd5d 100644
--- a/src/interfaces/ecpg/test/expected/pgtypeslib-dt_test.stderr
+++ b/src/interfaces/ecpg/test/expected/pgtypeslib-dt_test.stderr
@@ -42,7 +42,7 @@
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_get_data on line 41: RESULT: 2000-07-12 17:34:29 offset: -1; array: no
 [NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ECPGtrans on line 393: action "rollback"; connection "ecpg1_regression"
+[NO_PID]: ECPGtrans on line 412: action "rollback"; connection "ecpg1_regression"
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: ecpg_finish: connection ecpg1_regression closed
 [NO_PID]: sqlca: code: 0, state: 00000
diff --git a/src/interfaces/ecpg/test/expected/pgtypeslib-dt_test.stdout b/src/interfaces/ecpg/test/expected/pgtypeslib-dt_test.stdout
index 6b8bcc9fc27..38fb8a343af 100644
--- a/src/interfaces/ecpg/test/expected/pgtypeslib-dt_test.stdout
+++ b/src/interfaces/ecpg/test/expected/pgtypeslib-dt_test.stdout
@@ -6,6 +6,7 @@ date seems to get encoded to julian -622
 m: 4, d: 19, y: 1998
 date_day of 2003-12-04 17:34:29 is 4
 Above date in format "(ddd), mmm. dd, yyyy, repeat: (ddd), mmm. dd, yyyy. end" is "(Thu), Dec. 04, 2003, repeat: (Thu), Dec. 04, 2003. end"
+date_fmt_asc yyyy year>=10000: rc=-1 errno=310
 Which is day number 338 in 2003.
 date_defmt_asc1: 1995-12-25
 date_defmt_asc2: 0095-12-25
diff --git a/src/interfaces/ecpg/test/pgtypeslib/dt_test.pgc b/src/interfaces/ecpg/test/pgtypeslib/dt_test.pgc
index 645c273e503..8ea7e7839bc 100644
--- a/src/interfaces/ecpg/test/pgtypeslib/dt_test.pgc
+++ b/src/interfaces/ecpg/test/pgtypeslib/dt_test.pgc
@@ -76,6 +76,22 @@ main(void)
 	PGTYPESchar_free(text);
 	free(out);
 
+	/* year 10000 does not fit the four-digit "yyyy" token */
+	{
+		date		big_year_date;
+		char	   *big_out;
+		int			fmt_rc;
+
+		errno = 0;
+		big_year_date = PGTYPESdate_from_asc("10000-01-01", NULL);
+		fmt = "yyyy";
+		big_out = (char *) malloc(strlen(fmt) + 1);
+		fmt_rc = PGTYPESdate_fmt_asc(big_year_date, fmt, big_out);
+		printf("date_fmt_asc yyyy year>=10000: rc=%d errno=%d\n",
+			   fmt_rc, errno);
+		free(big_out);
+	}
+
 	out = (char*) malloc(48);
 	i = PGTYPEStimestamp_fmt_asc(&ts1, out, 47, "Which is day number %j in %Y.");
 	printf("%s\n", out);
