海湾合作委员会手册说:
-fsanitize=界限-严格
此选项启用数组边界的严格检测。检测大多数越界访问,包括灵活数组成员和灵活数组成员类数组。不检测具有静态存储的变量的初始化程序。
然而,在这里,尽管使用了-fsanitize=address,undefined,bounds-strict
,sum
仍然可以越界访问其参数(当选择offset
访问错误的数组时):
#include <stdio.h>
#include <stdlib.h>
#define n 1000
typedef double array[n];
double sum(array a, long offset)
{
double acc = 0;
for(int i = 0; i < n; ++i)
acc += a[i + offset];
return acc;
}
int main()
{
array a;
array b;
for(int i = 0; i < n; ++i) a[i] = 1;
for(int i = 0; i < n; ++i) b[i] = 2;
long offset = b - a;
printf("%ld\n", offset);
printf("%f\n", sum(a, offset));
}
用GCC 10.2编译这个代码可以得到:
$ gcc -pedantic -Wall -std=c17 -fsanitize=address,undefined,bounds-strict memory_errors_two_arrays.c && ./a.out
1032
2000.000000
所以代码使用2031解引用double[1000]
,甚至没有 Flink 。-fsanitize=bounds-strict
是否检查-fsanitize=address
不检查的内容?
1条答案
按热度按时间kb5ga3dv1#
是的。
-fsanitize=bounds-strict
是关于检查 * array * bounds,而不是一般对象的bounds。它使用关于数组长度的编译时信息,主要从用于访问的左值的类型中提取,来生成插装。这与动态分配对象的分配大小是正交的。并且所使用的信息种类不能从用于创建和访问动态分配对象的许多常用习惯用法中获得,比如这个问题的原始版本。例如,请考虑以下情况:
如果我用
-fsanitze=address
编译并运行结果,它只会打印"0"。如果我用
-fsanitize=bounds-strict
编译并运行它,我会得到以下报告:(and它还打印"0")。
显然,同样值得注意的是,尽管
-fsanitize
工具发出的诊断信息被设计为错误消息,但这些消息并不代表"致命"错误。目标是使此类信息性消息成为程序正常行为的补充,而不是替代或破坏正常行为。特别是,当检测到不正确的内存访问时,-fsanitize
不中止程序,尽管在某些情况下,某些其他机制可能会导致此类故障。在这个答案的原始版本发布后,这个问题被修改以呈现不同的情况。关于我写这篇文章时的当前版本,相关的是,"隐藏"或"模拟"
-fsanitize
的bounds-strict
模式所依赖的类型信息相对容易。问题中当前提供的示例代码通过在声明的数组和数组访问之间插入函数调用接口来实现这一点。在这方面,我认为在
,此函数声明...
100%等同于
尽管有
typedef
,但它并不传达关于在它是其成员的数组中*a
之后还有多少元素的任何信息(如果有的话)。该函数实际上必须接受指向任意长度的数组的指针,因此不仅没有bounds-strict
用于生成插装的信息,它被探测以假定特定的数组长度将是不正确的。与此变体相反:
使用
-fsanitize=bounds-strict
编译时,此程序的输出为:这说明了几件事,其中
bounds-strict
确实使用了用于访问的左值类型来生成插装,并且-fsanitize
版本(v8.5.0)将自动分配(和静态分配;未示出)不同于动态分配的阵列,以及-fsanitize=bounds-strict
版本在自动分配(和静态分配;未示出)数组,未能报告某些数组边界溢出。尽管有bug,
bounds-check
模式确实检查了address
模式没有检查的一些东西。对我来说,bounds-check
还需要一个额外的库libubsan
,而address
模式本身没有。这两种模式有重叠的应用领域,但它们的设计是不同的,每种模式都检测到一些其他模式没有检测到的问题。