Hi, Hackers!
PGTYPESdate_fmt_asc() replaces fixed-width tokens in place. "yyyy"
is four characters. Callers typically size outbuf as
strlen(fmtstring)+1, as dt_test does.
For year >= 10000, "%04u" produces five or more digits. memcpy()
over the "yyyy" span overruns that token and clobbers the trailing
NUL in a strlen(fmt)+1 buffer. ASan reports a heap-buffer-overflow
on the next strstr() in the token loop.
```
const char *fmt = "yyyy";
date d = PGTYPESdate_from_asc("10000-01-01", NULL);
char *out = malloc(strlen(fmt) + 1);
PGTYPESdate_fmt_asc(d, fmt, out);
```
The attached patch rejects a replacement longer than the token and
sets errno to PGTYPES_DATE_BAD_DATE. A dt_test case is included.
--
Regards,
Rachitskiy Andrey
From: Andrey Rachitskiy <[email protected]>
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 <[email protected]>
---
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);