Skip to content

Commit e611953

Browse files
committed
address review: implement a different solution with a mutex flag
Now modification of the Struct() while packing trigger a RuntimeError
1 parent 716e28c commit e611953

File tree

3 files changed

+31
-30
lines changed

3 files changed

+31
-30
lines changed

Lib/test/test_struct.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,7 @@ def test_Struct_reinitialization(self):
579579

580580
def check_sizeof(self, format_str, number_of_codes):
581581
# The size of 'PyStructObject'
582-
totalsize = support.calcobjsize('2n3P')
582+
totalsize = support.calcobjsize('2n3PB')
583583
# The size taken up by the 'formatcode' dynamic array
584584
totalsize += struct.calcsize('P3n0P') * (number_of_codes + 1)
585585
support.check_sizeof(self, struct.Struct(format_str), totalsize)
@@ -818,14 +818,16 @@ def test_endian_table_init_subinterpreters(self):
818818

819819
def test_Struct_object_mutation_via_dunders(self):
820820
S = struct.Struct('?I')
821+
buf = array.array('b', b' '*100)
821822

822823
class Evil():
823824
def __bool__(self):
824825
# This rebuilds format codes during S.pack().
825826
S.__init__('I')
826827
return True
827828

828-
self.assertEqual(S.pack(Evil(), 1), struct.Struct('?I').pack(True, 1))
829+
self.assertRaises(RuntimeError, S.pack, Evil(), 1)
830+
self.assertRaises(RuntimeError, S.pack_into, buf, 0, Evil(), 1)
829831

830832

831833
class UnpackIteratorTest(unittest.TestCase):
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
Fix use-after-free in :meth:`struct.Struct.pack` when the
22
:class:`struct.Struct` object is mutated by dunder methods (like
3-
:meth:`object.__bool__`) during packing of arguments. Patch by Sergey B
4-
Kirpichev.
3+
:meth:`object.__bool__`) during packing of arguments. Now this trigger
4+
:exc:`RuntimeError`. Patch by Sergey B Kirpichev.

Modules/_struct.c

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ typedef struct {
7070
formatcode *s_codes;
7171
PyObject *s_format;
7272
PyObject *weakreflist; /* List of weak references */
73+
PyMutex mutex; /* to prevent mutation during packing */
7374
} PyStructObject;
7475

7576
#define PyStructObject_CAST(op) ((PyStructObject *)(op))
@@ -1773,6 +1774,7 @@ s_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
17731774
s->s_codes = NULL;
17741775
s->s_size = -1;
17751776
s->s_len = -1;
1777+
s->mutex = (PyMutex){0};
17761778
}
17771779
return self;
17781780
}
@@ -1816,6 +1818,11 @@ Struct___init___impl(PyStructObject *self, PyObject *format)
18161818

18171819
Py_SETREF(self->s_format, format);
18181820

1821+
if (PyMutex_IsLocked(&self->mutex)) {
1822+
PyErr_SetString(PyExc_RuntimeError,
1823+
"Call Struct.__init__() in struct.pack()");
1824+
return -1;
1825+
}
18191826
ret = prepare_s(self);
18201827
return ret;
18211828
}
@@ -2146,24 +2153,14 @@ static int
21462153
s_pack_internal(PyStructObject *soself, PyObject *const *args, int offset,
21472154
char* buf, _structmodulestate *state)
21482155
{
2149-
formatcode *code, *frozen_codes;
2156+
formatcode *code;
21502157
/* XXX(nnorwitz): why does i need to be a local? can we use
21512158
the offset parameter or do we need the wider width? */
2152-
Py_ssize_t i, frozen_size = 1;
2159+
Py_ssize_t i;
21532160

2154-
/* Take copy of soself->s_codes, as dunder methods of arguments (e.g.
2155-
__bool__ of __float__) could modify the struct object during packing. */
2156-
for (code = soself->s_codes; code->fmtdef != NULL; code++) {
2157-
frozen_size++;
2158-
}
2159-
frozen_codes = PyMem_Malloc(frozen_size * sizeof(formatcode));
2160-
if (!frozen_codes) {
2161-
goto err;
2162-
}
2163-
memcpy(frozen_codes, soself->s_codes, frozen_size * sizeof(formatcode));
21642161
memset(buf, '\0', soself->s_size);
21652162
i = offset;
2166-
for (code = frozen_codes; code->fmtdef != NULL; code++) {
2163+
for (code = soself->s_codes; code->fmtdef != NULL; code++) {
21672164
const formatdef *e = code->fmtdef;
21682165
char *res = buf + code->offset;
21692166
Py_ssize_t j = code->repeat;
@@ -2177,7 +2174,7 @@ s_pack_internal(PyStructObject *soself, PyObject *const *args, int offset,
21772174
if (!isstring && !PyByteArray_Check(v)) {
21782175
PyErr_SetString(state->StructError,
21792176
"argument for 's' must be a bytes object");
2180-
goto err;
2177+
return -1;
21812178
}
21822179
if (isstring) {
21832180
n = PyBytes_GET_SIZE(v);
@@ -2199,7 +2196,7 @@ s_pack_internal(PyStructObject *soself, PyObject *const *args, int offset,
21992196
if (!isstring && !PyByteArray_Check(v)) {
22002197
PyErr_SetString(state->StructError,
22012198
"argument for 'p' must be a bytes object");
2202-
goto err;
2199+
return -1;
22032200
}
22042201
if (isstring) {
22052202
n = PyBytes_GET_SIZE(v);
@@ -2225,20 +2222,15 @@ s_pack_internal(PyStructObject *soself, PyObject *const *args, int offset,
22252222
if (PyLong_Check(v) && PyErr_ExceptionMatches(PyExc_OverflowError))
22262223
PyErr_SetString(state->StructError,
22272224
"int too large to convert");
2228-
goto err;
2225+
return -1;
22292226
}
22302227
}
22312228
res += code->size;
22322229
}
22332230
}
22342231

22352232
/* Success */
2236-
PyMem_Free(frozen_codes);
22372233
return 0;
2238-
err:
2239-
/* Failure */
2240-
PyMem_Free(frozen_codes);
2241-
return -1;
22422234
}
22432235

22442236

@@ -2272,17 +2264,22 @@ s_pack(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
22722264
return NULL;
22732265
}
22742266
char *buf = PyBytesWriter_GetData(writer);
2275-
/* Take copy of soself->s_size, as dunder methods of arguments (e.g.
2276-
__bool__ of __float__) could modify the struct object during packing. */
2277-
Py_ssize_t frozen_size = soself->s_size;
22782267

22792268
/* Call the guts */
2269+
if (PyMutex_IsLocked(&soself->mutex)) {
2270+
PyErr_SetString(PyExc_RuntimeError, "XXX");
2271+
PyBytesWriter_Discard(writer);
2272+
return NULL;
2273+
}
2274+
PyMutex_Lock(&soself->mutex);
22802275
if ( s_pack_internal(soself, args, 0, buf, state) != 0 ) {
2276+
PyMutex_Unlock(&soself->mutex);
22812277
PyBytesWriter_Discard(writer);
22822278
return NULL;
22832279
}
2280+
PyMutex_Unlock(&soself->mutex);
22842281

2285-
return PyBytesWriter_FinishWithSize(writer, frozen_size);
2282+
return PyBytesWriter_FinishWithSize(writer, soself->s_size);
22862283
}
22872284

22882285
PyDoc_STRVAR(s_pack_into__doc__,
@@ -2378,11 +2375,13 @@ s_pack_into(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
23782375
}
23792376

23802377
/* Call the guts */
2378+
PyMutex_Lock(&soself->mutex);
23812379
if (s_pack_internal(soself, args, 2, (char*)buffer.buf + offset, state) != 0) {
2380+
PyMutex_Unlock(&soself->mutex);
23822381
PyBuffer_Release(&buffer);
23832382
return NULL;
23842383
}
2385-
2384+
PyMutex_Unlock(&soself->mutex);
23862385
PyBuffer_Release(&buffer);
23872386
Py_RETURN_NONE;
23882387
}

0 commit comments

Comments
 (0)