diff mbox series

[meta-python,wrynose,9/10] python3-sqlparse: patch CVE-2026-71491

Message ID 20260914031300.3677365-9-ankur.tyagi85@gmail.com
State New
Headers show
Series [meta-python,wrynose,1/10] python3-django: upgrade 6.0.7 -> 6.0.8 | expand

Commit Message

Ankur Tyagi Sept. 14, 2026, 3:12 a.m. UTC
From: Ankur Tyagi <ankur.tyagi85@gmail.com>

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

Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
---
 .../python3-sqlparse/CVE-2026-71491.patch     | 147 ++++++++++++++++++
 .../python/python3-sqlparse_0.5.5.bb          |   1 +
 2 files changed, 148 insertions(+)
 create mode 100644 meta-python/recipes-devtools/python/python3-sqlparse/CVE-2026-71491.patch
diff mbox series

Patch

diff --git a/meta-python/recipes-devtools/python/python3-sqlparse/CVE-2026-71491.patch b/meta-python/recipes-devtools/python/python3-sqlparse/CVE-2026-71491.patch
new file mode 100644
index 0000000000..e0136cc4aa
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-sqlparse/CVE-2026-71491.patch
@@ -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)
+ 
diff --git a/meta-python/recipes-devtools/python/python3-sqlparse_0.5.5.bb b/meta-python/recipes-devtools/python/python3-sqlparse_0.5.5.bb
index a23f5ec0e2..61fe759ede 100644
--- a/meta-python/recipes-devtools/python/python3-sqlparse_0.5.5.bb
+++ b/meta-python/recipes-devtools/python/python3-sqlparse_0.5.5.bb
@@ -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"