Skip to content

Commit 4cf5fc4

Browse files
[3.12] gh-130163: Fix crashes related to PySys_GetObject() (GH-130503) (GH-130556)
The use of PySys_GetObject() and _PySys_GetAttr(), which return a borrowed reference, has been replaced by using one of the following functions, which return a strong reference and distinguish a missing attribute from an error: _PySys_GetOptionalAttr(), _PySys_GetOptionalAttrString(), _PySys_GetRequiredAttr(), and _PySys_GetRequiredAttrString(). (cherry picked from commit 0ef4ffe) (cherry picked from commit 7c1b76f) Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 7575abb commit 4cf5fc4

23 files changed

+502
-159
lines changed

Include/internal/pycore_sysmodule.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ extern "C" {
88
# error "this header requires Py_BUILD_CORE define"
99
#endif
1010

11+
PyAPI_FUNC(int) _PySys_GetOptionalAttr(PyObject *, PyObject **);
12+
PyAPI_FUNC(int) _PySys_GetOptionalAttrString(const char *, PyObject **);
13+
PyAPI_FUNC(PyObject *) _PySys_GetRequiredAttr(PyObject *);
14+
PyAPI_FUNC(PyObject *) _PySys_GetRequiredAttrString(const char *);
15+
1116
PyAPI_FUNC(int) _PySys_Audit(
1217
PyThreadState *tstate,
1318
const char *event,

Lib/test/test_builtin.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1526,6 +1526,29 @@ def test_input(self):
15261526
sys.stdout = savestdout
15271527
fp.close()
15281528

1529+
def test_input_gh130163(self):
1530+
class X(io.StringIO):
1531+
def __getattribute__(self, name):
1532+
nonlocal patch
1533+
if patch:
1534+
patch = False
1535+
sys.stdout = X()
1536+
sys.stderr = X()
1537+
sys.stdin = X('input\n')
1538+
support.gc_collect()
1539+
return io.StringIO.__getattribute__(self, name)
1540+
1541+
with (support.swap_attr(sys, 'stdout', None),
1542+
support.swap_attr(sys, 'stderr', None),
1543+
support.swap_attr(sys, 'stdin', None)):
1544+
patch = False
1545+
# the only references:
1546+
sys.stdout = X()
1547+
sys.stderr = X()
1548+
sys.stdin = X('input\n')
1549+
patch = True
1550+
input() # should not crash
1551+
15291552
# test_int(): see test_int.py for tests of built-in function int().
15301553

15311554
def test_repr(self):

Lib/test/test_print.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,17 @@ def flush(self):
129129
raise RuntimeError
130130
self.assertRaises(RuntimeError, print, 1, file=noflush(), flush=True)
131131

132+
def test_gh130163(self):
133+
class X:
134+
def __str__(self):
135+
sys.stdout = StringIO()
136+
support.gc_collect()
137+
return 'foo'
138+
139+
with support.swap_attr(sys, 'stdout', None):
140+
sys.stdout = StringIO() # the only reference
141+
print(X()) # should not crash
142+
132143

133144
class TestPy2MigrationHint(unittest.TestCase):
134145
"""Test that correct hint is produced analogous to Python3 syntax,

Lib/test/test_sys.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import builtins
22
import codecs
33
import gc
4+
import io
45
import locale
56
import operator
67
import os
@@ -79,6 +80,18 @@ def baddisplayhook(obj):
7980
code = compile("42", "<string>", "single")
8081
self.assertRaises(ValueError, eval, code)
8182

83+
def test_gh130163(self):
84+
class X:
85+
def __repr__(self):
86+
sys.stdout = io.StringIO()
87+
support.gc_collect()
88+
return 'foo'
89+
90+
with support.swap_attr(sys, 'stdout', None):
91+
sys.stdout = io.StringIO() # the only reference
92+
sys.displayhook(X()) # should not crash
93+
94+
8295
class ActiveExceptionTests(unittest.TestCase):
8396
def test_exc_info_no_exception(self):
8497
self.assertEqual(sys.exc_info(), (None, None, None))
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix possible crashes related to concurrent
2+
change and use of the :mod:`sys` module attributes.

Modules/_cursesmodule.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ static const char PyCursesVersion[] = "2.2";
109109
#include "Python.h"
110110
#include "pycore_long.h" // _PyLong_GetZero()
111111
#include "pycore_structseq.h" // _PyStructSequence_NewType()
112+
#include "pycore_sysmodule.h" // _PySys_GetOptionalAttrString()
112113

113114
#ifdef __hpux
114115
#define STRICT_SYSV_CURSES
@@ -3375,17 +3376,20 @@ _curses_setupterm_impl(PyObject *module, const char *term, int fd)
33753376
if (fd == -1) {
33763377
PyObject* sys_stdout;
33773378

3378-
sys_stdout = PySys_GetObject("stdout");
3379+
if (_PySys_GetOptionalAttrString("stdout", &sys_stdout) < 0) {
3380+
return NULL;
3381+
}
33793382

33803383
if (sys_stdout == NULL || sys_stdout == Py_None) {
33813384
PyErr_SetString(
33823385
PyCursesError,
33833386
"lost sys.stdout");
3387+
Py_XDECREF(sys_stdout);
33843388
return NULL;
33853389
}
33863390

33873391
fd = PyObject_AsFileDescriptor(sys_stdout);
3388-
3392+
Py_DECREF(sys_stdout);
33893393
if (fd == -1) {
33903394
return NULL;
33913395
}

Modules/_pickle.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "pycore_moduleobject.h" // _PyModule_GetState()
1414
#include "pycore_runtime.h" // _Py_ID()
1515
#include "pycore_pystate.h" // _PyThreadState_GET()
16+
#include "pycore_sysmodule.h" // _PySys_GetSizeOf()
1617
#include "structmember.h" // PyMemberDef
1718

1819
#include <stdlib.h> // strtol()
@@ -1984,10 +1985,8 @@ whichmodule(PyObject *global, PyObject *dotted_path)
19841985
assert(module_name == NULL);
19851986

19861987
/* Fallback on walking sys.modules */
1987-
PyThreadState *tstate = _PyThreadState_GET();
1988-
modules = _PySys_GetAttr(tstate, &_Py_ID(modules));
1988+
modules = _PySys_GetRequiredAttr(&_Py_ID(modules));
19891989
if (modules == NULL) {
1990-
PyErr_SetString(PyExc_RuntimeError, "unable to get sys.modules");
19911990
return NULL;
19921991
}
19931992
if (PyDict_CheckExact(modules)) {

Modules/_threadmodule.c

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#include "pycore_moduleobject.h" // _PyModule_GetState()
88
#include "pycore_pylifecycle.h"
99
#include "pycore_pystate.h" // _PyThreadState_SetCurrent()
10+
#include "pycore_sysmodule.h" // _PySys_GetOptionalAttr()
11+
1012
#include <stddef.h> // offsetof()
1113
#include "structmember.h" // PyMemberDef
1214

@@ -1567,9 +1569,12 @@ thread_excepthook(PyObject *module, PyObject *args)
15671569
PyObject *exc_tb = PyStructSequence_GET_ITEM(args, 2);
15681570
PyObject *thread = PyStructSequence_GET_ITEM(args, 3);
15691571

1570-
PyThreadState *tstate = _PyThreadState_GET();
1571-
PyObject *file = _PySys_GetAttr(tstate, &_Py_ID(stderr));
1572+
PyObject *file;
1573+
if (_PySys_GetOptionalAttr( &_Py_ID(stderr), &file) < 0) {
1574+
return NULL;
1575+
}
15721576
if (file == NULL || file == Py_None) {
1577+
Py_XDECREF(file);
15731578
if (thread == Py_None) {
15741579
/* do nothing if sys.stderr is None and thread is None */
15751580
Py_RETURN_NONE;
@@ -1586,9 +1591,6 @@ thread_excepthook(PyObject *module, PyObject *args)
15861591
Py_RETURN_NONE;
15871592
}
15881593
}
1589-
else {
1590-
Py_INCREF(file);
1591-
}
15921594

15931595
int res = thread_excepthook_file(file, exc_type, exc_value, exc_tb,
15941596
thread);

Modules/_tkinter.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Copyright (C) 1994 Steen Lumholt.
3333
#endif
3434

3535
#include "pycore_long.h"
36+
#include "pycore_sysmodule.h" // _PySys_GetOptionalAttrString()
3637

3738
#ifdef MS_WINDOWS
3839
#include <windows.h>
@@ -145,6 +146,7 @@ _get_tcl_lib_path(void)
145146
PyObject *prefix;
146147
struct stat stat_buf;
147148
int stat_return_value;
149+
PyObject *prefix;
148150

149151
prefix = PyUnicode_FromWideChar(Py_GetPrefix(), -1);
150152
if (prefix == NULL) {
@@ -154,9 +156,11 @@ _get_tcl_lib_path(void)
154156
/* Check expected location for an installed Python first */
155157
tcl_library_path = PyUnicode_FromString("\\tcl\\tcl" TCL_VERSION);
156158
if (tcl_library_path == NULL) {
159+
Py_DECREF(prefix);
157160
return NULL;
158161
}
159162
tcl_library_path = PyUnicode_Concat(prefix, tcl_library_path);
163+
Py_DECREF(prefix);
160164
if (tcl_library_path == NULL) {
161165
return NULL;
162166
}
@@ -3509,6 +3513,7 @@ PyInit__tkinter(void)
35093513
uexe = PyUnicode_FromWideChar(Py_GetProgramName(), -1);
35103514
if (uexe) {
35113515
cexe = PyUnicode_EncodeFSDefault(uexe);
3516+
Py_DECREF(uexe);
35123517
if (cexe) {
35133518
#ifdef MS_WINDOWS
35143519
int set_var = 0;
@@ -3521,12 +3526,14 @@ PyInit__tkinter(void)
35213526
if (!ret && GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
35223527
str_path = _get_tcl_lib_path();
35233528
if (str_path == NULL && PyErr_Occurred()) {
3529+
Py_DECREF(cexe);
35243530
Py_DECREF(m);
35253531
return NULL;
35263532
}
35273533
if (str_path != NULL) {
35283534
wcs_path = PyUnicode_AsWideCharString(str_path, NULL);
35293535
if (wcs_path == NULL) {
3536+
Py_DECREF(cexe);
35303537
Py_DECREF(m);
35313538
return NULL;
35323539
}
@@ -3548,6 +3555,9 @@ PyInit__tkinter(void)
35483555
Py_XDECREF(cexe);
35493556
Py_DECREF(uexe);
35503557
}
3558+
else {
3559+
Py_XDECREF(uexe);
3560+
}
35513561

35523562
if (PyErr_Occurred()) {
35533563
Py_DECREF(m);

Modules/faulthandler.c

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "pycore_pyerrors.h" // _Py_DumpExtensionModules
44
#include "pycore_pystate.h" // _PyThreadState_GET()
55
#include "pycore_signal.h" // Py_NSIG
6+
#include "pycore_sysmodule.h" // _PySys_GetRequiredAttr()
67
#include "pycore_traceback.h" // _Py_DumpTracebackThreads
78

89
#include <object.h>
@@ -104,14 +105,13 @@ faulthandler_get_fileno(PyObject **file_ptr)
104105
PyObject *file = *file_ptr;
105106

106107
if (file == NULL || file == Py_None) {
107-
PyThreadState *tstate = _PyThreadState_GET();
108-
file = _PySys_GetAttr(tstate, &_Py_ID(stderr));
108+
file = _PySys_GetRequiredAttr(&_Py_ID(stderr));
109109
if (file == NULL) {
110-
PyErr_SetString(PyExc_RuntimeError, "unable to get sys.stderr");
111110
return -1;
112111
}
113112
if (file == Py_None) {
114113
PyErr_SetString(PyExc_RuntimeError, "sys.stderr is None");
114+
Py_DECREF(file);
115115
return -1;
116116
}
117117
}
@@ -127,10 +127,15 @@ faulthandler_get_fileno(PyObject **file_ptr)
127127
*file_ptr = NULL;
128128
return fd;
129129
}
130+
else {
131+
Py_INCREF(file);
132+
}
130133

131134
result = PyObject_CallMethodNoArgs(file, &_Py_ID(fileno));
132-
if (result == NULL)
135+
if (result == NULL) {
136+
Py_DECREF(file);
133137
return -1;
138+
}
134139

135140
fd = -1;
136141
if (PyLong_Check(result)) {
@@ -143,6 +148,7 @@ faulthandler_get_fileno(PyObject **file_ptr)
143148
if (fd == -1) {
144149
PyErr_SetString(PyExc_RuntimeError,
145150
"file.fileno() is not a valid file descriptor");
151+
Py_DECREF(file);
146152
return -1;
147153
}
148154

@@ -225,19 +231,23 @@ faulthandler_dump_traceback_py(PyObject *self,
225231
return NULL;
226232

227233
tstate = get_thread_state();
228-
if (tstate == NULL)
234+
if (tstate == NULL) {
235+
Py_XDECREF(file);
229236
return NULL;
237+
}
230238

231239
if (all_threads) {
232240
errmsg = _Py_DumpTracebackThreads(fd, NULL, tstate);
233241
if (errmsg != NULL) {
234242
PyErr_SetString(PyExc_RuntimeError, errmsg);
243+
Py_XDECREF(file);
235244
return NULL;
236245
}
237246
}
238247
else {
239248
_Py_DumpTraceback(fd, tstate);
240249
}
250+
Py_XDECREF(file);
241251

242252
if (PyErr_CheckSignals())
243253
return NULL;
@@ -499,10 +509,11 @@ faulthandler_py_enable(PyObject *self, PyObject *args, PyObject *kwargs)
499509
return NULL;
500510

501511
tstate = get_thread_state();
502-
if (tstate == NULL)
512+
if (tstate == NULL) {
513+
Py_XDECREF(file);
503514
return NULL;
515+
}
504516

505-
Py_XINCREF(file);
506517
Py_XSETREF(fatal_error.file, file);
507518
fatal_error.fd = fd;
508519
fatal_error.all_threads = all_threads;
@@ -692,12 +703,14 @@ faulthandler_dump_traceback_later(PyObject *self,
692703
if (!thread.running) {
693704
thread.running = PyThread_allocate_lock();
694705
if (!thread.running) {
706+
Py_XDECREF(file);
695707
return PyErr_NoMemory();
696708
}
697709
}
698710
if (!thread.cancel_event) {
699711
thread.cancel_event = PyThread_allocate_lock();
700712
if (!thread.cancel_event || !thread.running) {
713+
Py_XDECREF(file);
701714
return PyErr_NoMemory();
702715
}
703716

@@ -709,14 +722,14 @@ faulthandler_dump_traceback_later(PyObject *self,
709722
/* format the timeout */
710723
header = format_timeout(timeout_us);
711724
if (header == NULL) {
725+
Py_XDECREF(file);
712726
return PyErr_NoMemory();
713727
}
714728
header_len = strlen(header);
715729

716730
/* Cancel previous thread, if running */
717731
cancel_dump_traceback_later();
718732

719-
Py_XINCREF(file);
720733
Py_XSETREF(thread.file, file);
721734
thread.fd = fd;
722735
/* the downcast is safe: we check that 0 < timeout_us < PY_TIMEOUT_MAX */
@@ -878,28 +891,31 @@ faulthandler_register_py(PyObject *self,
878891

879892
if (user_signals == NULL) {
880893
user_signals = PyMem_Calloc(Py_NSIG, sizeof(user_signal_t));
881-
if (user_signals == NULL)
894+
if (user_signals == NULL) {
895+
Py_XDECREF(file);
882896
return PyErr_NoMemory();
897+
}
883898
}
884899
user = &user_signals[signum];
885900

886901
if (!user->enabled) {
887902
#ifdef FAULTHANDLER_USE_ALT_STACK
888903
if (faulthandler_allocate_stack() < 0) {
904+
Py_XDECREF(file);
889905
return NULL;
890906
}
891907
#endif
892908

893909
err = faulthandler_register(signum, chain, &previous);
894910
if (err) {
895911
PyErr_SetFromErrno(PyExc_OSError);
912+
Py_XDECREF(file);
896913
return NULL;
897914
}
898915

899916
user->previous = previous;
900917
}
901918

902-
Py_XINCREF(file);
903919
Py_XSETREF(user->file, file);
904920
user->fd = fd;
905921
user->all_threads = all_threads;

0 commit comments

Comments
 (0)