Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
037b421
gh-143198: fix SIGSEGV in `sqlite3.execute[many]` with re-entrant par…
picnixz Dec 27, 2025
cc0f483
Update Misc/NEWS.d/next/Library/2025-12-27-10-36-18.gh-issue-143198.D…
picnixz Dec 27, 2025
3952cfe
Merge branch 'main' into fix/sqlite/uaf-in-cursor-143198
picnixz Dec 27, 2025
b0c8b16
correctly fix `sqlite3`'s `executemany`
picnixz Dec 27, 2025
ae2a5de
fix lint
picnixz Dec 27, 2025
f9f5416
add assertions
picnixz Dec 29, 2025
3b50390
protect against parmeters with bad values
picnixz Dec 29, 2025
baa7eec
.
picnixz Dec 29, 2025
f25c1f4
improve tests
picnixz Dec 29, 2025
d1bb010
test coverage for connections
picnixz Dec 29, 2025
a0026e3
split implementation of execute() and executemany()
picnixz Dec 29, 2025
e00919d
reduce diff
picnixz Dec 29, 2025
f0c5c4d
simplify test cases
picnixz Dec 29, 2025
75b2a0c
hard reduce the diff
picnixz Dec 31, 2025
049e663
Merge branch 'main' into fix/sqlite/uaf-in-cursor-143198
picnixz Dec 31, 2025
a10fec4
improve test docs
picnixz Jan 1, 2026
9697a16
Merge branch 'main' into fix/sqlite/uaf-in-cursor-143198
serhiy-storchaka Jan 9, 2026
2fab94a
Reduce the diff a bit more.
picnixz Jan 9, 2026
fe5b799
Reduce the diff again
picnixz Jan 9, 2026
3eaccab
update test names
picnixz Jan 10, 2026
bc0e186
update NEWS entry
picnixz Jan 10, 2026
263038c
defer connection sanity checks to `bind_param`'s callers
picnixz Jan 10, 2026
e78dc57
fixup! remove redundant check after calling `PySequence_Size`
picnixz Jan 10, 2026
36607ac
refactor! move regression tests in their own class to easily extend them
picnixz Jan 10, 2026
295370a
test! significantly increase test coverage
picnixz Jan 10, 2026
6757ed4
fixup! remove unused import
picnixz Jan 10, 2026
ffde2e2
chore! improve readability
picnixz Jan 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion Lib/test/test_sqlite3/test_dbapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import contextlib
import functools
import operator
import os
import sqlite3 as sqlite
import subprocess
Expand All @@ -32,7 +33,7 @@
import warnings

from test.support import (
SHORT_TIMEOUT, check_disallow_instantiation, requires_subprocess
SHORT_TIMEOUT, check_disallow_instantiation, requires_subprocess, subTests
)
from test.support import gc_collect
from test.support import threading_helper, import_helper
Expand Down Expand Up @@ -728,6 +729,27 @@ def test_database_keyword(self):
self.assertEqual(type(cx), sqlite.Connection)


class CxWrapper:
def __init__(self, cx):
self.cx = cx


class EvilParamsWithIter(CxWrapper):

def __iter__(self):
self.cx.close()
return iter([(1,)])


class EvilParamsWithNext(CxWrapper):
def __iter__(self):
return self

def __next__(self):
self.cx.close()
return (1,)


class CursorTests(unittest.TestCase):
def setUp(self):
self.cx = sqlite.connect(":memory:")
Expand All @@ -742,6 +764,16 @@ def tearDown(self):
self.cu.close()
self.cx.close()

def do_test_connection_use_after_close(self, method_name, params_class):
# Prevent SIGSEGV with iterable of parameters closing the connection.
# Regression test for https://github.com/python/cpython/issues/143198.
cx = sqlite.connect(":memory:")
self.addCleanup(cx.close)
cu = cx.cursor()
params = params_class(cx)
method = operator.methodcaller(method_name, "SELECT ?", params)
self.assertRaises(sqlite.ProgrammingError, method, cu)

def test_execute_no_args(self):
self.cu.execute("delete from test")

Expand Down Expand Up @@ -813,6 +845,10 @@ def test_execute_non_iterable(self):
self.cu.execute("insert into test(id) values (?)", 42)
self.assertEqual(str(cm.exception), 'parameters are of unsupported type')

@subTests("params_class", (EvilParamsWithIter, EvilParamsWithNext))
def test_execute_use_after_close(self, params_class):
self.do_test_connection_use_after_close("execute", params_class)

def test_execute_wrong_no_of_args1(self):
# too many parameters
with self.assertRaises(sqlite.ProgrammingError):
Expand Down Expand Up @@ -1030,6 +1066,10 @@ def test_execute_many_not_iterable(self):
with self.assertRaises(TypeError):
self.cu.executemany("insert into test(income) values (?)", 42)

@subTests("params_class", (EvilParamsWithIter, EvilParamsWithNext))
def test_executemany_use_after_close(self, params_class):
self.do_test_connection_use_after_close("executemany", params_class)

def test_fetch_iter(self):
# Optional DB-API extension.
self.cu.execute("delete from test")
Expand Down Expand Up @@ -1645,6 +1685,15 @@ def tearDown(self):
self.cur.close()
self.con.close()

def do_test_connection_use_after_close(self, method_name, params_class):
# Prevent SIGSEGV with iterable of parameters closing the connection.
# Regression test for https://github.com/python/cpython/issues/143198.
cx = sqlite.connect(":memory:")
self.addCleanup(cx.close)
params = params_class(cx)
method = operator.methodcaller(method_name, "SELECT ?", params)
self.assertRaises(sqlite.ProgrammingError, method, cx)

def test_script_string_sql(self):
cur = self.cur
cur.executescript("""
Expand Down Expand Up @@ -1711,6 +1760,10 @@ def test_connection_execute(self):
result = self.con.execute("select 5").fetchone()[0]
self.assertEqual(result, 5, "Basic test of Connection.execute")

@subTests("params_class", (EvilParamsWithIter, EvilParamsWithNext))
def test_connection_execute_use_after_close(self, params_class):
self.do_test_connection_use_after_close("execute", params_class)

def test_connection_executemany(self):
con = self.con
con.execute("create table test(foo)")
Expand All @@ -1719,6 +1772,10 @@ def test_connection_executemany(self):
self.assertEqual(result[0][0], 3, "Basic test of Connection.executemany")
self.assertEqual(result[1][0], 4, "Basic test of Connection.executemany")

@subTests("params_class", (EvilParamsWithIter, EvilParamsWithNext))
def test_connection_executemany_use_after_close(self, params_class):
self.do_test_connection_use_after_close("executemany", params_class)

def test_connection_executescript(self):
con = self.con
con.executescript("""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
:mod:`sqlite3`: fix crashes in :func:`~sqlite3.execute`, :func:`~sqlite3.executemany`,
:meth:`Cursor.execute <sqlite3.Cursor.execute>`, and :meth:`Cursor.executemany
<sqlite3.Cursor.executemany>` when iterating over the query's parameters closes
the current sqlite3 connection. Patch by Bénédikt Tran.
Comment thread
picnixz marked this conversation as resolved.
Outdated
6 changes: 6 additions & 0 deletions Modules/_sqlite/cursor.c
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,12 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
}
}

// PyObject_GetIter() may have a side-effect on the connection's state.
// See: https://github.com/python/cpython/issues/143198.
if (!pysqlite_check_connection(self->connection)) {
Comment thread
picnixz marked this conversation as resolved.
Outdated
goto error;
}

/* reset description */
Py_INCREF(Py_None);
Py_SETREF(self->description, Py_None);
Expand Down
Loading