]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-149738: Fix segmentation fault bug in sqllite3 (GH-149754) (#150769)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Tue, 2 Jun 2026 12:52:25 +0000 (14:52 +0200)
committerGitHub <noreply@github.com>
Tue, 2 Jun 2026 12:52:25 +0000 (14:52 +0200)
gh-149738: Fix segmentation fault bug in sqllite3 (GH-149754)

Deleting the `row_factory` or `text_factory` attribute is no longer allowed.
(cherry picked from commit 60fdb3192b897168ec0418fb0ea6c8d2d49ea513)

Co-authored-by: Sepehr Rasouli <sepehrrasouli06@gmail.com>
Doc/library/sqlite3.rst
Lib/test/test_sqlite3/test_factory.py
Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst [new file with mode: 0644]
Modules/_sqlite/connection.c

index c2d309b3afc6dffdfa77b05d02f6320cc1ca5e07..effba012510f4aa34ff019a8016ab95318e5b890 100644 (file)
@@ -1420,6 +1420,9 @@ Connection objects
 
       See :ref:`sqlite3-howto-row-factory` for more details.
 
+      .. versionchanged:: next
+         Deleting the ``row_factory`` attribute is no longer allowed.
+
    .. attribute:: text_factory
 
       A :term:`callable` that accepts a :class:`bytes` parameter
@@ -1429,6 +1432,9 @@ Connection objects
 
       See :ref:`sqlite3-howto-encoding` for more details.
 
+      .. versionchanged:: next
+         Deleting the ``text_factory`` attribute is no longer allowed.
+
    .. attribute:: total_changes
 
       Return the total number of database rows that have been modified, inserted, or
@@ -1712,6 +1718,9 @@ Cursor objects
 
       See :ref:`sqlite3-howto-row-factory` for more details.
 
+      .. versionchanged:: next
+         Deleting the ``row_factory`` attribute is no longer allowed.
+
 
 .. The sqlite3.Row example used to be a how-to. It has now been incorporated
    into the Row reference. We keep the anchor here in order not to break
index cc9f1ec5c4bec5e0c18dcecc42664f673be38647..40d4d529c90fd70ba31d7de0b910e03116d790f0 100644 (file)
@@ -155,6 +155,16 @@ class RowFactoryTests(MemoryDatabaseMixin, unittest.TestCase):
         with self.assertRaises(IndexError):
             row[complex()]  # index must be int or string
 
+    def test_delete_connection_row_factory(self):
+        # gh-149738: deleting row_factory should raise an exception
+        with self.assertRaises(AttributeError):
+            del self.con.row_factory
+
+    def test_delete_connection_text_factory(self):
+        # gh-149738: deleting text_factory should raise an exception
+        with self.assertRaises(AttributeError):
+            del self.con.text_factory
+
     def test_sqlite_row_index_unicode(self):
         row = self.con.execute("select 1 as \xff").fetchone()
         self.assertEqual(row["\xff"], 1)
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst
new file mode 100644 (file)
index 0000000..e62b681
--- /dev/null
@@ -0,0 +1,2 @@
+:mod:`sqlite3`: Disallow removing ``row_factory`` and ``text_factory`` attributes
+of a connection to prevent a crash on a query.
index 80500f4619987f764ecc63023782b8524b73d02d..462a35410fd7cbeee33d48ca24d5bc76eb9bfb69 100644 (file)
@@ -576,6 +576,47 @@ pysqlite_connection_cursor_impl(pysqlite_Connection *self, PyObject *factory)
     return cursor;
 }
 
+static PyObject *
+connection_get_row_factory(PyObject *op, void *closure)
+{
+    pysqlite_Connection *self = (pysqlite_Connection *)op;
+    return Py_NewRef(self->row_factory);
+}
+
+static int
+connection_set_row_factory(PyObject *op, PyObject *value, void *closure)
+{
+    pysqlite_Connection *self = (pysqlite_Connection *)op;
+    if (value == NULL) {
+        PyErr_SetString(PyExc_AttributeError,
+                        "cannot delete row_factory attribute");
+        return -1;
+    }
+    Py_XSETREF(self->row_factory, Py_NewRef(value));
+    return 0;
+}
+
+static PyObject *
+connection_get_text_factory(PyObject *op, void *closure)
+{
+    pysqlite_Connection *self = (pysqlite_Connection *)op;
+    return Py_NewRef(self->text_factory);
+}
+
+static int
+connection_set_text_factory(PyObject *op, PyObject *value, void *closure)
+{
+    pysqlite_Connection *self = (pysqlite_Connection *)op;
+    if (value == NULL) {
+        PyErr_SetString(PyExc_AttributeError,
+                        "cannot delete text_factory attribute");
+        return -1;
+    }
+    Py_XSETREF(self->text_factory, Py_NewRef(value));
+    return 0;
+}
+
+
 /*[clinic input]
 _sqlite3.Connection.blobopen as blobopen
 
@@ -2672,6 +2713,10 @@ static PyGetSetDef connection_getset[] = {
     {"in_transaction", pysqlite_connection_get_in_transaction, NULL},
     {"autocommit",  get_autocommit, set_autocommit},
     {"__text_signature__", get_sig, NULL},
+    {"row_factory", connection_get_row_factory,
+                    connection_set_row_factory},
+    {"text_factory", connection_get_text_factory,
+                     connection_set_text_factory},
     {NULL}
 };
 
@@ -2719,8 +2764,6 @@ static struct PyMemberDef connection_members[] =
     {"InternalError", _Py_T_OBJECT, offsetof(pysqlite_Connection, InternalError), Py_READONLY},
     {"ProgrammingError", _Py_T_OBJECT, offsetof(pysqlite_Connection, ProgrammingError), Py_READONLY},
     {"NotSupportedError", _Py_T_OBJECT, offsetof(pysqlite_Connection, NotSupportedError), Py_READONLY},
-    {"row_factory", _Py_T_OBJECT, offsetof(pysqlite_Connection, row_factory)},
-    {"text_factory", _Py_T_OBJECT, offsetof(pysqlite_Connection, text_factory)},
     {NULL}
 };