diff --git a/Lib/test/test_sqlite3/test_regression.py b/Lib/test/test_sqlite3/test_regression.py index aebea59b9e5..19bb84bf38a 100644 --- a/Lib/test/test_sqlite3/test_regression.py +++ b/Lib/test/test_sqlite3/test_regression.py @@ -27,6 +27,7 @@ import weakref import functools from test import support +from unittest.mock import patch from test.test_sqlite3.test_dbapi import memory_database, managed_connect, cx_limit @@ -469,5 +470,43 @@ class RegressionTests(unittest.TestCase): self.assertEqual(steps, values) +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}): + self.cur.execute(f'select x as "x [INIT]", x from test') + self.assertRaisesRegex(sqlite.ProgrammingError, self.msg, + self.cur.fetchall) + + def test_recursive_cursor_close(self): + conv = lambda x: self.cur.close() + with patch.dict(sqlite.converters, {"CLOSE": conv}): + self.cur.execute(f'select x as "x [CLOSE]", x from test') + self.assertRaisesRegex(sqlite.ProgrammingError, self.msg, + self.cur.fetchall) + + def test_recursive_cursor_iter(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') + self.assertRaisesRegex(sqlite.ProgrammingError, self.msg, + self.cur.fetchall) + + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2019-06-22-11-01-45.bpo-36073.ED8mB9.rst b/Misc/NEWS.d/next/Library/2019-06-22-11-01-45.bpo-36073.ED8mB9.rst new file mode 100644 index 00000000000..6c214d81916 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-06-22-11-01-45.bpo-36073.ED8mB9.rst @@ -0,0 +1,2 @@ +Raise :exc:`~sqlite3.ProgrammingError` instead of segfaulting on recursive +usage of cursors in :mod:`sqlite3` converters. Patch by Sergey Fedoseev. diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c index 87fed2e46e1..f72bf300240 100644 --- a/Modules/_sqlite/cursor.c +++ b/Modules/_sqlite/cursor.c @@ -38,6 +38,17 @@ typedef enum { #include "clinic/cursor.c.h" #undef clinic_state +static inline int +check_cursor_locked(pysqlite_Cursor *cur) +{ + if (cur->locked) { + PyErr_SetString(cur->connection->ProgrammingError, + "Recursive use of cursors not allowed."); + return 0; + } + return 1; +} + /*[clinic input] module _sqlite3 class _sqlite3.Cursor "pysqlite_Cursor *" "clinic_state()->CursorType" @@ -79,6 +90,10 @@ pysqlite_cursor_init_impl(pysqlite_Cursor *self, pysqlite_Connection *connection) /*[clinic end generated code: output=ac59dce49a809ca8 input=23d4265b534989fb]*/ { + if (!check_cursor_locked(self)) { + return -1; + } + Py_INCREF(connection); Py_XSETREF(self->connection, connection); Py_CLEAR(self->statement); @@ -456,13 +471,9 @@ static int check_cursor(pysqlite_Cursor* cur) return 0; } - if (cur->locked) { - PyErr_SetString(cur->connection->state->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 int @@ -1101,7 +1112,9 @@ pysqlite_cursor_iternext(pysqlite_Cursor *self) return NULL; } + self->locked = 1; // GH-80254: Prevent recursive use of cursors. PyObject *row = _pysqlite_fetch_one_row(self); + self->locked = 0; if (row == NULL) { return NULL; } @@ -1265,6 +1278,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) { PyTypeObject *tp = Py_TYPE(self); pysqlite_state *state = pysqlite_get_state_by_type(tp);