[libxslt] Check for overflow in exsltDateParseDuration



commit 36768481860e4ff46f87d873b1b75007635e4a0b
Author: Nick Wellnhofer <wellnhofer aevum de>
Date:   Thu May 18 15:54:54 2017 +0200

    Check for overflow in exsltDateParseDuration
    
    Also fix parsing of duSecondFrag, see
    
        https://www.w3.org/TR/xmlschema11-2/#nt-duSeFrag
    
    Fix memory leak in error case.

 libexslt/date.c                |  190 ++++++++++++++++++++--------------------
 tests/exslt/date/seconds.1.out |    4 +
 tests/exslt/date/seconds.1.xml |    2 +
 tests/exslt/date/seconds.2.out |    2 -
 tests/exslt/date/seconds.2.xml |    1 -
 5 files changed, 102 insertions(+), 97 deletions(-)
---
diff --git a/libexslt/date.c b/libexslt/date.c
index 31a6e35..d463876 100644
--- a/libexslt/date.c
+++ b/libexslt/date.c
@@ -177,9 +177,12 @@ static const unsigned long 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)
 #define DAYS_PER_EPOCH          (400 * 365 + 100 - 4 + 1)
 #define YEARS_PER_EPOCH         400
 
@@ -702,56 +705,6 @@ exsltDateFreeDate (exsltDateValPtr date) {
     xmlFree(date);
 }
 
-/**
- * PARSE_DIGITS:
- * @num:  the integer to fill in
- * @cur:  an #xmlChar *
- * @num_type: an integer flag
- *
- * Parses a digits integer and updates @num with the value. @cur is
- * updated to point just after the integer.
- * In case of error, @num_type is set to -1, values of @num and
- * @cur are undefined.
- */
-#define PARSE_DIGITS(num, cur, num_type)                       \
-       if ((*cur < '0') || (*cur > '9'))                       \
-           num_type = -1;                                      \
-        else                                                    \
-           while ((*cur >= '0') && (*cur <= '9')) {            \
-               num = num * 10 + (*cur - '0');                  \
-               cur++;                                          \
-            }
-
-/**
- * PARSE_NUM:
- * @num:  the double to fill in
- * @cur:  an #xmlChar *
- * @num_type: an integer flag
- *
- * Parses a float or integer and updates @num with the value. @cur is
- * updated to point just after the number. If the number is a float,
- * then it must have an integer part and a decimal part; @num_type will
- * be set to 1. If there is no decimal part, @num_type is set to zero.
- * In case of error, @num_type is set to -1, values of @num and
- * @cur are undefined.
- */
-#define PARSE_NUM(num, cur, num_type)                          \
-        num = 0;                                                \
-       PARSE_DIGITS(num, cur, num_type);                       \
-       if (!num_type && (*cur == '.')) {                       \
-           double mult = 1;                                    \
-           cur++;                                              \
-           if ((*cur < '0') || (*cur > '9'))                   \
-               num_type = -1;                                  \
-            else                                                \
-                num_type = 1;                                   \
-           while ((*cur >= '0') && (*cur <= '9')) {            \
-               mult /= 10;                                     \
-               num += (*cur - '0') * mult;                     \
-               cur++;                                          \
-           }                                                   \
-       }
-
 #ifdef WITH_TIME
 /**
  * exsltDateCurrent:
@@ -1032,6 +985,8 @@ exsltDateParseDuration (const xmlChar *duration)
     exsltDateValPtr dur;
     int isneg = 0;
     unsigned int seq = 0;
+    long days, secs = 0;
+    double sec_frac = 0.0;
 
     if (duration == NULL)
        return NULL;
@@ -1050,10 +1005,10 @@ exsltDateParseDuration (const xmlChar *duration)
        return NULL;
 
     while (*cur != 0) {
-        double         num;
-        int            num_type = 0;  /* -1 = invalid, 0 = int, 1 = floating */
+        long           num = 0;
+        size_t         has_digits = 0;
+        int            has_frac = 0;
         const xmlChar  desig[] = {'Y', 'M', 'D', 'H', 'M', 'S'};
-        const double   multi[] = { 0.0, 0.0, 0.0, 3600.0, 60.0, 1.0, 0.0};
 
         /* input string should be empty or invalid date/time item */
         if (seq >= sizeof(desig))
@@ -1061,56 +1016,103 @@ exsltDateParseDuration (const xmlChar *duration)
 
         /* T designator must be present for time items */
         if (*cur == 'T') {
-            if (seq <= 3) {
-                seq = 3;
-                cur++;
-            } else
-                return NULL;
+            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);
-
-        if ((num_type == -1) || (*cur == 0))
-            goto error;
-
-        /* update duration based on item type */
-        while (seq < sizeof(desig)) {
-            if (*cur == desig[seq]) {
+        /* Parse integral part. */
+        while (*cur >= '0' && *cur <= '9') {
+            long digit = *cur - '0';
 
-                /* verify numeric type; only seconds can be float */
-                if ((num_type != 0) && (seq < (sizeof(desig)-1)))
-                    goto error;
+            if (num > LONG_MAX / 10)
+                goto error;
+            num *= 10;
+            if (num > LONG_MAX - digit)
+                goto error;
+            num += digit;
 
-                switch (seq) {
-                    case 0:
-                        dur->value.dur.mon = (long)num * 12;
-                        break;
-                    case 1:
-                        dur->value.dur.mon += (long)num;
-                        break;
-                    case 2:
-                        dur->value.dur.day = (long)num;
-                    default:
-                        /* convert to seconds using multiplier */
-                        dur->value.dur.sec += num * multi[seq];
-                        seq++;
-                        break;
-                }
+            has_digits = 1;
+            cur++;
+        }
 
-                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)
+        }
+
+        while (*cur != desig[seq]) {
+            seq++;
+            /* No T designator or invalid char. */
+            if (seq == 3 || seq == sizeof(desig))
                 goto error;
         }
         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++;
     }
 
-    /* Clamp seconds to 0..SECS_PER_DAY range. */
-    dur->value.dur.day += (long)(dur->value.dur.sec / SECS_PER_DAY);
-    dur->value.dur.sec = fmod(dur->value.dur.sec, SECS_PER_DAY);
+    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;
diff --git a/tests/exslt/date/seconds.1.out b/tests/exslt/date/seconds.1.out
index 5d5e5da..9044a31 100644
--- a/tests/exslt/date/seconds.1.out
+++ b/tests/exslt/date/seconds.1.out
@@ -17,6 +17,10 @@ seconds : -P0Y0M31DT10H10M10.09S
 result  : -2715010.09
 seconds : PT100H100M100.001S
 result  : 366100.001
+seconds : PT10H10M.5S
+result  : 36600.5
+seconds : PT10H10M5.S
+result  : 36605
 seconds : 2001
 result  : 978307200
 seconds : 2001-10-29T10:31:07
diff --git a/tests/exslt/date/seconds.1.xml b/tests/exslt/date/seconds.1.xml
index 24d58fb..6e33f5c 100644
--- a/tests/exslt/date/seconds.1.xml
+++ b/tests/exslt/date/seconds.1.xml
@@ -10,6 +10,8 @@
   <date duration="P0Y0M31DT10H10M10.09S"/>
   <date duration="-P0Y0M31DT10H10M10.09S"/>
   <date duration="PT100H100M100.001S"/>
+  <date duration="PT10H10M.5S"/>
+  <date duration="PT10H10M5.S"/>
   <!-- date/times -->
   <date duration="2001"/>
   <date duration="2001-10-29T10:31:07"/>
diff --git a/tests/exslt/date/seconds.2.out b/tests/exslt/date/seconds.2.out
index e804d94..944b714 100644
--- a/tests/exslt/date/seconds.2.out
+++ b/tests/exslt/date/seconds.2.out
@@ -7,8 +7,6 @@ seconds : PT-10D
 result  : NaN
 seconds : --PT10H
 result  : NaN
-seconds : PT10H10M.5S
-result  : NaN
 seconds : PYT0.00001S
 result  : NaN
 seconds : PT49.00001
diff --git a/tests/exslt/date/seconds.2.xml b/tests/exslt/date/seconds.2.xml
index 276baef..9052b8f 100644
--- a/tests/exslt/date/seconds.2.xml
+++ b/tests/exslt/date/seconds.2.xml
@@ -5,7 +5,6 @@
   <date duration="P-T10D"/>
   <date duration="PT-10D"/>
   <date duration="--PT10H"/>
-  <date duration="PT10H10M.5S"/>
   <date duration="PYT0.00001S"/>
   <date duration="PT49.00001"/>
   <date duration="P0Y0MDT10H10M10.09S"/>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]