From 3fa3ba646929ac210ff9b90aae5b4483dcf4878f Mon Sep 17 00:00:00 2001 From: dgp Date: Mon, 2 Aug 2004 15:33:35 +0000 Subject: * generic/tclLiteral.c (TclCleanupLiteralTable): Corrected * tests/compile.test (compile-12.4): flawed deletion of literal internal reps that could lead to accessing of freed memory. Thanks to Kevin Kenny for test case and fix [Bug 1001997]. --- ChangeLog | 7 ++++++ generic/tclLiteral.c | 63 ++++++++++++++++++++++++++++++++++------------------ tests/compile.test | 59 +++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 107 insertions(+), 22 deletions(-) diff --git a/ChangeLog b/ChangeLog index f9877a7..4338ba3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2004-08-02 Don Porter + + * generic/tclLiteral.c (TclCleanupLiteralTable): Corrected + * tests/compile.test (compile-12.4): flawed deletion of literal + internal reps that could lead to accessing of freed memory. + Thanks to Kevin Kenny for test case and fix [Bug 1001997]. + 2004-07-30 Don Porter * tests/safe.test (safe-2.1): Disabled senseless test. [Bug 999612] diff --git a/generic/tclLiteral.c b/generic/tclLiteral.c index 58ef176..f5af52d 100644 --- a/generic/tclLiteral.c +++ b/generic/tclLiteral.c @@ -13,7 +13,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclLiteral.c,v 1.19 2004/07/21 00:42:39 kennykb Exp $ + * RCS: @(#) $Id: tclLiteral.c,v 1.20 2004/08/02 15:33:36 dgp Exp $ */ #include "tclInt.h" @@ -96,34 +96,55 @@ TclInitLiteralTable(tablePtr) void TclCleanupLiteralTable( interp, tablePtr ) - Tcl_Interp* interp; /* Interpreter containing literals to purge */ - LiteralTable* tablePtr; /* Points to the literal table being cleaned */ + Tcl_Interp* interp; /* Interpreter containing literals to purge */ + LiteralTable* tablePtr; /* Points to the literal table being cleaned */ { int i; - LiteralEntry* entryPtr; - LiteralEntry* nextPtr; - Tcl_Obj* objPtr; - Tcl_ObjType* typePtr; + LiteralEntry* entryPtr; /* Pointer to the current entry in the + * hash table of literals */ + LiteralEntry* nextPtr; /* Pointer to the next entry in tbe + * bucket */ + Tcl_Obj* objPtr; /* Pointer to a literal object whose internal + * rep is being freed */ + Tcl_ObjType* typePtr; /* Pointer to the object's type */ + int didOne; /* Flag for whether we've removed a literal + * in the current bucket */ #ifdef TCL_COMPILE_DEBUG TclVerifyGlobalLiteralTable( (Interp*) interp ); #endif /* TCL_COMPILE_DEBUG */ for ( i = 0; i < tablePtr->numBuckets; i++ ) { - entryPtr = tablePtr->buckets[i]; - while ( entryPtr != NULL ) { - objPtr = entryPtr->objPtr; - nextPtr = entryPtr->nextPtr; - typePtr = objPtr->typePtr; - if ( ( typePtr != NULL ) && ( typePtr->freeIntRepProc != NULL ) ) { - if ( objPtr->bytes == NULL ) { - Tcl_Panic( "literal without a string rep" ); - } - objPtr->typePtr = NULL; - typePtr->freeIntRepProc( objPtr ); - } - entryPtr = nextPtr; - } + + /* + * It is tempting simply to walk each hash bucket once and + * delete the internal representations of each literal in turn. + * It's also wrong. The problem is that freeing a literal's + * internal representation can delete other literals to which + * it refers, making nextPtr invalid. So each time we free an + * internal rep, we start its bucket over again. + */ + didOne = 1; + while ( didOne ) { + didOne = 0; + entryPtr = tablePtr->buckets[i]; + while ( entryPtr != NULL ) { + objPtr = entryPtr->objPtr; + nextPtr = entryPtr->nextPtr; + typePtr = objPtr->typePtr; + if ( ( typePtr != NULL ) + && ( typePtr->freeIntRepProc != NULL ) ) { + if ( objPtr->bytes == NULL ) { + Tcl_Panic( "literal without a string rep" ); + } + objPtr->typePtr = NULL; + typePtr->freeIntRepProc( objPtr ); + didOne = 1; + } else { + entryPtr = nextPtr; + } + } + } } } diff --git a/tests/compile.test b/tests/compile.test index 6461651..c4eb685 100644 --- a/tests/compile.test +++ b/tests/compile.test @@ -11,7 +11,7 @@ # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # -# RCS: @(#) $Id: compile.test,v 1.30 2003/11/20 00:16:00 dgp Exp $ +# RCS: @(#) $Id: compile.test,v 1.31 2004/08/02 15:33:36 dgp Exp $ package require tcltest 2 namespace import -force ::tcltest::* @@ -320,6 +320,63 @@ test compile-12.3 {check for a buffer overrun} { list [catch crash msg] $msg } {1 {syntax error in expression "a+2": variable references require preceding $}} +test compile-12.4 {TclCleanupLiteralTable segfault} { + # Tcl Bug 1001997 + # Here, we're trying to test a case that causes a crash in + # TclCleanupLiteralTable. The conditions that we're trying to + # establish are: + # - TclCleanupLiteralTable is attempting to clean up a bytecode + # object in the literal table. + # - The bytecode object in question contains the only reference + # to another literal. + # - The literal in question is in the same hash bucket as the bytecode + # object, and immediately follows it in the chain. + # Since newly registered literals are added at the FRONT of the + # bucket chains, and since the bytecode object is registered before + # its literals, this is difficult to achieve. What we do is: + # (a) do a [namespace eval] of a string that's calculated to + # hash into the same bucket as a literal that it contains. + # In this case, the script and the variable 'bugbug' + # land in the same bucket. + # (b) do a [namespace eval] of a string that contains enough + # literals to force TclRegisterLiteral to rebuild the global + # literal table. The newly created hash buckets will contain + # the literals, IN REVERSE ORDER, thus putting the bytecode + # immediately ahead of 'bugbug' and 'bug4345bug'. The bytecode + # object will contain the only references to those two literals. + # (c) Delete the interpreter to invoke TclCleanupLiteralTable + # and tickle the bug. + proc foo {} { + set i [interp create] + $i eval { + namespace eval ::w {concat 4649; variable bugbug} + namespace eval ::w { + concat x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 \ + x11 x12 x13 x14 x15 x16 x17 x18 x19 x20 \ + x21 x22 x23 x24 x25 x26 x27 x28 x29 x30 \ + x31 x32 X33 X34 X35 X36 X37 X38 X39 X40 \ + x41 x42 x43 x44 x45 x46 x47 x48 x49 x50 \ + x51 x52 x53 x54 x55 x56 x57 x58 x59 x60 \ + x61 x62 x63 x64 + concat y1 y2 y3 y4 y5 y6 y7 y8 y9 y10 \ + y11 y12 y13 y14 y15 y16 y17 y18 y19 y20 \ + y21 y22 y23 y24 y25 y26 y27 y28 y29 y30 \ + y31 y32 Y33 Y34 Y35 Y36 Y37 Y38 Y39 Y40 \ + y41 y42 y43 y44 y45 y46 y47 y48 y49 y50 \ + y51 y52 y53 y54 y55 y56 y57 y58 y59 y60 \ + y61 y62 y63 y64 + concat z1 z2 z3 z4 z5 z6 z7 z8 z9 z10 \ + z11 z12 z13 z14 z15 z16 z17 z18 z19 z20 \ + z21 z22 z23 z24 z25 z26 z27 z28 z29 z30 \ + z31 z32 + } + } + interp delete $i; # must not crash + return ok + } + foo +} ok + # Special test for underestimating the maxStackSize required for a # compiled command. A failure will cause a segfault in the child # process. -- cgit v0.12