[Python-checkins] [3.8] gh-80254: Disallow recursive usage of cursors in sqlite3 converters (#92333)

ambv webhook-mailer at python.org
Mon May 16 11:39:29 EDT 2022


https://github.com/python/cpython/commit/69cf0203ab47692efbc261c028e15e0d7a245c57
commit: 69cf0203ab47692efbc261c028e15e0d7a245c57
branch: 3.8
author: Erlend Egeberg Aasland <erlend.aasland at protonmail.com>
committer: ambv <lukasz at langa.pl>
date: 2022-05-16T17:39:17+02:00
summary:

[3.8] gh-80254: Disallow recursive usage of cursors in sqlite3 converters (#92333)

(cherry picked from commit c908dc5b4798c311981bd7e1f7d92fb623ee448b)

Co-authored-by: Sergey Fedoseev <fedoseev.sergey at gmail.com>
Co-authored-by: Jelle Zijlstra <jelle.zijlstra at gmail.com>

files:
A Misc/NEWS.d/next/Library/2019-06-22-11-01-45.bpo-36073.ED8mB9.rst
M Lib/sqlite3/test/regression.py
M Modules/_sqlite/cursor.c

diff --git a/Lib/sqlite3/test/regression.py b/Lib/sqlite3/test/regression.py
index 206ecd7ac7537..9ef5a80c6ee0d 100644
--- a/Lib/sqlite3/test/regression.py
+++ b/Lib/sqlite3/test/regression.py
@@ -28,6 +28,9 @@
 import functools
 from test import support
 
+from unittest.mock import patch
+
+
 class RegressionTests(unittest.TestCase):
     def setUp(self):
         self.con = sqlite.connect(":memory:")
@@ -413,10 +416,50 @@ def log(self, *args):
 
 
 
+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()
+        del self.cur
+        del self.con
+
+    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():
     regression_suite = unittest.makeSuite(RegressionTests, "Check")
+    recursive_cursor = unittest.makeSuite(RecursiveUseOfCursors)
     return unittest.TestSuite((
         regression_suite,
+        recursive_cursor,
     ))
 
 def test():
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 0000000000000..6c214d8191601
--- /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 8cfa6e50e8222..996a83e501318 100644
--- a/Modules/_sqlite/cursor.c
+++ b/Modules/_sqlite/cursor.c
@@ -27,10 +27,25 @@
 
 PyObject* pysqlite_cursor_iternext(pysqlite_Cursor* self);
 
+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;
+}
+
 static const char errmsg_fetch_across_rollback[] = "Cursor needed to be reset because of commit/rollback and can no longer be fetched from.";
 
 static int pysqlite_cursor_init(pysqlite_Cursor* self, PyObject* args, PyObject* kwargs)
 {
+    if (!check_cursor_locked(self)) {
+        return -1;
+    }
+
     pysqlite_Connection* connection;
 
     if (!PyArg_ParseTuple(args, "O!", &pysqlite_ConnectionType, &connection))
@@ -357,12 +372,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 *
@@ -761,27 +773,29 @@ PyObject* pysqlite_cursor_iternext(pysqlite_Cursor *self)
     if (self->statement) {
         rc = pysqlite_step(self->statement->st, self->connection);
         if (PyErr_Occurred()) {
-            (void)pysqlite_statement_reset(self->statement);
-            Py_DECREF(next_row);
-            return NULL;
+            goto error;
         }
         if (rc != SQLITE_DONE && rc != SQLITE_ROW) {
-            (void)pysqlite_statement_reset(self->statement);
-            Py_DECREF(next_row);
             _pysqlite_seterror(self->connection->db, NULL);
-            return NULL;
+            goto error;
         }
 
         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;
+                goto error;
             }
         }
     }
 
     return next_row;
+
+error:
+    (void)pysqlite_statement_reset(self->statement);
+    Py_DECREF(next_row);
+    return NULL;
 }
 
 PyObject* pysqlite_cursor_fetchone(pysqlite_Cursor* self, PyObject* args)
@@ -876,6 +890,10 @@ PyObject* pysqlite_noop(pysqlite_Connection* self, PyObject* args)
 
 PyObject* pysqlite_cursor_close(pysqlite_Cursor* self, PyObject* args)
 {
+    if (!check_cursor_locked(self)) {
+        return NULL;
+    }
+
     if (!self->connection) {
         PyErr_SetString(pysqlite_ProgrammingError,
                         "Base Cursor.__init__ not called.");



More information about the Python-checkins mailing list