Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2a353b2

Browse files
erlend-aaslandsir-sigurdJelleZijlstra
authored
[3.7] gh-80254: Disallow recursive usage of cursors in sqlite3 converters (GH-92334)
(cherry picked from commit c908dc5) Co-authored-by: Sergey Fedoseev <[email protected]> Co-authored-by: Jelle Zijlstra <[email protected]>
1 parent aebbd75 commit 2a353b2

File tree

3 files changed

+76
-14
lines changed

3 files changed

+76
-14
lines changed

Lib/sqlite3/test/regression.py

+42
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
import weakref
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:")
@@ -444,11 +447,50 @@ class UnhashableType(type):
444447
self.con.execute('SELECT %s()' % aggr_name)
445448

446449

450+
class RecursiveUseOfCursors(unittest.TestCase):
451+
# GH-80254: sqlite3 should not segfault for recursive use of cursors.
452+
msg = "Recursive use of cursors not allowed"
453+
454+
def setUp(self):
455+
self.con = sqlite.connect(":memory:",
456+
detect_types=sqlite.PARSE_COLNAMES)
457+
self.cur = self.con.cursor()
458+
self.cur.execute("create table test(x foo)")
459+
self.cur.executemany("insert into test(x) values (?)",
460+
[("foo",), ("bar",)])
461+
462+
def tearDown(self):
463+
self.cur.close()
464+
self.con.close()
465+
del self.cur
466+
del self.con
467+
468+
def test_recursive_cursor_init(self):
469+
conv = lambda x: self.cur.__init__(self.con)
470+
with patch.dict(sqlite.converters, {"INIT": conv}):
471+
with self.assertRaisesRegex(sqlite.ProgrammingError, self.msg):
472+
self.cur.execute(f'select x as "x [INIT]", x from test')
473+
474+
def test_recursive_cursor_close(self):
475+
conv = lambda x: self.cur.close()
476+
with patch.dict(sqlite.converters, {"CLOSE": conv}):
477+
with self.assertRaisesRegex(sqlite.ProgrammingError, self.msg):
478+
self.cur.execute(f'select x as "x [CLOSE]", x from test')
479+
480+
def test_recursive_cursor_fetch(self):
481+
conv = lambda x, l=[]: self.cur.fetchone() if l else l.append(None)
482+
with patch.dict(sqlite.converters, {"ITER": conv}):
483+
self.cur.execute(f'select x as "x [ITER]", x from test')
484+
with self.assertRaisesRegex(sqlite.ProgrammingError, self.msg):
485+
self.cur.fetchall()
486+
487+
447488
def suite():
448489
regression_suite = unittest.makeSuite(RegressionTests, "Check")
449490
return unittest.TestSuite((
450491
regression_suite,
451492
unittest.makeSuite(UnhashableCallbacksTestCase),
493+
unittest.makeSuite(RecursiveUseOfCursors),
452494
))
453495

454496
def test():
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

+32-14
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,25 @@
2727

2828
PyObject* pysqlite_cursor_iternext(pysqlite_Cursor* self);
2929

30+
static inline int
31+
check_cursor_locked(pysqlite_Cursor *cur)
32+
{
33+
if (cur->locked) {
34+
PyErr_SetString(pysqlite_ProgrammingError,
35+
"Recursive use of cursors not allowed.");
36+
return 0;
37+
}
38+
return 1;
39+
}
40+
3041
static const char errmsg_fetch_across_rollback[] = "Cursor needed to be reset because of commit/rollback and can no longer be fetched from.";
3142

3243
static int pysqlite_cursor_init(pysqlite_Cursor* self, PyObject* args, PyObject* kwargs)
3344
{
45+
if (!check_cursor_locked(self)) {
46+
return -1;
47+
}
48+
3449
pysqlite_Connection* connection;
3550

3651
if (!PyArg_ParseTuple(args, "O!", &pysqlite_ConnectionType, &connection))
@@ -376,12 +391,9 @@ static int check_cursor(pysqlite_Cursor* cur)
376391
return 0;
377392
}
378393

379-
if (cur->locked) {
380-
PyErr_SetString(pysqlite_ProgrammingError, "Recursive use of cursors not allowed.");
381-
return 0;
382-
}
383-
384-
return pysqlite_check_thread(cur->connection) && pysqlite_check_connection(cur->connection);
394+
return (pysqlite_check_thread(cur->connection)
395+
&& pysqlite_check_connection(cur->connection)
396+
&& check_cursor_locked(cur));
385397
}
386398

387399
PyObject* _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* args)
@@ -790,27 +802,29 @@ PyObject* pysqlite_cursor_iternext(pysqlite_Cursor *self)
790802
if (self->statement) {
791803
rc = pysqlite_step(self->statement->st, self->connection);
792804
if (PyErr_Occurred()) {
793-
(void)pysqlite_statement_reset(self->statement);
794-
Py_DECREF(next_row);
795-
return NULL;
805+
goto error;
796806
}
797807
if (rc != SQLITE_DONE && rc != SQLITE_ROW) {
798-
(void)pysqlite_statement_reset(self->statement);
799-
Py_DECREF(next_row);
800808
_pysqlite_seterror(self->connection->db, NULL);
801-
return NULL;
809+
goto error;
802810
}
803811

804812
if (rc == SQLITE_ROW) {
813+
self->locked = 1; // GH-80254: Prevent recursive use of cursors.
805814
self->next_row = _pysqlite_fetch_one_row(self);
815+
self->locked = 0;
806816
if (self->next_row == NULL) {
807-
(void)pysqlite_statement_reset(self->statement);
808-
return NULL;
817+
goto error;
809818
}
810819
}
811820
}
812821

813822
return next_row;
823+
824+
error:
825+
(void)pysqlite_statement_reset(self->statement);
826+
Py_DECREF(next_row);
827+
return NULL;
814828
}
815829

816830
PyObject* pysqlite_cursor_fetchone(pysqlite_Cursor* self, PyObject* args)
@@ -905,6 +919,10 @@ PyObject* pysqlite_noop(pysqlite_Connection* self, PyObject* args)
905919

906920
PyObject* pysqlite_cursor_close(pysqlite_Cursor* self, PyObject* args)
907921
{
922+
if (!check_cursor_locked(self)) {
923+
return NULL;
924+
}
925+
908926
if (!self->connection) {
909927
PyErr_SetString(pysqlite_ProgrammingError,
910928
"Base Cursor.__init__ not called.");

0 commit comments

Comments
 (0)