]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Adjust datetime parsing to be more robust. We now pass the length of the
authorNeil Conway <neilc@samurai.com>
Thu, 26 May 2005 02:10:03 +0000 (02:10 +0000)
committerNeil Conway <neilc@samurai.com>
Thu, 26 May 2005 02:10:03 +0000 (02:10 +0000)
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.

src/backend/utils/adt/date.c
src/backend/utils/adt/datetime.c
src/backend/utils/adt/nabstime.c
src/backend/utils/adt/timestamp.c
src/include/utils/datetime.h
src/test/regress/expected/interval.out
src/test/regress/sql/interval.sql

index 4e344eb091ec314eb72c8b904e2387cb78246346..0e2eb9546564206b410c0104879242d3336786dc 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/adt/date.c,v 1.104.4.1 2005/04/23 22:53:26 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/adt/date.c,v 1.104.4.2 2005/05/26 02:10:02 neilc Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -65,12 +65,10 @@ date_in(PG_FUNCTION_ARGS)
        int                     dterr;
        char       *field[MAXDATEFIELDS];
        int                     ftype[MAXDATEFIELDS];
-       char            lowstr[MAXDATELEN + 1];
+       char            workbuf[MAXDATELEN + 1];
 
-       if (strlen(str) >= sizeof(lowstr))
-               dterr = DTERR_BAD_FORMAT;
-       else
-               dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
+       dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
+                                                 field, ftype, MAXDATEFIELDS, &nf);
        if (dterr == 0)
                dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp);
        if (dterr != 0)
@@ -894,15 +892,13 @@ time_in(PG_FUNCTION_ARGS)
        int                     tz;
        int                     nf;
        int                     dterr;
-       char            lowstr[MAXDATELEN + 1];
+       char            workbuf[MAXDATELEN + 1];
        char       *field[MAXDATEFIELDS];
        int                     dtype;
        int                     ftype[MAXDATEFIELDS];
 
-       if (strlen(str) >= sizeof(lowstr))
-               dterr = DTERR_BAD_FORMAT;
-       else
-               dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
+       dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
+                                                 field, ftype, MAXDATEFIELDS, &nf);
        if (dterr == 0)
                dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz);
        if (dterr != 0)
@@ -1734,15 +1730,13 @@ timetz_in(PG_FUNCTION_ARGS)
        int                     tz;
        int                     nf;
        int                     dterr;
-       char            lowstr[MAXDATELEN + 1];
+       char            workbuf[MAXDATELEN + 1];
        char       *field[MAXDATEFIELDS];
        int                     dtype;
        int                     ftype[MAXDATEFIELDS];
 
-       if (strlen(str) >= sizeof(lowstr))
-               dterr = DTERR_BAD_FORMAT;
-       else
-               dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
+       dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
+                                                 field, ftype, MAXDATEFIELDS, &nf);
        if (dterr == 0)
                dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz);
        if (dterr != 0)
index ae85fcaf302ff60ef792be1c6df664dc03a16a37..996583061a4605757cba41ad21c1dcfbc51202bc 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.137.4.1 2005/04/20 17:14:58 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.137.4.2 2005/05/26 02:10:02 neilc Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -700,21 +700,23 @@ TrimTrailingZeros(char *str)
        }
 }
 
-
 /* ParseDateTime()
  *     Break string into tokens based on a date/time context.
  *     Returns 0 if successful, DTERR code if bogus input detected.
  *
  * timestr - the input string
- * lowstr - workspace for field string storage (must be large enough for
- *             a copy of the input string, including trailing null)
+ * workbuf - workspace for field string storage. This must be
+ *   larger than the largest legal input for this datetime type --
+ *   some additional space will be needed to NUL terminate fields.
+ * buflen - the size of workbuf
  * field[] - pointers to field strings are returned in this array
  * ftype[] - field type indicators are returned in this array
  * maxfields - dimensions of the above two arrays
  * *numfields - set to the actual number of fields detected
  *
- * The fields extracted from the input are stored as separate, null-terminated
- * strings in the workspace at lowstr. Any text is converted to lower case.
+ * The fields extracted from the input are stored as separate,
+ * null-terminated strings in the workspace at workbuf. Any text is
+ * converted to lower case.
  *
  * Several field types are assigned:
  *     DTK_NUMBER - digits and (possibly) a decimal point
@@ -730,12 +732,27 @@ TrimTrailingZeros(char *str)
  *     DTK_DATE can hold Posix time zones (GMT-8)
  */
 int
-ParseDateTime(const char *timestr, char *lowstr,
+ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
                          char **field, int *ftype, int maxfields, int *numfields)
 {
        int                     nf = 0;
        const char *cp = timestr;
-       char       *lp = lowstr;
+       char       *bufp = workbuf;
+       const char *bufend = workbuf + buflen;
+
+       /*
+        * Set the character pointed-to by "bufptr" to "newchar", and
+        * increment "bufptr". "end" gives the end of the buffer -- we
+        * return an error if there is no space left to append a character
+        * to the buffer. Note that "bufptr" is evaluated twice.
+        */
+#define APPEND_CHAR(bufptr, end, newchar)              \
+       do                                                                                      \
+       {                                                                                       \
+               if (((bufptr) + 1) >= (end))                    \
+                       return DTERR_BAD_FORMAT;                        \
+               *(bufptr)++ = newchar;                                  \
+       } while (0)
 
        /* outer loop through fields */
        while (*cp != '\0')
@@ -750,23 +767,23 @@ ParseDateTime(const char *timestr, char *lowstr,
                /* Record start of current field */
                if (nf >= maxfields)
                        return DTERR_BAD_FORMAT;
-               field[nf] = lp;
+               field[nf] = bufp;
 
                /* leading digit? then date or time */
                if (isdigit((unsigned char) *cp))
                {
-                       *lp++ = *cp++;
+                       APPEND_CHAR(bufp, bufend, *cp++);
                        while (isdigit((unsigned char) *cp))
-                               *lp++ = *cp++;
+                               APPEND_CHAR(bufp, bufend, *cp++);
 
                        /* time field? */
                        if (*cp == ':')
                        {
                                ftype[nf] = DTK_TIME;
-                               *lp++ = *cp++;
+                               APPEND_CHAR(bufp, bufend, *cp++);
                                while (isdigit((unsigned char) *cp) ||
                                           (*cp == ':') || (*cp == '.'))
-                                       *lp++ = *cp++;
+                                       APPEND_CHAR(bufp, bufend, *cp++);
                        }
                        /* date field? allow embedded text month */
                        else if ((*cp == '-') || (*cp == '/') || (*cp == '.'))
@@ -774,13 +791,13 @@ ParseDateTime(const char *timestr, char *lowstr,
                                /* save delimiting character to use later */
                                char            delim = *cp;
 
-                               *lp++ = *cp++;
+                               APPEND_CHAR(bufp, bufend, *cp++);
                                /* second field is all digits? then no embedded text month */
                                if (isdigit((unsigned char) *cp))
                                {
                                        ftype[nf] = ((delim == '.') ? DTK_NUMBER : DTK_DATE);
                                        while (isdigit((unsigned char) *cp))
-                                               *lp++ = *cp++;
+                                               APPEND_CHAR(bufp, bufend, *cp++);
 
                                        /*
                                         * insist that the delimiters match to get a
@@ -789,16 +806,16 @@ ParseDateTime(const char *timestr, char *lowstr,
                                        if (*cp == delim)
                                        {
                                                ftype[nf] = DTK_DATE;
-                                               *lp++ = *cp++;
+                                               APPEND_CHAR(bufp, bufend, *cp++);
                                                while (isdigit((unsigned char) *cp) || (*cp == delim))
-                                                       *lp++ = *cp++;
+                                                       APPEND_CHAR(bufp, bufend, *cp++);
                                        }
                                }
                                else
                                {
                                        ftype[nf] = DTK_DATE;
                                        while (isalnum((unsigned char) *cp) || (*cp == delim))
-                                               *lp++ = pg_tolower((unsigned char) *cp++);
+                                               APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++));
                                }
                        }
 
@@ -812,9 +829,9 @@ ParseDateTime(const char *timestr, char *lowstr,
                /* Leading decimal point? Then fractional seconds... */
                else if (*cp == '.')
                {
-                       *lp++ = *cp++;
+                       APPEND_CHAR(bufp, bufend, *cp++);
                        while (isdigit((unsigned char) *cp))
-                               *lp++ = *cp++;
+                               APPEND_CHAR(bufp, bufend, *cp++);
 
                        ftype[nf] = DTK_NUMBER;
                }
@@ -826,9 +843,9 @@ ParseDateTime(const char *timestr, char *lowstr,
                else if (isalpha((unsigned char) *cp))
                {
                        ftype[nf] = DTK_STRING;
-                       *lp++ = pg_tolower((unsigned char) *cp++);
+                       APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++));
                        while (isalpha((unsigned char) *cp))
-                               *lp++ = pg_tolower((unsigned char) *cp++);
+                               APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++));
 
                        /*
                         * Full date string with leading text month? Could also be a
@@ -839,15 +856,15 @@ ParseDateTime(const char *timestr, char *lowstr,
                                char            delim = *cp;
 
                                ftype[nf] = DTK_DATE;
-                               *lp++ = *cp++;
+                               APPEND_CHAR(bufp, bufend, *cp++);
                                while (isdigit((unsigned char) *cp) || (*cp == delim))
-                                       *lp++ = *cp++;
+                                       APPEND_CHAR(bufp, bufend, *cp++);
                        }
                }
                /* sign? then special or numeric timezone */
                else if ((*cp == '+') || (*cp == '-'))
                {
-                       *lp++ = *cp++;
+                       APPEND_CHAR(bufp, bufend, *cp++);
                        /* soak up leading whitespace */
                        while (isspace((unsigned char) *cp))
                                cp++;
@@ -855,18 +872,18 @@ ParseDateTime(const char *timestr, char *lowstr,
                        if (isdigit((unsigned char) *cp))
                        {
                                ftype[nf] = DTK_TZ;
-                               *lp++ = *cp++;
+                               APPEND_CHAR(bufp, bufend, *cp++);
                                while (isdigit((unsigned char) *cp) ||
                                           (*cp == ':') || (*cp == '.'))
-                                       *lp++ = *cp++;
+                                       APPEND_CHAR(bufp, bufend, *cp++);
                        }
                        /* special? */
                        else if (isalpha((unsigned char) *cp))
                        {
                                ftype[nf] = DTK_SPECIAL;
-                               *lp++ = pg_tolower((unsigned char) *cp++);
+                               APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++));
                                while (isalpha((unsigned char) *cp))
-                                       *lp++ = pg_tolower((unsigned char) *cp++);
+                                       APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++));
                        }
                        /* otherwise something wrong... */
                        else
@@ -883,7 +900,7 @@ ParseDateTime(const char *timestr, char *lowstr,
                        return DTERR_BAD_FORMAT;
 
                /* force in a delimiter after each field */
-               *lp++ = '\0';
+               *bufp++ = '\0';
                nf++;
        }
 
index 4e2069f5fb6cee19c5430a0856ec3701a192bed5..dd2f6fe95f6a6a595bbddef13bb2a464c291b005 100644 (file)
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/adt/nabstime.c,v 1.127 2004/12/31 22:01:22 pgsql Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/adt/nabstime.c,v 1.127.4.1 2005/05/26 02:10:02 neilc Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -306,15 +306,13 @@ abstimein(PG_FUNCTION_ARGS)
                           *tm = &date;
        int                     dterr;
        char       *field[MAXDATEFIELDS];
-       char            lowstr[MAXDATELEN + 1];
+       char            workbuf[MAXDATELEN + 1];
        int                     dtype;
        int                     nf,
                                ftype[MAXDATEFIELDS];
 
-       if (strlen(str) >= sizeof(lowstr))
-               dterr = DTERR_BAD_FORMAT;
-       else
-               dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
+       dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
+                                                 field, ftype, MAXDATEFIELDS, &nf);
        if (dterr == 0)
                dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
        if (dterr != 0)
@@ -711,12 +709,10 @@ reltimein(PG_FUNCTION_ARGS)
        char       *field[MAXDATEFIELDS];
        int                     nf,
                                ftype[MAXDATEFIELDS];
-       char            lowstr[MAXDATELEN + 1];
+       char            workbuf[MAXDATELEN + 1];
 
-       if (strlen(str) >= sizeof(lowstr))
-               dterr = DTERR_BAD_FORMAT;
-       else
-               dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
+       dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
+                                                 field, ftype, MAXDATEFIELDS, &nf);
        if (dterr == 0)
                dterr = DecodeInterval(field, ftype, nf, &dtype, tm, &fsec);
        if (dterr != 0)
index fe9cfbcf946cfc346e89df2498a366899b7cc86d..6ade3fb71fdd2688f81cfd25f71a4ecd8819d5ef 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.117.4.1 2005/04/01 14:25:39 momjian Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/adt/timestamp.c,v 1.117.4.2 2005/05/26 02:10:02 neilc Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -77,12 +77,10 @@ timestamp_in(PG_FUNCTION_ARGS)
        int                     dterr;
        char       *field[MAXDATEFIELDS];
        int                     ftype[MAXDATEFIELDS];
-       char            lowstr[MAXDATELEN + MAXDATEFIELDS];
+       char            workbuf[MAXDATELEN + MAXDATEFIELDS];
 
-       if (strlen(str) >= sizeof(lowstr))
-               dterr = DTERR_BAD_FORMAT;
-       else
-               dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
+       dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
+                                                 field, ftype, MAXDATEFIELDS, &nf);
        if (dterr == 0)
                dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
        if (dterr != 0)
@@ -318,12 +316,10 @@ timestamptz_in(PG_FUNCTION_ARGS)
        int                     dterr;
        char       *field[MAXDATEFIELDS];
        int                     ftype[MAXDATEFIELDS];
-       char            lowstr[MAXDATELEN + MAXDATEFIELDS];
+       char            workbuf[MAXDATELEN + MAXDATEFIELDS];
 
-       if (strlen(str) >= sizeof(lowstr))
-               dterr = DTERR_BAD_FORMAT;
-       else
-               dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
+       dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
+                                                 field, ftype, MAXDATEFIELDS, &nf);
        if (dterr == 0)
                dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
        if (dterr != 0)
@@ -494,7 +490,7 @@ interval_in(PG_FUNCTION_ARGS)
        int                     dterr;
        char       *field[MAXDATEFIELDS];
        int                     ftype[MAXDATEFIELDS];
-       char            lowstr[MAXDATELEN + MAXDATEFIELDS];
+       char            workbuf[256];
 
        tm->tm_year = 0;
        tm->tm_mon = 0;
@@ -504,10 +500,8 @@ interval_in(PG_FUNCTION_ARGS)
        tm->tm_sec = 0;
        fsec = 0;
 
-       if (strlen(str) >= sizeof(lowstr))
-               dterr = DTERR_BAD_FORMAT;
-       else
-               dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
+       dterr = ParseDateTime(str, workbuf, sizeof(workbuf), field,
+                                                 ftype, MAXDATEFIELDS, &nf);
        if (dterr == 0)
                dterr = DecodeInterval(field, ftype, nf, &dtype, tm, &fsec);
        if (dterr != 0)
index 0c2da14a0410753903cf59751f5d6223b19818b8..1e25e205cfc2e3f05c8b3e225bee64b9a368871d 100644 (file)
@@ -9,7 +9,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/datetime.h,v 1.52 2004/12/31 22:03:45 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/utils/datetime.h,v 1.52.4.1 2005/05/26 02:10:03 neilc Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -276,7 +276,7 @@ extern void GetCurrentTimeUsec(struct pg_tm * tm, fsec_t *fsec, int *tzp);
 extern void j2date(int jd, int *year, int *month, int *day);
 extern int     date2j(int year, int month, int day);
 
-extern int ParseDateTime(const char *timestr, char *lowstr,
+extern int ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
                          char **field, int *ftype,
                          int maxfields, int *numfields);
 extern int DecodeDateTime(char **field, int *ftype,
index 8ba178c8bf3ce2e1b6eaec2ce926d43c5d3907ed..87cbcfa428893e37b2624cccf1c02a7d6c4f8be7 100644 (file)
@@ -221,3 +221,10 @@ select avg(f1) from interval_tbl;
  @ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
 (1 row)
 
+-- test long interval input
+select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
+                  interval                  
+--------------------------------------------
+ @ 4541 years 4 mons 4 days 17 mins 31 secs
+(1 row)
+
index b6a6eb20ef30b1986beb00bf60c29f944ab0c670..2ae8f1f785745dab9cd6f71754b7c72124698ffa 100644 (file)
@@ -66,3 +66,6 @@ SELECT '' AS ten, INTERVAL_TBL.*;
 -- updating pg_aggregate.agginitval
 
 select avg(f1) from interval_tbl;
+
+-- test long interval input
+select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;