Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ab6f2f6

Browse files
committed
Fix segfaults when running test_exceptions with coverage tracing, caused by wrongly defining Exception.__context__ as a T_OBJECT structmember which does not set the member to NULL on None assignment, and generally does not do type checks. This could be used to crash the interpreter by setting any object to __context__. The same applies to __cause__. Also document the PyException_* functions.
1 parent 71095ea commit ab6f2f6

4 files changed

Lines changed: 114 additions & 10 deletions

File tree

Doc/c-api/exceptions.rst

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,52 @@ in various ways. There is a separate error indicator for each thread.
400400
the warning message.
401401

402402

403+
Exception Objects
404+
=================
405+
406+
.. cfunction:: PyObject* PyException_GetTraceback(PyObject *ex)
407+
408+
Return the traceback associated with the exception as a new reference, as
409+
accessible from Python through :attr:`__traceback__`. If there is no
410+
traceback associated, this returns *NULL*.
411+
412+
413+
.. cfunction:: int PyException_SetTraceback(PyObject *ex, PyObject *tb)
414+
415+
Set the traceback associated with the exception to *tb*. Use ``Py_None`` to
416+
clear it.
417+
418+
419+
.. cfunction:: PyObject* PyException_GetContext(PyObject *ex)
420+
421+
Return the context (another exception instance during whose handling *ex* was
422+
raised) associated with the exception as a new reference, as accessible from
423+
Python through :attr:`__context__`. If there is no context associated, this
424+
returns *NULL*.
425+
426+
427+
.. cfunction:: void PyException_SetContext(PyObject *ex, PyObject *ctx)
428+
429+
Set the context associated with the exception to *ctx*. Use *NULL* to clear
430+
it. There is no type check to make sure that *ctx* is an exception instance.
431+
This steals a reference to *ctx*.
432+
433+
434+
.. cfunction:: PyObject* PyException_GetCause(PyObject *ex)
435+
436+
Return the cause (another exception instance set by ``raise ... from ...``)
437+
associated with the exception as a new reference, as accessible from Python
438+
through :attr:`__cause__`. If there is no cause associated, this returns
439+
*NULL*.
440+
441+
442+
.. cfunction:: void PyException_SetCause(PyObject *ex, PyObject *ctx)
443+
444+
Set the cause associated with the exception to *ctx*. Use *NULL* to clear
445+
it. There is no type check to make sure that *ctx* is an exception instance.
446+
This steals a reference to *ctx*.
447+
448+
403449
.. _standardexceptions:
404450

405451
Standard Exceptions

Lib/test/test_exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,12 @@ def testInvalidTraceback(self):
341341
else:
342342
self.fail("No exception raised")
343343

344+
def testInvalidAttrs(self):
345+
self.assertRaises(TypeError, setattr, Exception(), '__cause__', 1)
346+
self.assertRaises(TypeError, delattr, Exception(), '__cause__')
347+
self.assertRaises(TypeError, setattr, Exception(), '__context__', 1)
348+
self.assertRaises(TypeError, delattr, Exception(), '__context__')
349+
344350
def testNoneClearsTracebackAttr(self):
345351
try:
346352
raise IndexError(4)

Misc/NEWS

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
+++++++++++ Python News
1+
+++++++++++
2+
Python News
23
+++++++++++
34

45
(editors: check NEWS.help for information about editing NEWS using ReST.)
@@ -11,6 +12,9 @@ What's New in Python 3.1 alpha 2?
1112
Core and Builtins
1213
-----------------
1314

15+
- Fix a segfault when running test_exceptions with coverage, caused by
16+
insufficient checks in accessors of Exception.__context__.
17+
1418
- Issue #5604: non-ASCII characters in module name passed to
1519
imp.find_module() were converted to UTF-8 while the path is
1620
converted to the default filesystem encoding, causing nonsense.

Objects/exceptions.c

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -250,11 +250,67 @@ BaseException_set_tb(PyBaseExceptionObject *self, PyObject *tb)
250250
return 0;
251251
}
252252

253+
static PyObject *
254+
BaseException_get_context(PyObject *self) {
255+
PyObject *res = PyException_GetContext(self);
256+
if (res) return res; /* new reference already returned above */
257+
Py_RETURN_NONE;
258+
}
259+
260+
static int
261+
BaseException_set_context(PyObject *self, PyObject *arg) {
262+
if (arg == NULL) {
263+
PyErr_SetString(PyExc_TypeError, "__context__ may not be deleted");
264+
return -1;
265+
} else if (arg == Py_None) {
266+
arg = NULL;
267+
} else if (!PyExceptionInstance_Check(arg)) {
268+
PyErr_SetString(PyExc_TypeError, "exception context must be None "
269+
"or derive from BaseException");
270+
return -1;
271+
} else {
272+
/* PyException_SetContext steals this reference */
273+
Py_INCREF(arg);
274+
}
275+
PyException_SetContext(self, arg);
276+
return 0;
277+
}
278+
279+
static PyObject *
280+
BaseException_get_cause(PyObject *self) {
281+
PyObject *res = PyException_GetCause(self);
282+
if (res) return res; /* new reference already returned above */
283+
Py_RETURN_NONE;
284+
}
285+
286+
static int
287+
BaseException_set_cause(PyObject *self, PyObject *arg) {
288+
if (arg == NULL) {
289+
PyErr_SetString(PyExc_TypeError, "__cause__ may not be deleted");
290+
return -1;
291+
} else if (arg == Py_None) {
292+
arg = NULL;
293+
} else if (!PyExceptionInstance_Check(arg)) {
294+
PyErr_SetString(PyExc_TypeError, "exception cause must be None "
295+
"or derive from BaseException");
296+
return -1;
297+
} else {
298+
/* PyException_SetCause steals this reference */
299+
Py_INCREF(arg);
300+
}
301+
PyException_SetCause(self, arg);
302+
return 0;
303+
}
304+
253305

254306
static PyGetSetDef BaseException_getset[] = {
255307
{"__dict__", (getter)BaseException_get_dict, (setter)BaseException_set_dict},
256308
{"args", (getter)BaseException_get_args, (setter)BaseException_set_args},
257309
{"__traceback__", (getter)BaseException_get_tb, (setter)BaseException_set_tb},
310+
{"__context__", (getter)BaseException_get_context,
311+
(setter)BaseException_set_context, PyDoc_STR("exception context")},
312+
{"__cause__", (getter)BaseException_get_cause,
313+
(setter)BaseException_set_cause, PyDoc_STR("exception cause")},
258314
{NULL},
259315
};
260316

@@ -303,14 +359,6 @@ PyException_SetContext(PyObject *self, PyObject *context) {
303359
}
304360

305361

306-
static PyMemberDef BaseException_members[] = {
307-
{"__context__", T_OBJECT, offsetof(PyBaseExceptionObject, context), 0,
308-
PyDoc_STR("exception context")},
309-
{"__cause__", T_OBJECT, offsetof(PyBaseExceptionObject, cause), 0,
310-
PyDoc_STR("exception cause")},
311-
{NULL} /* Sentinel */
312-
};
313-
314362
static PyTypeObject _PyExc_BaseException = {
315363
PyVarObject_HEAD_INIT(NULL, 0)
316364
"BaseException", /*tp_name*/
@@ -341,7 +389,7 @@ static PyTypeObject _PyExc_BaseException = {
341389
0, /* tp_iter */
342390
0, /* tp_iternext */
343391
BaseException_methods, /* tp_methods */
344-
BaseException_members, /* tp_members */
392+
0, /* tp_members */
345393
BaseException_getset, /* tp_getset */
346394
0, /* tp_base */
347395
0, /* tp_dict */

0 commit comments

Comments
 (0)