Tcl Source Code

View Ticket
Login
Ticket UUID: 526b01c0b9921b90618f82c398dc2684a60a053a
Title: lset with 2+ indices on an unshared lseq list is a use-after-free (crash)
Type: Bug Created on: 2026-10-18 09:48:35
Submitter: rocketship98 Assigned to: nobody
Subsystem: 14. List Object Severity: Severe
Priority: 7 High Last modified: 2026-10-30 09:23:40
Status: Closed Closed by: apnadkarni
Resolution: Fixed Closed on: 2026-10-30 09:23:40
Version: 9.0.5, 9.1.0
Description:
set l [lseq 5]
lset l 1 0 X
for {set i 0} {$i < 2000} {incr i} {lappend z [list $i]}

On an -O2 build the loop segfaults (TclThreadAllocObj, object pool corrupted). On a --enable-symbols=mem build it panics at once: Tcl_DbIncrRefCount: Tcl_Obj allocated in another thread, from TclListObjSetElement (tclListObj.c:3289) called by TclLsetFlat (tclListObj.c:3108).

Same with lset l {1 0} X, with lreverse [lseq 5] and lrange [lseq 10] 2 6 as the list, and after a lindex $l 1 has run on the lseq list. Not affected: single-index lset, native lists, lrepeat.

Cause: in TclLsetFlat, elemPtrs comes from Tcl_ListObjGetElements on the arithseries list, i.e. its cached element array, with no reference added. subListObj = elemPtrs[index] is then passed to TclListObjSetElement(parentList, index, subListObj). That call converts the abstract list to a native list (SetListFromAny, building new elements through indexProc) and frees the arithseries internal rep, including the cache, so subListObj (refCount 1, owned only by the cache) is freed before it is stored. A fix could be to take a reference on subListObj around the conversion, or to convert parentList to a native list before reading elemPtrs.

Found by mutation testing of the abstract-list code (using claude, so can't say i understand his reasoning). But it crashes my windows wish91.exe

User Comments:
serhiy.storchaka added on 2026-10-18 10:19:50:

Confirmed on trunk and 9.0.5, and the analysis is right. Proposed fix in branch lset-abstract-list-uaf: TclLsetFlat() holds a reference to the sublist while storing it, the first option suggested here. Tests lset-19.1 and 19.2.


juliannoble2 added on 2026-10-18 11:03:01:
Verified on one of APN's tclsfe9.1 builds and my own Tcl 9.0 builds (using non-standard zig build system)

Can crash even without the for loop in an interactive tclsh

    set l [lseq 5]
    lset l 1 0 X

or sometimes repeat the lset command.

apnadkarni added on 2026-10-30 04:23:30:

Reopening as I think the fix is both incorrect and incomplete.

In particular, the lines

/* It may be held only by a cache that is freed. [Bug 526b01c0b9] */
Tcl_IncrRefCount(subListObj);
TclListObjSetElement(NULL, parentList, index, subListObj);
Tcl_DecrRefCount(subListObj);

can land up calling TclListObjSetElement with a shared object which will cause a panic. Is there something that guarantees subListObj will only have a refcount of 0 before the IncrRefCount?

However, an attempt to demonstrate is unexpected behavior:

% set l [lseq 5]
0 1 2 3 4
% set x X
X
% lset l 1 0 $x
0 X 2 3 4
% set m [lseq 5]
0 1 2 3 4
% lset m {2 0} $x
-> crash

The fact that the above caused a crash and not a panic leaves me wondering if this is one bug or two.


serhiy.storchaka added on 2026-10-30 09:12:21:

TclListObjSetElement() panics only if the list it modifies (parentList) is shared, and it is unshared at this point. The element may be shared: TclListObjSetElement() takes its own reference, and the code right after the call makes a copy if subListObj became shared. The added Tcl_IncrRefCount()/Tcl_DecrRefCount() only keeps the old element alive while it is stored back, since it may be held only by the cache of the abstract list, which is freed during the call.

I cannot reproduce the crash with the current main (1436d27450) or 9.0 (336b75d084), on Linux (also under valgrind) and on Windows 11 (MSVC):

% set m [lseq 5]
0 1 2 3 4
% lset m {2 0} X
0 1 X 3 4
What build and platform did you use?


apnadkarni added on 2026-10-30 09:23:15:

Sorry, mea culpa. Was testing a branch, not trunk, but the wrong branch, abstract-list-hook-fixes instead of lset-abstract-list-uaf. Confused between the various lset/abstract list tickets and branches.

Apologies for the waste of time.


apnadkarni added on 2026-10-30 09:23:40:
Changed back to closed.