为什么编译器不能插入代码来检测缓冲区溢出?

3
为什么C编译器没有一个选项(我说的是选项,有时候你不想这样做)来转换这样的代码:
char a1[8];

int main( int argc, char *argv[] )
{
  char a2[16];
  char *p = (char *)malloc( 24 );
  int argv1_len = strlen( argv[1] );
  memcpy( a1, argv[1], argv1_len );
  memcpy( a2, argv[1], argv1_len );
  memcpy( p, argv[1], argv1_len );
  return 0;
}

转换为:

char a1[8];
addAddr( a1, sizeof( a1 ) ); // build database of addresses and their lengths

int main( int argc, char *argv[] )
{
  char a2[16];
  addAddr( a2, sizeof( a2 ) );
  char *p = (char *)malloc( 24 );
  int argv1_len = strlen( argv[1] );
  addAddr( p, 24 );
  ptrCheck( a1, argv1_len ); // exit if argv1_len > size of a1
  memcpy( a1, argv[1], argv1_len );
  ptrCheck( a2, argv1_len );
  memcpy( a2, argv[1], argv1_len );
  ptrCheck( p, argv1_len );
  memcpy( p, argv[1], argv1_len );
  ptrCheck( p+5, argv1_len );
  memcpy( p+5, argv[1], argv1_len );
  return 0;
}

C编译器对于局部变量和全局变量的内存布局信息是否足够丰富,能否在编译时或运行时插入代码,建立一个内存位置和长度的数据库,并对于任何使用strcpy、memcpy、memset等函数或者类似于*ch1 = * ch2这样赋值操作的代码进行检查,确保内存访问不越界?我认为这种方法可能无法完全覆盖所有情况,并且会带来一定的性能损失,但这个功能可以通过开关或针对代码的某些行或片段进行重新编译来解决。这有点像valgrind,但更好,因为它利用了编译器的帮助,而不仅仅依靠二进制文件并只检查堆栈。
甚至可以将checkPtr API提供给开发人员,以便自己编写strcpy等函数。
char *mystrcpy( char *dst, const char *src )
{
  if ( checkPtr( dst, strlen( src ) ) )
  { /* do something custom */ }
  return strcpy( dst, src );
}

4
嘘,AddressSanitizer :一种用于检测内存错误的工具。 - Iwillnotexist Idonotexist
2
嘘:更完整的技术列表,具有各种权衡,其中大多数已在大多数编译器中实现:http://en.wikipedia.org/wiki/Buffer_overflow_protection - Pascal Cuoq
2
最好的这类工具是专有的,要么是商业机密,要么成本很高。尽管如此,静态分析永远无法做得比捕捉一些情况更好。捕捉所有情况是一个不可判定的问题(没有程序可以完美地完成它)。C语言的运行时检查很复杂,是大学研究课题,例如https://www.doc.ic.ac.uk/~phjk/Publications/BoundsCheckingForC.pdf。 - Gene
1
在clang-3.5上使用了相同的参数-fsanitize=address,并且它也没有报告任何缓冲区溢出。 - user3642186
1
是的,"./a.out 12345678901234567" 捕捉到了第二个 memcpy,但它应该捕捉到第一个。 - user3642186
显示剩余6条评论
2个回答

2

相对较新版本的编译器有选项可以在某种程度上启用此类检查。

例如,这里是 Clang Address Sanitizer 的文档。

您可以通过使用 -fsanitize=address(gcc 和 clang)进行编译来启用它们。

Clang(我相信较新版本的 gcc 也是如此)还包括未定义行为 (-fsanitize=undefined)、未初始化读取(-fsanitize=memory)和数据竞争 (-fsanitize=thread) 的检查器。


2
在嵌入式领域中,编译器和工具链通常存在各种非标准检查选项:NULL指针解引用、缓冲区溢出等。正如您所想象的那样,这些功能计算成本高昂(对时间和性能产生负面影响),会导致膨胀、增加编译时间,以及其他潜在的不良影响。因此,我看到这些“安全”编译选项仅在开发/调试期间启用(就像应用静态源代码检查器一样)。我很少看到发布的代码带有这些内容。

既然我已经提到了静态源代码分析器,我建议看一下Coverity、Code Sonar和其他工具。根据我的经验,这些工具比通常配备此类检查器的编译器更好地检测不安全的代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接