Skip to content

Commit c908dc5

Browse files
erlend-aaslandsir-sigurdJelleZijlstra
committed
[3.10] gh-80254: Disallow recursive usage of cursors in sqlite3 converters (#29054)
(cherry picked from commit f629dcf) Co-authored-by: Sergey Fedoseev <[email protected]> Co-authored-by: Jelle Zijlstra <[email protected]>
1 parent 31d9a88 commit c908dc5

File tree

3 files changed

+67
-7
lines changed

3 files changed

+67
-7
lines changed

Lib/sqlite3/test/regression.py

+41-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
import functools
2828
from test import support
2929

30+
from unittest.mock import patch
31+
32+
3033
class RegressionTests(unittest.TestCase):
3134
def setUp(self):
3235
self.con = sqlite.connect(":memory:")
@@ -415,9 +418,46 @@ def test_return_empty_bytestring(self):
415418
self.assertEqual(val, b'')
416419

417420

421+
class RecursiveUseOfCursors(unittest.TestCase):
422+
# GH-80254: sqlite3 should not segfault for recursive use of cursors.
423+
msg = "Recursive use of cursors not allowed"
424+
425+
def setUp(self):
426+
self.con = sqlite.connect(":memory:",
427+
detect_types=sqlite.PARSE_COLNAMES)
428+
self.cur = self.con.cursor()
429+
self.cur.execute("create table test(x foo)")
430+
self.cur.executemany("insert into test(x) values (?)",
431+
[("foo",), ("bar",)])
432+
433+
def tearDown(self):
434+
self.cur.close()
435+
self.con.close()
436+
437+
def test_recursive_cursor_init(self):
438+
conv = lambda x: self.cur.__init__(self.con)
439+
with patch.dict(sqlite.converters, {"INIT": conv}):
440+
with self.assertRaisesRegex(sqlite.ProgrammingError, self.msg):
441+
self.cur.execute(f'select x as "x [INIT]", x from test')
442+
443+
def test_recursive_cursor_close(self):
444+
conv = lambda x: self.cur.close()
445+
with patch.dict(sqlite.converters, {"CLOSE": conv}):
446+
with self.assertRaisesRegex(sqlite.ProgrammingError, self.msg):
447+
self.cur.execute(f'select x as "x [CLOSE]", x from test')
448+
449+
def test_recursive_cursor_fetch(self):
450+
conv = lambda x, l=[]: self.cur.fetchone() if l else l.append(None)
451+
with patch.dict(sqlite.converters, {"ITER": conv}):
452+
self.cur.execute(f'select x as "x [ITER]", x from test')
453+
with self.assertRaisesRegex(sqlite.ProgrammingError, self.msg):
454+
self.cur.fetchall()
455+
456+
418457
def suite():
419458
tests = [
420-
RegressionTests
459+
RegressionTests,
460+
RecursiveUseOfCursors,
421461
]
422462
return unittest.TestSuite(
423463
[unittest.TestLoader().loadTestsFromTestCase(t) for t in tests]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Raise :exc:`~sqlite3.ProgrammingError` instead of segfaulting on recursive
2+
usage of cursors in :mod:`sqlite3` converters. Patch by Sergey Fedoseev.

Modules/_sqlite/cursor.c

+24-6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@
2626
#include "util.h"
2727
#include "clinic/cursor.c.h"
2828

29+
static inline int
30+
check_cursor_locked(pysqlite_Cursor *cur)
31+
{
32+
if (cur->locked) {
33+
PyErr_SetString(pysqlite_ProgrammingError,
34+
"Recursive use of cursors not allowed.");
35+
return 0;
36+
}
37+
return 1;
38+
}
39+
2940
/*[clinic input]
3041
module _sqlite3
3142
class _sqlite3.Cursor "pysqlite_Cursor *" "pysqlite_CursorType"
@@ -47,6 +58,10 @@ pysqlite_cursor_init_impl(pysqlite_Cursor *self,
4758
pysqlite_Connection *connection)
4859
/*[clinic end generated code: output=ac59dce49a809ca8 input=a8a4f75ac90999b2]*/
4960
{
61+
if (!check_cursor_locked(self)) {
62+
return -1;
63+
}
64+
5065
Py_INCREF(connection);
5166
Py_XSETREF(self->connection, connection);
5267
Py_CLEAR(self->statement);
@@ -407,12 +422,9 @@ static int check_cursor(pysqlite_Cursor* cur)
407422
return 0;
408423
}
409424

410-
if (cur->locked) {
411-
PyErr_SetString(pysqlite_ProgrammingError, "Recursive use of cursors not allowed.");
412-
return 0;
413-
}
414-
415-
return pysqlite_check_thread(cur->connection) && pysqlite_check_connection(cur->connection);
425+
return (pysqlite_check_thread(cur->connection)
426+
&& pysqlite_check_connection(cur->connection)
427+
&& check_cursor_locked(cur));
416428
}
417429

418430
static PyObject *
@@ -822,7 +834,9 @@ pysqlite_cursor_iternext(pysqlite_Cursor *self)
822834
}
823835

824836
if (rc == SQLITE_ROW) {
837+
self->locked = 1; // GH-80254: Prevent recursive use of cursors.
825838
self->next_row = _pysqlite_fetch_one_row(self);
839+
self->locked = 0;
826840
if (self->next_row == NULL) {
827841
(void)pysqlite_statement_reset(self->statement);
828842
return NULL;
@@ -973,6 +987,10 @@ static PyObject *
973987
pysqlite_cursor_close_impl(pysqlite_Cursor *self)
974988
/*[clinic end generated code: output=b6055e4ec6fe63b6 input=08b36552dbb9a986]*/
975989
{
990+
if (!check_cursor_locked(self)) {
991+
return NULL;
992+
}
993+
976994
if (!self->connection) {
977995
PyErr_SetString(pysqlite_ProgrammingError,
978996
"Base Cursor.__init__ not called.");

0 commit comments

Comments
 (0)