Tcl Source Code

View Ticket
Login
Ticket UUID: 34870ab5756911d134d1417a0b149cff3adc9478
Title: Double free of assocdata entry
Type: Bug Version: 8.7+
Submitter: chpock Created on: 2024-05-30 16:41:27
Subsystem: 20. [interp] Assigned To: nobody
Priority: 5 Medium Severity: Minor
Status: Closed Last Modified: 2024-07-15 01:37:29
Resolution: Fixed Closed By: sebres
    Closed on: 2024-07-15 01:37:29
Description:

tcl9.0 first call assocdata deletion callbacks, then release each data from hash after the callback, and then set iPtr->assocData to NULL when all assocdata is removed: https://github.com/tcltk/tcl/blob/db5ec326417727be7fdfcfe92d15193c7d886648/generic/tclBasic.c#L1982-L1990

if (iPtr->assocData != NULL) {
	AssocData *dPtr;

	hTablePtr = iPtr->assocData;
	/*
	 * Invoke deletion callbacks; note that a callback can create new
	 * callbacks, so we iterate.
	 */
	for (hPtr = Tcl_FirstHashEntry(hTablePtr, &search);
		hPtr != NULL;
		hPtr = Tcl_FirstHashEntry(hTablePtr, &search)) {
	    dPtr = (AssocData *)Tcl_GetHashValue(hPtr);
	    if (dPtr->proc != NULL) {
		dPtr->proc(dPtr->clientData, interp);
	    }
	    Tcl_DeleteHashEntry(hPtr);
	    Tcl_Free(dPtr);
	}
	Tcl_DeleteHashTable(hTablePtr);
	Tcl_Free(hTablePtr);
	iPtr->assocData = NULL;
    }

In tcl8.6.14 the behavior was different: it first set iPtr->assocData to NULL, then for each assocData it removes hash entry, and after that invokes callback: https://github.com/tcltk/tcl/blob/1d84e70c2decaf650f0f1a41b3de81bb0e19b58e/generic/tclBasic.c#L1539-L1550

The difference in behavior leads to crash if assocdata deletion callback invokes Tcl_DeleteAssocData(). Tcl9 tries to call the callback, callback releases assocdata hash entry, then DeleteInterpProc() continues and releases the hash entry again.

I don't see in documentation, that assocdata deletion callback must not call Tcl_DeleteAssocData(). Also, at least tclvfs is affected by this case: https://github.com/chpock/tclvfs/blob/289dfa5ebf0c7374737c7a1fdef7103a0100b303/generic/vfs.c#L434

User Comments: sebres added on 2024-07-15 01:37:29:

The fix for that has a side effect detected in load library module (caused a memory leak, see [ae09f6b190ceec31]) due to improper free of assoc data in its clean-up proc. Moreover it looks like the full rewrite of LoadCleanupProc in 8.7+ (as well as UnloadLibrary) was previously implemented incorrectly for the case of interpreter deletion.
Ultimately fixed in [24ace090aad63399].


sebres added on 2024-05-30 18:39:33:

Fixed in [0aedd271b215a880] and merged to 9.0.

Thx for the report.


sebres added on 2024-05-30 18:32:51:

Although I don't think it is good idea to invoke Tcl_DeleteAssocData from deletion callback (just an unneeded hash search), it looks indeed like an oversight:

the issue going to [b95643fa3926f2e3] fixing loss of line (probably by conflict resolving) in [ec7e558c34856b07]. So it's safe to restore it back.

Again, a next illustration why a certain hygiene by space/spelling/line-length/whatever "fixing" is important and what pure artificial "fix" may cause in the long term. (I don't stop to repeat it.)

It affects 8.7 in the same way. I'll fix it now for 8.7+.