8000 bpo-30061: Check if PyObject_Size()/PySequence_Size()/PyMapping_Size() by serhiy-storchaka · Pull Request #1096 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

bpo-30061: Check if PyObject_Size()/PySequence_Size()/PyMapping_Size() < 8000 span class="f1-light color-fg-muted">#1096

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

Merged
Merged
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
16 changes: 16 additions & 0 deletions Lib/test/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,22 @@ def test_readline(self):
with self.open(support.TESTFN, "r") as f:
self.assertRaises(TypeError, f.readline, 5.3)

def test_readline_nonsizeable(self):
# Issue #30061
# Crash when readline() returns an object without __len__
class R(self.IOBase):
def readline(self):
return None
self.assertRaises((TypeError, StopIteration), next, R())

def test_next_nonsizeable(self):
# Issue #30061
# Crash when __next__() returns an object without __len__
class R(self.IOBase):
def __next__(self):
return None
self.assertRaises(TypeError, R().readlines, 1)

def test_raw_bytes_io(self):
f = self.BytesIO()
self.write_ops(f)
Expand Down
5 changes: 5 additions & 0 deletions Misc/NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,11 @@ Extension Modules
Library
-------

- bpo-30061: Fixed crashes in IOBase methods __next__() and readlines() when
readline() or __next__() respectively return non-sizeable object.
Fixed possible other errors caused by not checking results of PyObject_Size(),
PySequence_Size(), or PyMapping_Size().

- bpo-10076: Compiled regular expression and match objects in the re module
now support copy.copy() and copy.deepcopy() (they are considered atomic).

Expand Down
2 changes: 1 addition & 1 deletion Modules/_ctypes/_ctypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -5143,7 +5143,7 @@ comerror_init(PyObject *self, PyObject *args, PyObject *kwds)
if (!PyArg_ParseTuple(args, "OOO:COMError", &hresult, &text, &details))
return -1;

a = PySequence_GetSlice(args, 1, PySequence_Size(args));
a = PySequence_GetSlice(args, 1, PyTuple_GET_SIZE(args));
if (!a)
return -1;
status = PyObject_SetAttrString(self, "args", a);
Expand Down
4 changes: 2 additions & 2 deletions Modules/_elementtree.c
Original file line number Diff line number Diff line change
Expand Up @@ -1878,7 +1878,7 @@ element_ass_subscr(PyObject* self_, PyObject* item, PyObject* value)
);
return -1;
}
newlen = PySequence_Size(seq);
newlen = PySequence_Fast_GET_SIZE(seq);

if (step != 1 && newlen != slicelen)
{
Expand Down Expand Up @@ -3660,7 +3660,7 @@ _elementtree_XMLParser__setevents_impl(XMLParserObject *self,
return NULL;
}

for (i = 0; i < PySequence_Size(events_seq); ++i) {
for (i = 0; i < PySequence_Fast_GET_SIZE(events_seq); ++i) {
PyObject *event_name_obj = PySequence_Fast_GET_ITEM(events_seq, i);
const char *event_name = NULL;
if (PyUnicode_Check(event_name_obj)) {
Expand Down
13 changes: 9 additions & 4 deletions Modules/_io/iobase.c
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,8 @@ iobase_iternext(PyObject *self)
if (line == NULL)
return NULL;

if (PyO 8000 bject_Size(line) == 0) {
if (PyObject_Size(line) <= 0) {
/* Error or empty */
Py_DECREF(line);
return NULL;
}
Expand Down Expand Up @@ -670,6 +671,7 @@ _io__IOBase_readlines_impl(PyObject *self, Py_ssize_t hint)
}

while (1) {
Py_ssize_t line_length;
PyObject *line = PyIter_Next(it);
if (line == NULL) {
if (PyErr_Occurred()) {
Expand All @@ -683,11 +685,14 @@ _io__IOBase_readlines_impl(PyObject *self, Py_ssize_t hint)
Py_DECREF(line);
goto error;
}
length += PyObject_Size(line);
line_length = PyObject_Size(line);
Py_DECREF(line);

if (length > hint)
if (line_length < 0) {
goto error;
}
if (line_length > hint - length)
break;
length += line_length;
}

Py_DECREF(it);
Expand Down
17 changes: 11 additions & 6 deletions Modules/_winapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -722,17 +722,22 @@ getenvironment(PyObject* environment)
return NULL;
}

envsize = PyMapping_Length(environment);

keys = PyMapping_Keys(environment);
values = PyMapping_Values(environment);
if (!keys || !values)
goto error;

envsize = PySequence_Fast_GET_SIZE(keys);
if (PySequence_Fast_GET_SIZE(values) != envsize) {
PyErr_SetString(PyExc_RuntimeError,
"environment changed size during iteration");
goto error;
}

totalsize = 1; /* trailing null character */
for (i = 0; i < envsize; i++) {
PyObject* key = PyList_GET_ITEM(keys, i);
PyObject* value = PyList_GET_ITEM(values, i);
PyObject* key = PySequence_Fast_GET_ITEM(keys, i);
PyObject* value = PySequence_Fast_GET_ITEM(values, i);

if (! PyUnicode_Check(key) || ! PyUnicode_Check(value)) {
PyErr_SetString(PyExc_TypeError,
Expand Down Expand Up @@ -760,8 +765,8 @@ getenvironment(PyObject* environment)
end = buffer + totalsize;

for (i = 0; i < envsize; i++) {
PyObject* key = PyList_GET_ITEM(keys, i);
PyObject* value = PyList_GET_ITEM(values, i);
PyObject* key = PySequence_Fast_GET_ITEM(keys, i);
PyObject* value = PySequence_Fast_GET_ITEM(values, i);
if (!PyUnicode_AsUCS4(key, p, end - p, 0))
goto error;
p += PyUnicode_GET_LENGTH(key);
Expand Down
3 changes: 3 additions & 0 deletions Modules/cjkcodecs/multibytecodec.c
Original file line number Diff line number Diff line change
Expand Up @@ -1670,6 +1670,9 @@ _multibytecodec_MultibyteStreamWriter_writelines(MultibyteStreamWriterObject *se
if (r == -1)
return NULL;
}
/* PySequence_Length() can fail */
if (PyErr_Occurred())
Copy link
Member
@zhangyangyu zhangyangyu Apr 14, 2017

Choose a reason for hiding this comment

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

Maybe cache the result of PySequence_Length. It's not free. And even lines is changed the behaviour is not bad.

Copy link
Member Author

Choose a reason for hiding this comment

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

This would be a behavior change. Since I'm going to backport most of these changes I prefer to keep the current behavior.

return NULL;

Py_RETURN_NONE;
}
Expand Down
3 changes: 2 additions & 1 deletion Modules/itertoolsmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -4325,7 +4325,7 @@ zip_longest_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
PyObject *ittuple; /* tuple of iterators */
PyObject *result;
PyObject *fillvalue = Py_None;
Py_ssize_t tuplesize = PySequence_Length(args);
Py_ssize_t tuplesize;

if (kwds != NULL && PyDict_CheckExact(kwds) && PyDict_GET_SIZE(kwds) > 0) {
fillvalue = PyDict_GetItemString(kwds, "fillvalue");
Expand All @@ -4338,6 +4338,7 @@ zip_longest_new(PyTypeObject *type, PyObject *args, PyObject *kwds)

/* args must be a tuple */
assert(PyTuple_Check(args));
tuplesize = PyTuple_GET_SIZE(args);

/* obtain iterators */
ittuple = PyTuple_New(tuplesize);
Expand Down
60 changes: 42 additions & 18 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -6642,14 +6642,17 @@ static PyObject *
os_setgroups(PyObject *module, PyObject *groups)
/*[clinic end generated code: output=3fcb32aad58c5ecd input=fa742ca3daf85a7e]*/
{
int i, len;
Py_ssize_t i, len;
gid_t grouplist[MAX_GROUPS];

if (!PySequence_Check(groups)) {
PyErr_SetString(PyExc_TypeError, "setgroups argument must be a sequence");
return NULL;
}
len = PySequence_Size(groups);
if (len < 0) {
return NULL;
}
if (len > MAX_GROUPS) {
PyErr_SetString(PyExc_ValueError, "too many groups");
return NULL;
Expand Down Expand Up @@ -7877,9 +7880,9 @@ os_read_impl(PyObject *module, int fd, Py_ssize_t length)
#if (defined(HAVE_SENDFILE) && (defined(__FreeBSD__) || defined(__DragonFly__) \
|| defined(__APPLE__))) || defined(HAVE_READV) || defined(HAVE_WRITEV)
static Py_ssize_t
iov_setup(struct iovec **iov, Py_buffer **buf, PyObject *seq, int cnt, int type)
iov_setup(struct iovec **iov, Py_buffer **buf, PyObject *seq, Py_ssize_t cnt, int type)
{
int i, j;
Py_ssize_t i, j;
Py_ssize_t blen, total = 0;

*iov = PyMem_New(struct iovec, cnt);
Expand Down Expand Up @@ -7956,8 +7959,7 @@ static Py_ssize_t
os_readv_impl(PyObject *module, int fd, PyObject *buffers)
/*[clinic end generated code: output=792da062d3fcebdb input=e679eb5dbfa0357d]*/
{
int cnt;
Py_ssize_t n;
Py_ssize_t cnt, n;
int async_err = 0;
struct iovec *iov;
Py_buffer *buf;
Expand All @@ -7969,6 +7971,8 @@ os_readv_impl(PyObject *module, int fd, PyObject *buffers)
}

cnt = PySequence_Size(buffers);
if (cnt < 0)
return -1;

if (iov_setup(&iov, &buf, buffers, cnt, PyBUF_WRITABLE) < 0)
return -1;
Expand Down Expand Up @@ -8107,15 +8111,24 @@ posix_sendfile(PyObject *self, PyObject *args, PyObject *kwdict)
"sendfile() headers must be a sequence");
return NULL;
} else {
Py_ssize_t i = 0; /* Avoid uninitialized warning */
sf.hdr_cnt = PySequence_Size(headers);
if (sf.hdr_cnt > 0 &&
(i = iov_setup(&(sf.headers), &hbuf,
headers, sf.hdr_cnt, PyBUF_SIMPLE)) < 0)
Py_ssize_t i = PySequence_Size(headers);
if (i < 0)
return NULL;
if (i > INT_MAX) {
PyErr_SetString(PyExc_OverflowError,
"sendfile() header is too large");
return NULL;
}
if (i > 0) {
sf.hdr_cnt = (int)i;
Copy link
Member

Choose a reason for hiding this comment

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

Re-scanning the patch I get a question here. Before PySequence_Size(headers) returns 0 and sf.hdr_cnt is set. Now when it returns 0, sf.hdr_cnt is uninitialized. Codes below still use sf but I don't know sf.hdr_cnt matters or not.

Copy link
Member Author

Choose a reason for hiding this comment

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

This doesn't differ from the case when arguments headers or trailers are not specified. sf.headers and sf.trailers are set to NULL and according to the documentation this should be enough.

Copy link
Member

Choose a reason for hiding this comment

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

I mean ret = sendfile(in, out, offset, &sbytes, &sf, flags). Is there any guarantee this function won't depend on sf.hdr_cnt? Could it be just a for loop and then cause dereferencing a NULL pointer?

i = iov_setup(&(sf.headers), &hbuf,
headers, sf.hdr_cnt, PyBUF_SIMPLE);
if (i < 0)
return NULL;
#ifdef __APPLE__
sbytes += i;
sbytes += i;
#endif
}
}
}
if (trailers != NULL) {
Expand All @@ -8124,15 +8137,24 @@ posix_sendfile(PyObject *self, PyObject *args, PyObject *kwdict)
"sendfile() trailers must be a sequence");
return NULL;
} else {
Py_ssize_t i = 0; /* Avoid uninitialized warning */
sf.trl_cnt = PySequence_Size(trailers);
if (sf.trl_cnt > 0 &&
(i = iov_setup(&(sf.trailers), &tbuf,
trailers, sf.trl_cnt, PyBUF_SIMPLE)) < 0)
Py_ssize_t i = PySequence_Size(trailers);
if (i < 0)
return NULL;
if (i > INT_MAX) {
PyErr_SetString(PyExc_OverflowError,
"sendfile() trailer is too large");
return NULL;
}
if (i > 0) {
sf.trl_cnt = (int)i;
i = iov_setup(&(sf.trailers), &tbuf,
trailers, sf.trl_cnt, PyBUF_SIMPLE);
if (i < 0)
return NULL;
#ifdef __APPLE__
sbytes += i;
sbytes += i;
#endif
}
}
}

Expand Down Expand Up @@ -8402,7 +8424,7 @@ static Py_ssize_t
os_writev_impl(PyObject *module, int fd, PyObject *buffers)
/*[clinic end generated code: output=56565cfac3aac15b input=5b8d17fe4189d2fe]*/
{
int cnt;
Py_ssize_t cnt;
Py_ssize_t result;
int async_err = 0;
struct iovec *iov;
Expand All @@ -8414,6 +8436,8 @@ os_writev_impl(PyObject *module, int fd, PyObject *buffers)
return -1;
}
cnt = PySequence_Size(buffers);
if (cnt < 0)
return -1;

if (iov_setup(&iov, &buf, buffers, cnt, PyBUF_SIMPLE) < 0) {
return -1;
Expand Down
4 changes: 2 additions & 2 deletions Modules/selectmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2021,8 +2021,8 @@ newKqueue_Object(PyTypeObject *type, SOCKET fd)
static PyObject *
kqueue_queue_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
if ((args != NULL && PyObject_Size(args)) ||
(kwds != NULL && PyObject_Size(kwds))) {
if (PyTuple_GET_SIZE(args) ||
(kwds != NULL && PyDict_GET_SIZE(kwds))) {
PyErr_SetString(PyExc_ValueError,
"select.kqueue doesn't accept arguments");
return NULL;
Expand Down
2 changes: 1 addition & 1 deletion Objects/exceptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -2790,7 +2790,7 @@ _PyErr_TrySetFromCause(const char *format, ...)
/* Ensure the instance dict is also empty */
dictptr = _PyObject_GetDictPtr(val);
if (dictptr != NULL && *dictptr != NULL &&
PyObject_Length(*dictptr) > 0) {
PyDict_GET_SIZE(*dictptr) > 0) {
/* While we could potentially copy a non-empty instance dictionary
* to the replacement exception, for now we take the more
* conservative path of leaving exceptions with attributes set
Expand Down
12 changes: 3 additions & 9 deletions Objects/namespaceobject.c
10000
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,9 @@ namespace_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
static int
namespace_init(_PyNamespaceObject *ns, PyObject *args, PyObject *kwds)
{
// ignore args if it's NULL or empty
if (args != NULL) {
Py_ssize_t argcount = PyObject_Size(args);
if (argcount < 0)
return -1;
else if (argcount > 0) {
PyErr_Format(PyExc_TypeError, "no positional arguments expected");
return -1;
}
if (PyTuple_GET_SIZE(args) != 0) {
PyErr_Format(PyExc_TypeError, "no positional arguments expected");
return -1;
}
if (kwds == NULL)
return 0;
Expand Down
12 changes: 9 additions & 3 deletions Objects/setobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1546,20 +1546,26 @@ set_difference(PySetObject *so, PyObject *other)
PyObject *key;
Py_hash_t hash;
setentry *entry;
Py_ssize_t pos = 0;
Py_ssize_t pos = 0, other_size;
int rv;

if (PySet_GET_SIZE(so) == 0) {
return set_copy(so);
}

if (!PyAnySet_Check(other) && !PyDict_CheckExact(other)) {
if (PyAnySet_Check(other)) {
other_size = PySet_GET_SIZE(other);
}
else if (PyDict_CheckExact(other)) {
other_size = PyDict_GET_SIZE(other);
}
else {
return set_copy_and_difference(so, other);
}

/* If len(so) much more than len(other), it's more efficient to simply copy
* so and then iterate other looking for common elements. */
if ((PySet_GET_SIZE(so) >> 2) > PyObject_Size(other)) {
if ((PySet_GET_SIZE(so) >> 2) > other_size) {
return set_copy_and_difference(so, other);
}

Expand Down
3 changes: 2 additions & 1 deletion Python/bltinmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2442,13 +2442,14 @@ zip_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
Py_ssize_t i;
PyObject *ittuple; /* tuple of iterators */
PyObject *result;
Py_ssize_t tuplesize = PySequence_Length(args);
Py_ssize_t tuplesize;

if (type == &PyZip_Type && !_PyArg_NoKeywords("zip()", kwds))
return NULL;

/* args must be a tuple */
assert(PyTuple_Check(args));
tuplesize = PyTuple_GET_SIZE(args);

/* obtain iterators */
ittuple = PyTuple_New(tuplesize);
Expand Down
0