Skip to content
Open
97 changes: 97 additions & 0 deletions Lib/test/test_ctypes/test_arrays.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,103 @@ def test_simple(self):
with self.assertRaises(TypeError):
del ca[0]

def test_ctypes_array_class_assignment_incompatible(self):
A = c_long * 3
B = c_long * 5
x = A(1, 2, 3)

with self.assertRaises(TypeError):
x.__class__ = B

def test_ctypes_array_class_assignment_incompatible_target(self):
A = c_int * 3
class OtherArray(Array):
_type_ = c_int
_length_ = 4 # incompatible length

a = A()

with self.assertRaises(TypeError):
a.__class__ = OtherArray


def test_ctypes_array_class_assignment_zero_length(self):
A = c_long * 0
B = c_long * 1
a = A()

with self.assertRaises(TypeError):
a.__class__ = B

def test_ctypes_array_class_assignment_incompatible_element_type(self):
A = c_int * 3
B = c_double * 3
a = A()

with self.assertRaises(TypeError):
a.__class__ = B

def test_ctypes_array_class_assignment_signed_unsigned(self):
A = c_long * 3
B = c_ulonglong * 3
a = A()

with self.assertRaises(TypeError):
a.__class__ = B

def test_ctypes_array_class_assignment_compatible(self):
A = c_int * 3
class SameArray(Array):
_type_ = c_int
_length_ = 3
a = A(1, 2, 3)
a.__class__ = SameArray

def test_ctypes_array_class_assignment_non_ctypes_target(self):
A = c_int * 3
a = A()
class Dummy:
pass

with self.assertRaises(TypeError):
a.__class__ = Dummy

def test_ctypes_array_class_assignment_abstract_target(self):
A = c_int * 3
a = A()
AbstractArray = PyCArrayType.__new__(PyCArrayType, "AbstractArray", (Array,), {})

with self.assertRaises(TypeError):
a.__class__ = AbstractArray

def test_ctypes_array_class_assignment_same_size_ints(self):
if sizeof(c_int) != sizeof(c_long):
self.skipTest("sizes differ on this platform")
A = c_int * 3
B = c_long * 3
a = A(1, 2, 3)
a.__class__ = B

def test_ctypes_array_class_assignment_structs(self):
class S1(Structure):
_fields_ = [("x", c_int)]
A = S1 * 2
B = S1 * 2
a = A()
a.__class__ = B

def test_ctypes_array_class_assignment_pointer_arrays(self):
from ctypes import POINTER
A = POINTER(c_int) * 2
B = POINTER(c_int) * 2
a = A()
a.__class__ = B

def test_ctypes_array_from_param_incompatible(self):
A = c_int * 3
with self.assertRaises(TypeError):
A.from_param(object())

def test_step_overflow(self):
a = (c_int * 5)()
a[3::sys.maxsize] = (1,)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix a memory safety issue in ctypes arrays by rejecting ``__class__``
assignment to incompatible array types.
72 changes: 72 additions & 0 deletions Modules/_ctypes/_ctypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -1097,6 +1097,28 @@ CDataType_from_param_impl(PyObject *type, PyTypeObject *cls, PyObject *value)
return Py_NewRef(value);
}
ctypes_state *st = get_module_state_by_class(cls);

/* Disallow incompatible ctypes array __class__ reassignment */
StgInfo *old_info = NULL;
StgInfo *new_info = NULL;
if (PyStgInfo_FromObject(st, value, &old_info) == 0 &&
PyStgInfo_FromType(st, type, &new_info) == 0 &&
old_info != NULL &&
new_info != NULL &&
old_info->length >= 0 &&
new_info->length >= 0)
{
if (old_info->length != new_info->length ||
old_info->size != new_info->size ||
old_info->proto != new_info->proto)
{
PyErr_SetString(
PyExc_TypeError,
"cannot assign incompatible ctypes array type"
);
return NULL;
}
}
if (PyCArg_CheckExact(st, value)) {
PyCArgObject *p = (PyCArgObject *)value;
PyObject *ob = p->obj;
Expand Down Expand Up @@ -4992,6 +5014,55 @@ Array_init(PyObject *self, PyObject *args, PyObject *kw)
return 0;
}

static int
PyCArray_setattro(PyObject *self, PyObject *key, PyObject *value)
{
if (PyUnicode_Check(key) &&
_PyUnicode_EqualToASCIIString(key, "__class__"))
{
ctypes_state *st = get_module_state_by_def(Py_TYPE(Py_TYPE(self)));
StgInfo *old_info = NULL;
StgInfo *new_info = NULL;

if (PyStgInfo_FromObject(st, self, &old_info) < 0) {
return -1;
}
if (PyStgInfo_FromType(st, value, &new_info) < 0) {
return -1;
}

/* If one side has no storage info, disallow */
if (old_info == NULL || new_info == NULL) {
PyErr_SetString(
PyExc_TypeError,
"cannot assign incompatible ctypes array type"
);
return -1;
}

/* Only care about array to array */
if (old_info->length < 0 || new_info->length < 0) {
PyErr_SetString(
PyExc_TypeError,
"cannot assign incompatible ctypes array type");
return -1;
}

/* Must match layout */
if (old_info->length != new_info->length ||
old_info->size != new_info->size ||
old_info->proto != new_info->proto)
{
PyErr_SetString(
PyExc_TypeError,
"cannot assign incompatible ctypes array type");
return -1;
}
}

return PyObject_GenericSetAttr(self, key, value);
}

static PyObject *
Array_item_lock_held(PyObject *myself, Py_ssize_t index)
{
Expand Down Expand Up @@ -5310,6 +5381,7 @@ static PyType_Slot pycarray_slots[] = {
{Py_mp_length, Array_length},
{Py_mp_subscript, Array_subscript},
{Py_mp_ass_subscript, Array_ass_subscript},
{Py_tp_setattro, PyCArray_setattro},
{0, NULL},
};

Expand Down
Loading