Skip to content

Commit 9c0cfb1

Browse files
authored
gh-160176: Fix crash in tkinter on deeply nested or recursive lists (GH-160177)
Converting a nested list or tuple to a Tcl object now raises ValueError above 1000 levels, the same limit _flatten() uses. Converting a deeply nested Tcl list back to Python now raises RecursionError. ANT-2026-7UKC5QED
1 parent ee074d1 commit 9c0cfb1

3 files changed

Lines changed: 73 additions & 11 deletions

File tree

‎Lib/test/test_tcl.py‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,37 @@ class List(list):
565565
self.assertEqual(self.passValue(values := [Value(), "pad"]), expect)
566566
self.assertEqual(self.passValue(values := List([Value(), "pad"])), expect)
567567

568+
def test_passing_deeply_nested(self):
569+
call = self.interp.tk.call
570+
setvar = self.interp.tk.setvar
571+
for wrap in (lambda x: [x]), (lambda x: (x,)):
572+
value = 'a'
573+
for i in range(1001):
574+
value = wrap(value)
575+
self.assertEqual(int(call('string', 'length', value)), 1)
576+
value = wrap(value)
577+
self.assertRaises(ValueError, call, 'set', '_', value)
578+
self.assertRaises(ValueError, setvar, '_', value)
579+
for i in range(support.exceeds_recursion_limit()):
580+
value = wrap(value)
581+
self.assertRaises(ValueError, call, 'set', '_', value)
582+
self.assertRaises(ValueError, setvar, '_', value)
583+
value = ['a', 1]
584+
value.append(value)
585+
self.assertRaises(ValueError, call, 'set', '_', value)
586+
self.assertRaises(ValueError, setvar, '_', value)
587+
588+
@support.run_with_limited_c_stack()
589+
def test_returning_deeply_nested(self):
590+
if not self.wantobjects:
591+
self.skipTest('requires wantobjects')
592+
# run_with_limited_c_stack() runs the test in a new thread, and a
593+
# Tcl interpreter can only be used in the thread that created it.
594+
interp = Tcl()
595+
interp.eval('set x a; for {set i 0} {$i < %d} {incr i} {set x [list $x]}'
596+
% support.exceeds_recursion_limit())
597+
self.assertRaises(RecursionError, interp.getvar, 'x')
598+
568599
def test_user_command(self):
569600
result = None
570601
def testfunc(arg):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix a crash in :mod:`tkinter` when a deeply nested or recursive list or
2+
tuple is passed to Tcl, or a deeply nested Tcl list is converted to Python.
3+
These now raise :exc:`ValueError` (beyond 1000 levels) and
4+
:exc:`RecursionError` respectively.

‎Modules/_tkinter.c‎

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,13 +1047,21 @@ asBignumObj(PyObject *value)
10471047
return result;
10481048
}
10491049

1050-
static Tcl_Obj* AsObj(PyObject *value);
1050+
static Tcl_Obj* AsObjNested(PyObject *value, int depth);
10511051

10521052
static Tcl_Obj*
1053-
TupleAsObj(PyObject *value, int wrapped)
1053+
TupleAsObj(PyObject *value, int wrapped, int depth)
10541054
{
10551055
Tcl_Obj *result = NULL;
10561056
Py_ssize_t size = PyTuple_GET_SIZE(value);
1057+
/* The same limit as in _flatten1(). It bounds not only the recursion
1058+
here, but also the recursion in Tcl when it generates the string
1059+
representation of the nested list, so a C stack check is not enough. */
1060+
if (depth > 1000) {
1061+
PyErr_SetString(PyExc_ValueError,
1062+
"nesting too deep in conversion to Tcl object");
1063+
return NULL;
1064+
}
10571065
if (size == 0) {
10581066
return Tcl_NewListObj(0, NULL);
10591067
}
@@ -1068,8 +1076,15 @@ TupleAsObj(PyObject *value, int wrapped)
10681076
return NULL;
10691077
}
10701078
for (Py_ssize_t i = 0; i < size; i++) {
1071-
Tcl_Obj *item = AsObj(PyTuple_GET_ITEM(value, i));
1079+
Tcl_Obj *item = AsObjNested(PyTuple_GET_ITEM(value, i), depth + 1);
10721080
if (item == NULL) {
1081+
/* Free the already converted items. A new Tcl object has a
1082+
zero reference count, so it must be acquired before it can
1083+
be released. */
1084+
while (--i >= 0) {
1085+
Tcl_IncrRefCount(argv[i]);
1086+
Tcl_DecrRefCount(argv[i]);
1087+
}
10731088
goto exit;
10741089
}
10751090
argv[i] = item;
@@ -1082,7 +1097,7 @@ TupleAsObj(PyObject *value, int wrapped)
10821097
}
10831098

10841099
static Tcl_Obj*
1085-
AsObj(PyObject *value)
1100+
AsObjNested(PyObject *value, int depth)
10861101
{
10871102
Tcl_Obj *result;
10881103

@@ -1128,15 +1143,15 @@ AsObj(PyObject *value)
11281143
return Tcl_NewDoubleObj(PyFloat_AS_DOUBLE(value));
11291144

11301145
if (PyTuple_Check(value)) {
1131-
return TupleAsObj(value, false);
1146+
return TupleAsObj(value, false, depth);
11321147
}
11331148

11341149
if (PyList_Check(value)) {
11351150
PyObject *value_as_tuple = PyList_AsTuple(value);
11361151
if (value_as_tuple == NULL) {
11371152
return NULL;
11381153
}
1139-
result = TupleAsObj(value_as_tuple, true);
1154+
result = TupleAsObj(value_as_tuple, true, depth);
11401155
Py_DECREF(value_as_tuple);
11411156
return result;
11421157
}
@@ -1221,12 +1236,18 @@ AsObj(PyObject *value)
12211236
PyObject *v = PyObject_Str(value);
12221237
if (!v)
12231238
return 0;
1224-
result = AsObj(v);
1239+
result = AsObjNested(v, depth);
12251240
Py_DECREF(v);
12261241
return result;
12271242
}
12281243
}
12291244

1245+
static Tcl_Obj*
1246+
AsObj(PyObject *value)
1247+
{
1248+
return AsObjNested(value, 0);
1249+
}
1250+
12301251
static PyObject *
12311252
fromBoolean(TkappObject *tkapp, Tcl_Obj *value)
12321253
{
@@ -1384,19 +1405,25 @@ FromObj(TkappObject *tkapp, Tcl_Obj *value)
13841405
result = PyTuple_New(size);
13851406
if (!result)
13861407
return NULL;
1408+
if (Py_EnterRecursiveCall(" while converting a Tcl list")) {
1409+
Py_DECREF(result);
1410+
return NULL;
1411+
}
13871412
for (i = 0; i < size; i++) {
13881413
status = Tcl_ListObjIndex(interp, value, i, &tcl_elem);
13891414
if (status == TCL_ERROR) {
1390-
Py_DECREF(result);
1391-
return Tkinter_Error(tkapp);
1415+
Tkinter_Error(tkapp);
1416+
Py_CLEAR(result);
1417+
break;
13921418
}
13931419
elem = FromObj(tkapp, tcl_elem);
13941420
if (!elem) {
1395-
Py_DECREF(result);
1396-
return NULL;
1421+
Py_CLEAR(result);
1422+
break;
13971423
}
13981424
PyTuple_SET_ITEM(result, i, elem);
13991425
}
1426+
Py_LeaveRecursiveCall();
14001427
return result;
14011428
}
14021429

0 commit comments

Comments
 (0)