From efffb29d09f1ad3b1713461a52d6631668baf28c Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 5 Nov 2026 12:30:26 +0300 Subject: [PATCH 1/4] gh-161388: [tkinter] Fix crash on raising `__str__` method passed to `setvar` --- Lib/test/test_tcl.py | 11 +++++++++++ .../2026-11-05-12-29-04.gh-issue-161388.s7ECT8.rst | 2 ++ Modules/_tkinter.c | 5 ++++- 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-11-05-12-29-04.gh-issue-161388.s7ECT8.rst diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py index 1d90ac10ad77ade..85912fd7ebbb733 100644 --- a/Lib/test/test_tcl.py +++ b/Lib/test/test_tcl.py @@ -596,6 +596,17 @@ def test_returning_deeply_nested(self): % support.exceeds_recursion_limit()) self.assertRaises(RecursionError, interp.getvar, 'x') + def test_setvar_evil(self): + # gh-161388 + class StrRaises: + def __str__(self): + raise RuntimeError("setvar evil") + + setvar = self.interp.tk.setvar + default = StrRaises() + with self.assertRaisesRegex(RuntimeError, "setvar evil"): + setvar("name1", "name2", default) + def test_user_command(self): result = None def testfunc(arg): diff --git a/Misc/NEWS.d/next/Library/2026-11-05-12-29-04.gh-issue-161388.s7ECT8.rst b/Misc/NEWS.d/next/Library/2026-11-05-12-29-04.gh-issue-161388.s7ECT8.rst new file mode 100644 index 000000000000000..762c3dfe7d90516 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-11-05-12-29-04.gh-issue-161388.s7ECT8.rst @@ -0,0 +1,2 @@ +Fixes a crash when passing objects with raising :meth:`~object.__str__` +method to :meth:`~tkinter.Misc.setvar`. diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 9a09687e55b9817..191ef3f1bb0b7c0 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -1235,7 +1235,7 @@ AsObjNested(PyObject *value, int depth) { PyObject *v = PyObject_Str(value); if (!v) - return 0; + return NULL; result = AsObjNested(v, depth); Py_DECREF(v); return result; @@ -2101,6 +2101,9 @@ SetVar(TkappObject *self, PyObject *args, int flags) /* XXX must hold tcl lock already??? */ newval = AsObj(newValue); + if (newval == NULL) { + return NULL; + } if (self->trace) { if (flags & TCL_GLOBAL_ONLY) { TRACE(self, ("((sssNO))", "uplevel", "#0", "set", From f177872730f04ed8d46e7b61172eadaeb01909f8 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 5 Nov 2026 21:44:32 +0300 Subject: [PATCH 2/4] Apply suggestions from code review --- Modules/_tkinter.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 191ef3f1bb0b7c0..4ff1d211d068851 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -1234,8 +1234,9 @@ AsObjNested(PyObject *value, int depth) { PyObject *v = PyObject_Str(value); - if (!v) + if (!v) { return NULL; + } result = AsObjNested(v, depth); Py_DECREF(v); return result; From 90eb61270e7bbd263ba5f89990323aa9f2a17bb0 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 5 Nov 2026 21:54:41 +0300 Subject: [PATCH 3/4] Update Modules/_tkinter.c --- Modules/_tkinter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 4ff1d211d068851..70a10cea038348d 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -1236,7 +1236,7 @@ AsObjNested(PyObject *value, int depth) PyObject *v = PyObject_Str(value); if (!v) { return NULL; - } + } result = AsObjNested(v, depth); Py_DECREF(v); return result; From 09ad219152c16120b679a8a8ffc77da291db5f5b Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 5 Nov 2026 23:41:13 +0300 Subject: [PATCH 4/4] Address review --- Lib/test/test_tcl.py | 24 ++++++++++++++++--- ...-11-05-12-29-04.gh-issue-161388.s7ECT8.rst | 2 -- 2 files changed, 21 insertions(+), 5 deletions(-) delete mode 100644 Misc/NEWS.d/next/Library/2026-11-05-12-29-04.gh-issue-161388.s7ECT8.rst diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py index 85912fd7ebbb733..f1953e4a2371173 100644 --- a/Lib/test/test_tcl.py +++ b/Lib/test/test_tcl.py @@ -602,10 +602,28 @@ class StrRaises: def __str__(self): raise RuntimeError("setvar evil") - setvar = self.interp.tk.setvar default = StrRaises() - with self.assertRaisesRegex(RuntimeError, "setvar evil"): - setvar("name1", "name2", default) + + for getvar, setvar, unsetvar in [ + ( + self.interp.tk.getvar, + self.interp.tk.setvar, + self.interp.tk.unsetvar, + ), + ( + self.interp.tk.globalgetvar, + self.interp.tk.globalsetvar, + self.interp.tk.globalunsetvar, + ) + ]: + with self.subTest(getvar=getvar, setvar=setvar, unsetvar=unsetvar): + with self.assertRaisesRegex(RuntimeError, "setvar evil"): + setvar("name1", "name2", default) + + self.assertRaises(TclError, getvar, "name1") + self.assertIsNone(setvar("name1", "name2")) + self.assertEqual(getvar("name1"), "name2") + self.assertIsNone(unsetvar("name1")) def test_user_command(self): result = None diff --git a/Misc/NEWS.d/next/Library/2026-11-05-12-29-04.gh-issue-161388.s7ECT8.rst b/Misc/NEWS.d/next/Library/2026-11-05-12-29-04.gh-issue-161388.s7ECT8.rst deleted file mode 100644 index 762c3dfe7d90516..000000000000000 --- a/Misc/NEWS.d/next/Library/2026-11-05-12-29-04.gh-issue-161388.s7ECT8.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fixes a crash when passing objects with raising :meth:`~object.__str__` -method to :meth:`~tkinter.Misc.setvar`.