Check for overflow in strtol() while parsing datetime inputs.
authorTom Lane <[email protected]>
Thu, 1 Dec 2005 17:56:54 +0000 (17:56 +0000)
committerTom Lane <[email protected]>
Thu, 1 Dec 2005 17:56:54 +0000 (17:56 +0000)
Michael Fuhr.

src/backend/utils/adt/datetime.c

index 55a948655cd61d4d7062612c160f1fa6e10ee8a8..3932231ddaeaae27391cacd90044bfba30dd77aa 100644 (file)
@@ -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++;
        }
 
@@ -964,7 +981,10 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                        if (tzp == NULL)
                                                return DTERR_BAD_FORMAT;
 
+                                       errno = 0;
                                        val = strtol(field[i], &cp, 10);
+                                       if (errno == ERANGE)
+                                               return DTERR_FIELD_OVERFLOW;
 
                                        j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
                                        /* Get the time zone from the end of the string */
@@ -1109,7 +1129,10 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                        char       *cp;
                                        int                     val;
 
+                                       errno = 0;
                                        val = strtol(field[i], &cp, 10);
+                                       if (errno == ERANGE)
+                                               return DTERR_FIELD_OVERFLOW;
 
                                        /*
                                         * only a few kinds are allowed to have an embedded
@@ -1863,7 +1886,10 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
                                                        break;
                                        }
 
+                                       errno = 0;
                                        val = strtol(field[i], &cp, 10);
+                                       if (errno == ERANGE)
+                                               return DTERR_FIELD_OVERFLOW;
 
                                        /*
                                         * only a few kinds are allowed to have an embedded
@@ -2405,11 +2431,17 @@ DecodeTime(char *str, int fmask, int *tmask, struct pg_tm * tm, fsec_t *fsec)
 
        *tmask = DTK_TIME_M;
 
+       errno = 0;
        tm->tm_hour = strtol(str, &cp, 10);
+       if (errno == ERANGE)
+               return DTERR_FIELD_OVERFLOW;
        if (*cp != ':')
                return DTERR_BAD_FORMAT;
        str = cp + 1;
+       errno = 0;
        tm->tm_min = strtol(str, &cp, 10);
+       if (errno == ERANGE)
+               return DTERR_FIELD_OVERFLOW;
        if (*cp == '\0')
        {
                tm->tm_sec = 0;
@@ -2420,7 +2452,10 @@ DecodeTime(char *str, int fmask, int *tmask, struct pg_tm * tm, fsec_t *fsec)
        else
        {
                str = cp + 1;
+               errno = 0;
                tm->tm_sec = strtol(str, &cp, 10);
+               if (errno == ERANGE)
+                       return DTERR_FIELD_OVERFLOW;
                if (*cp == '\0')
                        *fsec = 0;
                else if (*cp == '.')
@@ -2474,7 +2509,10 @@ DecodeNumber(int flen, char *str, bool haveTextMonth, int fmask,
 
        *tmask = 0;
 
+       errno = 0;
        val = strtol(str, &cp, 10);
+       if (errno == ERANGE)
+               return DTERR_FIELD_OVERFLOW;
        if (cp == str)
                return DTERR_BAD_FORMAT;
 
@@ -2763,11 +2801,19 @@ DecodeTimezone(char *str, int *tzp)
        if (*str != '+' && *str != '-')
                return DTERR_BAD_FORMAT;
 
+       errno = 0;
        hr = strtol((str + 1), &cp, 10);
+       if (errno == ERANGE)
+               return DTERR_TZDISP_OVERFLOW;
 
        /* explicit delimiter? */
        if (*cp == ':')
+       {
+               errno = 0;
                min = strtol((cp + 1), &cp, 10);
+               if (errno == ERANGE)
+                       return DTERR_TZDISP_OVERFLOW;
+       }
        /* otherwise, might have run things together... */
        else if ((*cp == '\0') && (strlen(str) > 3))
        {
@@ -3010,7 +3056,10 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct pg_tm * tm,
 
                        case DTK_DATE:
                        case DTK_NUMBER:
+                               errno = 0;
                                val = strtol(field[i], &cp, 10);
+                               if (errno == ERANGE)
+                                       return DTERR_FIELD_OVERFLOW;
 
                                if (type == IGNORE_DTF)
                                        type = DTK_SECOND;
@@ -3883,17 +3932,25 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
                        /* fractional seconds? */
                        if (fsec != 0)
                        {
+                               fsec_t          sec;
+
 #ifdef HAVE_INT64_TIMESTAMP
+                               sec = fsec;
                                if (is_before || ((!is_nonzero) && (tm->tm_sec < 0)))
+                               {
                                        tm->tm_sec = -tm->tm_sec;
+                                       sec = -sec;
+                                       is_before = TRUE;
+                               }
+                               else if ((!is_nonzero) && (tm->tm_sec == 0) && (fsec < 0))
+                               {
+                                       sec = -sec;
+                                       is_before = TRUE;
+                               }
                                sprintf(cp, "%s%d.%02d secs", (is_nonzero ? " " : ""),
-                                               tm->tm_sec, (((int) fsec) / 10000));
+                                               tm->tm_sec, (((int) sec) / 10000));
                                cp += strlen(cp);
-                               if (!is_nonzero)
-                                       is_before = (fsec < 0);
 #else
-                               fsec_t          sec;
-
                                fsec += tm->tm_sec;
                                sec = fsec;
                                if (is_before || ((!is_nonzero) && (fsec < 0)))
@@ -3905,9 +3962,8 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
                                        is_before = (fsec < 0);
 #endif
                                is_nonzero = TRUE;
-
-                               /* otherwise, integer seconds only? */
                        }
+                       /* otherwise, integer seconds only? */
                        else if (tm->tm_sec != 0)
                        {
                                int                     sec = tm->tm_sec;