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]

Reply via email to