1
0
Fork 0

test(redos): the lexicon-script-tag row denied the literal but not the bound

The unit fix alone ("<script>" -> "<script ") wasn't sufficient: at the
shared _REDOS_N=100_000, the now-fixed [^><] pattern's vulnerable
predecessor ([^>]) only measures ~1.2-1.4s, under the 2.0s assert -- so
the row passed under both the safe and the vulnerable form and proved
nothing. Gave this row its own N=200_000, where the unsafe form
measures ~3.7s (fails) and the shipped form ~0.8s (passes). Verified
both directions by temporarily reverting the lexicon pattern and
restoring it.
This commit is contained in:
Kjell Tore Guttormsen 2026-08-11 21:40:43 +02:00
commit ee73062864

View file

@ -394,27 +394,31 @@ _REDOS_PAYLOADS = [
("active-html-tag-attrs", scan_active_content, "<a"),
# 0.6.0 put a value parser behind the URL-attribute presence test. Its one run
# is the `\s*` in front of the required `=`, so the unit has to DENY the `=`:
# a unit that supplies it matches immediately and never exercises the run. The
# lexicon's `script-tag` row is the cautionary case — its `<script>` unit hands
# the pattern the `>` it needs, so that row stayed green under a form measured
# at ~84s on the input this gate accepts.
# a unit that supplies it matches immediately and never exercises the run.
("active-url-attr-value", scan_active_content, "<a href >"),
# The lexicon is on the output path too, and it had the same defect in the
# JSON pattern table -- found only because the composed-gate test below
# stayed red after every scanner above was already linear. `<a:` drove the
# six html-obfuscation `<[^>]+style...` patterns to 204s at 80_000 chars.
("lexicon-html-obfuscation", scan_lexicon, "<a:"),
("lexicon-script-tag", scan_lexicon, "<script>"),
# A denying unit alone isn't enough here: at the shared _REDOS_N, the
# now-fixed `[^>]` form measures ~1.2-1.4s -- under the 2.0s bound, so the
# row would pass under the vulnerable form too and prove nothing. Measured
# this row's own N: `[^>]` crosses the bound between 100k and 200k chars
# (~3.7s at 200k) while the shipped `[^><]` form stays at ~0.8s. 200_000
# is this row's own override, not the shared _REDOS_N.
("lexicon-script-tag", scan_lexicon, "<script ", 200_000),
("lexicon-iframe-src", scan_lexicon, "<iframe "),
]
@pytest.mark.parametrize(
"scanner,unit", [(s, u) for _, s, u in _REDOS_PAYLOADS],
ids=[i for i, _, _ in _REDOS_PAYLOADS],
"scanner,unit,n",
[(row[1], row[2], row[3] if len(row) > 3 else _REDOS_N) for row in _REDOS_PAYLOADS],
ids=[row[0] for row in _REDOS_PAYLOADS],
)
def test_crafted_redos_payload_stays_bounded(scanner, unit):
payload = (unit * (_REDOS_N // len(unit) + 1))[:_REDOS_N]
def test_crafted_redos_payload_stays_bounded(scanner, unit, n):
payload = (unit * (n // len(unit) + 1))[:n]
start = time.monotonic()
scanner(payload)
assert time.monotonic() - start < 2.0