【问题标题】:Catching weird C pointer arithmetic bugs捕捉奇怪的 C 指针算术错误
【发布时间】:2010-02-02 18:10:55
【问题描述】:

我最近遇到了一个非常狡猾的错误,我忘记取消引用指向字符串(char 数组)的指针,因此有时会覆盖堆栈上的一个字节。

不好:

char ** str;
(*str) = malloc(10);
...
str[2] = 'a'; //overwrites 3 bytes from the location in which str is stored

更正:

char ** str;
(*str) = malloc(10);
...
(*str)[2] = 'a'; 

GCC 没有产生任何警告,这个错误会导致一个非常严重和真实的漏洞,因为它有时覆盖的值包含缓冲区的大小。我只是因为幸运而发现了这个错误,并且它导致了明显的失败。

  • 除了依靠运气和/或从不使用 C 做任何事情之外,您还使用哪些防御性编码技术和技巧来捕获奇怪的 C 错误?

  • 我正在考虑转到 valgrind 的 MemCheck,有人用过吗?我怀疑它不会发现这个错误。有人知道吗?

  • 是否有用于捕获指针取消引用或算术错误的工具?这可能吗?

更新

这是请求的示例代码,它不会引发任何警告。

#include <stdlib.h>

void test(unsigned char** byteArray){
    (*byteArray) = (unsigned char*)malloc(5);
    byteArray[4] = 0x0;
}

int main(void){
    unsigned char* str;
    test(&str);  
    return 0;
}

编译不会出错:

gcc -Wall testBug.c -o testBug

运行导致段错误:

./testBug
Segmentation fault

这是我正在使用的 GCC 版本:

gcc -v

Using built-in specs.
Target: i486-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.4.1-4ubuntu9' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --program-suffix=-4.4 --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --enable-targets=all --disable-werror --with-arch-32=i486 --with-tune=generic --enable-checking=release --build=i486-linux-gnu --host=i486-linux-gnu --target=i486-linux-gnu
Thread model: posix
gcc version 4.4.1 (Ubuntu 4.4.1-4ubuntu9) 

【问题讨论】:

  • 我从这样做得到一个警告:“警告:赋值使指针从整数不进行强制转换”。
  • @Jefromi 试试我刚刚发布的内容,我当然没有收到警告。我也在用 -Wall 编译。
  • 您的示例代码分配了0x0,而不是初始问题描述中的'a'。编译器不会警告分配 0,因为它是一个空指针常量。编辑:哦,R Samuel Klatchko 已经在另一个线程中说过......
  • 我的评论提到了您的原始代码,这肯定会产生警告。

标签: c pointers findbugs


【解决方案1】:

我最好的防守指针策略:强烈避免使用多于一级的间接性。取消引用指向指针以为其分配内存是可以的。但是,然后将分配的内存用作数组是自找麻烦,你得到了。我会把它做成这样的:

char **outStr;
*outStr = malloc(10);
char *str = *outStr;
str[2] = 10;

好的,实际上这只是一种保持理智的策略,恰好也具有防御价值。当一次不超过一个间接级别时,指针是相当容易理解的,并且当您理解它时更容易使代码正常工作。

【讨论】:

    【解决方案2】:

    我使用 Valgrind,它是救命稻草!

    valgrind --tool=memcheck -v ./yourapp
    

    MemCheck 会检测到您使用 `str[2] = 'a';´ 进行了无效写入。

    【讨论】:

      【解决方案3】:

      GCC 应该给你

       warning: assignment makes pointer from integer without a cast
      

      没有?

      【讨论】:

      • 很遗憾,我没有收到这样的错误。当我遇到这个错误时,我修复了 gcc 生成的唯一警告。
      • @e5 你用的是什么版本的 gcc?它肯定给了我这个警告。
      • gcc 4.4.1,我刚刚测试过,它没有给出任何警告。代码如下: unsigned char** str; (str) = (char)malloc(5); str[5] = 0x0;难道是我将 (char*) 转换为 unsigned char* 的事实吗?
      • @e5 - 这不会给您警告,因为 0x0 既是有效整数又是有效指针。将str[5] = 0x0; 更改为str[5] = 'a'; 以查看警告。
      • +1 @R Samuel Klatchko 对!做得好。我想将最终字符设置为 NULL,因为它是一个以空字符结尾的字符串。我怎样才能安全地做到这一点?
      【解决方案4】:

      请使用 Valgrind。这是我遇到的最好的内存检查工具之一。它肯定会检测到您的错误。

      除了检测内存错误,valgrind 还有助于检测内存泄漏、正在使用的内存块等。

      即使IBM Rational Purify 也会帮助您检测此类错误。虽然我个人最喜欢的是 Valgrind。

      【讨论】:

        【解决方案5】:

        我的建议不是工具,而是最佳实践:测试。从最低级别的单元测试开始,通过严格的代码测试通常很容易发现此类错误。

        您显示的代码永远不会产生正确的结果 - 这不是有时有效有时无效的东西。对那段代码进行单元测试可以在以后与系统的其他部分集成时节省数小时的调试时间。

        单元测试可以通过覆盖检查来补充:或者使用自动工具,或者只是手动扫描代码并编写针对每个部分的测试 - 这实际上是重新阅读代码的好方法(另一个调试工具)和效果惊人。

        【讨论】:

        • 我完全同意一个好的测试代码会有很大帮助。但是,在良好的测试代码上运行 valgring 将仔细检查您的代码并使其保持完美。
        • 单元测试很棒,我完全赞成,但它可能不会发现这个错误,因为这个错误只有在两个组件(单元)集成时才可见。此外,如果覆盖值增加而不是减少覆盖值,则不会激活任何故障模式。
        猜你喜欢
        • 2014-02-19
        • 2013-06-12
        • 2016-01-31
        • 2013-04-14
        • 2012-09-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-27
        相关资源
        最近更新 更多