Skip to content

Commit 4840300

Browse files
Merge pull request #634 from Crozzers/fix-redos-issue633
Fix ReDoS in HTML tokenizer regex (#633)
2 parents adf4e81 + 101f1eb commit 4840300

File tree

5 files changed

+99
-1
lines changed

5 files changed

+99
-1
lines changed

.github/workflows/python.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,6 @@ jobs:
2727
- name: Test
2828
run: |
2929
make testone
30+
- name: Test ReDoS
31+
run: |
32+
make testredos

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- [pull #626] Fix XSS when encoding incomplete tags (#625)
99
- [pull #628] Fix TypeError in MiddleWordEm extra when options was None (#627)
1010
- [pull #630] Fix nbsp breaking tables (#629)
11+
- [pull #634] Fix ReDoS in HTML tokenizer regex (#633)
1112

1213

1314
## python-markdown2 2.5.3

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ test:
1212
testone:
1313
cd test && python test.py -- -knownfailure
1414

15+
.PHONY: testredos
16+
testredos:
17+
python test/test_redos.py
18+
1519
.PHONY: pygments
1620
pygments:
1721
[[ -d deps/pygments ]] || ( \

lib/markdown2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1273,7 +1273,7 @@ def _run_span_gamut(self, text: str) -> str:
12731273
\s+ # whitespace after tag
12741274
(?:[^\t<>"'=/]+:)?
12751275
[^<>"'=/]+= # attr name
1276-
(?:".*?"|'.*?'|[^<>"'=/\s]+) # value, quoted or unquoted. If unquoted, no spaces allowed
1276+
(?:"[^"]*?"|'[^']*?'|[^<>"'=/\s]+) # value, quoted or unquoted. If unquoted, no spaces allowed
12771277
)*
12781278
\s*/?>
12791279
|

test/test_redos.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import logging
2+
import subprocess
3+
import sys
4+
import time
5+
from pathlib import Path
6+
7+
log = logging.getLogger("test")
8+
LIB_DIR = Path(__file__).parent.parent / "lib"
9+
10+
11+
def pull_387_example_1():
12+
# https://github.com/trentm/python-markdown2/pull/387
13+
return "[#a" + " " * 3456
14+
15+
16+
def pull_387_example_2():
17+
# https://github.com/trentm/python-markdown2/pull/387
18+
return "```" + "\n" * 3456
19+
20+
21+
def pull_387_example_3():
22+
# https://github.com/trentm/python-markdown2/pull/387
23+
return "-*-" + " " * 3456
24+
25+
26+
def pull_402():
27+
# https://github.com/trentm/python-markdown2/pull/402
28+
return " " * 100_000 + "$"
29+
30+
31+
def issue493():
32+
# https://github.com/trentm/python-markdown2/issues/493
33+
return "**_" + "*_" * 38730 * 10 + "\x00"
34+
35+
36+
def issue_633():
37+
# https://github.com/trentm/python-markdown2/issues/633
38+
return '<p m="1"' * 2500 + " " * 5000 + "</div"
39+
40+
41+
# whack everything in a dict for easy lookup later on
42+
CASES = {
43+
fn.__name__: fn
44+
for fn in [
45+
pull_387_example_1,
46+
pull_387_example_2,
47+
pull_387_example_3,
48+
pull_402,
49+
issue493,
50+
issue_633,
51+
]
52+
}
53+
54+
55+
if __name__ == "__main__":
56+
logging.basicConfig()
57+
58+
if "--execute" in sys.argv:
59+
testcase = CASES[sys.argv[sys.argv.index("--execute") + 1]]
60+
sys.path.insert(0, str(LIB_DIR))
61+
from markdown2 import markdown
62+
63+
markdown(testcase())
64+
sys.exit(0)
65+
66+
print("-- ReDoS tests")
67+
68+
fails = []
69+
start_time = time.time()
70+
for testcase in CASES:
71+
print(f"markdown2/redos/{testcase} ... ", end="")
72+
73+
testcase_start_time = time.time()
74+
try:
75+
subprocess.run([sys.executable, __file__, "--execute", testcase], timeout=3)
76+
except subprocess.TimeoutExpired:
77+
fails.append(testcase)
78+
print(f"FAIL ({time.time() - testcase_start_time:.3f}s)")
79+
else:
80+
print(f"ok ({time.time() - testcase_start_time:.3f}s)")
81+
82+
print("----------------------------------------------------------------------")
83+
print(f"Ran {len(CASES)} tests in {time.time() - start_time:.3f}s")
84+
85+
if fails:
86+
print("FAIL:", fails)
87+
else:
88+
print("OK")
89+
90+
sys.exit(len(fails))

0 commit comments

Comments
 (0)