Skip to content

Commit 36fb19a

Browse files
committed
ctypes: Return existing pointer when possible
Addresses gh-46376
1 parent ff5f94b commit 36fb19a

File tree

3 files changed

+57
-0
lines changed

3 files changed

+57
-0
lines changed

Lib/test/test_ctypes/test_keeprefs.py

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

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

102129
class PointerToStructure(unittest.TestCase):
103130
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
@@ -5129,6 +5129,8 @@ static PyObject *
51295129
Pointer_get_contents(CDataObject *self, void *closure)
51305130
{
51315131
StgDictObject *stgdict;
5132+
PyObject *keep, *ptr_probe;
5133+
CDataObject *ptr2ptr;
51325134

51335135
if (*(void **)self->b_ptr == NULL) {
51345136
PyErr_SetString(PyExc_ValueError,
@@ -5138,6 +5140,33 @@ Pointer_get_contents(CDataObject *self, void *closure)
51385140

51395141
stgdict = PyObject_stgdict((PyObject *)self);
51405142
assert(stgdict); /* Cannot be NULL for pointer instances */
5143+
5144+
keep = GetKeepedObjects(self);
5145+
if (keep != NULL) {
5146+
// check if it's a pointer to a pointer:
5147+
// pointers will have '0' key in the _objects
5148+
ptr_probe = PyDict_GetItemString(keep, "0");
5149+
5150+
if (ptr_probe != NULL) {
5151+
ptr2ptr = (CDataObject*) PyDict_GetItemString(keep, "1");
5152+
if (ptr2ptr == NULL) {
5153+
PyErr_SetString(PyExc_ValueError,
5154+
"Unexpected NULL pointer in _objects");
5155+
return NULL;
5156+
}
5157+
// don't construct a new object,
5158+
// return existing one instead to preserve refcount
5159+
assert(
5160+
*(void**) self->b_ptr == ptr2ptr->b_ptr ||
5161+
*(void**) self->b_value.c == ptr2ptr->b_ptr ||
5162+
*(void**) self->b_ptr == ptr2ptr->b_value.c ||
5163+
*(void**) self->b_value.c == ptr2ptr->b_value.c
5164+
);
5165+
Py_INCREF(ptr2ptr);
5166+
return ptr2ptr;
5167+
}
5168+
}
5169+
51415170
return PyCData_FromBaseObj(stgdict->proto,
51425171
(PyObject *)self, 0,
51435172
*(void **)self->b_ptr);

0 commit comments

Comments
 (0)