8000 bpo-38368: Added fix for ctypes crash when handling arrays in structs… by vsajip · Pull Request #16589 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

bpo-38368: Added fix for ctypes crash when handling arrays in structs… #16589

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Oct 8, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions Lib/ctypes/test/test_structures.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import platform
import sys
import unittest
from ctypes import *
from ctypes.test import need_symbol
Expand Down Expand Up @@ -525,6 +526,28 @@ class Test3(Structure):
self.assertEqual(s.data[0], 3.14159)
self.assertEqual(s.data[1], 2.71828)

def test_38368(self):
class U(Union):
_fields_ = [
('f1', c_uint8 * 16),
('f2', c_uint16 * 8),
('f3', c_uint32 * 4),
]
u = U()
u.f3[0] = 0x01234567
u.f3[1] = 0x89ABCDEF
u.f3[2] = 0x76543210
u.f3[3] = 0xFEDCBA98
f1 = [u.f1[i] for i in range(16)]
f2 = [u.f2[i] for i in range(8)]
if sys.byteorder == 'little':
self.assertEqual(f1, [0x67, 0x45, 0x23, 0x01,
0xef, 0xcd, 0xab, 0x89,
0x10, 0x32, 0x54, 0x76,
0x98, 0xba, 0xdc, 0xfe])
self.assertEqual(f2, [0x4567, 0x0123, 0xcdef, 0x89ab,
0x3210, 0x7654, 0xba98, 0xfedc])

class PointerMemberTestCase(unittest.TestCase):

def test(self):
Expand Down
94 changes: 61 additions & 33 deletions Modules/_ctypes/stgdict.c
Original file line number Diff line number Diff line change
Expand Up @@ -667,11 +667,37 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
* Although the passing in registers is specific to 64-bit Linux, the
* array-in-struct vs. pointer problem is general. But we restrict the
* type transformation to small structs nonetheless.
*
* Note that although a union may be small in terms of memory usage, it
* could contain many overlapping declarations of arrays, e.g.
*
* union {
* unsigned int_8 foo [16];
* unsigned uint_8 bar [16];
* unsigned int_16 baz[8];
* unsigned uint_16 bozz[8];
* unsigned int_32 fizz[4];
* unsigned uint_32 buzz[4];
* }
*
* which is still only 16 bytes in size. We need to convert this into
* the following equivalent for libffi:
*
* union {
* struct { int_8 e1; int_8 e2; ... int_8 e_16; } f1;
* struct { uint_8 e1; uint_8 e2; ... uint_8 e_16; } f2;
* struct { int_16 e1; int_16 e2; ... int_16 e_8; } f3;
* struct { uint_16 e1; uint_16 e2; ... uint_16 e_8; } f4;
* struct { int_32 e1; int_32 e2; ... int_32 e_4; } f5;
* struct { uint_32 e1; uint_32 e2; ... uint_32 e_4; } f6;
* }
*
* So the struct/union needs setting up as follows: all non-array
* elements copied across as is, and all array elements replaced with
* an equivalent struct which has as many fields as the array has
* elements, plus one NULL pointer.
*/
ffi_type *actual_types[MAX_ELEMENTS + 1];
int actual_type_index = 0;

memset(actual_types, 0, sizeof(actual_types));
for (i = 0; i < len; ++i) {
PyObject *name, *desc;
PyObject *pair = PySequence_GetItem(fields, i);
Expand All @@ -695,14 +721,17 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
i);
return -1;
}
if (!PyCArrayTypeObject_Check(desc)) {
/* Not an array. Just copy over the element ffi_type. */
actual_types[actual_type_index++] = &dict->ffi_type_pointer;
assert(actual_type_index <= MAX_ELEMENTS);
}
else {
/*
* If it's an array, the corresponding entry in
* stgdict->ffi_type_pointer.elements needs to be replaced with
* a pointer to a dummy struct.
*/
if (PyCArrayTypeObject_Check(desc)) {
Py_ssize_t length = dict->length;
StgDictObject *edict;
ffi_type * dummy_struct;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ffi_type * dummy_struct;
ffi_type *dummy_struct;

ffi_type ** dummy_fields;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ffi_type ** dummy_fields;
ffi_type **dummy_fields;

Py_ssize_t dummy_index;

edict = PyType_stgdict(dict->proto);
if (edict == NULL) {
Expand All @@ -712,37 +741,36 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
i);
return -1;
}

/* Do separate allocations for now. */
dummy_struct = PyMem_New(ffi_type, 1);
if (dummy_struct == NULL) {
PyErr_NoMemory();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there should be a Py_DECREF(pair); here.

return -1;
}
dummy_fields = PyMem_New(ffi_type *, length + 1);
if (dummy_fields == NULL) {
PyErr_NoMemory();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think dummy_struct should be freed here. Also, pair should be decrefed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the problem is that we're doing these allocations in a loop, and unless we keep some sort of linked list or dynamically allocated structure to keep track of all our allocations (both dummy_struct and dummy_fields in earlier iterations of the loop), we will leak this memory in an Out Of Memory (OOM) situation. I'm not aware that we currently do this type of recovery for these internal allocations in an OOM situation, but happy to be proved wrong.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will have a think about an alternative allocation strategy which allows an easier recovery, but it will be potentially a little harder to follow the logic compared to what's there now.

return -1;
}
memset(dummy_struct, 0, sizeof(ffi_type));
dummy_struct->size = length * edict->ffi_type_pointer.size;
dummy_struct->alignment = edict->ffi_type_pointer.alignment;
dummy_struct->type = FFI_TYPE_STRUCT;
dummy_struct->elements = dummy_fields;

stgdict->ffi_type_pointer.elements[ffi_ofs + i] = dummy_struct;

/* Copy over the element's type, length times. */
dummy_index = 0;
while (length > 0) {
actual_types[actual_type_index++] = &edict->ffi_type_pointer;
assert(actual_type_index <= MAX_ELEMENTS);
dummy_fields[dummy_index++] = &edict->ffi_type_pointer;
length--;
}
dummy_fields[dummy_index] = NULL;
}
Py_DECREF(pair);
}

actual_types[actual_type_index++] = NULL;
/*
* Replace the old elements with the new, taking into account
* base class elements where necessary.
*/
assert(stgdict->ffi_type_pointer.elements);
PyMem_Free(stgdict->ffi_type_pointer.elements);
stgdict->ffi_type_pointer.elements = PyMem_New(ffi_type *,
ffi_ofs + actual_type_index);
if (stgdict->ffi_type_pointer.elements == NULL) {
PyErr_NoMemory();
return -1;
}
if (ffi_ofs) {
memcpy(stgdict->ffi_type_pointer.elements,
basedict->ffi_type_pointer.elements,
ffi_ofs * sizeof(ffi_type *));

}
memcpy(&stgdict->ffi_type_pointer.elements[ffi_ofs], actual_types,
actual_type_index * sizeof(ffi_type *));
}

/* We did check that this flag was NOT set above, it must not
Expand Down
0