diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index a896468848e4fd..8beb2e51574bca 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -626,6 +626,20 @@ def test_format_attr(self): s2 = struct.Struct(s.format.encode()) self.assertEqual(s2.format, s.format) + @support.cpython_only + def test_uninitialized(self): + # A crash or an assertion failure shouldn't happen in case of calling + # methods or accessing attributes of an uninitialized Struct object. + s = struct.Struct.__new__(struct.Struct) + self.assertRaises(ValueError, s.iter_unpack, b'foo') + self.assertRaises(ValueError, s.pack, 42) + self.assertRaises(ValueError, s.pack_into, bytearray(1), 0, 42) + self.assertRaises(ValueError, s.unpack, b'bar') + self.assertRaises(ValueError, s.unpack_from, b'spam', 0) + self.assertRaises(ValueError, s.__sizeof__) + with self.assertRaises(ValueError): + s.format + class UnpackIteratorTest(unittest.TestCase): """ diff --git a/Misc/NEWS.d/next/Core and Builtins/2017-10-13-13-57-17.bpo-31779.SOlNo7.rst b/Misc/NEWS.d/next/Core and Builtins/2017-10-13-13-57-17.bpo-31779.SOlNo7.rst new file mode 100644 index 00000000000000..b8f5823ceb6cd0 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2017-10-13-13-57-17.bpo-31779.SOlNo7.rst @@ -0,0 +1,2 @@ +Prevent assertion failures and a crash when using an uninitialized +``struct.Struct`` object. Patch by Oren Milman. diff --git a/Modules/_struct.c b/Modules/_struct.c index 04d7f8e1871925..6c19852272b45e 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -1514,6 +1514,16 @@ s_unpack_internal(PyStructObject *soself, const char *startfrom) { return NULL; } +Py_LOCAL_INLINE(int) +check_struct(PyStructObject *self) +{ + if (self->s_codes == NULL) { + PyErr_SetString(PyExc_ValueError, + "Struct.__init__() wasn't called"); + return 0; + } + return 1; +} /*[clinic input] Struct.unpack @@ -1533,7 +1543,9 @@ static PyObject * Struct_unpack_impl(PyStructObject *self, Py_buffer *buffer) /*[clinic end generated code: output=873a24faf02e848a input=3113f8e7038b2f6c]*/ { - assert(self->s_codes != NULL); + if (!check_struct(self)) { + return NULL; + } if (buffer->len != self->s_size) { PyErr_Format(StructError, "unpack requires a buffer of %zd bytes", @@ -1563,7 +1575,9 @@ Struct_unpack_from_impl(PyStructObject *self, Py_buffer *buffer, Py_ssize_t offset) /*[clinic end generated code: output=57fac875e0977316 input=97ade52422f8962f]*/ { - assert(self->s_codes != NULL); + if (!check_struct(self)) { + return NULL; + } if (offset < 0) offset += buffer->len; @@ -1691,7 +1705,9 @@ Struct_iter_unpack(PyStructObject *self, PyObject *buffer) { unpackiterobject *iter; - assert(self->s_codes != NULL); + if (!check_struct(self)) { + return NULL; + } if (self->s_size == 0) { PyErr_Format(StructError, @@ -1828,7 +1844,9 @@ s_pack(PyObject *self, PyObject **args, Py_ssize_t nargs) /* Validate arguments. */ soself = (PyStructObject *)self; assert(PyStruct_Check(self)); - assert(soself->s_codes != NULL); + if (!check_struct(soself)) { + return NULL; + } if (nargs != soself->s_len) { PyErr_Format(StructError, @@ -1868,7 +1886,9 @@ s_pack_into(PyObject *self, PyObject **args, Py_ssize_t nargs) /* Validate arguments. +1 is for the first arg as buffer. */ soself = (PyStructObject *)self; assert(PyStruct_Check(self)); - assert(soself->s_codes != NULL); + if (!check_struct(soself)) { + return NULL; + } if (nargs != (soself->s_len + 2)) { if (nargs == 0) { @@ -1954,6 +1974,9 @@ s_pack_into(PyObject *self, PyObject **args, Py_ssize_t nargs) static PyObject * s_get_format(PyStructObject *self, void *unused) { + if (!check_struct(self)) { + return NULL; + } return PyUnicode_FromStringAndSize(PyBytes_AS_STRING(self->s_format), PyBytes_GET_SIZE(self->s_format)); } @@ -1973,6 +1996,9 @@ s_sizeof(PyStructObject *self, void *unused) Py_ssize_t size; formatcode *code; + if (!check_struct(self)) { + return NULL; + } size = _PyObject_SIZE(Py_TYPE(self)) + sizeof(formatcode); for (code = self->s_codes; code->fmtdef != NULL; code++) size += sizeof(formatcode);