Running the libiberty testsuite
./test-demangle < libiberty/testsuite/d-demangle-expected
libiberty/d-demangle.c:214:14: runtime error: signed integer overflow:
922337203 * 10 cannot be represented in type 'long int'
On looking at silencing ubsan, I found a real bug in dlang_number.
For a 32-bit long, some overflows won't be detected. For example,
21474836480. Why? Well 214748364 * 10 is 0x7FFFFFF8 (no overflow so
far). Adding 8 gives 0x80000000 (which does overflow but there is no
test for that overflow in the code). Then multiplying 0x80000000 * 10
= 0x500000000 = 0 won't be caught by the multiplication overflow test.
The same holds for a 64-bit long using similarly crafted digit
sequences.
This patch replaces the mod 10 test with a simpler limit test, and
similarly the mod 26 test in dlang_decode_backref.
About the limit test:
val * 10 + digit > ULONG_MAX is the condition for overflow
ie.
val * 10 > ULONG_MAX - digit
or
val > (ULONG_MAX - digit) / 10
or assuming the largest digit
val > (ULONG_MAX - 9) / 10
I resisted the aesthetic appeal of simplifying this further to
val > -10UL / 10
since -1UL for ULONG_MAX is only correct for 2's complement numbers.
Passes all the libiberty tests, on both 32-bit and 64-bit hosts. OK
to apply?
* d-demangle.c: Include limits.h.
(ULONG_MAX): Provide fall-back definition.
(dlang_number): Simplify and correct overflow test. Only
write *ret on returning non-NULL.
(dlang_decode_backref): Likewise.
diff --git a/libiberty/d-demangle.c b/libiberty/d-demangle.c
index f2d6946eca..59e6ae007a 100644
--- a/libiberty/d-demangle.c
+++ b/libiberty/d-demangle.c
@@ -31,6 +31,9 @@ If not, see <http://www.gnu.org/licenses/>. */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
#include "safe-ctype.h"
@@ -45,6 +48,10 @@ If not, see <http://www.gnu.org/licenses/>. */
#include <demangle.h>
#include "libiberty.h"
+#ifndef ULONG_MAX
+#define ULONG_MAX (~0UL)
+#endif
+
/* A mini string-handling package */
typedef struct string /* Beware: these aren't required to be */
@@ -207,24 +214,24 @@ dlang_number (const char *mangled, long *ret)
if (mangled == NULL || !ISDIGIT (*mangled))
return NULL;
- (*ret) = 0;
+ unsigned long val = 0;
while (ISDIGIT (*mangled))
{
- (*ret) *= 10;
-
- /* If an overflow occured when multiplying by ten, the result
- will not be a multiple of ten. */
- if ((*ret % 10) != 0)
+ /* Check for overflow. Yes, we return NULL here for some digits
+ that don't overflow "val * 10 + digit", but that doesn't
+ matter given the later "(long) val < 0" test. */
+ if (val > (ULONG_MAX - 9) / 10)
return NULL;
- (*ret) += mangled[0] - '0';
+ val = val * 10 + mangled[0] - '0';
mangled++;
}
- if (*mangled == '\0' || *ret < 0)
+ if (*mangled == '\0' || (long) val < 0)
return NULL;
+ *ret = val;
return mangled;
}
@@ -294,24 +301,24 @@ dlang_decode_backref (const char *mangled, long *ret)
[A-Z] NumberBackRef
^
*/
- (*ret) = 0;
+ unsigned long val = 0;
while (ISALPHA (*mangled))
{
- (*ret) *= 26;
+ /* Check for overflow. */
+ if (val > (ULONG_MAX - 25) / 26)
+ break;
- /* If an overflow occured when multiplying by 26, the result
- will not be a multiple of 26. */
- if ((*ret % 26) != 0)
- return NULL;
+ val *= 26;
if (mangled[0] >= 'a' && mangled[0] <= 'z')
{
- (*ret) += mangled[0] - 'a';
+ val += mangled[0] - 'a';
+ *ret = val;
return mangled + 1;
}
- (*ret) += mangled[0] - 'A';
+ val += mangled[0] - 'A';
mangled++;
}
--
Alan Modra
Australia Development Lab, IBM