diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 963da1b0eae27c..3883c0efb9ad4f 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1804,6 +1804,21 @@ def __init__(self, code, message, filename, filename2): exc = LeakingOSError(1, "some message", "filename.py", "filename2.py") exc.__init__(2, "another message", "filename3.py", "filename4.py") + def test_oserror_str_evil_mutation(self): + # gh-160773: str(OSError) must not crash when formatting one + # attribute clears the others. + class Evil: + def __repr__(self): + exc.filename = None + exc.filename2 = None + return 'evil' + + exc = OSError(2, Evil(), Evil()) + self.assertEqual(str(exc), "[Errno 2] evil: evil") + exc = OSError(2, Evil(), Evil()) + exc.filename2 = Evil() + self.assertEqual(str(exc), "[Errno 2] evil: evil -> evil") + def test_errno_ENOTDIR(self): # Issue #12802: "not a directory" errors are ENOTDIR even on Windows with self.assertRaises(OSError) as cm: diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-28-13-47-43.gh-issue-160773.7VFxkA.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-28-13-47-43.gh-issue-160773.7VFxkA.rst new file mode 100644 index 00000000000000..09d282e1dbeee7 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-28-13-47-43.gh-issue-160773.7VFxkA.rst @@ -0,0 +1,2 @@ +Fix a crash in :func:`str` of an :exc:`OSError` when formatting one of its +attributes modifies the others. diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 86a64783372cea..e2f757c5ee9c0b 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2325,46 +2325,70 @@ static PyObject * OSError_str(PyObject *op) { PyOSErrorObject *self = PyOSErrorObject_CAST(op); + PyObject *res; + /* Hold strong references: formatting may run arbitrary code + which modifies the attributes. */ + PyObject *myerrno = Py_XNewRef(self->myerrno); + PyObject *strerror = Py_XNewRef(self->strerror); + PyObject *filename = Py_XNewRef(self->filename); + PyObject *filename2 = Py_XNewRef(self->filename2); +#ifdef MS_WINDOWS + PyObject *winerror = Py_XNewRef(self->winerror); +#endif #define OR_NONE(x) ((x)?(x):Py_None) #ifdef MS_WINDOWS /* If available, winerror has the priority over myerrno */ - if (self->winerror && self->filename) { - if (self->filename2) { - return PyUnicode_FromFormat("[WinError %S] %S: %R -> %R", - OR_NONE(self->winerror), - OR_NONE(self->strerror), - self->filename, - self->filename2); + if (winerror && filename) { + if (filename2) { + res = PyUnicode_FromFormat("[WinError %S] %S: %R -> %R", + OR_NONE(winerror), + OR_NONE(strerror), + filename, + filename2); } else { - return PyUnicode_FromFormat("[WinError %S] %S: %R", - OR_NONE(self->winerror), - OR_NONE(self->strerror), - self->filename); + res = PyUnicode_FromFormat("[WinError %S] %S: %R", + OR_NONE(winerror), + OR_NONE(strerror), + filename); } + goto done; + } + if (winerror && strerror) { + res = PyUnicode_FromFormat("[WinError %S] %S", + winerror, strerror); + goto done; } - if (self->winerror && self->strerror) - return PyUnicode_FromFormat("[WinError %S] %S", - self->winerror ? self->winerror: Py_None, - self->strerror ? self->strerror: Py_None); #endif - if (self->filename) { - if (self->filename2) { - return PyUnicode_FromFormat("[Errno %S] %S: %R -> %R", - OR_NONE(self->myerrno), - OR_NONE(self->strerror), - self->filename, - self->filename2); + if (filename) { + if (filename2) { + res = PyUnicode_FromFormat("[Errno %S] %S: %R -> %R", + OR_NONE(myerrno), + OR_NONE(strerror), + filename, + filename2); } else { - return PyUnicode_FromFormat("[Errno %S] %S: %R", - OR_NONE(self->myerrno), - OR_NONE(self->strerror), - self->filename); + res = PyUnicode_FromFormat("[Errno %S] %S: %R", + OR_NONE(myerrno), + OR_NONE(strerror), + filename); } + goto done; } - if (self->myerrno && self->strerror) - return PyUnicode_FromFormat("[Errno %S] %S", - self->myerrno, self->strerror); - return BaseException_str(op); + if (myerrno && strerror) { + res = PyUnicode_FromFormat("[Errno %S] %S", + myerrno, strerror); + goto done; + } + res = BaseException_str(op); +done: + Py_XDECREF(myerrno); + Py_XDECREF(strerror); + Py_XDECREF(filename); + Py_XDECREF(filename2); +#ifdef MS_WINDOWS + Py_XDECREF(winerror); +#endif + return res; } static PyObject *