Skip to content

Commit 037b421

Browse files
committed
gh-143198: fix SIGSEGV in sqlite3.execute[many] with re-entrant parameter iterator
1 parent 57d5699 commit 037b421

File tree

3 files changed

+68
-1
lines changed

3 files changed

+68
-1
lines changed

Lib/test/test_sqlite3/test_dbapi.py

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import contextlib
2424
import functools
25+
import operator
2526
import os
2627
import sqlite3 as sqlite
2728
import subprocess
@@ -32,7 +33,7 @@
3233
import warnings
3334

3435
from test.support import (
35-
SHORT_TIMEOUT, check_disallow_instantiation, requires_subprocess
36+
SHORT_TIMEOUT, check_disallow_instantiation, requires_subprocess, subTests
3637
)
3738
from test.support import gc_collect
3839
from test.support import threading_helper, import_helper
@@ -728,6 +729,27 @@ def test_database_keyword(self):
728729
self.assertEqual(type(cx), sqlite.Connection)
729730

730731

732+
class CxWrapper:
733+
def __init__(self, cx):
734+
self.cx = cx
735+
736+
737+
class EvilParamsWithIter(CxWrapper):
738+
739+
def __iter__(self):
740+
self.cx.close()
741+
return iter([(1,)])
742+
743+
744+
class EvilParamsWithNext(CxWrapper):
745+
def __iter__(self):
746+
return self
747+
748+
def __next__(self):
749+
self.cx.close()
750+
return (1,)
751+
752+
731753
class CursorTests(unittest.TestCase):
732754
def setUp(self):
733755
self.cx = sqlite.connect(":memory:")
@@ -742,6 +764,16 @@ def tearDown(self):
742764
self.cu.close()
743765
self.cx.close()
744766

767+
def do_test_connection_use_after_close(self, method_name, params_class):
768+
# Prevent SIGSEGV with iterable of parameters closing the connection.
769+
# Regression test for https://github.com/python/cpython/issues/143198.
770+
cx = sqlite.connect(":memory:")
771+
self.addCleanup(cx.close)
772+
cu = cx.cursor()
773+
params = params_class(cx)
774+
method = operator.methodcaller(method_name, "SELECT ?", params)
775+
self.assertRaises(sqlite.ProgrammingError, method, cu)
776+
745777
def test_execute_no_args(self):
746778
self.cu.execute("delete from test")
747779

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

848+
@subTests("params_class", (EvilParamsWithIter, EvilParamsWithNext))
849+
def test_execute_use_after_close(self, params_class):
850+
self.do_test_connection_use_after_close("execute", params_class)
851+
816852
def test_execute_wrong_no_of_args1(self):
817853
# too many parameters
818854
with self.assertRaises(sqlite.ProgrammingError):
@@ -1030,6 +1066,10 @@ def test_execute_many_not_iterable(self):
10301066
with self.assertRaises(TypeError):
10311067
self.cu.executemany("insert into test(income) values (?)", 42)
10321068

1069+
@subTests("params_class", (EvilParamsWithIter, EvilParamsWithNext))
1070+
def test_executemany_use_after_close(self, params_class):
1071+
self.do_test_connection_use_after_close("executemany", params_class)
1072+
10331073
def test_fetch_iter(self):
10341074
# Optional DB-API extension.
10351075
self.cu.execute("delete from test")
@@ -1645,6 +1685,15 @@ def tearDown(self):
16451685
self.cur.close()
16461686
self.con.close()
16471687

1688+
def do_test_connection_use_after_close(self, method_name, params_class):
1689+
# Prevent SIGSEGV with iterable of parameters closing the connection.
1690+
# Regression test for https://github.com/python/cpython/issues/143198.
1691+
cx = sqlite.connect(":memory:")
1692+
self.addCleanup(cx.close)
1693+
params = params_class(cx)
1694+
method = operator.methodcaller(method_name, "SELECT ?", params)
1695+
self.assertRaises(sqlite.ProgrammingError, method, cx)
1696+
16481697
def test_script_string_sql(self):
16491698
cur = self.cur
16501699
cur.executescript("""
@@ -1711,6 +1760,10 @@ def test_connection_execute(self):
17111760
result = self.con.execute("select 5").fetchone()[0]
17121761
self.assertEqual(result, 5, "Basic test of Connection.execute")
17131762

1763+
@subTests("params_class", (EvilParamsWithIter, EvilParamsWithNext))
1764+
def test_connection_execute_use_after_close(self, params_class):
1765+
self.do_test_connection_use_after_close("execute", params_class)
1766+
17141767
def test_connection_executemany(self):
17151768
con = self.con
17161769
con.execute("create table test(foo)")
@@ -1719,6 +1772,10 @@ def test_connection_executemany(self):
17191772
self.assertEqual(result[0][0], 3, "Basic test of Connection.executemany")
17201773
self.assertEqual(result[1][0], 4, "Basic test of Connection.executemany")
17211774

1775+
@subTests("params_class", (EvilParamsWithIter, EvilParamsWithNext))
1776+
def test_connection_executemany_use_after_close(self, params_class):
1777+
self.do_test_connection_use_after_close("executemany", params_class)
1778+
17221779
def test_connection_executescript(self):
17231780
con = self.con
17241781
con.executescript("""
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:mod:`sqlite3`: fix crashes in :func:`~sqlite3.execute`, :func:`~sqlite3.executemany`,
2+
:meth:`Cursor.execute <sqlite3.Cursor.execute>`, and :meth:`Cursor.executemany
3+
<sqlite3.Cursor.executemany>` when iterating over the query's parameters closes
4+
the current sqlite3 connection. Patch by Bénédikt Tran.

Modules/_sqlite/cursor.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,12 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
870870
}
871871
}
872872

873+
// PyObject_GetIter() may have a side-effect on the connection's state.
874+
// See: https://github.com/python/cpython/issues/143198.
875+
if (!pysqlite_check_connection(self->connection)) {
876+
goto error;
877+
}
878+
873879
/* reset description */
874880
Py_INCREF(Py_None);
875881
Py_SETREF(self->description, Py_None);

0 commit comments

Comments
 (0)