Skip to content

Commit 7c1b76f

Browse files
[3.13] 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)
1 parent b0d3f49 commit 7c1b76f

23 files changed

+504
-179
lines changed

Include/internal/pycore_sysmodule.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ extern "C" {
88
# error "this header requires Py_BUILD_CORE define"
99
#endif
1010

11-
// Export for '_pickle' shared extension
12-
PyAPI_FUNC(PyObject*) _PySys_GetAttr(PyThreadState *tstate, PyObject *name);
11+
PyAPI_FUNC(PyObject *) _PySys_GetAttr(PyThreadState *, PyObject *); /* unused */
12+
PyAPI_FUNC(int) _PySys_GetOptionalAttr(PyObject *, PyObject **);
13+
PyAPI_FUNC(int) _PySys_GetOptionalAttrString(const char *, PyObject **);
14+
PyAPI_FUNC(PyObject *) _PySys_GetRequiredAttr(PyObject *);
15+
PyAPI_FUNC(PyObject *) _PySys_GetRequiredAttrString(const char *);
1316

1417
// Export for '_pickle' shared extension
1518
PyAPI_FUNC(size_t) _PySys_GetSizeOf(PyObject *);

Lib/test/test_builtin.py

+23
Original file line numberDiff line numberDiff line change
@@ -1576,6 +1576,29 @@ def test_input(self):
15761576
sys.stdout = savestdout
15771577
fp.close()
15781578

1579+
def test_input_gh130163(self):
1580+
class X(io.StringIO):
1581+
def __getattribute__(self, name):
1582+
nonlocal patch
1583+
if patch:
1584+
patch = False
1585+
sys.stdout = X()
1586+
sys.stderr = X()
1587+
sys.stdin = X('input\n')
1588+
support.gc_collect()
1589+
return io.StringIO.__getattribute__(self, name)
1590+
1591+
with (support.swap_attr(sys, 'stdout', None),
1592+
support.swap_attr(sys, 'stderr', None),
1593+
support.swap_attr(sys, 'stdin', None)):
1594+
patch = False
1595+
# the only references:
1596+
sys.stdout = X()
1597+
sys.stderr = X()
1598+
sys.stdin = X('input\n')
1599+
patch = True
1600+
input() # should not crash
1601+
15791602
# test_int(): see test_int.py for tests of built-in function int().
15801603

15811604
def test_repr(self):

Lib/test/test_print.py

+11
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

+13
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import codecs
33
import _datetime
44
import gc
5+
import io
56
import locale
67
import operator
78
import os
@@ -80,6 +81,18 @@ def baddisplayhook(obj):
8081
code = compile("42", "<string>", "single")
8182
self.assertRaises(ValueError, eval, code)
8283

84+
def test_gh130163(self):
85+
class X:
86+
def __repr__(self):
87+
sys.stdout = io.StringIO()
88+
support.gc_collect()
89+
return 'foo'
90+
91+
with support.swap_attr(sys, 'stdout', None):
92+
sys.stdout = io.StringIO() # the only reference
93+
sys.displayhook(X()) # should not crash
94+
95+
8396
class ActiveExceptionTests(unittest.TestCase):
8497
def test_exc_info_no_exception(self):
8598
self.assertEqual(sys.exc_info(), (None, None, None))
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

+6-2
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ static const char PyCursesVersion[] = "2.2";
107107
#include "Python.h"
108108
#include "pycore_long.h" // _PyLong_GetZero()
109109
#include "pycore_structseq.h" // _PyStructSequence_NewType()
110+
#include "pycore_sysmodule.h" // _PySys_GetOptionalAttrString()
110111

111112
#ifdef __hpux
112113
#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

+2-4
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#include "pycore_pystate.h" // _PyThreadState_GET()
1919
#include "pycore_runtime.h" // _Py_ID()
2020
#include "pycore_setobject.h" // _PySet_NextEntry()
21-
#include "pycore_sysmodule.h" // _PySys_GetAttr()
21+
#include "pycore_sysmodule.h" // _PySys_GetSizeOf()
2222

2323
#include <stdlib.h> // strtol()
2424

@@ -1930,10 +1930,8 @@ whichmodule(PyObject *global, PyObject *dotted_path)
19301930
assert(module_name == NULL);
19311931

19321932
/* Fallback on walking sys.modules */
1933-
PyThreadState *tstate = _PyThreadState_GET();
1934-
modules = _PySys_GetAttr(tstate, &_Py_ID(modules));
1933+
modules = _PySys_GetRequiredAttr(&_Py_ID(modules));
19351934
if (modules == NULL) {
1936-
PyErr_SetString(PyExc_RuntimeError, "unable to get sys.modules");
19371935
return NULL;
19381936
}
19391937
if (PyDict_CheckExact(modules)) {

Modules/_threadmodule.c

+6-6
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
#include "pycore_modsupport.h" // _PyArg_NoKeywords()
99
#include "pycore_pylifecycle.h"
1010
#include "pycore_pystate.h" // _PyThreadState_SetCurrent()
11-
#include "pycore_sysmodule.h" // _PySys_GetAttr()
11+
#include "pycore_sysmodule.h" // _PySys_GetOptionalAttr()
1212
#include "pycore_time.h" // _PyTime_FromSeconds()
1313
#include "pycore_weakref.h" // _PyWeakref_GET_REF()
1414

@@ -2275,9 +2275,12 @@ thread_excepthook(PyObject *module, PyObject *args)
22752275
PyObject *exc_tb = PyStructSequence_GET_ITEM(args, 2);
22762276
PyObject *thread = PyStructSequence_GET_ITEM(args, 3);
22772277

2278-
PyThreadState *tstate = _PyThreadState_GET();
2279-
PyObject *file = _PySys_GetAttr(tstate, &_Py_ID(stderr));
2278+
PyObject *file;
2279+
if (_PySys_GetOptionalAttr( &_Py_ID(stderr), &file) < 0) {
2280+
return NULL;
2281+
}
22802282
if (file == NULL || file == Py_None) {
2283+
Py_XDECREF(file);
22812284
if (thread == Py_None) {
22822285
/* do nothing if sys.stderr is None and thread is None */
22832286
Py_RETURN_NONE;
@@ -2294,9 +2297,6 @@ thread_excepthook(PyObject *module, PyObject *args)
22942297
Py_RETURN_NONE;
22952298
}
22962299
}
2297-
else {
2298-
Py_INCREF(file);
2299-
}
23002300

23012301
int res = thread_excepthook_file(file, exc_type, exc_value, exc_tb,
23022302
thread);

Modules/_tkinter.c

+12-2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Copyright (C) 1994 Steen Lumholt.
3131
#endif
3232

3333
#include "pycore_long.h" // _PyLong_IsNegative()
34+
#include "pycore_sysmodule.h" // _PySys_GetOptionalAttrString()
3435

3536
#ifdef MS_WINDOWS
3637
# include <windows.h>
@@ -142,18 +143,21 @@ _get_tcl_lib_path(void)
142143
if (already_checked == 0) {
143144
struct stat stat_buf;
144145
int stat_return_value;
146+
PyObject *prefix;
145147

146-
PyObject *prefix = PySys_GetObject("base_prefix"); // borrowed reference
148+
(void) _PySys_GetOptionalAttrString("base_prefix", &prefix);
147149
if (prefix == NULL) {
148150
return NULL;
149151
}
150152

151153
/* Check expected location for an installed Python first */
152154
tcl_library_path = PyUnicode_FromString("\\tcl\\tcl" TCL_VERSION);
153155
if (tcl_library_path == NULL) {
156+
Py_DECREF(prefix);
154157
return NULL;
155158
}
156159
tcl_library_path = PyUnicode_Concat(prefix, tcl_library_path);
160+
Py_DECREF(prefix);
157161
if (tcl_library_path == NULL) {
158162
return NULL;
159163
}
@@ -3485,9 +3489,10 @@ PyInit__tkinter(void)
34853489

34863490
/* This helps the dynamic loader; in Unicode aware Tcl versions
34873491
it also helps Tcl find its encodings. */
3488-
uexe = PySys_GetObject("executable"); // borrowed reference
3492+
(void) _PySys_GetOptionalAttrString("executable", &uexe);
34893493
if (uexe && PyUnicode_Check(uexe)) { // sys.executable can be None
34903494
cexe = PyUnicode_EncodeFSDefault(uexe);
3495+
Py_DECREF(uexe);
34913496
if (cexe) {
34923497
#ifdef MS_WINDOWS
34933498
int set_var = 0;
@@ -3500,12 +3505,14 @@ PyInit__tkinter(void)
35003505
if (!ret && GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
35013506
str_path = _get_tcl_lib_path();
35023507
if (str_path == NULL && PyErr_Occurred()) {
3508+
Py_DECREF(cexe);
35033509
Py_DECREF(m);
35043510
return NULL;
35053511
}
35063512
if (str_path != NULL) {
35073513
wcs_path = PyUnicode_AsWideCharString(str_path, NULL);
35083514
if (wcs_path == NULL) {
3515+
Py_DECREF(cexe);
35093516
Py_DECREF(m);
35103517
return NULL;
35113518
}
@@ -3526,6 +3533,9 @@ PyInit__tkinter(void)
35263533
}
35273534
Py_XDECREF(cexe);
35283535
}
3536+
else {
3537+
Py_XDECREF(uexe);
3538+
}
35293539

35303540
if (PyErr_Occurred()) {
35313541
Py_DECREF(m);

0 commit comments

Comments
 (0)