CVE-2024-4340 (published 2024-04-30T15:15Z)

Passing a heavily nested list to sqlparse.parse() leads to a Denial of Service due to RecursionError.\n\n


Weakness information:
cwe_id: CWE-674
cwe_name: Uncontrolled Recursion
description: The product does not properly control the amount of recursion that takes place,  consuming excessive resources, such as allocated memory or the program stack.
extended_description: 
        
      
      

Fix information:
repo_url: https://github.com/andialbrecht/sqlparse
commit_details: repo_url: https://github.com/andialbrecht/sqlparse
author: Andi Albrecht
author_date: 2024-04-13 13:59:00+02:00
msg: Raise SQLParseError instead of RecursionError.
repository: repo_url: https://github.com/andialbrecht/sqlparse
repo_name: andialbrecht/sqlparse
description: A non-validating SQL parser module for Python
repo_language: Python
owner: andialbrecht
file_changes: filename: CHANGELOG
change_type: ModificationType.MODIFY
diff: @@ -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:
 

programming_language: Markdown
filename: sql.py
change_type: ModificationType.MODIFY
diff: @@ -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 @@ class TokenList(Token):
 
         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:

programming_language: Python
filename: test_regressions.py
change_type: ModificationType.MODIFY
diff: @@ -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)

programming_language: Python