Skip to content

Commit 451f291

Browse files
authored
gh-128130: Fix unhandled keyboard interrupt data race (gh-129975)
Use an atomic operation when setting `_PyRuntime.signals.unhandled_keyboard_interrupt`. We now only clear the variable at the start of `_PyRun_Main`, which is the same function where we check it. This avoids race conditions where previously another thread might call `run_eval_code_obj()` and erroneously clear the unhandled keyboard interrupt.
1 parent aa28423 commit 451f291

File tree

4 files changed

+13
-36
lines changed

4 files changed

+13
-36
lines changed

Include/internal/pycore_pylifecycle.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ extern PyStatus _Py_PreInitializeFromConfig(
7575

7676
extern wchar_t * _Py_GetStdlibDir(void);
7777

78-
extern int _Py_HandleSystemExit(int *exitcode_p);
78+
extern int _Py_HandleSystemExitAndKeyboardInterrupt(int *exitcode_p);
7979

8080
extern PyObject* _PyErr_WriteUnraisableDefaultHook(PyObject *unraisable);
8181

Modules/main.c

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ static int
9999
pymain_err_print(int *exitcode_p)
100100
{
101101
int exitcode;
102-
if (_Py_HandleSystemExit(&exitcode)) {
102+
if (_Py_HandleSystemExitAndKeyboardInterrupt(&exitcode)) {
103103
*exitcode_p = exitcode;
104104
return 1;
105105
}
@@ -292,11 +292,7 @@ pymain_start_pyrepl_no_main(void)
292292
goto done;
293293
}
294294
if (!PyDict_SetItemString(kwargs, "pythonstartup", _PyLong_GetOne())) {
295-
_PyRuntime.signals.unhandled_keyboard_interrupt = 0;
296295
console_result = PyObject_Call(console, empty_tuple, kwargs);
297-
if (!console_result && PyErr_Occurred() == PyExc_KeyboardInterrupt) {
298-
_PyRuntime.signals.unhandled_keyboard_interrupt = 1;
299-
}
300296
if (console_result == NULL) {
301297
res = pymain_exit_err_print();
302298
}
@@ -338,11 +334,7 @@ pymain_run_module(const wchar_t *modname, int set_argv0)
338334
Py_DECREF(module);
339335
return pymain_exit_err_print();
340336
}
341-
_PyRuntime.signals.unhandled_keyboard_interrupt = 0;
342337
result = PyObject_Call(runmodule, runargs, NULL);
343-
if (!result && PyErr_Occurred() == PyExc_KeyboardInterrupt) {
344-
_PyRuntime.signals.unhandled_keyboard_interrupt = 1;
345-
}
346338
Py_DECREF(runmodule);
347339
Py_DECREF(module);
348340
Py_DECREF(runargs);
@@ -763,6 +755,8 @@ Py_RunMain(void)
763755
{
764756
int exitcode = 0;
765757

758+
_PyRuntime.signals.unhandled_keyboard_interrupt = 0;
759+
766760
pymain_run_python(&exitcode);
767761

768762
if (Py_FinalizeEx() < 0) {

Python/pythonrun.c

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -589,8 +589,13 @@ parse_exit_code(PyObject *code, int *exitcode_p)
589589
}
590590

591591
int
592-
_Py_HandleSystemExit(int *exitcode_p)
592+
_Py_HandleSystemExitAndKeyboardInterrupt(int *exitcode_p)
593593
{
594+
if (PyErr_ExceptionMatches(PyExc_KeyboardInterrupt)) {
595+
_Py_atomic_store_int(&_PyRuntime.signals.unhandled_keyboard_interrupt, 1);
596+
return 0;
597+
}
598+
594599
int inspect = _Py_GetConfig()->inspect;
595600
if (inspect) {
596601
/* Don't exit if -i flag was given. This flag is set to 0
@@ -646,7 +651,7 @@ static void
646651
handle_system_exit(void)
647652
{
648653
int exitcode;
649-
if (_Py_HandleSystemExit(&exitcode)) {
654+
if (_Py_HandleSystemExitAndKeyboardInterrupt(&exitcode)) {
650655
Py_Exit(exitcode);
651656
}
652657
}
@@ -1105,8 +1110,6 @@ _PyErr_Display(PyObject *file, PyObject *unused, PyObject *value, PyObject *tb)
11051110
}
11061111
}
11071112

1108-
int unhandled_keyboard_interrupt = _PyRuntime.signals.unhandled_keyboard_interrupt;
1109-
11101113
// Try first with the stdlib traceback module
11111114
PyObject *print_exception_fn = PyImport_ImportModuleAttrString(
11121115
"traceback",
@@ -1120,11 +1123,9 @@ _PyErr_Display(PyObject *file, PyObject *unused, PyObject *value, PyObject *tb)
11201123
Py_XDECREF(print_exception_fn);
11211124
if (result) {
11221125
Py_DECREF(result);
1123-
_PyRuntime.signals.unhandled_keyboard_interrupt = unhandled_keyboard_interrupt;
11241126
return;
11251127
}
11261128
fallback:
1127-
_PyRuntime.signals.unhandled_keyboard_interrupt = unhandled_keyboard_interrupt;
11281129
#ifdef Py_DEBUG
11291130
if (PyErr_Occurred()) {
11301131
PyErr_FormatUnraisable(
@@ -1297,20 +1298,6 @@ flush_io(void)
12971298
static PyObject *
12981299
run_eval_code_obj(PyThreadState *tstate, PyCodeObject *co, PyObject *globals, PyObject *locals)
12991300
{
1300-
PyObject *v;
1301-
/*
1302-
* We explicitly re-initialize _Py_UnhandledKeyboardInterrupt every eval
1303-
* _just in case_ someone is calling into an embedded Python where they
1304-
* don't care about an uncaught KeyboardInterrupt exception (why didn't they
1305-
* leave config.install_signal_handlers set to 0?!?) but then later call
1306-
* Py_Main() itself (which _checks_ this flag and dies with a signal after
1307-
* its interpreter exits). We don't want a previous embedded interpreter's
1308-
* uncaught exception to trigger an unexplained signal exit from a future
1309-
* Py_Main() based one.
1310-
*/
1311-
// XXX Isn't this dealt with by the move to _PyRuntimeState?
1312-
_PyRuntime.signals.unhandled_keyboard_interrupt = 0;
1313-
13141301
/* Set globals['__builtins__'] if it doesn't exist */
13151302
if (!globals || !PyDict_Check(globals)) {
13161303
PyErr_SetString(PyExc_SystemError, "globals must be a real dict");
@@ -1328,11 +1315,7 @@ run_eval_code_obj(PyThreadState *tstate, PyCodeObject *co, PyObject *globals, Py
13281315
}
13291316
}
13301317

1331-
v = PyEval_EvalCode((PyObject*)co, globals, locals);
1332-
if (!v && _PyErr_Occurred(tstate) == PyExc_KeyboardInterrupt) {
1333-
_PyRuntime.signals.unhandled_keyboard_interrupt = 1;
1334-
}
1335-
return v;
1318+
return PyEval_EvalCode((PyObject*)co, globals, locals);
13361319
}
13371320

13381321
static PyObject *

Tools/c-analyzer/TODO

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ Python/pythonrun.c:PyId_stdin _Py_IDENTIFIER(
532532
Python/pythonrun.c:PyId_stdout _Py_IDENTIFIER(stdout)
533533
Python/pythonrun.c:PyRun_InteractiveOneObjectEx():PyId___main__ _Py_IDENTIFIER(__main__)
534534
Python/pythonrun.c:PyRun_InteractiveOneObjectEx():PyId_encoding _Py_IDENTIFIER(encoding)
535-
Python/pythonrun.c:_Py_HandleSystemExit():PyId_code _Py_IDENTIFIER(code)
535+
Python/pythonrun.c:_Py_HandleSystemExitAndKeyboardInterrupt():PyId_code _Py_IDENTIFIER(code)
536536
Python/pythonrun.c:parse_syntax_error():PyId_filename _Py_IDENTIFIER(filename)
537537
Python/pythonrun.c:parse_syntax_error():PyId_lineno _Py_IDENTIFIER(lineno)
538538
Python/pythonrun.c:parse_syntax_error():PyId_msg _Py_IDENTIFIER(msg)

0 commit comments

Comments
 (0)