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

Skip to content

Commit 69cf020

Browse files
erlend-aaslandsir-sigurdJelleZijlstra
authored
[3.8] gh-80254: Disallow recursive usage of cursors in sqlite3 converters (#92333)
(cherry picked from commit c908dc5) Co-authored-by: Sergey Fedoseev <[email protected]> Co-authored-by: Jelle Zijlstra <[email protected]>
1 parent 7ccdec3 commit 69cf020

File tree

3 files changed

+77
-14
lines changed

3 files changed

+77
-14
lines changed

Lib/sqlite3/test/regression.py

+43
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
import functools
2929
from test import support
3030

31+
from unittest.mock import patch
32+
33+
3134
class RegressionTests(unittest.TestCase):
3235
def setUp(self):
3336
self.con = sqlite.connect(":memory:")
@@ -413,10 +416,50 @@ def log(self, *args):
413416

414417

415418

419+
class RecursiveUseOfCursors(unittest.TestCase):
420+
# GH-80254: sqlite3 should not segfault for recursive use of cursors.
421+
msg = "Recursive use of cursors not allowed"
422+
423+
def setUp(self):
424+
self.con = sqlite.connect(":memory:",
425+
detect_types=sqlite.PARSE_COLNAMES)
426+
self.cur = self.con.cursor()
427+
self.cur.execute("create table test(x foo)")
428+
self.cur.executemany("insert into test(x) values (?)",
429+
[("foo",), ("bar",)])
430+
431+
def tearDown(self):
432+
self.cur.close()
433+
self.con.close()
434+
del self.cur
435+
del self.con
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+
416457
def suite():
417458
regression_suite = unittest.makeSuite(RegressionTests, "Check")
459+
recursive_cursor = unittest.makeSuite(RecursiveUseOfCursors)
418460
return unittest.TestSuite((
419461
regression_suite,
462+
recursive_cursor,
420463
))
421464

422465
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))
@@ -357,12 +372,9 @@ static int check_cursor(pysqlite_Cursor* cur)
357372
return 0;
358373
}
359374

360-
if (cur->locked) {
361-
PyErr_SetString(pysqlite_ProgrammingError, "Recursive use of cursors not allowed.");
362-
return 0;
363-
}
364-
365-
return pysqlite_check_thread(cur->connection) && pysqlite_check_connection(cur->connection);
375+
return (pysqlite_check_thread(cur->connection)
376+
&& pysqlite_check_connection(cur->connection)
377+
&& check_cursor_locked(cur));
366378
}
367379

368380
static PyObject *
@@ -761,27 +773,29 @@ PyObject* pysqlite_cursor_iternext(pysqlite_Cursor *self)
761773
if (self->statement) {
762774
rc = pysqlite_step(self->statement->st, self->connection);
763775
if (PyErr_Occurred()) {
764-
(void)pysqlite_statement_reset(self->statement);
765-
Py_DECREF(next_row);
766-
return NULL;
776+
goto error;
767777
}
768778
if (rc != SQLITE_DONE && rc != SQLITE_ROW) {
769-
(void)pysqlite_statement_reset(self->statement);
770-
Py_DECREF(next_row);
771779
_pysqlite_seterror(self->connection->db, NULL);
772-
return NULL;
780+
goto error;
773781
}
774782

775783
if (rc == SQLITE_ROW) {
784+
self->locked = 1; // GH-80254: Prevent recursive use of cursors.
776785
self->next_row = _pysqlite_fetch_one_row(self);
786+
self->locked = 0;
777787
if (self->next_row == NULL) {
778-
(void)pysqlite_statement_reset(self->statement);
779-
return NULL;
788+
goto error;
780789
}
781790
}
782791
}
783792

784793
return next_row;
794+
795+
error:
796+
(void)pysqlite_statement_reset(self->statement);
797+
Py_DECREF(next_row);
798+
return NULL;
785799
}
786800

787801
PyObject* pysqlite_cursor_fetchone(pysqlite_Cursor* self, PyObject* args)
@@ -876,6 +890,10 @@ PyObject* pysqlite_noop(pysqlite_Connection* self, PyObject* args)
876890

877891
PyObject* pysqlite_cursor_close(pysqlite_Cursor* self, PyObject* args)
878892
{
893+
if (!check_cursor_locked(self)) {
894+
return NULL;
895+
}
896+
879897
if (!self->connection) {
880898
PyErr_SetString(pysqlite_ProgrammingError,
881899
"Base Cursor.__init__ not called.");

0 commit comments

Comments
 (0)