Skip to content

Commit 63e0d61

Browse files
author
Neil Conway
committed
Adjust datetime parsing to be more robust. We now pass the length of the
working buffer into ParseDateTime() and reject too-long input there, rather than checking the length of the input string before calling ParseDateTime(). The old method was bogus because ParseDateTime() can use a variable amount of working space, depending on the content of the input string (e.g. how many fields need to be NUL terminated). This fixes a minor stack overrun -- I don't _think_ it's exploitable, although I won't claim to be an expert. Along the way, fix a bug reported by Mark Dilger: the working buffer allocated by interval_in() was too short, which resulted in rejecting some perfectly valid interval input values. I added a regression test for this fix.
1 parent 15e4d1e commit 63e0d61

File tree

7 files changed

+86
-75
lines changed

7 files changed

+86
-75
lines changed

src/backend/utils/adt/date.c

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/utils/adt/date.c,v 1.108 2005/05/24 02:09:45 momjian Exp $
11+
* $PostgreSQL: pgsql/src/backend/utils/adt/date.c,v 1.109 2005/05/26 02:04:13 neilc Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -65,12 +65,10 @@ date_in(PG_FUNCTION_ARGS)
6565
int dterr;
6666
char *field[MAXDATEFIELDS];
6767
int ftype[MAXDATEFIELDS];
68-
char lowstr[MAXDATELEN + 1];
68+
char workbuf[MAXDATELEN + 1];
6969

70-
if (strlen(str) >= sizeof(lowstr))
71-
dterr = DTERR_BAD_FORMAT;
72-
else
73-
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
70+
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
71+
field, ftype, MAXDATEFIELDS, &nf);
7472
if (dterr == 0)
7573
dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp);
7674
if (dterr != 0)
@@ -894,15 +892,13 @@ time_in(PG_FUNCTION_ARGS)
894892
int tz;
895893
int nf;
896894
int dterr;
897-
char lowstr[MAXDATELEN + 1];
895+
char workbuf[MAXDATELEN + 1];
898896
char *field[MAXDATEFIELDS];
899897
int dtype;
900898
int ftype[MAXDATEFIELDS];
901899

902-
if (strlen(str) >= sizeof(lowstr))
903-
dterr = DTERR_BAD_FORMAT;
904-
else
905-
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
900+
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
901+
field, ftype, MAXDATEFIELDS, &nf);
906902
if (dterr == 0)
907903
dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz);
908904
if (dterr != 0)
@@ -1733,15 +1729,13 @@ timetz_in(PG_FUNCTION_ARGS)
17331729
int tz;
17341730
int nf;
17351731
int dterr;
1736-
char lowstr[MAXDATELEN + 1];
1732+
char workbuf[MAXDATELEN + 1];
17371733
char *field[MAXDATEFIELDS];
17381734
int dtype;
17391735
int ftype[MAXDATEFIELDS];
17401736

1741-
if (strlen(str) >= sizeof(lowstr))
1742-
dterr = DTERR_BAD_FORMAT;
1743-
else
1744-
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
1737+
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
1738+
field, ftype, MAXDATEFIELDS, &nf);
17451739
if (dterr == 0)
17461740
dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz);
17471741
if (dterr != 0)

src/backend/utils/adt/datetime.c

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.144 2005/05/24 02:09:45 momjian Exp $
11+
* $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.145 2005/05/26 02:04:13 neilc Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -699,21 +699,23 @@ TrimTrailingZeros(char *str)
699699
}
700700
}
701701

702-
703702
/* ParseDateTime()
704703
* Break string into tokens based on a date/time context.
705704
* Returns 0 if successful, DTERR code if bogus input detected.
706705
*
707706
* timestr - the input string
708-
* lowstr - workspace for field string storage (must be large enough for
709-
* a copy of the input string, including trailing null)
707+
* workbuf - workspace for field string storage. This must be
708+
* larger than the largest legal input for this datetime type --
709+
* some additional space will be needed to NUL terminate fields.
710+
* buflen - the size of workbuf
710711
* field[] - pointers to field strings are returned in this array
711712
* ftype[] - field type indicators are returned in this array
712713
* maxfields - dimensions of the above two arrays
713714
* *numfields - set to the actual number of fields detected
714715
*
715-
* The fields extracted from the input are stored as separate, null-terminated
716-
* strings in the workspace at lowstr. Any text is converted to lower case.
716+
* The fields extracted from the input are stored as separate,
717+
* null-terminated strings in the workspace at workbuf. Any text is
718+
* converted to lower case.
717719
*
718720
* Several field types are assigned:
719721
* DTK_NUMBER - digits and (possibly) a decimal point
@@ -729,12 +731,27 @@ TrimTrailingZeros(char *str)
729731
* DTK_DATE can hold Posix time zones (GMT-8)
730732
*/
731733
int
732-
ParseDateTime(const char *timestr, char *lowstr,
734+
ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
733735
char **field, int *ftype, int maxfields, int *numfields)
734736
{
735737
int nf = 0;
736738
const char *cp = timestr;
737-
char *lp = lowstr;
739+
char *bufp = workbuf;
740+
const char *bufend = workbuf + buflen;
741+
742+
/*
743+
* Set the character pointed-to by "bufptr" to "newchar", and
744+
* increment "bufptr". "end" gives the end of the buffer -- we
745+
* return an error if there is no space left to append a character
746+
* to the buffer. Note that "bufptr" is evaluated twice.
747+
*/
748+
#define APPEND_CHAR(bufptr, end, newchar) \
749+
do \
750+
{ \
751+
if (((bufptr) + 1) >= (end)) \
752+
return DTERR_BAD_FORMAT; \
753+
*(bufptr)++ = newchar; \
754+
} while (0)
738755

739756
/* outer loop through fields */
740757
while (*cp != '\0')
@@ -749,37 +766,37 @@ ParseDateTime(const char *timestr, char *lowstr,
749766
/* Record start of current field */
750767
if (nf >= maxfields)
751768
return DTERR_BAD_FORMAT;
752-
field[nf] = lp;
769+
field[nf] = bufp;
753770

754771
/* leading digit? then date or time */
755772
if (isdigit((unsigned char) *cp))
756773
{
757-
*lp++ = *cp++;
774+
APPEND_CHAR(bufp, bufend, *cp++);
758775
while (isdigit((unsigned char) *cp))
759-
*lp++ = *cp++;
776+
APPEND_CHAR(bufp, bufend, *cp++);
760777

761778
/* time field? */
762779
if (*cp == ':')
763780
{
764781
ftype[nf] = DTK_TIME;
765-
*lp++ = *cp++;
782+
APPEND_CHAR(bufp, bufend, *cp++);
766783
while (isdigit((unsigned char) *cp) ||
767784
(*cp == ':') || (*cp == '.'))
768-
*lp++ = *cp++;
785+
APPEND_CHAR(bufp, bufend, *cp++);
769786
}
770787
/* date field? allow embedded text month */
771788
else if (*cp == '-' || *cp == '/' || *cp == '.')
772789
{
773790
/* save delimiting character to use later */
774791
char delim = *cp;
775792

776-
*lp++ = *cp++;
793+
APPEND_CHAR(bufp, bufend, *cp++);
777794
/* second field is all digits? then no embedded text month */
778795
if (isdigit((unsigned char) *cp))
779796
{
780797
ftype[nf] = ((delim == '.') ? DTK_NUMBER : DTK_DATE);
781798
while (isdigit((unsigned char) *cp))
782-
*lp++ = *cp++;
799+
APPEND_CHAR(bufp, bufend, *cp++);
783800

784801
/*
785802
* insist that the delimiters match to get a
@@ -788,16 +805,16 @@ ParseDateTime(const char *timestr, char *lowstr,
788805
if (*cp == delim)
789806
{
790807
ftype[nf] = DTK_DATE;
791-
*lp++ = *cp++;
808+
APPEND_CHAR(bufp, bufend, *cp++);
792809
while (isdigit((unsigned char) *cp) || *cp == delim)
793-
*lp++ = *cp++;
810+
APPEND_CHAR(bufp, bufend, *cp++);
794811
}
795812
}
796813
else
797814
{
798815
ftype[nf] = DTK_DATE;
799816
while (isalnum((unsigned char) *cp) || *cp == delim)
800-
*lp++ = pg_tolower((unsigned char) *cp++);
817+
APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++));
801818
}
802819
}
803820

@@ -811,9 +828,9 @@ ParseDateTime(const char *timestr, char *lowstr,
811828
/* Leading decimal point? Then fractional seconds... */
812829
else if (*cp == '.')
813830
{
814-
*lp++ = *cp++;
831+
APPEND_CHAR(bufp, bufend, *cp++);
815832
while (isdigit((unsigned char) *cp))
816-
*lp++ = *cp++;
833+
APPEND_CHAR(bufp, bufend, *cp++);
817834

818835
ftype[nf] = DTK_NUMBER;
819836
}
@@ -825,9 +842,9 @@ ParseDateTime(const char *timestr, char *lowstr,
825842
else if (isalpha((unsigned char) *cp))
826843
{
827844
ftype[nf] = DTK_STRING;
828-
*lp++ = pg_tolower((unsigned char) *cp++);
845+
APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++));
829846
while (isalpha((unsigned char) *cp))
830-
*lp++ = pg_tolower((unsigned char) *cp++);
847+
APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++));
831848

832849
/*
833850
* Full date string with leading text month? Could also be a
@@ -838,34 +855,34 @@ ParseDateTime(const char *timestr, char *lowstr,
838855
char delim = *cp;
839856

840857
ftype[nf] = DTK_DATE;
841-
*lp++ = *cp++;
858+
APPEND_CHAR(bufp, bufend, *cp++);
842859
while (isdigit((unsigned char) *cp) || *cp == delim)
843-
*lp++ = *cp++;
860+
APPEND_CHAR(bufp, bufend, *cp++);
844861
}
845862
}
846863
/* sign? then special or numeric timezone */
847864
else if (*cp == '+' || *cp == '-')
848865
{
849-
*lp++ = *cp++;
866+
APPEND_CHAR(bufp, bufend, *cp++);
850867
/* soak up leading whitespace */
851868
while (isspace((unsigned char) *cp))
852869
cp++;
853870
/* numeric timezone? */
854871
if (isdigit((unsigned char) *cp))
855872
{
856873
ftype[nf] = DTK_TZ;
857-
*lp++ = *cp++;
874+
APPEND_CHAR(bufp, bufend, *cp++);
858875
while (isdigit((unsigned char) *cp) ||
859876
*cp == ':' || *cp == '.')
860-
*lp++ = *cp++;
877+
APPEND_CHAR(bufp, bufend, *cp++);
861878
}
862879
/* special? */
863880
else if (isalpha((unsigned char) *cp))
864881
{
865882
ftype[nf] = DTK_SPECIAL;
866-
*lp++ = pg_tolower((unsigned char) *cp++);
883+
APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++));
867884
while (isalpha((unsigned char) *cp))
868-
*lp++ = pg_tolower((unsigned char) *cp++);
885+
APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++));
869886
}
870887
/* otherwise something wrong... */
871888
else
@@ -882,7 +899,7 @@ ParseDateTime(const char *timestr, char *lowstr,
882899
return DTERR_BAD_FORMAT;
883900

884901
/* force in a delimiter after each field */
885-
*lp++ = '\0';
902+
*bufp++ = '\0';
886903
nf++;
887904
}
888905

src/backend/utils/adt/nabstime.c

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
*
1111
*
1212
* IDENTIFICATION
13-
* $PostgreSQL: pgsql/src/backend/utils/adt/nabstime.c,v 1.131 2005/05/24 02:09:45 momjian Exp $
13+
* $PostgreSQL: pgsql/src/backend/utils/adt/nabstime.c,v 1.132 2005/05/26 02:04:13 neilc Exp $
1414
*
1515
*-------------------------------------------------------------------------
1616
*/
@@ -306,15 +306,13 @@ abstimein(PG_FUNCTION_ARGS)
306306
*tm = &date;
307307
int dterr;
308308
char *field[MAXDATEFIELDS];
309-
char lowstr[MAXDATELEN + 1];
309+
char workbuf[MAXDATELEN + 1];
310310
int dtype;
311311
int nf,
312312
ftype[MAXDATEFIELDS];
313313

314-
if (strlen(str) >= sizeof(lowstr))
315-
dterr = DTERR_BAD_FORMAT;
316-
else
317-
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
314+
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
315+
field, ftype, MAXDATEFIELDS, &nf);
318316
if (dterr == 0)
319317
dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
320318
if (dterr != 0)
@@ -711,12 +709,10 @@ reltimein(PG_FUNCTION_ARGS)
711709
char *field[MAXDATEFIELDS];
712710
int nf,
713711
ftype[MAXDATEFIELDS];
714-
char lowstr[MAXDATELEN + 1];
712+
char workbuf[MAXDATELEN + 1];
715713

716-
if (strlen(str) >= sizeof(lowstr))
717-
dterr = DTERR_BAD_FORMAT;
718-
else
719-
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
714+
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
715+
field, ftype, MAXDATEFIELDS, &nf);
720716
if (dterr == 0)
721717
dterr = DecodeInterval(field, ftype, nf, &dtype, tm, &fsec);
722718
if (dterr != 0)

src/backend/utils/adt/timestamp.c

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.123 2005/05/24 02:09:45 momjian Exp $
11+
* $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.124 2005/05/26 02:04:13 neilc Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -77,12 +77,10 @@ timestamp_in(PG_FUNCTION_ARGS)
7777
int dterr;
7878
char *field[MAXDATEFIELDS];
7979
int ftype[MAXDATEFIELDS];
80-
char lowstr[MAXDATELEN + MAXDATEFIELDS];
80+
char workbuf[MAXDATELEN + MAXDATEFIELDS];
8181

82-
if (strlen(str) >= sizeof(lowstr))
83-
dterr = DTERR_BAD_FORMAT;
84-
else
85-
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
82+
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
83+
field, ftype, MAXDATEFIELDS, &nf);
8684
if (dterr == 0)
8785
dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
8886
if (dterr != 0)
@@ -317,12 +315,10 @@ timestamptz_in(PG_FUNCTION_ARGS)
317315
int dterr;
318316
char *field[MAXDATEFIELDS];
319317
int ftype[MAXDATEFIELDS];
320-
char lowstr[MAXDATELEN + MAXDATEFIELDS];
318+
char workbuf[MAXDATELEN + MAXDATEFIELDS];
321319

322-
if (strlen(str) >= sizeof(lowstr))
323-
dterr = DTERR_BAD_FORMAT;
324-
else
325-
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
320+
dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
321+
field, ftype, MAXDATEFIELDS, &nf);
326322
if (dterr == 0)
327323
dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
328324
if (dterr != 0)
@@ -493,7 +489,7 @@ interval_in(PG_FUNCTION_ARGS)
493489
int dterr;
494490
char *field[MAXDATEFIELDS];
495491
int ftype[MAXDATEFIELDS];
496-
char lowstr[MAXDATELEN + MAXDATEFIELDS];
492+
char workbuf[256];
497493

498494
tm->tm_year = 0;
499495
tm->tm_mon = 0;
@@ -503,10 +499,8 @@ interval_in(PG_FUNCTION_ARGS)
503499
tm->tm_sec = 0;
504500
fsec = 0;
505501

506-
if (strlen(str) >= sizeof(lowstr))
507-
dterr = DTERR_BAD_FORMAT;
508-
else
509-
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
502+
dterr = ParseDateTime(str, workbuf, sizeof(workbuf), field,
503+
ftype, MAXDATEFIELDS, &nf);
510504
if (dterr == 0)
511505
dterr = DecodeInterval(field, ftype, nf, &dtype, tm, &fsec);
512506
if (dterr != 0)

src/include/utils/datetime.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
1010
* Portions Copyright (c) 1994, Regents of the University of California
1111
*
12-
* $PostgreSQL: pgsql/src/include/utils/datetime.h,v 1.53 2005/05/24 04:03:01 momjian Exp $
12+
* $PostgreSQL: pgsql/src/include/utils/datetime.h,v 1.54 2005/05/26 02:04:14 neilc Exp $
1313
*
1414
*-------------------------------------------------------------------------
1515
*/
@@ -276,7 +276,7 @@ extern void GetCurrentTimeUsec(struct pg_tm * tm, fsec_t *fsec, int *tzp);
276276
extern void j2date(int jd, int *year, int *month, int *day);
277277
extern int date2j(int year, int month, int day);
278278

279-
extern int ParseDateTime(const char *timestr, char *lowstr,
279+
extern int ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
280280
char **field, int *ftype,
281281
int maxfields, int *numfields);
282282
extern int DecodeDateTime(char **field, int *ftype,

0 commit comments

Comments
 (0)