Skip to content

Commit 3d35b21

Browse files
[3.15] gh-160176: Fix crash in tkinter on deeply nested or recursive lists (GH-160177) (#161071)
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 (cherry picked from commit 9c0cfb1) Co-authored-by: Gregory P. Smith <68491+gpshead@users.noreply.github.com> Co-authored-by: Gregory P. Smith <greg@krypto.org>
1 parent 59ef77b commit 3d35b21

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_setvar_evil(self):
569600
# gh-161388
570601
class StrRaises:
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
@@ -1026,13 +1026,21 @@ asBignumObj(PyObject *value)
10261026
return result;
10271027
}
10281028

1029-
static Tcl_Obj* AsObj(PyObject *value);
1029+
static Tcl_Obj* AsObjNested(PyObject *value, int depth);
10301030

10311031
static Tcl_Obj*
1032-
TupleAsObj(PyObject *value, int wrapped)
1032+
TupleAsObj(PyObject *value, int wrapped, int depth)
10331033
{
10341034
Tcl_Obj *result = NULL;
10351035
Py_ssize_t size = PyTuple_GET_SIZE(value);
1036+
/* The same limit as in _flatten1(). It bounds not only the recursion
1037+
here, but also the recursion in Tcl when it generates the string
1038+
representation of the nested list, so a C stack check is not enough. */
1039+
if (depth > 1000) {
1040+
PyErr_SetString(PyExc_ValueError,
1041+
"nesting too deep in conversion to Tcl object");
1042+
return NULL;
1043+
}
10361044
if (size == 0) {
10371045
return Tcl_NewListObj(0, NULL);
10381046
}
@@ -1047,8 +1055,15 @@ TupleAsObj(PyObject *value, int wrapped)
10471055
return NULL;
10481056
}
10491057
for (Py_ssize_t i = 0; i < size; i++) {
1050-
Tcl_Obj *item = AsObj(PyTuple_GET_ITEM(value, i));
1058+
Tcl_Obj *item = AsObjNested(PyTuple_GET_ITEM(value, i), depth + 1);
10511059
if (item == NULL) {
1060+
/* Free the already converted items. A new Tcl object has a
1061+
zero reference count, so it must be acquired before it can
1062+
be released. */
1063+
while (--i >= 0) {
1064+
Tcl_IncrRefCount(argv[i]);
1065+
Tcl_DecrRefCount(argv[i]);
1066+
}
10521067
goto exit;
10531068
}
10541069
argv[i] = item;
@@ -1061,7 +1076,7 @@ TupleAsObj(PyObject *value, int wrapped)
10611076
}
10621077

10631078
static Tcl_Obj*
1064-
AsObj(PyObject *value)
1079+
AsObjNested(PyObject *value, int depth)
10651080
{
10661081
Tcl_Obj *result;
10671082

@@ -1107,15 +1122,15 @@ AsObj(PyObject *value)
11071122
return Tcl_NewDoubleObj(PyFloat_AS_DOUBLE(value));
11081123

11091124
if (PyTuple_Check(value)) {
1110-
return TupleAsObj(value, false);
1125+
return TupleAsObj(value, false, depth);
11111126
}
11121127

11131128
if (PyList_Check(value)) {
11141129
PyObject *value_as_tuple = PyList_AsTuple(value);
11151130
if (value_as_tuple == NULL) {
11161131
return NULL;
11171132
}
1118-
result = TupleAsObj(value_as_tuple, true);
1133+
result = TupleAsObj(value_as_tuple, true, depth);
11191134
Py_DECREF(value_as_tuple);
11201135
return result;
11211136
}
@@ -1201,12 +1216,18 @@ AsObj(PyObject *value)
12011216
if (!v) {
12021217
return NULL;
12031218
}
1204-
result = AsObj(v);
1219+
result = AsObjNested(v, depth);
12051220
Py_DECREF(v);
12061221
return result;
12071222
}
12081223
}
12091224

1225+
static Tcl_Obj*
1226+
AsObj(PyObject *value)
1227+
{
1228+
return AsObjNested(value, 0);
1229+
}
1230+
12101231
static PyObject *
12111232
fromBoolean(TkappObject *tkapp, Tcl_Obj *value)
12121233
{
@@ -1331,19 +1352,25 @@ FromObj(TkappObject *tkapp, Tcl_Obj *value)
13311352
result = PyTuple_New(size);
13321353
if (!result)
13331354
return NULL;
1355+
if (Py_EnterRecursiveCall(" while converting a Tcl list")) {
1356+
Py_DECREF(result);
1357+
return NULL;
1358+
}
13341359
for (i = 0; i < size; i++) {
13351360
status = Tcl_ListObjIndex(interp, value, i, &tcl_elem);
13361361
if (status == TCL_ERROR) {
1337-
Py_DECREF(result);
1338-
return Tkinter_Error(tkapp);
1362+
Tkinter_Error(tkapp);
1363+
Py_CLEAR(result);
1364+
break;
13391365
}
13401366
elem = FromObj(tkapp, tcl_elem);
13411367
if (!elem) {
1342-
Py_DECREF(result);
1343-
return NULL;
1368+
Py_CLEAR(result);
1369+
break;
13441370
}
13451371
PyTuple_SET_ITEM(result, i, elem);
13461372
}
1373+
Py_LeaveRecursiveCall();
13471374
return result;
13481375
}
13491376

0 commit comments

Comments
 (0)