Skip to content

Commit da5c4a6

Browse files
committed
gh-145040: Fix crash in sqlite3 when connection is closed during aggregate callback
Fix a segmentation fault in the _sqlite module that occurs when Connection.close() is called inside an aggregate step() callback. After stmt_step() returns, _pysqlite_query_execute() calls sqlite3_last_insert_rowid(self->connection->db) without checking if self->connection->db is still valid. If the connection was closed during the callback, self->connection->db is NULL, causing a NULL pointer dereference. The fix adds a NULL check for self->connection->db after stmt_step() returns, raising ProgrammingError instead of crashing.
1 parent 175ab31 commit da5c4a6

3 files changed

Lines changed: 35 additions & 1 deletion

File tree

Lib/test/test_sqlite3/test_userfunctions.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,29 @@ def test_agg_keyword_args(self):
723723
'takes exactly 3 positional arguments'):
724724
self.con.create_aggregate("test", 1, aggregate_class=AggrText)
725725

726+
def test_aggr_close_conn_in_step(self):
727+
"""Connection.close() in an aggregate step callback must not crash."""
728+
con = sqlite.connect(":memory:", autocommit=True)
729+
cur = con.cursor()
730+
cur.execute("CREATE TABLE t(x INTEGER)")
731+
for i in range(50):
732+
cur.execute("INSERT INTO t VALUES (?)", (i,))
733+
734+
class CloseConnAgg:
735+
def __init__(self):
736+
self.total = 0
737+
738+
def step(self, value):
739+
self.total += value
740+
con.close()
741+
742+
def finalize(self):
743+
return self.total
744+
745+
con.create_aggregate("agg_close", 1, CloseConnAgg)
746+
with self.assertRaises(sqlite.ProgrammingError):
747+
con.execute("SELECT agg_close(x) FROM t")
748+
726749

727750
class AuthorizerTests(unittest.TestCase):
728751
@staticmethod
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Fixed a crash in the :mod:`sqlite3` module when
2+
:meth:`~sqlite3.Connection.close` is called on the connection during an
3+
aggregate callback (e.g., in the ``step`` method). The interpreter now raises
4+
:exc:`~sqlite3.ProgrammingError` instead of crashing with a segmentation
5+
fault.
6+

Modules/_sqlite/cursor.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,11 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
906906
}
907907

908908
rc = stmt_step(self->statement->st);
909+
if (self->connection->db == NULL) {
910+
PyErr_SetString(state->ProgrammingError,
911+
"Cannot operate on a closed database.");
912+
goto error;
913+
}
909914
if (rc != SQLITE_DONE && rc != SQLITE_ROW) {
910915
if (PyErr_Occurred()) {
911916
/* there was an error that occurred in a user-defined callback */
@@ -967,7 +972,7 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
967972
Py_XDECREF(parameters);
968973
}
969974

970-
if (!multiple) {
975+
if (!multiple && self->connection->db) {
971976
sqlite_int64 lastrowid;
972977

973978
Py_BEGIN_ALLOW_THREADS

0 commit comments

Comments
 (0)