From c0440868c3a86aa2a488aa226cde34d606f0511d Mon Sep 17 00:00:00 2001 From: Nick Wellnhofer Date: Mon, 22 Jun 2020 13:08:11 +0200 Subject: [PATCH] Copy xs:duration parser from libexslt The duration parser in libexslt checks for integer overflows. --- xmlschemastypes.c | 140 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 98 insertions(+), 42 deletions(-) diff --git a/xmlschemastypes.c b/xmlschemastypes.c index 164db94b..4249d700 100644 --- a/xmlschemastypes.c +++ b/xmlschemastypes.c @@ -1160,9 +1160,12 @@ static const unsigned int daysInMonthLeap[12] = #define VALID_DATETIME(dt) \ (VALID_DATE(dt) && VALID_TIME(dt)) -#define SECS_PER_MIN (60) -#define SECS_PER_HOUR (60 * SECS_PER_MIN) -#define SECS_PER_DAY (24 * SECS_PER_HOUR) +#define SECS_PER_MIN 60 +#define MINS_PER_HOUR 60 +#define HOURS_PER_DAY 24 +#define SECS_PER_HOUR (MINS_PER_HOUR * SECS_PER_MIN) +#define SECS_PER_DAY (HOURS_PER_DAY * SECS_PER_HOUR) +#define MINS_PER_DAY (HOURS_PER_DAY * MINS_PER_HOUR) static const long dayInYearByMonth[12] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; @@ -1798,10 +1801,8 @@ xmlSchemaValidateDuration (xmlSchemaTypePtr type ATTRIBUTE_UNUSED, xmlSchemaValPtr dur; int isneg = 0; unsigned int seq = 0; - double num; - int num_type = 0; /* -1 = invalid, 0 = int, 1 = floating */ - const xmlChar desig[] = {'Y', 'M', 'D', 'H', 'M', 'S'}; - const double multi[] = { 0.0, 0.0, 86400.0, 3600.0, 60.0, 1.0, 0.0}; + long days, secs = 0; + double sec_frac = 0.0; if (duration == NULL) return -1; @@ -1826,6 +1827,10 @@ xmlSchemaValidateDuration (xmlSchemaTypePtr type ATTRIBUTE_UNUSED, return -1; while (*cur != 0) { + long num = 0; + size_t has_digits = 0; + int has_frac = 0; + const xmlChar desig[] = {'Y', 'M', 'D', 'H', 'M', 'S'}; /* input string should be empty or invalid date/time item */ if (seq >= sizeof(desig)) @@ -1833,53 +1838,104 @@ xmlSchemaValidateDuration (xmlSchemaTypePtr type ATTRIBUTE_UNUSED, /* T designator must be present for time items */ if (*cur == 'T') { - if (seq <= 3) { - seq = 3; - cur++; - } else - return 1; + if (seq > 3) + goto error; + cur++; + seq = 3; } else if (seq == 3) goto error; - /* parse the number portion of the item */ - PARSE_NUM(num, cur, num_type); + /* Parse integral part. */ + while (*cur >= '0' && *cur <= '9') { + long digit = *cur - '0'; - if ((num_type == -1) || (*cur == 0)) - goto error; + if (num > LONG_MAX / 10) + goto error; + num *= 10; + if (num > LONG_MAX - digit) + goto error; + num += digit; - /* update duration based on item type */ - while (seq < sizeof(desig)) { - if (*cur == desig[seq]) { + has_digits = 1; + cur++; + } - /* verify numeric type; only seconds can be float */ - if ((num_type != 0) && (seq < (sizeof(desig)-1))) - goto error; - - switch (seq) { - case 0: - dur->value.dur.mon = (long)num * 12; - break; - case 1: - dur->value.dur.mon += (long)num; - break; - default: - /* convert to seconds using multiplier */ - dur->value.dur.sec += num * multi[seq]; - seq++; - break; - } - - break; /* exit loop */ + if (*cur == '.') { + /* Parse fractional part. */ + double mult = 1.0; + cur++; + has_frac = 1; + while (*cur >= '0' && *cur <= '9') { + mult /= 10.0; + sec_frac += (*cur - '0') * mult; + has_digits = 1; + cur++; } - /* no date designators found? */ - if ((++seq == 3) || (seq == 6)) + } + + while (*cur != desig[seq]) { + seq++; + /* No T designator or invalid char. */ + if (seq == 3 || seq == sizeof(desig)) goto error; } cur++; - if (collapse) - while IS_WSP_BLANK_CH(*cur) cur++; + + if (!has_digits || (has_frac && (seq != 5))) + goto error; + + switch (seq) { + case 0: + /* Year */ + if (num > LONG_MAX / 12) + goto error; + dur->value.dur.mon = num * 12; + break; + case 1: + /* Month */ + if (dur->value.dur.mon > LONG_MAX - num) + goto error; + dur->value.dur.mon += num; + break; + case 2: + /* Day */ + dur->value.dur.day = num; + break; + case 3: + /* Hour */ + days = num / HOURS_PER_DAY; + if (dur->value.dur.day > LONG_MAX - days) + goto error; + dur->value.dur.day += days; + secs = (num % HOURS_PER_DAY) * SECS_PER_HOUR; + break; + case 4: + /* Minute */ + days = num / MINS_PER_DAY; + if (dur->value.dur.day > LONG_MAX - days) + goto error; + dur->value.dur.day += days; + secs += (num % MINS_PER_DAY) * SECS_PER_MIN; + break; + case 5: + /* Second */ + days = num / SECS_PER_DAY; + if (dur->value.dur.day > LONG_MAX - days) + goto error; + dur->value.dur.day += days; + secs += num % SECS_PER_DAY; + break; + } + + seq++; } + days = secs / SECS_PER_DAY; + if (dur->value.dur.day > LONG_MAX - days) + goto error; + dur->value.dur.day += days; + dur->value.dur.sec = (secs % SECS_PER_DAY) + sec_frac; + if (isneg) { dur->value.dur.mon = -dur->value.dur.mon; dur->value.dur.day = -dur->value.dur.day;