Skip to content

Commit

Permalink
gh-130163: Fix crashes related to PySys_GetObject() (GH-130503)
Browse files Browse the repository at this point in the history
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().
  • Loading branch information
serhiy-storchaka authored Feb 25, 2025
1 parent 2dad1e0 commit 0ef4ffe
Show file tree
Hide file tree
Showing 23 changed files with 509 additions and 209 deletions.
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

0 comments on commit 0ef4ffe

Please sign in to comment.