summaryrefslogtreecommitdiffstats
path: root/Modules
diff options
context:
space:
mode:
authorAntoine Pitrou <antoine@python.org>2021-03-05 09:32:50 (GMT)
committerGitHub <noreply@github.com>2021-03-05 09:32:50 (GMT)
commit68245b7a1030287294c65c298975ab9026543fd2 (patch)
tree629f43bc1fe007f83456358633362358b51535e9 /Modules
parent02ac6f41e5569ec28d625bb005155903f64cc9ee (diff)
downloadcpython-68245b7a1030287294c65c298975ab9026543fd2.zip
cpython-68245b7a1030287294c65c298975ab9026543fd2.tar.gz
cpython-68245b7a1030287294c65c298975ab9026543fd2.tar.bz2
bpo-43406: Fix possible race condition where ``PyErr_CheckSignals`` tries to execute a non-Python signal handler (GH-24756)
We can receive signals (at the C level, in `trip_signal()` in signalmodule.c) while `signal.signal` is being called to modify the corresponding handler. Later when `PyErr_CheckSignals()` is called to handle the given signal, the handler may be a non-callable object and would raise a cryptic asynchronous exception.
Diffstat (limited to 'Modules')
-rw-r--r--Modules/signalmodule.c26
1 files changed, 25 insertions, 1 deletions
diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c
index 7ac797a..c6564be 100644
--- a/Modules/signalmodule.c
+++ b/Modules/signalmodule.c
@@ -1706,10 +1706,34 @@ _PyErr_CheckSignalsTstate(PyThreadState *tstate)
}
_Py_atomic_store_relaxed(&Handlers[i].tripped, 0);
+ /* Signal handlers can be modified while a signal is received,
+ * and therefore the fact that trip_signal() or PyErr_SetInterrupt()
+ * was called doesn't guarantee that there is still a Python
+ * signal handler for it by the time PyErr_CheckSignals() is called
+ * (see bpo-43406).
+ */
+ PyObject *func = Handlers[i].func;
+ if (func == NULL || func == Py_None || func == IgnoreHandler ||
+ func == DefaultHandler) {
+ /* No Python signal handler due to aforementioned race condition.
+ * We can't call raise() as it would break the assumption
+ * that PyErr_SetInterrupt() only *simulates* an incoming
+ * signal (i.e. it will never kill the process).
+ * We also don't want to interrupt user code with a cryptic
+ * asynchronous exception, so instead just write out an
+ * unraisable error.
+ */
+ PyErr_Format(PyExc_OSError,
+ "Signal %i ignored due to race condition",
+ i);
+ PyErr_WriteUnraisable(Py_None);
+ continue;
+ }
+
PyObject *arglist = Py_BuildValue("(iO)", i, frame);
PyObject *result;
if (arglist) {
- result = _PyObject_Call(tstate, Handlers[i].func, arglist, NULL);
+ result = _PyObject_Call(tstate, func, arglist, NULL);
Py_DECREF(arglist);
}
else {