Skip to content

Commit

Permalink
[3.12] gh-46376: Return existing pointer when possible in ctypes (GH-…
Browse files Browse the repository at this point in the history
…107131) (#107487)

(cherry picked from commit 08447b5)

Co-authored-by: Konstantin <kpp.live+github@gmail.com>
  • Loading branch information
ambv and code-of-kpp committed Jul 31, 2023
1 parent 04bd8c7 commit 54aaaad
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 0 deletions.
27 changes: 27 additions & 0 deletions Lib/test/test_ctypes/test_keeprefs.py
Expand Up @@ -93,6 +93,33 @@ def test_p_cint(self):
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):
Expand Down
@@ -0,0 +1 @@
Prevent memory leak and use-after-free when using pointers to pointers with ctypes
29 changes: 29 additions & 0 deletions Modules/_ctypes/_ctypes.c
Expand Up @@ -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,
Expand All @@ -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);
Expand Down

0 comments on commit 54aaaad

Please sign in to comment.