Skip to content

[3.12] gh-130163: Fix crashes related to PySys_GetObject() (GH-130503) (GH-130556) #130576

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Include/internal/pycore_sysmodule.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ extern "C" {
# error "this header requires Py_BUILD_CORE define"
#endif

PyAPI_FUNC(int) _PySys_GetOptionalAttr(PyObject *, PyObject **);
PyAPI_FUNC(int) _PySys_GetOptionalAttrString(const char *, PyObject **);
PyAPI_FUNC(PyObject *) _PySys_GetRequiredAttr(PyObject *);
PyAPI_FUNC(PyObject *) _PySys_GetRequiredAttrString(const char *);

PyAPI_FUNC(int) _PySys_Audit(
PyThreadState *tstate,
const char *event,
Expand Down
23 changes: 23 additions & 0 deletions Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1526,6 +1526,29 @@ def test_input(self):
sys.stdout = savestdout
fp.close()

def test_input_gh130163(self):
class X(io.StringIO):
def __getattribute__(self, name):
nonlocal patch
if patch:
patch = False
sys.stdout = X()
sys.stderr = X()
sys.stdin = X('input\n')
support.gc_collect()
return io.StringIO.__getattribute__(self, name)

with (support.swap_attr(sys, 'stdout', None),
support.swap_attr(sys, 'stderr', None),
support.swap_attr(sys, 'stdin', None)):
patch = False
# the only references:
sys.stdout = X()
sys.stderr = X()
sys.stdin = X('input\n')
patch = True
input() # should not crash

# test_int(): see test_int.py for tests of built-in function int().

def test_repr(self):
Expand Down
11 changes: 11 additions & 0 deletions Lib/test/test_print.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,17 @@ def flush(self):
raise RuntimeError
self.assertRaises(RuntimeError, print, 1, file=noflush(), flush=True)

def test_gh130163(self):
class X:
def __str__(self):
sys.stdout = StringIO()
support.gc_collect()
return 'foo'

with support.swap_attr(sys, 'stdout', None):
sys.stdout = StringIO() # the only reference
print(X()) # should not crash


class TestPy2MigrationHint(unittest.TestCase):
"""Test that correct hint is produced analogous to Python3 syntax,
Expand Down
13 changes: 13 additions & 0 deletions Lib/test/test_sys.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import builtins
import codecs
import gc
import io
import locale
import operator
import os
Expand Down Expand Up @@ -79,6 +80,18 @@ def baddisplayhook(obj):
code = compile("42", "<string>", "single")
self.assertRaises(ValueError, eval, code)

def test_gh130163(self):
class X:
def __repr__(self):
sys.stdout = io.StringIO()
support.gc_collect()
return 'foo'

with support.swap_attr(sys, 'stdout', None):
sys.stdout = io.StringIO() # the only reference
sys.displayhook(X()) # should not crash


class ActiveExceptionTests(unittest.TestCase):
def test_exc_info_no_exception(self):
self.assertEqual(sys.exc_info(), (None, None, None))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix possible crashes related to concurrent
change and use of the :mod:`sys` module attributes.
8 changes: 6 additions & 2 deletions Modules/_cursesmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ static const char PyCursesVersion[] = "2.2";
#include "Python.h"
#include "pycore_long.h" // _PyLong_GetZero()
#include "pycore_structseq.h" // _PyStructSequence_NewType()
#include "pycore_sysmodule.h" // _PySys_GetOptionalAttrString()

#ifdef __hpux
#define STRICT_SYSV_CURSES
Expand Down Expand Up @@ -3375,17 +3376,20 @@ _curses_setupterm_impl(PyObject *module, const char *term, int fd)
if (fd == -1) {
PyObject* sys_stdout;

sys_stdout = PySys_GetObject("stdout");
if (_PySys_GetOptionalAttrString("stdout", &sys_stdout) < 0) {
return NULL;
}

if (sys_stdout == NULL || sys_stdout == Py_None) {
PyErr_SetString(
PyCursesError,
"lost sys.stdout");
Py_XDECREF(sys_stdout);
return NULL;
}

fd = PyObject_AsFileDescriptor(sys_stdout);

Py_DECREF(sys_stdout);
if (fd == -1) {
return NULL;
}
Expand Down
12 changes: 9 additions & 3 deletions Modules/_pickle.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "pycore_moduleobject.h" // _PyModule_GetState()
#include "pycore_runtime.h" // _Py_ID()
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_sysmodule.h" // _PySys_GetRequiredAttr()
#include "structmember.h" // PyMemberDef

#include <stdlib.h> // strtol()
Expand Down Expand Up @@ -1984,49 +1985,54 @@ whichmodule(PyObject *global, PyObject *dotted_path)
assert(module_name == NULL);

/* Fallback on walking sys.modules */
PyThreadState *tstate = _PyThreadState_GET();
modules = _PySys_GetAttr(tstate, &_Py_ID(modules));
modules = _PySys_GetRequiredAttr(&_Py_ID(modules));
if (modules == NULL) {
PyErr_SetString(PyExc_RuntimeError, "unable to get sys.modules");
return NULL;
}
if (PyDict_CheckExact(modules)) {
i = 0;
while (PyDict_Next(modules, &i, &module_name, &module)) {
if (_checkmodule(module_name, module, global, dotted_path) == 0) {
Py_DECREF(modules);
return Py_NewRef(module_name);
}
if (PyErr_Occurred()) {
Py_DECREF(modules);
return NULL;
}
}
}
else {
PyObject *iterator = PyObject_GetIter(modules);
if (iterator == NULL) {
Py_DECREF(modules);
return NULL;
}
while ((module_name = PyIter_Next(iterator))) {
module = PyObject_GetItem(modules, module_name);
if (module == NULL) {
Py_DECREF(module_name);
Py_DECREF(iterator);
Py_DECREF(modules);
return NULL;
}
if (_checkmodule(module_name, module, global, dotted_path) == 0) {
Py_DECREF(module);
Py_DECREF(iterator);
Py_DECREF(modules);
return module_name;
}
Py_DECREF(module);
Py_DECREF(module_name);
if (PyErr_Occurred()) {
Py_DECREF(iterator);
Py_DECREF(modules);
return NULL;
}
}
Py_DECREF(iterator);
}
Py_DECREF(modules);

/* If no module is found, use __main__. */
module_name = &_Py_ID(__main__);
Expand Down
12 changes: 7 additions & 5 deletions Modules/_threadmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include "pycore_moduleobject.h" // _PyModule_GetState()
#include "pycore_pylifecycle.h"
#include "pycore_pystate.h" // _PyThreadState_SetCurrent()
#include "pycore_sysmodule.h" // _PySys_GetOptionalAttr()

#include <stddef.h> // offsetof()
#include "structmember.h" // PyMemberDef

Expand Down Expand Up @@ -1567,9 +1569,12 @@ thread_excepthook(PyObject *module, PyObject *args)
PyObject *exc_tb = PyStructSequence_GET_ITEM(args, 2);
PyObject *thread = PyStructSequence_GET_ITEM(args, 3);

PyThreadState *tstate = _PyThreadState_GET();
PyObject *file = _PySys_GetAttr(tstate, &_Py_ID(stderr));
PyObject *file;
if (_PySys_GetOptionalAttr( &_Py_ID(stderr), &file) < 0) {
return NULL;
}
if (file == NULL || file == Py_None) {
Py_XDECREF(file);
if (thread == Py_None) {
/* do nothing if sys.stderr is None and thread is None */
Py_RETURN_NONE;
Expand All @@ -1586,9 +1591,6 @@ thread_excepthook(PyObject *module, PyObject *args)
Py_RETURN_NONE;
}
}
else {
Py_INCREF(file);
}

int res = thread_excepthook_file(file, exc_type, exc_value, exc_tb,
thread);
Expand Down
7 changes: 6 additions & 1 deletion Modules/_tkinter.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Copyright (C) 1994 Steen Lumholt.
#endif

#include "pycore_long.h"
#include "pycore_sysmodule.h" // _PySys_GetOptionalAttrString()

#ifdef MS_WINDOWS
#include <windows.h>
Expand Down Expand Up @@ -154,9 +155,11 @@ _get_tcl_lib_path(void)
/* Check expected location for an installed Python first */
tcl_library_path = PyUnicode_FromString("\\tcl\\tcl" TCL_VERSION);
if (tcl_library_path == NULL) {
Py_DECREF(prefix);
return NULL;
}
tcl_library_path = PyUnicode_Concat(prefix, tcl_library_path);
Py_DECREF(prefix);
if (tcl_library_path == NULL) {
return NULL;
}
Expand Down Expand Up @@ -3509,6 +3512,7 @@ PyInit__tkinter(void)
uexe = PyUnicode_FromWideChar(Py_GetProgramName(), -1);
if (uexe) {
cexe = PyUnicode_EncodeFSDefault(uexe);
Py_DECREF(uexe);
if (cexe) {
#ifdef MS_WINDOWS
int set_var = 0;
Expand All @@ -3521,12 +3525,14 @@ PyInit__tkinter(void)
if (!ret && GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
str_path = _get_tcl_lib_path();
if (str_path == NULL && PyErr_Occurred()) {
Py_DECREF(cexe);
Py_DECREF(m);
return NULL;
}
if (str_path != NULL) {
wcs_path = PyUnicode_AsWideCharString(str_path, NULL);
if (wcs_path == NULL) {
Py_DECREF(cexe);
Py_DECREF(m);
return NULL;
}
Expand All @@ -3546,7 +3552,6 @@ PyInit__tkinter(void)
#endif /* MS_WINDOWS */
}
Py_XDECREF(cexe);
Py_DECREF(uexe);
}

if (PyErr_Occurred()) {
Expand Down
Loading
Loading