Skip to content

Commit 460f114

Browse files
Miss Islington (bot)mcepl
authored andcommitted
[CVE-2026-18503] Fix quadratic time in csv.Sniffer for quoted fields
The patch fixes a quadratic time complexity Denial of Service vulnerability in csv.Sniffer.sniff() when processing samples with quoted fields. Fixes: bsc#1274683 (CVE-2026-18503) Fixes: gh#python#98820 From-PR: gh#python/cpython!154867 Patch: CVE-2026-18503-csv-sniffer-quadratic-time.patch
1 parent 1cf3478 commit 460f114

3 files changed

Lines changed: 20 additions & 5 deletions

File tree

‎Lib/csv.py‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -214,12 +214,18 @@ def _guess_quote_and_delimiter(self, data, delimiters):
214214
this way.
215215
"""
216216

217+
# The body of a quoted field ends at the first quote which is
218+
# not doubled, as it does for a reader. A lazy ".*?" scans to
219+
# the end of the sample instead, from every start: quadratically.
220+
# As an unrolled loop it is unambiguous, so it does not backtrack.
221+
other = r'(?:(?!(?P=quote)).)*'
222+
body = r'%s(?:(?P=quote){2}%s)*' % (other, other)
217223
matches = []
218-
for restr in (r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\']).*?(?P=quote)(?P=delim)', # ,".*?",
219-
r'(?:^|\n)(?P<quote>["\']).*?(?P=quote)(?P<delim>[^\w\n"\'])(?P<space> ?)', # ".*?",
220-
r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\']).*?(?P=quote)(?:$|\n)', # ,".*?"
221-
r'(?:^|\n)(?P<quote>["\']).*?(?P=quote)(?:$|\n)'): # ".*?" (no delim, no space)
222-
regexp = re.compile(restr, re.DOTALL | re.MULTILINE)
224+
for restr in (r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\'])%s(?P=quote)(?P=delim)', # ,"...",
225+
r'(?:^|\n)(?P<quote>["\'])%s(?P=quote)(?P<delim>[^\w\n"\'])(?P<space> ?)', # "...",
226+
r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\'])%s(?P=quote)(?:$|\n)', # ,"..."
227+
r'(?:^|\n)(?P<quote>["\'])%s(?P=quote)(?:$|\n)'): # "..." (no delim, no space)
228+
regexp = re.compile(restr % body, re.DOTALL | re.MULTILINE)
223229
matches = regexp.findall(data)
224230
if matches:
225231
break

‎Lib/test/test_csv.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,6 +1199,13 @@ def test__all__(self):
11991199
extra = {'__doc__', '__version__'}
12001200
support.check__all__(self, csv, ('csv', '_csv'), extra=extra)
12011201

1202+
def test_sniff_quoted_single_column(self):
1203+
# gh-98820: this sample used to take minutes.
1204+
sniffer = csv.Sniffer()
1205+
sample = '"abcdefghijklmnopqrstuvwxyz"\n' * 10000
1206+
with self.assertRaisesRegex(csv.Error, "Could not determine delimiter"):
1207+
sniffer.sniff(sample, delimiters=',:|\t')
1208+
12021209

12031210
if __name__ == '__main__':
12041211
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix quadratic time in :meth:`csv.Sniffer.sniff` for a sample which contains
2+
quoted fields, in particular for a single column of quoted fields.

0 commit comments

Comments
 (0)