]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.11] gh-46376: Return existing pointer when possible in ctypes (GH-107131) (#107488)
authorŁukasz Langa <lukasz@langa.pl>
Mon, 31 Jul 2023 09:16:59 +0000 (09:16 +0000)
committerGitHub <noreply@github.com>
Mon, 31 Jul 2023 09:16:59 +0000 (11:16 +0200)
(cherry picked from commit 08447b5deb47e2a0df87fa0a0576d300e5c909b4)

Co-authored-by: Konstantin <kpp.live+github@gmail.com>
Lib/ctypes/test/test_keeprefs.py
Misc/NEWS.d/next/Library/2023-07-24-01-21-16.gh-issue-46376.w-xuDL.rst [new file with mode: 0644]
Modules/_ctypes/_ctypes.c

index e20adc7696f501ceab83ea2b6b1337f0ed80071b..61650ad1704753d33e4f1e140d53e5850e4cca26 100644 (file)
@@ -93,6 +93,33 @@ class PointerTestCase(unittest.TestCase):
         x = pointer(i)
         self.assertEqual(x._objects, {'1': i})
 
+    def test_pp_ownership(self):
+        d = c_int(123)
+        n = c_int(456)
+
+        p = pointer(d)
+        pp = pointer(p)
+
+        self.assertIs(pp._objects['1'], p)
+        self.assertIs(pp._objects['0']['1'], d)
+
+        pp.contents.contents = n
+
+        self.assertIs(pp._objects['1'], p)
+        self.assertIs(pp._objects['0']['1'], n)
+
+        self.assertIs(p._objects['1'], n)
+        self.assertEqual(len(p._objects), 1)
+
+        del d
+        del p
+
+        self.assertIs(pp._objects['0']['1'], n)
+        self.assertEqual(len(pp._objects), 2)
+
+        del n
+
+        self.assertEqual(len(pp._objects), 2)
 
 class PointerToStructure(unittest.TestCase):
     def test(self):
diff --git a/Misc/NEWS.d/next/Library/2023-07-24-01-21-16.gh-issue-46376.w-xuDL.rst b/Misc/NEWS.d/next/Library/2023-07-24-01-21-16.gh-issue-46376.w-xuDL.rst
new file mode 100644 (file)
index 0000000..8e8f024
--- /dev/null
@@ -0,0 +1 @@
+Prevent memory leak and use-after-free when using pointers to pointers with ctypes
index fc73264ff5a32c8b5ef85682ca6ab64d7b45f9c3..9cc518132f92f5e4f81a67dff979ce59e63dc25f 100644 (file)
@@ -5158,6 +5158,8 @@ static PyObject *
 Pointer_get_contents(CDataObject *self, void *closure)
 {
     StgDictObject *stgdict;
+    PyObject *keep, *ptr_probe;
+    CDataObject *ptr2ptr;
 
     if (*(void **)self->b_ptr == NULL) {
         PyErr_SetString(PyExc_ValueError,
@@ -5167,6 +5169,33 @@ Pointer_get_contents(CDataObject *self, void *closure)
 
     stgdict = PyObject_stgdict((PyObject *)self);
     assert(stgdict); /* Cannot be NULL for pointer instances */
+
+    keep = GetKeepedObjects(self);
+    if (keep != NULL) {
+        // check if it's a pointer to a pointer:
+        // pointers will have '0' key in the _objects
+        ptr_probe = PyDict_GetItemString(keep, "0");
+
+        if (ptr_probe != NULL) {
+            ptr2ptr = (CDataObject*) PyDict_GetItemString(keep, "1");
+            if (ptr2ptr ==  NULL) {
+                PyErr_SetString(PyExc_ValueError,
+                "Unexpected NULL pointer in _objects");
+                return NULL;
+            }
+            // don't construct a new object,
+            // return existing one instead to preserve refcount
+            assert(
+                *(void**) self->b_ptr == ptr2ptr->b_ptr ||
+                *(void**) self->b_value.c == ptr2ptr->b_ptr ||
+                *(void**) self->b_ptr == ptr2ptr->b_value.c ||
+                *(void**) self->b_value.c == ptr2ptr->b_value.c
+            ); // double-check that we are returning the same thing
+            Py_INCREF(ptr2ptr);
+            return (PyObject *) ptr2ptr;
+        }
+    }
+
     return PyCData_FromBaseObj(stgdict->proto,
                              (PyObject *)self, 0,
                              *(void **)self->b_ptr);