Skip to content

gh-130163: Fix possible crashes related to PySys_GetObject() #130503

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 7 commits into from
Feb 25, 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
6 changes: 4 additions & 2 deletions Include/internal/pycore_sysmodule.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ extern "C" {
# error "this header requires Py_BUILD_CORE define"
#endif

// Export for '_pickle' shared extension
PyAPI_FUNC(PyObject*) _PySys_GetAttr(PyThreadState *tstate, PyObject *name);
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 *);

// Export for '_pickle' shared extension
PyAPI_FUNC(size_t) _PySys_GetSizeOf(PyObject *);
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 @@ -1710,6 +1710,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
Expand Up @@ -2,6 +2,7 @@
import codecs
import _datetime
import gc
import io
import locale
import operator
import os
Expand Down Expand Up @@ -80,6 +81,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 @@ -108,6 +108,7 @@ static const char PyCursesVersion[] = "2.2";
#include "pycore_capsule.h" // _PyCapsule_SetTraverse()
#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 @@ -3542,16 +3543,19 @@ _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) {
cursesmodule_state *state = get_cursesmodule_state(module);
PyErr_SetString(state->error, "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
13 changes: 9 additions & 4 deletions Modules/_pickle.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_runtime.h" // _Py_ID()
#include "pycore_setobject.h" // _PySet_NextEntry()
#include "pycore_sysmodule.h" // _PySys_GetAttr()
#include "pycore_sysmodule.h" // _PySys_GetSizeOf()

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

Expand Down Expand Up @@ -1910,10 +1910,8 @@ whichmodule(PickleState *st, PyObject *global, PyObject *global_name, PyObject *
__module__ can be None. If it is so, then search sys.modules for
the module of global. */
Py_CLEAR(module_name);
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)) {
Expand All @@ -1923,41 +1921,48 @@ whichmodule(PickleState *st, PyObject *global, PyObject *global_name, PyObject *
Py_INCREF(module);
if (_checkmodule(module_name, module, global, dotted_path) == 0) {
Py_DECREF(module);
Py_DECREF(modules);
return module_name;
}
Py_DECREF(module);
Py_DECREF(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 (PyErr_Occurred()) {
return NULL;
}
Expand Down
12 changes: 6 additions & 6 deletions Modules/_threadmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#include "pycore_modsupport.h" // _PyArg_NoKeywords()
#include "pycore_pylifecycle.h"
#include "pycore_pystate.h" // _PyThreadState_SetCurrent()
#include "pycore_sysmodule.h" // _PySys_GetAttr()
#include "pycore_sysmodule.h" // _PySys_GetOptionalAttr()
#include "pycore_time.h" // _PyTime_FromSeconds()
#include "pycore_weakref.h" // _PyWeakref_GET_REF()

Expand Down Expand Up @@ -2249,9 +2249,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 @@ -2268,9 +2271,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
14 changes: 12 additions & 2 deletions Modules/_tkinter.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Copyright (C) 1994 Steen Lumholt.
#endif

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

#ifdef MS_WINDOWS
# include <windows.h>
Expand Down Expand Up @@ -142,18 +143,21 @@ _get_tcl_lib_path(void)
if (already_checked == 0) {
struct stat stat_buf;
int stat_return_value;
PyObject *prefix;

PyObject *prefix = PySys_GetObject("base_prefix"); // borrowed reference
(void) _PySys_GetOptionalAttrString("base_prefix", &prefix);
if (prefix == NULL) {
return NULL;
}

/* 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 @@ -3516,9 +3520,10 @@ PyInit__tkinter(void)

/* This helps the dynamic loader; in Unicode aware Tcl versions
it also helps Tcl find its encodings. */
uexe = PySys_GetObject("executable"); // borrowed reference
(void) _PySys_GetOptionalAttrString("executable", &uexe);
if (uexe && PyUnicode_Check(uexe)) { // sys.executable can be None
cexe = PyUnicode_EncodeFSDefault(uexe);
Py_DECREF(uexe);
if (cexe) {
#ifdef MS_WINDOWS
int set_var = 0;
Expand All @@ -3531,12 +3536,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 @@ -3557,6 +3564,9 @@ PyInit__tkinter(void)
}
Py_XDECREF(cexe);
}
else {
Py_XDECREF(uexe);
}

if (PyErr_Occurred()) {
Py_DECREF(m);
Expand Down
Loading
Loading