Skip to content
25 changes: 25 additions & 0 deletions Lib/test/test_kqueue.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,31 @@ def testPair(self):
b.close()
kq.close()

def test_issue30058(self):
# chagelist must be an iterable
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: chagelist -> changelist

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch @lulouie!

kq = select.kqueue()
a, b = socket.socketpair()
ev = select.kevent(a, select.KQ_FILTER_READ, select.KQ_EV_ADD | select.KQ_EV_ENABLE)

kq.control([ev], 0)
# not a list
kq.control((ev,), 0)
# __len__ is not consistent with __iter__
class BadList:
def __len__(self):
return 0
def __iter__(self):
for i in range(100):
yield select.kevent(a, select.KQ_FILTER_READ,
select.KQ_EV_ADD | select.KQ_EV_ENABLE)
kq.control(BadList(), 0)
# doesn't have __len__
kq.control(iter([ev]), 0)

a.close()
b.close()
kq.close()

def test_close(self):
open_file = open(__file__, "rb")
self.addCleanup(open_file.close)
Expand Down
3 changes: 3 additions & 0 deletions Misc/NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,9 @@ Extension Modules

Library
-------

- bpo-30058: Fixed buffer overflow in select.kqueue.control().

- bpo-29692: Fixed arbitrary unchaining of RuntimeError exceptions in
contextlib.contextmanager.
Patch by Siddharth Velankar.
Expand Down
24 changes: 9 additions & 15 deletions Modules/selectmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2102,7 +2102,7 @@ kqueue_queue_control(kqueue_queue_Object *self, PyObject *args)
int i = 0;
PyObject *otimeout = NULL;
PyObject *ch = NULL;
PyObject *it = NULL, *ei = NULL;
PyObject *seq = NULL, *ei = NULL;
PyObject *result = NULL;
struct kevent *evl = NULL;
struct kevent *chl = NULL;
Expand Down Expand Up @@ -2148,37 +2148,31 @@ kqueue_queue_control(kqueue_queue_Object *self, PyObject *args)
}

if (ch != NULL && ch != Py_None) {
it = PyObject_GetIter(ch);
if (it == NULL) {
PyErr_SetString(PyExc_TypeError,
"changelist is not iterable");
seq = PySequence_Fast(ch, "changelist is not iterable");
if (seq == NULL) {
return NULL;
}
nchanges = PyObject_Size(ch);
if (nchanges < 0) {
goto error;
}
nchanges = PySequence_Fast_GET_SIZE(arg);
Copy link
Member

Choose a reason for hiding this comment

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

PySequence_Fast_GET_SIZE returns a Py_ssize_t. This may trigger a compile warning.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point!


chl = PyMem_New(struct kevent, nchanges);
if (chl == NULL) {
PyErr_NoMemory();
goto error;
}
i = 0;
while ((ei = PyIter_Next(it)) != NULL) {
for (i = 0; i < nchanges; ++i) {
ei = PySequence_Fast_GET_ITEM(seq, i);
if (!kqueue_event_Check(ei)) {
Py_DECREF(ei);
PyErr_SetString(PyExc_TypeError,
"changelist must be an iterable of "
"select.kevent objects");
goto error;
} else {
chl[i++] = ((kqueue_event_Object *)ei)->e;
}
Py_DECREF(ei);
}
}
Py_CLEAR(it);
Py_CLEAR(seq);

/* event list */
if (nevents) {
Expand Down Expand Up @@ -2246,15 +2240,15 @@ kqueue_queue_control(kqueue_queue_Object *self, PyObject *args)
PyMem_Free(chl);
PyMem_Free(evl);
Py_XDECREF(result);
Py_XDECREF(it);
Py_XDECREF(seq);
return NULL;
}

PyDoc_STRVAR(kqueue_queue_control_doc,
"control(changelist, max_events[, timeout=None]) -> eventlist\n\
\n\
Calls the kernel kevent function.\n\
- changelist must be a list of kevent objects describing the changes\n\
- changelist must be an iterable of kevent objects describing the changes\n\
to be made to the kernel's watch list or None.\n\
- max_events lets you specify the maximum number of events that the\n\
kernel will return.\n\
Expand Down