https://github.com/python/cpython/commit/33fdd01c47946f1ee997ff5bb4f0d7faee63a0e1 commit: 33fdd01c47946f1ee997ff5bb4f0d7faee63a0e1 branch: 3.14 author: Miss Islington (bot) <[email protected]> committer: StanFromIreland <[email protected]> date: 2026-10-29T11:55:23Z summary:
[3.14] gh-160773: Fix use-after-free in `OSError.__str__` (GH-160822) (#160885) (cherry picked from commit 7e5227fb8c80c9cbf356881fa7c8658aba90b9d7) Co-authored-by: Alon <[email protected]> Co-authored-by: Stan Ulbrych <[email protected]> files: A Misc/NEWS.d/next/Core_and_Builtins/2026-10-28-13-47-43.gh-issue-160773.7VFxkA.rst M Lib/test/test_exceptions.py M Objects/exceptions.c diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 86cc3f23959198..565521c8a6d7cf 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1789,6 +1789,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 b3473751684f0f..b0a559a4d8984d 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2282,46 +2282,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 * _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]
