summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorƁukasz Langa <lukasz@langa.pl>2023-07-31 09:16:45 (GMT)
committerGitHub <noreply@github.com>2023-07-31 09:16:45 (GMT)
commit54aaaadef8a44324f6be674707c67a3516470ff6 (patch)
tree0ef479bbc5f089289e213cab9e1a44958524941b
parent04bd8c76b2578fb6715d6766c637f2b4d3e6a52f (diff)
downloadcpython-54aaaadef8a44324f6be674707c67a3516470ff6.zip
cpython-54aaaadef8a44324f6be674707c67a3516470ff6.tar.gz
cpython-54aaaadef8a44324f6be674707c67a3516470ff6.tar.bz2
[3.12] gh-46376: Return existing pointer when possible in ctypes (GH-107131) (#107487)
(cherry picked from commit 08447b5deb47e2a0df87fa0a0576d300e5c909b4) Co-authored-by: Konstantin <kpp.live+github@gmail.com>
-rw-r--r--Lib/test/test_ctypes/test_keeprefs.py27
-rw-r--r--Misc/NEWS.d/next/Library/2023-07-24-01-21-16.gh-issue-46376.w-xuDL.rst1
-rw-r--r--Modules/_ctypes/_ctypes.c29
3 files changed, 57 insertions, 0 deletions
diff --git a/Lib/test/test_ctypes/test_keeprefs.py b/Lib/test/test_ctypes/test_keeprefs.py
index e20adc7..61650ad 100644
--- a/Lib/test/test_ctypes/test_keeprefs.py
+++ b/Lib/test/test_ctypes/test_keeprefs.py
@@ -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
index 0000000..8e8f024
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-07-24-01-21-16.gh-issue-46376.w-xuDL.rst
@@ -0,0 +1 @@
+Prevent memory leak and use-after-free when using pointers to pointers with ctypes
diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c
index 534ef8c..8496077 100644
--- a/Modules/_ctypes/_ctypes.c
+++ b/Modules/_ctypes/_ctypes.c
@@ -5122,6 +5122,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,
@@ -5131,6 +5133,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);