From b4a39d9850969b4e1d6940d32094ee0b42a2cf03 Mon Sep 17 00:00:00 2001 From: Andi Albrecht Date: Sat, 13 Apr 2024 13:59:00 +0200 Subject: [PATCH] Raise SQLParseError instead of RecursionError. --- CHANGELOG | 5 +++++ sqlparse/sql.py | 14 +++++++++----- tests/test_regressions.py | 17 ++++++++++++++++- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4e98e7f6..6c442c05 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,11 @@ Notable Changes * Drop support for Python 3.5, 3.6, and 3.7. * Python 3.12 is now supported (pr725, by hugovk). +* IMPORTANT: Fixes a potential denial of service attack (DOS) due to recursion + error for deeply nested statements. Instead of recursion error a generic + SQLParseError is raised. See the security advisory for details: + https://github.com/andialbrecht/sqlparse/security/advisories/GHSA-2m57-hf25-phgg + The vulnerability was discovered by @uriyay-jfrog. Thanks for reporting! Enhancements: diff --git a/sqlparse/sql.py b/sqlparse/sql.py index 05e17748..bd5f35b1 100644 --- a/sqlparse/sql.py +++ b/sqlparse/sql.py @@ -10,6 +10,7 @@ import re from sqlparse import tokens as T +from sqlparse.exceptions import SQLParseError from sqlparse.utils import imt, remove_quotes @@ -209,11 +210,14 @@ def flatten(self): This method is recursively called for all child tokens. """ - for token in self.tokens: - if token.is_group: - yield from token.flatten() - else: - yield token + try: + for token in self.tokens: + if token.is_group: + yield from token.flatten() + else: + yield token + except RecursionError as err: + raise SQLParseError('Maximum recursion depth exceeded') from err def get_sublists(self): for token in self.tokens: diff --git a/tests/test_regressions.py b/tests/test_regressions.py index 29cb502c..1edd3da6 100644 --- a/tests/test_regressions.py +++ b/tests/test_regressions.py @@ -1,9 +1,11 @@ import copy +import sys import pytest import sqlparse from sqlparse import sql, tokens as T +from sqlparse.exceptions import SQLParseError def test_issue9(): @@ -449,4 +451,17 @@ def test_copy_issue672(): def test_primary_key_issue740(): p = sqlparse.parse('PRIMARY KEY')[0] assert len(p.tokens) == 1 - assert p.tokens[0].ttype == T.Keyword \ No newline at end of file + assert p.tokens[0].ttype == T.Keyword + + +@pytest.fixture +def limit_recursion(): + curr_limit = sys.getrecursionlimit() + sys.setrecursionlimit(70) + yield + sys.setrecursionlimit(curr_limit) + + +def test_max_recursion(limit_recursion): + with pytest.raises(SQLParseError): + sqlparse.parse('[' * 100 + ']' * 100)