1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-30 11:03:19 +03:00

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.
This commit is contained in:
Neil Conway
2005-05-26 02:04:14 +00:00
parent 15e4d1e2a7
commit 63e0d612f5
7 changed files with 86 additions and 75 deletions

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.144 2005/05/24 02:09:45 momjian Exp $
* $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.145 2005/05/26 02:04:13 neilc Exp $
*
*-------------------------------------------------------------------------
*/
@ -699,21 +699,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
@ -729,12 +731,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')
@ -749,23 +766,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 == '.')
@ -773,13 +790,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
@ -788,16 +805,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++));
}
}
@ -811,9 +828,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;
}
@ -825,9 +842,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
@ -838,15 +855,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++;
@ -854,18 +871,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
@ -882,7 +899,7 @@ ParseDateTime(const char *timestr, char *lowstr,
return DTERR_BAD_FORMAT;
/* force in a delimiter after each field */
*lp++ = '\0';
*bufp++ = '\0';
nf++;
}