Skip to content

Commit f629dcf

Browse files
erlend-aaslandsir-sigurdJelleZijlstra
authored
gh-80254: Disallow recursive usage of cursors in sqlite3 converters (#29054)
Co-authored-by: Sergey Fedoseev <[email protected]> Co-authored-by: Jelle Zijlstra <[email protected]>
1 parent 836b17c commit f629dcf

File tree

3 files changed

+65
-7
lines changed

3 files changed

+65
-7
lines changed

Lib/test/test_sqlite3/test_regression.py

+39
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import functools
2828

2929
from test import support
30+
from unittest.mock import patch
3031
from test.test_sqlite3.test_dbapi import memory_database, managed_connect, cx_limit
3132

3233

@@ -469,5 +470,43 @@ def test_executescript_step_through_select(self):
469470
self.assertEqual(steps, values)
470471

471472

473+
class RecursiveUseOfCursors(unittest.TestCase):
474+
# GH-80254: sqlite3 should not segfault for recursive use of cursors.
475+
msg = "Recursive use of cursors not allowed"
476+
477+
def setUp(self):
478+
self.con = sqlite.connect(":memory:",
479+
detect_types=sqlite.PARSE_COLNAMES)
480+
self.cur = self.con.cursor()
481+
self.cur.execute("create table test(x foo)")
482+
self.cur.executemany("insert into test(x) values (?)",
483+
[("foo",), ("bar",)])
484+
485+
def tearDown(self):
486+
self.cur.close()
487+
self.con.close()
488+
489+
def test_recursive_cursor_init(self):
490+
conv = lambda x: self.cur.__init__(self.con)
491+
with patch.dict(sqlite.converters, {"INIT": conv}):
492+
self.cur.execute(f'select x as "x [INIT]", x from test')
493+
self.assertRaisesRegex(sqlite.ProgrammingError, self.msg,
494+
self.cur.fetchall)
495+
496+
def test_recursive_cursor_close(self):
497+
conv = lambda x: self.cur.close()
498+
with patch.dict(sqlite.converters, {"CLOSE": conv}):
499+
self.cur.execute(f'select x as "x [CLOSE]", x from test')
500+
self.assertRaisesRegex(sqlite.ProgrammingError, self.msg,
501+
self.cur.fetchall)
502+
503+
def test_recursive_cursor_iter(self):
504+
conv = lambda x, l=[]: self.cur.fetchone() if l else l.append(None)
505+
with patch.dict(sqlite.converters, {"ITER": conv}):
506+
self.cur.execute(f'select x as "x [ITER]", x from test')
507+
self.assertRaisesRegex(sqlite.ProgrammingError, self.msg,
508+
self.cur.fetchall)
509+
510+
472511
if __name__ == "__main__":
473512
unittest.main()
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-7
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ typedef enum {
3838
#include "clinic/cursor.c.h"
3939
#undef clinic_state
4040

41+
static inline int
42+
check_cursor_locked(pysqlite_Cursor *cur)
43+
{
44+
if (cur->locked) {
45+
PyErr_SetString(cur->connection->ProgrammingError,
46+
"Recursive use of cursors not allowed.");
47+
return 0;
48+
}
49+
return 1;
50+
}
51+
4152
/*[clinic input]
4253
module _sqlite3
4354
class _sqlite3.Cursor "pysqlite_Cursor *" "clinic_state()->CursorType"
@@ -79,6 +90,10 @@ pysqlite_cursor_init_impl(pysqlite_Cursor *self,
7990
pysqlite_Connection *connection)
8091
/*[clinic end generated code: output=ac59dce49a809ca8 input=23d4265b534989fb]*/
8192
{
93+
if (!check_cursor_locked(self)) {
94+
return -1;
95+
}
96+
8297
Py_INCREF(connection);
8398
Py_XSETREF(self->connection, connection);
8499
Py_CLEAR(self->statement);
@@ -456,13 +471,9 @@ static int check_cursor(pysqlite_Cursor* cur)
456471
return 0;
457472
}
458473

459-
if (cur->locked) {
460-
PyErr_SetString(cur->connection->state->ProgrammingError,
461-
"Recursive use of cursors not allowed.");
462-
return 0;
463-
}
464-
465-
return pysqlite_check_thread(cur->connection) && pysqlite_check_connection(cur->connection);
474+
return (pysqlite_check_thread(cur->connection)
475+
&& pysqlite_check_connection(cur->connection)
476+
&& check_cursor_locked(cur));
466477
}
467478

468479
static int
@@ -1101,7 +1112,9 @@ pysqlite_cursor_iternext(pysqlite_Cursor *self)
11011112
return NULL;
11021113
}
11031114

1115+
self->locked = 1; // GH-80254: Prevent recursive use of cursors.
11041116
PyObject *row = _pysqlite_fetch_one_row(self);
1117+
self->locked = 0;
11051118
if (row == NULL) {
11061119
return NULL;
11071120
}
@@ -1265,6 +1278,10 @@ static PyObject *
12651278
pysqlite_cursor_close_impl(pysqlite_Cursor *self)
12661279
/*[clinic end generated code: output=b6055e4ec6fe63b6 input=08b36552dbb9a986]*/
12671280
{
1281+
if (!check_cursor_locked(self)) {
1282+
return NULL;
1283+
}
1284+
12681285
if (!self->connection) {
12691286
PyTypeObject *tp = Py_TYPE(self);
12701287
pysqlite_state *state = pysqlite_get_state_by_type(tp);

0 commit comments

Comments
 (0)