[2.7] closes bpo-32997: Fix REDOS in fpformat (GH-5984)

The regex to decode a number in fpformat is susceptible to catastrophic backtracking. This is a potential DOS vector if a server is using fpformat on untrusted number strings.

Replace it with an equivalent non-vulnerable regex. The match behavior of the new regex is slightly different. It captures the whole integer part of the number in one group, Leading zeros are stripped off later.
This commit is contained in:
Jamie Davis 2018-03-06 00:59:02 -05:00 committed by Benjamin Peterson
parent e052d40cea
commit 55d5bfba94
3 changed files with 16 additions and 1 deletions

View File

@ -19,7 +19,7 @@ import re
__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
@ -41,6 +41,7 @@ def extract(s):
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:])

View File

@ -67,6 +67,16 @@ class FpformatTest(unittest.TestCase):
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)

View File

@ -0,0 +1,4 @@
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.