]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Refactor numeric parsing routines into a single version without UB.
authorJoerg Sonnenberger <joerg@bec.de>
Sat, 29 Apr 2017 21:09:42 +0000 (23:09 +0200)
committerJoerg Sonnenberger <joerg@bec.de>
Sat, 29 Apr 2017 21:09:42 +0000 (23:09 +0200)
Reported-By: OSS-Fuzz issue 862

libarchive/archive_read_support_format_mtree.c

index 6edc4ec9a386b0f424f4347398325b7e88c6ad42..44b6083cb2f26c2dc98119a0b300c7d84d620cea 100644 (file)
@@ -130,9 +130,7 @@ static ssize_t      readline(struct archive_read *, struct mtree *, char **, ssize_t)
 static int     skip(struct archive_read *a);
 static int     read_header(struct archive_read *,
                    struct archive_entry *);
-static int64_t mtree_atol10(char **);
-static int64_t mtree_atol8(char **);
-static int64_t mtree_atol(char **);
+static int64_t mtree_atol(char **, int base);
 
 /*
  * There's no standard for TIME_T_MAX/TIME_T_MIN.  So we compute them
@@ -1418,7 +1416,7 @@ parse_device(dev_t *pdev, struct archive *a, char *val)
                                    "Too many arguments");
                                return ARCHIVE_WARN;
                        }
-                       numbers[argc++] = (unsigned long)mtree_atol(&p);
+                       numbers[argc++] = (unsigned long)mtree_atol(&p, 0);
                }
                if (argc < 2) {
                        archive_set_error(a, ARCHIVE_ERRNO_FILE_FORMAT,
@@ -1433,7 +1431,7 @@ parse_device(dev_t *pdev, struct archive *a, char *val)
                }
        } else {
                /* file system raw value. */
-               result = (dev_t)mtree_atol(&val);
+               result = (dev_t)mtree_atol(&val, 0);
        }
        *pdev = result;
        return ARCHIVE_OK;
@@ -1513,7 +1511,7 @@ parse_keyword(struct archive_read *a, struct mtree *mtree,
        case 'g':
                if (strcmp(key, "gid") == 0) {
                        *parsed_kws |= MTREE_HAS_GID;
-                       archive_entry_set_gid(entry, mtree_atol10(&val));
+                       archive_entry_set_gid(entry, mtree_atol(&val, 10));
                        break;
                }
                if (strcmp(key, "gname") == 0) {
@@ -1523,7 +1521,7 @@ parse_keyword(struct archive_read *a, struct mtree *mtree,
                }
        case 'i':
                if (strcmp(key, "inode") == 0) {
-                       archive_entry_set_ino(entry, mtree_atol10(&val));
+                       archive_entry_set_ino(entry, mtree_atol(&val, 10));
                        break;
                }
        case 'l':
@@ -1538,7 +1536,7 @@ parse_keyword(struct archive_read *a, struct mtree *mtree,
                        if (val[0] >= '0' && val[0] <= '7') {
                                *parsed_kws |= MTREE_HAS_PERM;
                                archive_entry_set_perm(entry,
-                                   (mode_t)mtree_atol8(&val));
+                                   (mode_t)mtree_atol(&val, 8));
                        } else {
                                archive_set_error(&a->archive,
                                    ARCHIVE_ERRNO_FILE_FORMAT,
@@ -1551,7 +1549,7 @@ parse_keyword(struct archive_read *a, struct mtree *mtree,
                if (strcmp(key, "nlink") == 0) {
                        *parsed_kws |= MTREE_HAS_NLINK;
                        archive_entry_set_nlink(entry,
-                               (unsigned int)mtree_atol10(&val));
+                               (unsigned int)mtree_atol(&val, 10));
                        break;
                }
        case 'r':
@@ -1582,7 +1580,7 @@ parse_keyword(struct archive_read *a, struct mtree *mtree,
                    strcmp(key, "sha512digest") == 0)
                        break;
                if (strcmp(key, "size") == 0) {
-                       archive_entry_set_size(entry, mtree_atol10(&val));
+                       archive_entry_set_size(entry, mtree_atol(&val, 10));
                        break;
                }
        case 't':
@@ -1601,13 +1599,13 @@ parse_keyword(struct archive_read *a, struct mtree *mtree,
                        long ns = 0;
 
                        *parsed_kws |= MTREE_HAS_MTIME;
-                       m = mtree_atol10(&val);
+                       m = mtree_atol(&val, 10);
                        /* Replicate an old mtree bug:
                         * 123456789.1 represents 123456789
                         * seconds and 1 nanosecond. */
                        if (*val == '.') {
                                ++val;
-                               ns = (long)mtree_atol10(&val);
+                               ns = (long)mtree_atol(&val, 10);
                                if (ns < 0)
                                        ns = 0;
                                else if (ns > 999999999)
@@ -1670,7 +1668,7 @@ parse_keyword(struct archive_read *a, struct mtree *mtree,
        case 'u':
                if (strcmp(key, "uid") == 0) {
                        *parsed_kws |= MTREE_HAS_UID;
-                       archive_entry_set_uid(entry, mtree_atol10(&val));
+                       archive_entry_set_uid(entry, mtree_atol(&val, 10));
                        break;
                }
                if (strcmp(key, "uname") == 0) {
@@ -1825,77 +1823,9 @@ parse_escapes(char *src, struct mtree_entry *mentry)
        *dest = '\0';
 }
 
-/*
- * Note that this implementation does not (and should not!) obey
- * locale settings; you cannot simply substitute strtol here, since
- * it does obey locale.
- */
-static int64_t
-mtree_atol8(char **p)
-{
-       int64_t l, limit, last_digit_limit;
-       int digit, base;
-
-       base = 8;
-       limit = INT64_MAX / base;
-       last_digit_limit = INT64_MAX % base;
-
-       l = 0;
-       digit = **p - '0';
-       while (digit >= 0 && digit < base) {
-               if (l>limit || (l == limit && digit > last_digit_limit)) {
-                       l = INT64_MAX; /* Truncate on overflow. */
-                       break;
-               }
-               l = (l * base) + digit;
-               digit = *++(*p) - '0';
-       }
-       return (l);
-}
-
-/*
- * Note that this implementation does not (and should not!) obey
- * locale settings; you cannot simply substitute strtol here, since
- * it does obey locale.
- *
- * Convert the number pointed to by 'p' into a 64-bit signed integer.
- * On return, 'p' points to the first non-digit following the number.
- * On overflow, the function returns INT64_MIN or INT64_MAX.
- */
-static int64_t
-mtree_atol10(char **p)
-{
-       const int base = 10;
-       const int64_t limit = INT64_MAX / base;
-       const int64_t last_digit_limit = INT64_MAX % base;
-       int64_t l;
-       int sign;
-
-       if (**p == '-') {
-               sign = -1;
-               ++(*p);
-       } else {
-               sign = 1;
-       }
-
-       l = 0;
-       while (**p >= '0' && **p < '0' + base) {
-               int digit = **p - '0';
-               if (l > limit || (l == limit && digit > last_digit_limit)) {
-                       while (**p >= '0' && **p < '0' + base) {
-                               ++(*p);
-                       }
-                       return (sign < 0) ? INT64_MIN : INT64_MAX;
-               }
-               l = (l * base) + digit;
-               ++(*p);
-       }
-       return (sign < 0) ? -l : l;
-}
-
 /* Parse a hex digit. */
 static int
-parsehex(char c)
+parsedigit(char c)
 {
        if (c >= '0' && c <= '9')
                return c - '0';
@@ -1913,45 +1843,50 @@ parsehex(char c)
  * it does obey locale.
  */
 static int64_t
-mtree_atol16(char **p)
+mtree_atol(char **p, int base)
 {
-       int64_t l, limit, last_digit_limit;
-       int base, digit, sign;
-
-       base = 16;
+       int64_t l, limit;
+       int digit, last_digit_limit;
+
+       if (base == 0) {
+               if (**p != '0')
+                       base = 10;
+               else if ((*p)[1] == 'x' || (*p)[1] == 'X') {
+                       *p += 2;
+                       base = 16;
+               } else {
+                       base = 8;
+               }
+       }
 
        if (**p == '-') {
-               sign = -1;
-               limit = ((uint64_t)(INT64_MAX) + 1) / base;
-               last_digit_limit = ((uint64_t)(INT64_MAX) + 1) % base;
+               limit = INT64_MIN / base;
+               last_digit_limit = INT64_MIN % base;
                ++(*p);
+
+               l = 0;
+               digit = parsedigit(**p);
+               while (digit >= 0 && digit < base) {
+                       if (l < limit || (l == limit && digit > last_digit_limit))
+                               return INT64_MIN;
+                       l = (l * base) - digit;
+                       digit = parsedigit(*++(*p));
+               }
+               return l;
        } else {
-               sign = 1;
                limit = INT64_MAX / base;
                last_digit_limit = INT64_MAX % base;
-       }
 
-       l = 0;
-       digit = parsehex(**p);
-       while (digit >= 0 && digit < base) {
-               if (l > limit || (l == limit && digit > last_digit_limit))
-                       return (sign < 0) ? INT64_MIN : INT64_MAX;
-               l = (l * base) + digit;
-               digit = parsehex(*++(*p));
-       }
-       return (sign < 0) ? -l : l;
-}
-
-static int64_t
-mtree_atol(char **p)
-{
-       if (**p != '0')
-               return mtree_atol10(p);
-       if ((*p)[1] == 'x' || (*p)[1] == 'X') {
-               *p += 2;
-               return mtree_atol16(p);
+               l = 0;
+               digit = parsedigit(**p);
+               while (digit >= 0 && digit < base) {
+                       if (l > limit || (l == limit && digit > last_digit_limit))
+                               return INT64_MAX;
+                       l = (l * base) + digit;
+                       digit = parsedigit(*++(*p));
+               }
+               return l;
        }
-       return mtree_atol8(p);
 }
 
 /*