【问题标题】:Best approach for safeguarding char[] when getting back from function?从函数返回时保护 char[] 的最佳方法?
【发布时间】:2009-04-06 04:58:37
【问题描述】:

我正在维护一段 C 代码,其中经常填充 char 数组,方法是将它们传递给函数并将结果用作写入输出的字符串。但是,在函数处理数组后,没有对数组进行检查,我想知道最好的方法是什么?

一种方法是在数组中的最后一个元素返回后将其设置为 \0,但我怀疑可能有更好的元素。

void Unpack(char* inbuf, char* outbuf);

int main(int argc, char* argv[])
{
    char* inData = "abc";
    char outData[4];
    char result[14];

    Unpack(inData, outData);
    outData[3] = '\0';  // Insert this to safeguard array before using as string.

    _snprintf(result, sizeof(result), "blah %0s blah", outData);
    printf(result);

    return 0;
}

void Unpack(char* inbuf, char* outbuf) {
    for(int index=0; index<3; index++) {
        *outbuf++ = *inbuf++;
    } 
}

【问题讨论】:

    标签: c arrays


    【解决方案1】:

    您对这个问题的解决方案实现了几件事,但不幸的是,还有很多其他问题没有解决。

    如果您担心输出数组中甚至可能没有有效的 '\0' 终止的 C 字符串,那么您已经通过蛮力插入一个 '\0' 来解决该问题。此外,您已经选择了最佳位置,如果数组应该包含 '\0' 终止的字符串,则数组的最后一个字节不能用于其他任何内容。

    不幸的是,您没有采取任何措施来阻止被调用函数践踏超出您为其分配的数组的内存。这是我最担心的第一件事。

    避免内存践踏(也称为缓冲区溢出)不是火箭科学,但它确实需要纪律和一致性。通常所涉及的基本思想是永远不要简单地传递要填充的某些内存的地址,而是始终伴随该地址以及可用内存块的长度。当然,被调用的代码必须尊重对其施加的限制。由于您提到了 snprintf(),它是这种方法的经典示例,因此您似乎了解了基本思想。

    【讨论】:

      【解决方案2】:

      如果您拥有这两个代码,更好的方法是更改​​ Unpack 签名以传递输出缓冲区大小,因此它可以负责附加一个 '\0' 字符。

      如果您无法控制 Unpack,我会编写一个薄层来完成这项工作。

      unpack_safe(char *in, char* out, size_t len) {
        unpack(in, out);
        out[len-1]='\0';
      }
      

      这样你只能保护一个非空终止的字符串,但如果你的字符串可以包含空字符,这将无法按预期工作,传递输入大小和输出大小将是正确的行为。

      【讨论】:

        【解决方案3】:

        保护他们免受什么伤害?如果你试图防止像缓冲区溢出这样的事情,那你就太晚了。您调用的函数可以完全访问使用和滥用,如果选择“滥用”,那么事后您无能为力。

        【讨论】:

        • MarkusQ 是对的。你真的是指“检测”,即在发生内存覆盖后检测它吗?
        • 没有。我更感兴趣的是确保在 snprintf 调用中使用 outData 时不会产生意外结果。 Unpack 功能不受我控制。
        【解决方案4】:

        将最后一个元素设置为 '\0' 将正确终止字符串,但它不会修复函数在缓冲区外写入​​时造成的损坏。在这种情况下,您只会隐藏一个错误,而程序可能会在稍后在完全不相关的函数调用或返回时产生错误的结果或崩溃。

        在我看来,最好检测一下函数是否写到了缓冲区之外,如果写到了就让程序崩溃。这使得查找和修复错误更容易。为了实现这一点,您可以在调用函数之前将缓冲区的最后一个字节设置为 '\0' 并测试(使用 assert()),如果函数返回时仍然是这种情况。

        而且,正如 Bill Forster 已经说过的,将缓冲区长度也传递给被调用函数总是一个好主意

        【讨论】:

          【解决方案5】:

          使用原始指针总是存在缓冲区溢出的可能性。使用 std::string、CString 等包装类总是安全的。

          这适用于您编写的新代码。对于这样的现有代码,你只能祈祷它不会崩溃。

          【讨论】:

            【解决方案6】:

            如果您担心 0x55AA 之类的 END-MARKER 或某些保留字符。但是,如果您担心的是数据的完整性,您可以尝试一些带有校验和或 CRC(循环冗余校验)的标头。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2010-09-26
              • 1970-01-01
              • 2017-05-22
              • 2013-04-14
              • 2010-09-07
              • 1970-01-01
              相关资源
              最近更新 更多