Repository navigation
2.7.18.16: csv.Sniffer quadratic regex on quoted fields (CVE-2026-18503) - #93
Conversation
…VE-2026-18503) Backport of python/cpython pythongh-98820 (3.12 063d455). Sniffer._guess_quote_and_delimiter() matched a quoted field body with a lazy ".*?", which scans to the end of the sample from every start position: quadratic in the sample size. An attacker-controlled sample passed to Sniffer.sniff() could burn significant CPU. The body of a quoted field now ends at the first quote that is not doubled, as it does for a csv reader. 2.7.18.x: Python 2.7's re module has no possessive quantifiers (*+ is 3.11+), so the body is matched atomically with the equivalent lookahead + backreference idiom (?=(?P<body>...*))(?P=body). A differential fuzz of 20000 random samples under Python 3.12 found the 2.7 pattern and upstream's possessive pattern produce identical matches. assertRaisesRegexp instead of assertRaisesRegex in the test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ezequielp-activestate
left a comment
There was a problem hiding this comment.
No blockers.
-
Fix matches upstream. The lookahead-plus-backreference body is an exact atomic-group emulation of upstream's possessive
(?:(?P=quote){2}|(?!(?P=quote)).)*+(pythongh-98820, b30c7fa). The rest of the pattern is unchanged from upstream, andfindall's extrabodygroup is harmless because the code indexes groups throughgroupindex. -
Repro: the upstream test sample (
'"abcdefghijklmnopqrstuvwxyz"\n' * n,delimiters=',:|\t'):n base this branch 1,000 0.49 s 0.10 s 2,000 1.97 s 0.18 s 4,000 7.40 s 0.38 s -
No regressions spotted: normal and doubled-quote samples sniff the same as before, and a single 2 MB quoted field still sniffs (0.7 s, no recursion error).
-
Tests:
test_csvpasses. -
FYI, not for this PR: the doublequote check in the same function (
dq_regexp) is still quadratic.'",' * 20000takes about 9 s on both base and this branch, and upstream 3.14 shows the same timing for that regex, so it isn't a porting gap.
Backport of python/cpython pythongh-98820 (CVE-2026-18503, PSF-2026-35) to 2.7.18.x.
Problem:
Sniffer._guess_quote_and_delimiter()matched a quoted field body with a lazy.*?. That scans to the end of the sample from every start position, so the cost is quadratic in the sample size.Fix: a quoted field body now ends at the first quote that isn't doubled, as it does for a csv reader. This changes some sniffing results on messy samples; the change is upstream's.
2.7 adaptation: Python 2.7's
rehas no possessive*+, so the body is matched atomically using(?=(?P<body>…*))(?P=body). Under Python 3.12 I fuzzed 20,000 random samples, and this pattern gives the same matches as upstream's possessive pattern on every one.Test:
TestSniffer.test_sniff_quoted_single_column. Without the fix this single test takes 28.7s; with it, all oftest_csvpasses in 1.3s.Part of 2.7.18.16. It targets the
2.7.18.16integration branch, like #92.