__all__ = ["fix","sci","NotANumber"]
# Compiled regular expression to "decode" a number
-decoder = re.compile(r'^([-+]?)0*(\d*)((?:\.\d*)?)(([eE][-+]?\d+)?)$')
+decoder = re.compile(r'^([-+]?)(\d*)((?:\.\d*)?)(([eE][-+]?\d+)?)$')
# \0 the whole thing
# \1 leading sign or empty
# \2 digits left of decimal point
res = decoder.match(s)
if res is None: raise NotANumber, s
sign, intpart, fraction, exppart = res.group(1,2,3,4)
+ intpart = intpart.lstrip('0');
if sign == '+': sign = ''
if fraction: fraction = fraction[1:]
if exppart: expo = int(exppart[1:])
else:
self.fail("No exception on non-numeric sci")
+ def test_REDOS(self):
+ # This attack string will hang on the old decoder pattern.
+ attack = '+0' + ('0' * 1000000) + '++'
+ digs = 5 # irrelevant
+
+ # fix returns input if it does not decode
+ self.assertEqual(fpformat.fix(attack, digs), attack)
+ # sci raises NotANumber
+ with self.assertRaises(NotANumber):
+ fpformat.sci(attack, digs)
def test_main():
run_unittest(FpformatTest)
--- /dev/null
+A regex in fpformat was vulnerable to catastrophic backtracking. This regex
+was a potential DOS vector (REDOS). Based on typical uses of fpformat the
+risk seems low. The regex has been refactored and is now safe. Patch by
+Jamie Davis.