Skip to content

bpo-34543: Fix SystemErrors and segfaults with uninitialized Structs #14777

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

Closed
Closed
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions Lib/test/test_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,17 @@ def test_format_attr(self):
s2 = struct.Struct(s.format.encode())
self.assertEqual(s2.format, s.format)

def test_uninitialized_struct(self):
s = struct.Struct.__new__(struct.Struct)
with self.assertRaises(ValueError):
s.format
with self.assertRaises(ValueError):
s.size
for meth in s.iter_unpack, s.pack, s.unpack, s.unpack_from:
self.assertRaises(ValueError, meth, b'0')
self.assertRaises(ValueError, s.pack_into, bytearray(1), 0, b'0')
self.assertRaises(ValueError, s.__sizeof__)
Copy link
Contributor

@aeros aeros Jul 15, 2019

Choose a reason for hiding this comment

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

Also, realized this after submitting the approval. This is quite minor, and I still approve of the PR either way. Instead of using s.__sizeof__, I would recommend using sys.getsizeof(s):

Suggested change
self.assertRaises(ValueError, s.__sizeof__)
self.assertRaises(ValueError, sys.getsizeof(s))

In general, it seems to be preferable to use functions instead of directly accessing the special object attributes when possible. If you had a specific reason for not using sys.getsizeof(), let me know. Here's the a link to the function defintion and the docs. I looked over it, but I'm not very experienced with the python c-api. I mostly rely on the docs when it comes to the modules implemented in c.



class UnpackIteratorTest(unittest.TestCase):
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix :exc:`SystemError`\ s and segfaults with uninitialized
:class:`struct.Struct`\ s.
21 changes: 14 additions & 7 deletions Modules/_struct.c
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,12 @@ get_size_t(PyObject *v, size_t *p)

#define RANGE_ERROR(x, f, flag, mask) return _range_error(f, flag)

#define CHECK_INITIALIZED(self) \
if (self->s_codes == NULL) { \
PyErr_SetString(PyExc_ValueError, \
"Struct.__init__() wasn't called"); \
return NULL; \
}

/* Floating point helpers */

Expand Down Expand Up @@ -1533,7 +1539,7 @@ static PyObject *
Struct_unpack_impl(PyStructObject *self, Py_buffer *buffer)
/*[clinic end generated code: output=873a24faf02e848a input=3113f8e7038b2f6c]*/
{
assert(self->s_codes != NULL);
CHECK_INITIALIZED(self);
if (buffer->len != self->s_size) {
PyErr_Format(StructError,
"unpack requires a buffer of %zd bytes",
Expand Down Expand Up @@ -1564,8 +1570,7 @@ Struct_unpack_from_impl(PyStructObject *self, Py_buffer *buffer,
Py_ssize_t offset)
/*[clinic end generated code: output=57fac875e0977316 input=cafd4851d473c894]*/
{
assert(self->s_codes != NULL);

CHECK_INITIALIZED(self);
if (offset < 0) {
if (offset + self->s_size > 0) {
PyErr_Format(StructError,
Expand Down Expand Up @@ -1714,8 +1719,7 @@ Struct_iter_unpack(PyStructObject *self, PyObject *buffer)
{
unpackiterobject *iter;

assert(self->s_codes != NULL);

CHECK_INITIALIZED(self);
if (self->s_size == 0) {
PyErr_Format(StructError,
"cannot iteratively unpack with a struct of length 0");
Expand Down Expand Up @@ -1851,7 +1855,7 @@ s_pack(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
/* Validate arguments. */
soself = (PyStructObject *)self;
assert(PyStruct_Check(self));
assert(soself->s_codes != NULL);
CHECK_INITIALIZED(soself);
if (nargs != soself->s_len)
{
PyErr_Format(StructError,
Expand Down Expand Up @@ -1891,7 +1895,7 @@ s_pack_into(PyObject *self, PyObject *const *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);
CHECK_INITIALIZED(soself);
if (nargs != (soself->s_len + 2))
{
if (nargs == 0) {
Expand Down Expand Up @@ -1977,13 +1981,15 @@ s_pack_into(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
static PyObject *
s_get_format(PyStructObject *self, void *unused)
{
CHECK_INITIALIZED(self);
return PyUnicode_FromStringAndSize(PyBytes_AS_STRING(self->s_format),
PyBytes_GET_SIZE(self->s_format));
}

static PyObject *
s_get_size(PyStructObject *self, void *unused)
{
CHECK_INITIALIZED(self);
return PyLong_FromSsize_t(self->s_size);
}

Expand All @@ -1995,6 +2001,7 @@ s_sizeof(PyStructObject *self, void *unused)
{
Py_ssize_t size;
formatcode *code;
CHECK_INITIALIZED(self);

size = _PyObject_SIZE(Py_TYPE(self)) + sizeof(formatcode);
for (code = self->s_codes; code->fmtdef != NULL; code++)
Expand Down