From 914f9a078f997e58cfcfabcbb30fafdd1f277bef Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 21 Oct 2018 15:25:53 +0300 Subject: [PATCH] bpo-34973: Fix crash in bytes constructor. (GH-9841) Constructing bytes from mutating list could cause a crash. --- Lib/test/test_bytes.py | 17 +++ .../2018-10-13-16-42-03.bpo-34973.B5M-3g.rst | 2 + Objects/bytesobject.c | 104 ++++++++++++------ 3 files changed, 88 insertions(+), 35 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2018-10-13-16-42-03.bpo-34973.B5M-3g.rst diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index b9c5b628c4e..145411efbb9 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -113,6 +113,23 @@ class BaseBytesTest: b = self.type2test([1, 2, 3]) self.assertEqual(b, b"\x01\x02\x03") + def test_from_mutating_list(self): + # Issue #34973: Crash in bytes constructor with mutating list. + class X: + def __index__(self): + a.clear() + return 42 + a = [X(), X()] + self.assertEqual(bytes(a), b'*') + + class Y: + def __index__(self): + if len(a) < 1000: + a.append(self) + return 42 + a = [Y()] + self.assertEqual(bytes(a), b'*' * 1000) # should not crash + def test_from_index(self): b = self.type2test([Indexable(), Indexable(1), Indexable(254), Indexable(255)]) diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-10-13-16-42-03.bpo-34973.B5M-3g.rst b/Misc/NEWS.d/next/Core and Builtins/2018-10-13-16-42-03.bpo-34973.B5M-3g.rst new file mode 100644 index 00000000000..6e403cd4cec --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-10-13-16-42-03.bpo-34973.B5M-3g.rst @@ -0,0 +1,2 @@ +Fixed crash in :func:`bytes` when the :class:`list` argument is mutated +while it is iterated. diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index 22d46878a38..d1057b9c0c2 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -2639,49 +2639,83 @@ fail: return NULL; } -#define _PyBytes_FROM_LIST_BODY(x, GET_ITEM) \ - do { \ - PyObject *bytes; \ - Py_ssize_t i; \ - Py_ssize_t value; \ - char *str; \ - PyObject *item; \ - \ - bytes = PyBytes_FromStringAndSize(NULL, Py_SIZE(x)); \ - if (bytes == NULL) \ - return NULL; \ - str = ((PyBytesObject *)bytes)->ob_sval; \ - \ - for (i = 0; i < Py_SIZE(x); i++) { \ - item = GET_ITEM((x), i); \ - value = PyNumber_AsSsize_t(item, NULL); \ - if (value == -1 && PyErr_Occurred()) \ - goto error; \ - \ - if (value < 0 || value >= 256) { \ - PyErr_SetString(PyExc_ValueError, \ - "bytes must be in range(0, 256)"); \ - goto error; \ - } \ - *str++ = (char) value; \ - } \ - return bytes; \ - \ - error: \ - Py_DECREF(bytes); \ - return NULL; \ - } while (0) - static PyObject* _PyBytes_FromList(PyObject *x) { - _PyBytes_FROM_LIST_BODY(x, PyList_GET_ITEM); + Py_ssize_t i, size = PyList_GET_SIZE(x); + Py_ssize_t value; + char *str; + PyObject *item; + _PyBytesWriter writer; + + _PyBytesWriter_Init(&writer); + str = _PyBytesWriter_Alloc(&writer, size); + if (str == NULL) + return NULL; + writer.overallocate = 1; + size = writer.allocated; + + for (i = 0; i < PyList_GET_SIZE(x); i++) { + item = PyList_GET_ITEM(x, i); + Py_INCREF(item); + value = PyNumber_AsSsize_t(item, NULL); + Py_DECREF(item); + if (value == -1 && PyErr_Occurred()) + goto error; + + if (value < 0 || value >= 256) { + PyErr_SetString(PyExc_ValueError, + "bytes must be in range(0, 256)"); + goto error; + } + + if (i >= size) { + str = _PyBytesWriter_Resize(&writer, str, size+1); + if (str == NULL) + return NULL; + size = writer.allocated; + } + *str++ = (char) value; + } + return _PyBytesWriter_Finish(&writer, str); + + error: + _PyBytesWriter_Dealloc(&writer); + return NULL; } static PyObject* _PyBytes_FromTuple(PyObject *x) { - _PyBytes_FROM_LIST_BODY(x, PyTuple_GET_ITEM); + PyObject *bytes; + Py_ssize_t i, size = PyTuple_GET_SIZE(x); + Py_ssize_t value; + char *str; + PyObject *item; + + bytes = PyBytes_FromStringAndSize(NULL, size); + if (bytes == NULL) + return NULL; + str = ((PyBytesObject *)bytes)->ob_sval; + + for (i = 0; i < size; i++) { + item = PyTuple_GET_ITEM(x, i); + value = PyNumber_AsSsize_t(item, NULL); + if (value == -1 && PyErr_Occurred()) + goto error; + + if (value < 0 || value >= 256) { + PyErr_SetString(PyExc_ValueError, + "bytes must be in range(0, 256)"); + goto error; + } + *str++ = (char) value; + } + return bytes; + + error: + Py_DECREF(bytes); + return NULL; } static PyObject *