Skip to content

Commit 57f27e4

Browse files
ambvcode-of-kpp
andauthored
[3.11] gh-46376: Return existing pointer when possible in ctypes (GH-107131) (#107488)
(cherry picked from commit 08447b5) Co-authored-by: Konstantin <[email protected]>
1 parent aa5f2b1 commit 57f27e4

File tree

3 files changed

+57
-0
lines changed

3 files changed

+57
-0
lines changed

Lib/ctypes/test/test_keeprefs.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,33 @@ def test_p_cint(self):
9393
x = pointer(i)
9494
self.assertEqual(x._objects, {'1': i})
9595

96+
def test_pp_ownership(self):
97+
d = c_int(123)
98+
n = c_int(456)
99+
100+
p = pointer(d)
101+
pp = pointer(p)
102+
103+
self.assertIs(pp._objects['1'], p)
104+
self.assertIs(pp._objects['0']['1'], d)
105+
106+
pp.contents.contents = n
107+
108+
self.assertIs(pp._objects['1'], p)
109+
self.assertIs(pp._objects['0']['1'], n)
110+
111+
self.assertIs(p._objects['1'], n)
112+
self.assertEqual(len(p._objects), 1)
113+
114+
del d
115+
del p
116+
117+
self.assertIs(pp._objects['0']['1'], n)
118+
self.assertEqual(len(pp._objects), 2)
119+
120+
del n
121+
122+
self.assertEqual(len(pp._objects), 2)
96123

97124
class PointerToStructure(unittest.TestCase):
98125
def test(self):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Prevent memory leak and use-after-free when using pointers to pointers with ctypes

Modules/_ctypes/_ctypes.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5158,6 +5158,8 @@ static PyObject *
51585158
Pointer_get_contents(CDataObject *self, void *closure)
51595159
{
51605160
StgDictObject *stgdict;
5161+
PyObject *keep, *ptr_probe;
5162+
CDataObject *ptr2ptr;
51615163

51625164
if (*(void **)self->b_ptr == NULL) {
51635165
PyErr_SetString(PyExc_ValueError,
@@ -5167,6 +5169,33 @@ Pointer_get_contents(CDataObject *self, void *closure)
51675169

51685170
stgdict = PyObject_stgdict((PyObject *)self);
51695171
assert(stgdict); /* Cannot be NULL for pointer instances */
5172+
5173+
keep = GetKeepedObjects(self);
5174+
if (keep != NULL) {
5175+
// check if it's a pointer to a pointer:
5176+
// pointers will have '0' key in the _objects
5177+
ptr_probe = PyDict_GetItemString(keep, "0");
5178+
5179+
if (ptr_probe != NULL) {
5180+
ptr2ptr = (CDataObject*) PyDict_GetItemString(keep, "1");
5181+
if (ptr2ptr == NULL) {
5182+
PyErr_SetString(PyExc_ValueError,
5183+
"Unexpected NULL pointer in _objects");
5184+
return NULL;
5185+
}
5186+
// don't construct a new object,
5187+
// return existing one instead to preserve refcount
5188+
assert(
5189+
*(void**) self->b_ptr == ptr2ptr->b_ptr ||
5190+
*(void**) self->b_value.c == ptr2ptr->b_ptr ||
5191+
*(void**) self->b_ptr == ptr2ptr->b_value.c ||
5192+
*(void**) self->b_value.c == ptr2ptr->b_value.c
5193+
); // double-check that we are returning the same thing
5194+
Py_INCREF(ptr2ptr);
5195+
return (PyObject *) ptr2ptr;
5196+
}
5197+
}
5198+
51705199
return PyCData_FromBaseObj(stgdict->proto,
51715200
(PyObject *)self, 0,
51725201
*(void **)self->b_ptr);

0 commit comments

Comments
 (0)