From 55d5bfba9482d39080f7b9ec3e6257ecd23f264f Mon Sep 17 00:00:00 2001 From: Jamie Davis Date: Tue, 6 Mar 2018 00:59:02 -0500 Subject: [PATCH] [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. --- Lib/fpformat.py | 3 ++- Lib/test/test_fpformat.py | 10 ++++++++++ .../Security/2018-03-05-10-14-42.bpo-32997.hp2s8n.rst | 4 ++++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Security/2018-03-05-10-14-42.bpo-32997.hp2s8n.rst diff --git a/Lib/fpformat.py b/Lib/fpformat.py index 71cbb25f3c8..0537a27b882 100644 --- a/Lib/fpformat.py +++ b/Lib/fpformat.py @@ -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:]) diff --git a/Lib/test/test_fpformat.py b/Lib/test/test_fpformat.py index e6de3b0c11b..428623ebb35 100644 --- a/Lib/test/test_fpformat.py +++ b/Lib/test/test_fpformat.py @@ -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) diff --git a/Misc/NEWS.d/next/Security/2018-03-05-10-14-42.bpo-32997.hp2s8n.rst b/Misc/NEWS.d/next/Security/2018-03-05-10-14-42.bpo-32997.hp2s8n.rst new file mode 100644 index 00000000000..3c78ba61ae3 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2018-03-05-10-14-42.bpo-32997.hp2s8n.rst @@ -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.