Skip to content

Commit

Permalink
[3.10] pythongh-80254: Disallow recursive usage of cursors in `sqlite…
Browse files Browse the repository at this point in the history
…3` converters (python#29054)

(cherry picked from commit f629dcf)

Co-authored-by: Sergey Fedoseev <[email protected]>
Co-authored-by: Jelle Zijlstra <[email protected]>
  • Loading branch information
3 people committed May 3, 2022
1 parent 31d9a88 commit c908dc5
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 7 deletions.
42 changes: 41 additions & 1 deletion Lib/sqlite3/test/regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
import functools
from test import support

from unittest.mock import patch


class RegressionTests(unittest.TestCase):
def setUp(self):
self.con = sqlite.connect(":memory:")
Expand Down Expand Up @@ -415,9 +418,46 @@ def test_return_empty_bytestring(self):
self.assertEqual(val, b'')


class RecursiveUseOfCursors(unittest.TestCase):
# GH-80254: sqlite3 should not segfault for recursive use of cursors.
msg = "Recursive use of cursors not allowed"

def setUp(self):
self.con = sqlite.connect(":memory:",
detect_types=sqlite.PARSE_COLNAMES)
self.cur = self.con.cursor()
self.cur.execute("create table test(x foo)")
self.cur.executemany("insert into test(x) values (?)",
[("foo",), ("bar",)])

def tearDown(self):
self.cur.close()
self.con.close()

def test_recursive_cursor_init(self):
conv = lambda x: self.cur.__init__(self.con)
with patch.dict(sqlite.converters, {"INIT": conv}):
with self.assertRaisesRegex(sqlite.ProgrammingError, self.msg):
self.cur.execute(f'select x as "x [INIT]", x from test')

def test_recursive_cursor_close(self):
conv = lambda x: self.cur.close()
with patch.dict(sqlite.converters, {"CLOSE": conv}):
with self.assertRaisesRegex(sqlite.ProgrammingError, self.msg):
self.cur.execute(f'select x as "x [CLOSE]", x from test')

def test_recursive_cursor_fetch(self):
conv = lambda x, l=[]: self.cur.fetchone() if l else l.append(None)
with patch.dict(sqlite.converters, {"ITER": conv}):
self.cur.execute(f'select x as "x [ITER]", x from test')
with self.assertRaisesRegex(sqlite.ProgrammingError, self.msg):
self.cur.fetchall()


def suite():
tests = [
RegressionTests
RegressionTests,
RecursiveUseOfCursors,
]
return unittest.TestSuite(
[unittest.TestLoader().loadTestsFromTestCase(t) for t in tests]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Raise :exc:`~sqlite3.ProgrammingError` instead of segfaulting on recursive
usage of cursors in :mod:`sqlite3` converters. Patch by Sergey Fedoseev.
30 changes: 24 additions & 6 deletions Modules/_sqlite/cursor.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@
#include "util.h"
#include "clinic/cursor.c.h"

static inline int
check_cursor_locked(pysqlite_Cursor *cur)
{
if (cur->locked) {
PyErr_SetString(pysqlite_ProgrammingError,
"Recursive use of cursors not allowed.");
return 0;
}
return 1;
}

/*[clinic input]
module _sqlite3
class _sqlite3.Cursor "pysqlite_Cursor *" "pysqlite_CursorType"
Expand All @@ -47,6 +58,10 @@ pysqlite_cursor_init_impl(pysqlite_Cursor *self,
pysqlite_Connection *connection)
/*[clinic end generated code: output=ac59dce49a809ca8 input=a8a4f75ac90999b2]*/
{
if (!check_cursor_locked(self)) {
return -1;
}

Py_INCREF(connection);
Py_XSETREF(self->connection, connection);
Py_CLEAR(self->statement);
Expand Down Expand Up @@ -407,12 +422,9 @@ static int check_cursor(pysqlite_Cursor* cur)
return 0;
}

if (cur->locked) {
PyErr_SetString(pysqlite_ProgrammingError, "Recursive use of cursors not allowed.");
return 0;
}

return pysqlite_check_thread(cur->connection) && pysqlite_check_connection(cur->connection);
return (pysqlite_check_thread(cur->connection)
&& pysqlite_check_connection(cur->connection)
&& check_cursor_locked(cur));
}

static PyObject *
Expand Down Expand Up @@ -822,7 +834,9 @@ pysqlite_cursor_iternext(pysqlite_Cursor *self)
}

if (rc == SQLITE_ROW) {
self->locked = 1; // GH-80254: Prevent recursive use of cursors.
self->next_row = _pysqlite_fetch_one_row(self);
self->locked = 0;
if (self->next_row == NULL) {
(void)pysqlite_statement_reset(self->statement);
return NULL;
Expand Down Expand Up @@ -973,6 +987,10 @@ static PyObject *
pysqlite_cursor_close_impl(pysqlite_Cursor *self)
/*[clinic end generated code: output=b6055e4ec6fe63b6 input=08b36552dbb9a986]*/
{
if (!check_cursor_locked(self)) {
return NULL;
}

if (!self->connection) {
PyErr_SetString(pysqlite_ProgrammingError,
"Base Cursor.__init__ not called.");
Expand Down

0 comments on commit c908dc5

Please sign in to comment.