python3-sqlparse: patch CVE-2026-71491

Details:
https://nvd.nist.gov/vuln/detail/cve-2026-71491

Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
Ankur Tyagi authored and Anuj Mittal committed 2026-10-05 14:40:48 +05:30
1 parent d17a7718d4
commit c6d34c3765
2 files changed
+148

No files matched your search

@@ -0,0 +1,147 @@
From ca01c323483883f205ee7bb44581c674fcfe6e49 Mon Sep 17 00:00:00 2001
From: Andi Albrecht <albrecht.andi@gmail.com>
Date: Mon, 10 Aug 2026 07:35:42 +0200
Subject: [PATCH] Fix quadratic DoS in group_comments (GHSA-f2ff-p2ww-7p4p)
A comment-only statement ('-- c\n' repeated) made group_comments rescan
the whole remaining token tail once per comment token, costing O(n^2).
Because group_comments runs before the MAX_GROUPING_TOKENS guard, the
cost was paid even on oversized input.
Stop as soon as token_not_matching finds no terminator in the remaining
tokens: from that point on nothing can group, so re-scanning the tail is
wasted work. This makes the pass O(n) while preserving grouping output.
Add benchmarks/validate_group_comments_dos.py to check the scaling.
Reported by sanktjodel.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit ef2012a5eeb491e604dea2b00d516904a3830c87)
CVE: CVE-2026-71491
Upstream-Status: Backport [https://github.com/andialbrecht/sqlparse/commit/ef2012a5eeb491e604dea2b00d516904a3830c87]
Dropped changes to the CHANGELOG file.
Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
---
benchmarks/validate_group_comments_dos.py | 85 +++++++++++++++++++++++
sqlparse/engine/grouping.py | 11 ++-
2 files changed, 93 insertions(+), 3 deletions(-)
create mode 100644 benchmarks/validate_group_comments_dos.py
diff --git a/benchmarks/validate_group_comments_dos.py b/benchmarks/validate_group_comments_dos.py
new file mode 100644
index 0000000..58b3e01
--- /dev/null
+++ b/benchmarks/validate_group_comments_dos.py
@@ -0,0 +1,85 @@
+"""Validate that ``group_comments`` scales linearly on comment-only input.
+
+Regression check for the quadratic O(n^2) DoS in ``group_comments``
+(sqlparse/engine/grouping.py), reported as GHSA-f2ff-p2ww-7p4p.
+
+A statement made only of single-line comments (``'-- c\\n'`` repeated n times)
+lexes in O(n) but ``group_comments`` rescans the O(n) remaining tokens for every
+comment token, giving O(n^2) total work. ``group_comments`` runs first in
+``group()``, before the ``MAX_GROUPING_TOKENS`` guard, so the token cap does not
+protect this vector. The path is reachable via ``sqlparse.parse()`` and
+``sqlparse.format(sql, strip_comments=True)``.
+
+This script measures the scaling of the vulnerable path and reports whether the
+observed growth is quadratic (vulnerable) or roughly linear (patched).
+
+Run with: python benchmarks/validate_group_comments_dos.py
+
+Exit code 0 => behaviour looks linear (advisory mitigated).
+Exit code 1 => behaviour looks quadratic (advisory reproduced).
+"""
+
+import sys
+import time
+
+import sqlparse
+
+
+def payload(n):
+ """A comment-only statement of n single-line comments."""
+ return '-- c\n' * n
+
+
+def measure(fn, sql):
+ t0 = time.perf_counter()
+ fn(sql)
+ return (time.perf_counter() - t0) * 1000
+
+
+def run(label, fn):
+ print(f'{label}:')
+ sizes = (1000, 2000, 4000, 8000)
+ timings = []
+ for n in sizes:
+ dt = measure(fn, payload(n))
+ timings.append(dt)
+ print(f' n={n:5d} {dt:8.1f} ms ({len(payload(n))} B)')
+
+ # For each doubling of the input, quadratic growth ~4x, linear ~2x.
+ ratios = [b / a for a, b in zip(timings, timings[1:]) if a > 0]
+ print(f' doubling ratios: {", ".join(f"{r:.2f}x" for r in ratios)}')
+ return ratios
+
+
+def classify(ratios):
+ """Quadratic if the average per-doubling ratio is closer to 4x than 2x."""
+ if not ratios:
+ return 'inconclusive', 0.0
+ avg = sum(ratios) / len(ratios)
+ # Midpoint between linear (2x) and quadratic (4x) is 3x.
+ return ('quadratic' if avg >= 3.0 else 'linear'), avg
+
+
+def main():
+ print('GHSA-f2ff-p2ww-7p4p: quadratic DoS in group_comments\n')
+
+ all_ratios = []
+ all_ratios += run('sqlparse.parse', sqlparse.parse)
+ print()
+ all_ratios += run(
+ 'sqlparse.format(strip_comments=True)',
+ lambda s: sqlparse.format(s, strip_comments=True),
+ )
+ print()
+
+ verdict, avg = classify(all_ratios)
+ print(f'Average doubling ratio: {avg:.2f}x => {verdict}')
+ if verdict == 'quadratic':
+ print('VULNERABLE: growth is quadratic, advisory reproduced.')
+ return 1
+ print('OK: growth is roughly linear, advisory mitigated.')
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/sqlparse/engine/grouping.py b/sqlparse/engine/grouping.py
index 43ca5b5..7a3ca1a 100644
--- a/sqlparse/engine/grouping.py
+++ b/sqlparse/engine/grouping.py
@@ -339,9 +339,14 @@ def group_comments(tlist):
while token:
eidx, end = tlist.token_not_matching(
lambda tk: imt(tk, t=T.Comment) or tk.is_newline, idx=tidx)
- if end is not None:
- eidx, end = tlist.token_prev(eidx, skip_ws=False)
- tlist.group_tokens(sql.Comment, tidx, eidx)
+ if end is None:
+ # From tidx onward everything is comment/newline: there is no
+ # terminator to group against, and every later start would hit
+ # the same dead end. Stop instead of re-scanning the tail once
+ # per remaining comment token (which is O(n**2)).
+ break
+ eidx, end = tlist.token_prev(eidx, skip_ws=False)
+ tlist.group_tokens(sql.Comment, tidx, eidx)
tidx, token = tlist.token_next_by(t=T.Comment, idx=tidx)
@@ -10,6 +10,7 @@ SRC_URI += "file://CVE-2026-54284-1.patch \
file://CVE-2026-54284-2.patch \
file://CVE-2026-59893.patch \
file://CVE-2026-59894.patch \
file://CVE-2026-71491.patch \
"
CVE_PRODUCT = "sqlparse"