Skip to content

Commit 140d70a

Browse files
davisjamtim-onetiran
committed
Prevent difflib REDOS (CVE-2018-1061)
The default regex for IS_LINE_JUNK is susceptible to catastrophic backtracking. This is a potential DOS vector. Replace it with an equivalent non-vulnerable regex. Also introduce unit and REDOS tests for difflib. Co-authored-by: Tim Peters <tim.peters@gmail.com> Co-authored-by: Christian Heimes <christian@python.org>
1 parent 4f72e60 commit 140d70a

File tree

2 files changed

+22
-2
lines changed

2 files changed

+22
-2
lines changed

Lib/difflib.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1083,7 +1083,7 @@ def _qformat(self, aline, bline, atags, btags):
10831083

10841084
import re
10851085

1086-
def IS_LINE_JUNK(line, pat=re.compile(r"\s*#?\s*$").match):
1086+
def IS_LINE_JUNK(line, pat=re.compile(r"\s*(?:#\s*)?$").match):
10871087
r"""
10881088
Return 1 for ignorable line: iff `line` is blank or contains a single '#'.
10891089

Lib/test/test_difflib.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,13 +466,33 @@ def _assert_type_error(self, msg, generator, *args):
466466
list(generator(*args))
467467
self.assertEqual(msg, str(ctx.exception))
468468

469+
class TestJunkAPIs(unittest.TestCase):
470+
def test_is_line_junk_true(self):
471+
for line in ['#', ' ', ' #', '# ', ' # ', '']:
472+
self.assertTrue(difflib.IS_LINE_JUNK(line), 'should be junk: {}'.format(line))
473+
474+
def test_is_line_junk_false(self):
475+
for line in ['##', ' ##', '## ', 'abc ', 'abc #', 'abc # ', 'Mr. Moose is up!']:
476+
self.assertFalse(difflib.IS_LINE_JUNK(line), 'should not be junk: {}'.format(line))
477+
478+
def test_is_line_junk_REDOS(self):
479+
evil_input = ('\t' * 1000000) + '##'
480+
self.assertFalse(difflib.IS_LINE_JUNK(evil_input))
481+
482+
def test_is_character_junk_true(self):
483+
for char in [' ', '\t']:
484+
self.assertTrue(difflib.IS_CHARACTER_JUNK(char), 'should be junk: {}'.format(char))
485+
486+
def test_is_character_junk_false(self):
487+
for char in ['a', '#', '\n', '\f', '\r', '\v']:
488+
self.assertFalse(difflib.IS_CHARACTER_JUNK(char), 'should not be junk: {}'.format(char))
469489

470490
def test_main():
471491
difflib.HtmlDiff._default_prefix = 0
472492
Doctests = doctest.DocTestSuite(difflib)
473493
run_unittest(
474494
TestWithAscii, TestAutojunk, TestSFpatches, TestSFbugs,
475-
TestOutputFormat, TestBytes, Doctests)
495+
TestOutputFormat, TestBytes, TestJunkAPIs, Doctests)
476496

477497
if __name__ == '__main__':
478498
test_main()

0 commit comments

Comments
 (0)