Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 4318dae

Browse files
committed
Fix handling of wide datetime input/output.
Many server functions use the MAXDATELEN constant to size a buffer for parsing or displaying a datetime value. It was much too small for the longest possible interval output and slightly too small for certain valid timestamp input, particularly input with a long timezone name. The long input was rejected needlessly; the long output caused interval_out() to overrun its buffer. ECPG's pgtypes library has a copy of the vulnerable functions, which bore the same vulnerabilities along with some of its own. In contrast to the server, certain long inputs caused stack overflow rather than failing cleanly. Back-patch to 8.4 (all supported versions). Reported by Daniel Schüssler, reviewed by Tom Lane. Security: CVE-2014-0063
1 parent 5f17304 commit 4318dae

File tree

11 files changed

+111
-35
lines changed

11 files changed

+111
-35
lines changed

src/include/utils/datetime.h

+11-6
Original file line numberDiff line numberDiff line change
@@ -188,12 +188,17 @@ struct tzEntry;
188188
#define DTK_DATE_M (DTK_M(YEAR) | DTK_M(MONTH) | DTK_M(DAY))
189189
#define DTK_TIME_M (DTK_M(HOUR) | DTK_M(MINUTE) | DTK_ALL_SECS_M)
190190

191-
#define MAXDATELEN 63 /* maximum possible length of an input date
192-
* string (not counting tr. null) */
193-
#define MAXDATEFIELDS 25 /* maximum possible number of fields in a date
194-
* string */
195-
#define TOKMAXLEN 10 /* only this many chars are stored in
196-
* datetktbl */
191+
/*
192+
* Working buffer size for input and output of interval, timestamp, etc.
193+
* Inputs that need more working space will be rejected early. Longer outputs
194+
* will overrun buffers, so this must suffice for all possible output. As of
195+
* this writing, interval_out() needs the most space at ~90 bytes.
196+
*/
197+
#define MAXDATELEN 128
198+
/* maximum possible number of fields in a date string */
199+
#define MAXDATEFIELDS 25
200+
/* only this many chars are stored in datetktbl */
201+
#define TOKMAXLEN 10
197202

198203
/* keep this struct small; it gets used a lot */
199204
typedef struct

src/interfaces/ecpg/pgtypeslib/datetime.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,14 @@ PGTYPESdate_from_asc(char *str, char **endptr)
6060
int nf;
6161
char *field[MAXDATEFIELDS];
6262
int ftype[MAXDATEFIELDS];
63-
char lowstr[MAXDATELEN + 1];
63+
char lowstr[MAXDATELEN + MAXDATEFIELDS];
6464
char *realptr;
6565
char **ptr = (endptr != NULL) ? endptr : &realptr;
6666

6767
bool EuroDates = FALSE;
6868

6969
errno = 0;
70-
if (strlen(str) >= sizeof(lowstr))
70+
if (strlen(str) > MAXDATELEN)
7171
{
7272
errno = PGTYPES_DATE_BAD_DATE;
7373
return INT_MIN;

src/interfaces/ecpg/pgtypeslib/dt.h

+11-6
Original file line numberDiff line numberDiff line change
@@ -192,12 +192,17 @@ typedef double fsec_t;
192192
#define DTK_DATE_M (DTK_M(YEAR) | DTK_M(MONTH) | DTK_M(DAY))
193193
#define DTK_TIME_M (DTK_M(HOUR) | DTK_M(MINUTE) | DTK_M(SECOND))
194194

195-
#define MAXDATELEN 63 /* maximum possible length of an input date
196-
* string (not counting tr. null) */
197-
#define MAXDATEFIELDS 25 /* maximum possible number of fields in a date
198-
* string */
199-
#define TOKMAXLEN 10 /* only this many chars are stored in
200-
* datetktbl */
195+
/*
196+
* Working buffer size for input and output of interval, timestamp, etc.
197+
* Inputs that need more working space will be rejected early. Longer outputs
198+
* will overrun buffers, so this must suffice for all possible output. As of
199+
* this writing, PGTYPESinterval_to_asc() needs the most space at ~90 bytes.
200+
*/
201+
#define MAXDATELEN 128
202+
/* maximum possible number of fields in a date string */
203+
#define MAXDATEFIELDS 25
204+
/* only this many chars are stored in datetktbl */
205+
#define TOKMAXLEN 10
201206

202207
/* keep this struct small; it gets used a lot */
203208
typedef struct

src/interfaces/ecpg/pgtypeslib/dt_common.c

+31-13
Original file line numberDiff line numberDiff line change
@@ -1171,15 +1171,22 @@ DecodeNumberField(int len, char *str, int fmask,
11711171
if ((cp = strchr(str, '.')) != NULL)
11721172
{
11731173
#ifdef HAVE_INT64_TIMESTAMP
1174-
char fstr[MAXDATELEN + 1];
1174+
char fstr[7];
1175+
int i;
1176+
1177+
cp++;
11751178

11761179
/*
11771180
* OK, we have at most six digits to care about. Let's construct a
1178-
* string and then do the conversion to an integer.
1181+
* string with those digits, zero-padded on the right, and then do
1182+
* the conversion to an integer.
1183+
*
1184+
* XXX This truncates the seventh digit, unlike rounding it as do
1185+
* the backend and the !HAVE_INT64_TIMESTAMP case.
11791186
*/
1180-
strcpy(fstr, (cp + 1));
1181-
strcpy(fstr + strlen(fstr), "000000");
1182-
*(fstr + 6) = '\0';
1187+
for (i = 0; i < 6; i++)
1188+
fstr[i] = *cp != '\0' ? *cp++ : '0';
1189+
fstr[i] = '\0';
11831190
*fsec = strtol(fstr, NULL, 10);
11841191
#else
11851192
*fsec = strtod(cp, NULL);
@@ -1531,15 +1538,22 @@ DecodeTime(char *str, int *tmask, struct tm * tm, fsec_t *fsec)
15311538
else if (*cp == '.')
15321539
{
15331540
#ifdef HAVE_INT64_TIMESTAMP
1534-
char fstr[MAXDATELEN + 1];
1541+
char fstr[7];
1542+
int i;
1543+
1544+
cp++;
15351545

15361546
/*
1537-
* OK, we have at most six digits to work with. Let's construct a
1538-
* string and then do the conversion to an integer.
1547+
* OK, we have at most six digits to care about. Let's construct a
1548+
* string with those digits, zero-padded on the right, and then do
1549+
* the conversion to an integer.
1550+
*
1551+
* XXX This truncates the seventh digit, unlike rounding it as do
1552+
* the backend and the !HAVE_INT64_TIMESTAMP case.
15391553
*/
1540-
strncpy(fstr, (cp + 1), 7);
1541-
strcpy(fstr + strlen(fstr), "000000");
1542-
*(fstr + 6) = '\0';
1554+
for (i = 0; i < 6; i++)
1555+
fstr[i] = *cp != '\0' ? *cp++ : '0';
1556+
fstr[i] = '\0';
15431557
*fsec = strtol(fstr, &cp, 10);
15441558
#else
15451559
str = cp;
@@ -1665,6 +1679,9 @@ DecodePosixTimezone(char *str, int *tzp)
16651679
* DTK_NUMBER can hold date fields (yy.ddd)
16661680
* DTK_STRING can hold months (January) and time zones (PST)
16671681
* DTK_DATE can hold Posix time zones (GMT-8)
1682+
*
1683+
* The "lowstr" work buffer must have at least strlen(timestr) + MAXDATEFIELDS
1684+
* bytes of space. On output, field[] entries will point into it.
16681685
*/
16691686
int
16701687
ParseDateTime(char *timestr, char *lowstr,
@@ -1677,7 +1694,10 @@ ParseDateTime(char *timestr, char *lowstr,
16771694
/* outer loop through fields */
16781695
while (*(*endstr) != '\0')
16791696
{
1697+
/* Record start of current field */
16801698
field[nf] = lp;
1699+
if (nf >= MAXDATEFIELDS)
1700+
return -1;
16811701

16821702
/* leading digit? then date or time */
16831703
if (isdigit((unsigned char) *(*endstr)))
@@ -1818,8 +1838,6 @@ ParseDateTime(char *timestr, char *lowstr,
18181838
/* force in a delimiter after each field */
18191839
*lp++ = '\0';
18201840
nf++;
1821-
if (nf > MAXDATEFIELDS)
1822-
return -1;
18231841
}
18241842

18251843
*numfields = nf;

src/interfaces/ecpg/pgtypeslib/interval.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -1094,7 +1094,7 @@ PGTYPESinterval_from_asc(char *str, char **endptr)
10941094
tm->tm_sec = 0;
10951095
fsec = 0;
10961096

1097-
if (strlen(str) >= sizeof(lowstr))
1097+
if (strlen(str) > MAXDATELEN)
10981098
{
10991099
errno = PGTYPES_INTVL_BAD_INTERVAL;
11001100
return NULL;

src/interfaces/ecpg/pgtypeslib/timestamp.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ PGTYPEStimestamp_from_asc(char *str, char **endptr)
294294
char *realptr;
295295
char **ptr = (endptr != NULL) ? endptr : &realptr;
296296

297-
if (strlen(str) >= sizeof(lowstr))
297+
if (strlen(str) > MAXDATELEN)
298298
{
299299
errno = PGTYPES_TS_BAD_TIMESTAMP;
300300
return (noresult);

src/interfaces/ecpg/test/expected/pgtypeslib-dt_test2.c

+16-6
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,23 @@ char *dates[] = { "19990108foobar",
4545
"1999.008",
4646
"J2451187",
4747
"January 8, 99 BC",
48+
/*
49+
* Maximize space usage in ParseDateTime() with 25
50+
* (MAXDATEFIELDS) fields and 128 (MAXDATELEN) total length.
51+
*/
52+
"........................Xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
53+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
54+
/* 26 fields */
55+
".........................aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
56+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
4857
NULL };
4958

5059
/* do not conflict with libc "times" symbol */
5160
static char *times[] = { "0:04",
5261
"1:59 PDT",
5362
"13:24:40 -8:00",
5463
"13:24:40.495+3",
64+
"13:24:40.123456789+3",
5565
NULL };
5666

5767
char *intervals[] = { "1 minute",
@@ -73,22 +83,22 @@ main(void)
7383

7484

7585

76-
#line 52 "dt_test2.pgc"
86+
#line 62 "dt_test2.pgc"
7787
date date1 ;
7888

79-
#line 53 "dt_test2.pgc"
89+
#line 63 "dt_test2.pgc"
8090
timestamp ts1 , ts2 ;
8191

82-
#line 54 "dt_test2.pgc"
92+
#line 64 "dt_test2.pgc"
8393
char * text ;
8494

85-
#line 55 "dt_test2.pgc"
95+
#line 65 "dt_test2.pgc"
8696
interval * i1 ;
8797

88-
#line 56 "dt_test2.pgc"
98+
#line 66 "dt_test2.pgc"
8999
date * dc ;
90100
/* exec sql end declare section */
91-
#line 57 "dt_test2.pgc"
101+
#line 67 "dt_test2.pgc"
92102

93103

94104
int i, j;

src/interfaces/ecpg/test/expected/pgtypeslib-dt_test2.stdout

+19
Original file line numberDiff line numberDiff line change
@@ -8,85 +8,104 @@ TS[3,0]: 1999-01-08 00:04:00
88
TS[3,1]: 1999-01-08 01:59:00
99
TS[3,2]: 1999-01-08 13:24:40
1010
TS[3,3]: 1999-01-08 13:24:40.495
11+
TS[3,4]: 1999-01-08 13:24:40.123456
1112
Date[4]: 1999-01-08 (N - F)
1213
TS[4,0]: 1999-01-08 00:04:00
1314
TS[4,1]: 1999-01-08 01:59:00
1415
TS[4,2]: 1999-01-08 13:24:40
1516
TS[4,3]: 1999-01-08 13:24:40.495
17+
TS[4,4]: 1999-01-08 13:24:40.123456
1618
Date[5]: 1999-01-08 (N - F)
1719
TS[5,0]: 1999-01-08 00:04:00
1820
TS[5,1]: 1999-01-08 01:59:00
1921
TS[5,2]: 1999-01-08 13:24:40
2022
TS[5,3]: 1999-01-08 13:24:40.495
23+
TS[5,4]: 1999-01-08 13:24:40.123456
2124
Date[6]: 1999-01-18 (N - F)
2225
TS[6,0]: 1999-01-18 00:04:00
2326
TS[6,1]: 1999-01-18 01:59:00
2427
TS[6,2]: 1999-01-18 13:24:40
2528
TS[6,3]: 1999-01-18 13:24:40.495
29+
TS[6,4]: 1999-01-18 13:24:40.123456
2630
Date[7]: 2003-01-02 (N - F)
2731
TS[7,0]: 2003-01-02 00:04:00
2832
TS[7,1]: 2003-01-02 01:59:00
2933
TS[7,2]: 2003-01-02 13:24:40
3034
TS[7,3]: 2003-01-02 13:24:40.495
35+
TS[7,4]: 2003-01-02 13:24:40.123456
3136
Date[8]: 1999-01-08 (N - F)
3237
TS[8,0]: 1999-01-08 00:04:00
3338
TS[8,1]: 1999-01-08 01:59:00
3439
TS[8,2]: 1999-01-08 13:24:40
3540
TS[8,3]: 1999-01-08 13:24:40.495
41+
TS[8,4]: 1999-01-08 13:24:40.123456
3642
Date[9]: 1999-01-08 (N - F)
3743
TS[9,0]: 1999-01-08 00:04:00
3844
TS[9,1]: 1999-01-08 01:59:00
3945
TS[9,2]: 1999-01-08 13:24:40
4046
TS[9,3]: 1999-01-08 13:24:40.495
47+
TS[9,4]: 1999-01-08 13:24:40.123456
4148
Date[10]: 1999-01-08 (N - F)
4249
TS[10,0]: 1999-01-08 00:04:00
4350
TS[10,1]: 1999-01-08 01:59:00
4451
TS[10,2]: 1999-01-08 13:24:40
4552
TS[10,3]: 1999-01-08 13:24:40.495
53+
TS[10,4]: 1999-01-08 13:24:40.123456
4654
Date[11]: 1999-01-08 (N - F)
4755
TS[11,0]: 1999-01-08 00:04:00
4856
TS[11,1]: 1999-01-08 01:59:00
4957
TS[11,2]: 1999-01-08 13:24:40
5058
TS[11,3]: 1999-01-08 13:24:40.495
59+
TS[11,4]: 1999-01-08 13:24:40.123456
5160
Date[12]: 1999-01-08 (N - F)
5261
TS[12,0]: 1999-01-08 00:04:00
5362
TS[12,1]: 1999-01-08 01:59:00
5463
TS[12,2]: 1999-01-08 13:24:40
5564
TS[12,3]: 1999-01-08 13:24:40.495
65+
TS[12,4]: 1999-01-08 13:24:40.123456
5666
Date[13]: 2006-01-08 (N - F)
5767
TS[13,0]: 2006-01-08 00:04:00
5868
TS[13,1]: 2006-01-08 01:59:00
5969
TS[13,2]: 2006-01-08 13:24:40
6070
TS[13,3]: 2006-01-08 13:24:40.495
71+
TS[13,4]: 2006-01-08 13:24:40.123456
6172
Date[14]: 1999-01-08 (N - F)
6273
TS[14,0]: 1999-01-08 00:04:00
6374
TS[14,1]: 1999-01-08 01:59:00
6475
TS[14,2]: 1999-01-08 13:24:40
6576
TS[14,3]: 1999-01-08 13:24:40.495
77+
TS[14,4]: 1999-01-08 13:24:40.123456
6678
Date[15]: 1999-01-08 (N - F)
6779
TS[15,0]: 1999-01-08 00:04:00
6880
TS[15,1]: 1999-01-08 01:59:00
6981
TS[15,2]: 1999-01-08 13:24:40
7082
TS[15,3]: 1999-01-08 13:24:40.495
83+
TS[15,4]: 1999-01-08 13:24:40.123456
7184
Date[16]: 1999-01-08 (N - F)
7285
TS[16,0]: 1999-01-08 00:04:00
7386
TS[16,1]: 1999-01-08 01:59:00
7487
TS[16,2]: 1999-01-08 13:24:40
7588
TS[16,3]: 1999-01-08 13:24:40.495
89+
TS[16,4]: 1999-01-08 13:24:40.123456
7690
Date[17]: 1999-01-08 (N - F)
7791
TS[17,0]: 1999-01-08 00:04:00
7892
TS[17,1]: 1999-01-08 01:59:00
7993
TS[17,2]: 1999-01-08 13:24:40
8094
TS[17,3]: 1999-01-08 13:24:40.495
95+
TS[17,4]: 1999-01-08 13:24:40.123456
8196
Date[18]: 1999-01-08 (N - F)
8297
TS[18,0]: 1999-01-08 00:04:00
8398
TS[18,1]: 1999-01-08 01:59:00
8499
TS[18,2]: 1999-01-08 13:24:40
85100
TS[18,3]: 1999-01-08 13:24:40.495
101+
TS[18,4]: 1999-01-08 13:24:40.123456
86102
Date[19]: 0099-01-08 BC (N - F)
87103
TS[19,0]: 0099-01-08 00:04:00 BC
88104
TS[19,1]: 0099-01-08 01:59:00 BC
89105
TS[19,2]: 0099-01-08 13:24:40 BC
106+
TS[19,4]: 0099-01-08 13:24:40.123456 BC
107+
Date[20]: - (N - T)
108+
Date[21]: - (N - T)
90109
interval[0]: @ 1 min
91110
interval_copy[0]: @ 1 min
92111
interval[1]: @ 1 day 12 hours 59 mins 10 secs

src/interfaces/ecpg/test/pgtypeslib/dt_test2.pgc

+10
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,23 @@ char *dates[] = { "19990108foobar",
2727
"1999.008",
2828
"J2451187",
2929
"January 8, 99 BC",
30+
/*
31+
* Maximize space usage in ParseDateTime() with 25
32+
* (MAXDATEFIELDS) fields and 128 (MAXDATELEN) total length.
33+
*/
34+
"........................Xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
35+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
36+
/* 26 fields */
37+
".........................aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
38+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
3039
NULL };
3140

3241
/* do not conflict with libc "times" symbol */
3342
static char *times[] = { "0:04",
3443
"1:59 PDT",
3544
"13:24:40 -8:00",
3645
"13:24:40.495+3",
46+
"13:24:40.123456789+3",
3747
NULL };
3848

3949
char *intervals[] = { "1 minute",

src/test/regress/expected/interval.out

+7
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,13 @@ select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31
306306
@ 4541 years 4 mons 4 days 17 mins 31 secs
307307
(1 row)
308308

309+
-- test long interval output
310+
select '100000000y 10mon -1000000000d -1000000000h -10min -10.000001s ago'::interval;
311+
interval
312+
-------------------------------------------------------------------------------------------
313+
@ 100000000 years 10 mons -1000000000 days -1000000000 hours -10 mins -10.000001 secs ago
314+
(1 row)
315+
309316
-- test justify_hours() and justify_days()
310317
SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as "6 mons 5 days 4 hours 3 mins 2 seconds";
311318
6 mons 5 days 4 hours 3 mins 2 seconds

src/test/regress/sql/interval.sql

+2
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ select avg(f1) from interval_tbl;
108108
-- test long interval input
109109
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
110110

111+
-- test long interval output
112+
select '100000000y 10mon -1000000000d -1000000000h -10min -10.000001s ago'::interval;
111113

112114
-- test justify_hours() and justify_days()
113115

0 commit comments

Comments
 (0)