Skip to content

Commit 91c15a5

Browse files
miss-islingtonvsajip
authored andcommitted
[3.7] bpo-16576: Add checks for bitfields passed by value to functions. (GH-17097) (GH-17224)
(cherry picked from commit 1062715)
1 parent 30e5bd8 commit 91c15a5

File tree

3 files changed

+145
-7
lines changed

3 files changed

+145
-7
lines changed

Lib/ctypes/test/test_structures.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,87 @@ class Test5(Structure):
612612
self.assertEqual(test5.nested.an_int, 0)
613613
self.assertEqual(test5.another_int, 0)
614614

615+
#@unittest.skipIf('s390' in MACHINE, 'Test causes segfault on S390')
616+
def test_bitfield_by_value(self):
617+
# See bpo-16576
618+
619+
# These should mirror the structures in Modules/_ctypes/_ctypes_test.c
620+
621+
class Test6(Structure):
622+
_fields_ = [
623+
('A', c_int, 1),
624+
('B', c_int, 2),
625+
('C', c_int, 3),
626+
('D', c_int, 2),
627+
]
628+
629+
test6 = Test6()
630+
# As these are signed int fields, all are logically -1 due to sign
631+
# extension.
632+
test6.A = 1
633+
test6.B = 3
634+
test6.C = 7
635+
test6.D = 3
636+
dll = CDLL(_ctypes_test.__file__)
637+
with self.assertRaises(TypeError) as ctx:
638+
func = dll._testfunc_bitfield_by_value1
639+
func.restype = c_long
640+
func.argtypes = (Test6,)
641+
result = func(test6)
642+
self.assertEqual(ctx.exception.args[0], 'item 1 in _argtypes_ passes '
643+
'a struct/union with a bitfield by value, which is '
644+
'unsupported.')
645+
# passing by reference should be OK
646+
func = dll._testfunc_bitfield_by_reference1
647+
func.restype = c_long
648+
func.argtypes = (POINTER(Test6),)
649+
result = func(byref(test6))
650+
self.assertEqual(result, -4)
651+
self.assertEqual(test6.A, 0)
652+
self.assertEqual(test6.B, 0)
653+
self.assertEqual(test6.C, 0)
654+
self.assertEqual(test6.D, 0)
655+
656+
class Test7(Structure):
657+
_fields_ = [
658+
('A', c_uint, 1),
659+
('B', c_uint, 2),
660+
('C', c_uint, 3),
661+
('D', c_uint, 2),
662+
]
663+
test7 = Test7()
664+
test7.A = 1
665+
test7.B = 3
666+
test7.C = 7
667+
test7.D = 3
668+
func = dll._testfunc_bitfield_by_reference2
669+
func.restype = c_long
670+
func.argtypes = (POINTER(Test7),)
671+
result = func(byref(test7))
672+
self.assertEqual(result, 14)
673+
self.assertEqual(test7.A, 0)
674+
self.assertEqual(test7.B, 0)
675+
self.assertEqual(test7.C, 0)
676+
self.assertEqual(test7.D, 0)
677+
678+
# for a union with bitfields, the union check happens first
679+
class Test8(Union):
680+
_fields_ = [
681+
('A', c_int, 1),
682+
('B', c_int, 2),
683+
('C', c_int, 3),
684+
('D', c_int, 2),
685+
]
686+
687+
test8 = Test8()
688+
with self.assertRaises(TypeError) as ctx:
689+
func = dll._testfunc_bitfield_by_value2
690+
func.restype = c_long
691+
func.argtypes = (Test8,)
692+
result = func(test8)
693+
self.assertEqual(ctx.exception.args[0], 'item 1 in _argtypes_ passes '
694+
'a union by value, which is unsupported.')
695+
615696
class PointerMemberTestCase(unittest.TestCase):
616697

617698
def test(self):

Modules/_ctypes/_ctypes.c

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2276,16 +2276,13 @@ converters_from_argtypes(PyObject *ob)
22762276

22772277
for (i = 0; i < nArgs; ++i) {
22782278
PyObject *tp = PyTuple_GET_ITEM(ob, i);
2279-
PyObject *cnv = PyObject_GetAttrString(tp, "from_param");
2280-
if (!cnv)
2281-
goto argtypes_error_1;
2279+
PyObject *cnv;
22822280
StgDictObject *stgdict = PyType_stgdict(tp);
22832281

22842282
if (stgdict != NULL) {
22852283
if (stgdict->flags & TYPEFLAG_HASUNION) {
22862284
Py_DECREF(converters);
22872285
Py_DECREF(ob);
2288-
Py_DECREF(cnv);
22892286
if (!PyErr_Occurred()) {
22902287
PyErr_Format(PyExc_TypeError,
22912288
"item %zd in _argtypes_ passes a union by "
@@ -2294,12 +2291,22 @@ converters_from_argtypes(PyObject *ob)
22942291
}
22952292
return NULL;
22962293
}
2297-
/*
22982294
if (stgdict->flags & TYPEFLAG_HASBITFIELD) {
2299-
printf("found stgdict with bitfield\n");
2295+
Py_DECREF(converters);
2296+
Py_DECREF(ob);
2297+
if (!PyErr_Occurred()) {
2298+
PyErr_Format(PyExc_TypeError,
2299+
"item %zd in _argtypes_ passes a struct/"
2300+
"union with a bitfield by value, which is "
2301+
"unsupported.",
2302+
i + 1);
2303+
}
2304+
return NULL;
23002305
}
2301-
*/
23022306
}
2307+
cnv = PyObject_GetAttrString(tp, "from_param");
2308+
if (!cnv)
2309+
goto argtypes_error_1;
23032310
PyTuple_SET_ITEM(converters, i, cnv);
23042311
}
23052312
Py_DECREF(ob);

Modules/_ctypes/_ctypes_test.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,56 @@ _testfunc_union_by_reference3(Test5 *in) {
198198
return result;
199199
}
200200

201+
typedef struct {
202+
signed int A: 1, B:2, C:3, D:2;
203+
} Test6;
204+
205+
EXPORT(long)
206+
_testfunc_bitfield_by_value1(Test6 in) {
207+
long result = in.A + in.B + in.C + in.D;
208+
209+
/* As the struct is passed by value, changes to it shouldn't be
210+
* reflected in the caller.
211+
*/
212+
memset(&in, 0, sizeof(in));
213+
return result;
214+
}
215+
216+
EXPORT(long)
217+
_testfunc_bitfield_by_reference1(Test6 *in) {
218+
long result = in->A + in->B + in->C + in->D;
219+
220+
memset(in, 0, sizeof(Test6));
221+
return result;
222+
}
223+
224+
typedef struct {
225+
unsigned int A: 1, B:2, C:3, D:2;
226+
} Test7;
227+
228+
EXPORT(long)
229+
_testfunc_bitfield_by_reference2(Test7 *in) {
230+
long result = in->A + in->B + in->C + in->D;
231+
232+
memset(in, 0, sizeof(Test7));
233+
return result;
234+
}
235+
236+
typedef union {
237+
signed int A: 1, B:2, C:3, D:2;
238+
} Test8;
239+
240+
EXPORT(long)
241+
_testfunc_bitfield_by_value2(Test8 in) {
242+
long result = in.A + in.B + in.C + in.D;
243+
244+
/* As the struct is passed by value, changes to it shouldn't be
245+
* reflected in the caller.
246+
*/
247+
memset(&in, 0, sizeof(in));
248+
return result;
249+
}
250+
201251
EXPORT(void)testfunc_array(int values[4])
202252
{
203253
printf("testfunc_array %d %d %d %d\n",

0 commit comments

Comments
 (0)