【问题标题】:Shouldn't TclInvalidateStringRep() reset length?TclInvalidateStringRep() 不应该重置长度吗?
【发布时间】:2019-06-17 15:55:33
【问题描述】:

我对 TCL 8.6.8 源码 tclInt.h 中的以下代码有疑问:

4277 #define TclInvalidateStringRep(objPtr) \
4278     if (objPtr->bytes != NULL) { \
4279         if (objPtr->bytes != tclEmptyStringRep) { \
4280             ckfree((char *) objPtr->bytes); \
4281         } \
4282         objPtr->bytes = NULL; \
4283     }

此宏由 tclObj.c 中的 Tcl_InvalidateStringRep() 调用。

我的疑问是,为什么 tclObj 的长度不重置为零?

这里是Tcl_Obj的定义:

 808 typedef struct Tcl_Obj {
 809     int refCount;               /* When 0 the object will be freed. */
 810     char *bytes;                /* This points to the first byte of the
 811                                  * object's string representation. The array
 812                                  * must be followed by a null byte (i.e., at
 813                                  * offset length) but may also contain
 814                                  * embedded null characters. The array's
 815                                  * storage is allocated by ckalloc. NULL means
 816                                  * the string rep is invalid and must be
 817                                  * regenerated from the internal rep.  Clients
 818                                  * should use Tcl_GetStringFromObj or
 819                                  * Tcl_GetString to get a pointer to the byte
 820                                  * array as a readonly value. */
 821     int length;                 /* The number of bytes at *bytes, not
 822                                  * including the terminating null. */

所以你可以看到长度与字节紧密耦合,当字节被清除时,我们不应该重置长度吗?

我的疑问来自以下代码,tclLiteral.c 中的 TclCreateLiteral():

 200     for (globalPtr=globalTablePtr->buckets[globalHash] ; globalPtr!=NULL;
 201             globalPtr = globalPtr->nextPtr) {
 202         objPtr = globalPtr->objPtr;
 203         if ((globalPtr->nsPtr == nsPtr)
 204                 && (objPtr->length == length) && ((length == 0)
 205                 || ((objPtr->bytes[0] == bytes[0])
 206                 && (memcmp(objPtr->bytes, bytes, (unsigned) length) == 0)))) {

所以在第 204 行,当 length 不为零而 bytes 为 NULL 时,程序会崩溃。

我的产品包含 TCL 源码,我在跟踪程序崩溃时发现了上述问题。我将解决方法放在我们的代码中,但想与社区确认它是否确实是一个漏洞。

【问题讨论】:

  • 对我来说这看起来像是TclCreateLiteral 中的一个错误;如果bytes 字段为NULL,则不应直接触及length 字段。使用 TclGetStringFromObj 宏来提取这些字段(而不是直接在结构内部四处寻找)确保语义正确。
  • 另外,你是如何触发这个代码路径的?如果我们要测试它(我们应该!),那么我们需要重现它……

标签: tcl


【解决方案1】:

你的方法似乎有问题。

TclInvalidateStringRep 的调用基本上允许用于没有引用 (refCount == 0) 或只有一个引用 (所以 refCount <= 1) 的对象,然后只有当您确定这 1 个引用是您自己的引用时.

Tcl 的共享对象 可以切换其内部表示,但字符串表示保持不变。否则会破坏 Tcl 的基本原则(如 EIAS 等)。

可以解释这一点的最简单的例子:

set k 0x7f
dict set d $k test
expr {$k};             # ==> 127 (obj is integer now, but...)
puts $k;               # ==> 0x7f (... still remains the string-representation)
puts [dict get $d $k]; # ==> test

# some code that fouls it up (despite of two references var `k` and key in dict `d`):
magic_happens_here $k; # string representation gets lost.

# and hereafter:
puts $k;               # ==> 127 (representation is now 127, so...)
puts [dict get $d $k]; # ==> ERROR: key "127" not known in dictionary

如您所见,分别重置。改变共享对象的字符串表示是错误的设计。
请在 Tcl 中避免这种情况。

【讨论】:

    【解决方案2】:

    我已经考虑过这一点,虽然我认为清除表示的代码这样做是错误的(因为原则上应该共享对象,因此不应观察到更改)我当然认为要真正证明这不可能发生是极其困难的。当然,如果发生这种情况,tclLiteral.c 中的 TclCreateLiteral 不应该爆炸!

    The fix 我使用的是让TclCreateLiteral 使用TclGetStringFromObjTcl_GetStringFromObj 的Tcl 内部宏化版本)来获取byteslength 字段,而不是直接使用它们,以便保留正确的约束。 如果删除它,这应该会使字符串表示再次存在。如果代码继续崩溃,问题是你的代码在文字上调用 TclInvalidateStringRep(并设置了一个可以' t 有为其生成的字符串;Tcl 有其中的一些,但那是因为它从不清除其中的原始字符串)。

    请记住,Tcl_Obj 应该只在它变得错误时清除其字符串代表, 而不仅仅是在它获得非字符串表示时。一个值被解释为整数的事实并不意味着它不应该被解释为一个列表(完全相反!),并且如果内部表示永远不会更新为不同的值(就地修改应该永远发生在未共享的对象上)它根本不需要丢失该字符串表示。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-01-28
      • 2019-02-25
      • 1970-01-01
      • 1970-01-01
      • 2018-05-04
      • 1970-01-01
      • 2011-11-19
      • 1970-01-01
      相关资源
      最近更新 更多