gcc '-fsanitize=bounds-strict'是否检查'-fsanitize=address'不检查的内容?

uxh89sit  于 2023-03-02  发布在  其他
关注(0)|答案(1)|浏览(181)

海湾合作委员会手册说:
-fsanitize=界限-严格
此选项启用数组边界的严格检测。检测大多数越界访问,包括灵活数组成员和灵活数组成员类数组。不检测具有静态存储的变量的初始化程序。
然而,在这里,尽管使用了-fsanitize=address,undefined,bounds-strictsum仍然可以越界访问其参数(当选择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不检查的内容?

kb5ga3dv

kb5ga3dv1#

  • fsanitize = bounds-strict是否检查-fsanitize = address不检查的内容?
    是的。
    -fsanitize=bounds-strict是关于检查 * array * bounds,而不是一般对象的bounds。它使用关于数组长度的编译时信息,主要从用于访问的左值的类型中提取,来生成插装。这与动态分配对象的分配大小是正交的。并且所使用的信息种类不能从用于创建和访问动态分配对象的许多常用习惯用法中获得,比如这个问题的原始版本。
    例如,请考虑以下情况:
#include <stdio.h>
int main(void) {
    int a[2][256] = {0};

    printf("%d\n", a[0][256]);
}

如果我用-fsanitze=address编译并运行结果,它只会打印"0"。
如果我用-fsanitize=bounds-strict编译并运行它,我会得到以下报告:

santest.c:7:24: runtime error: index 256 out of bounds for type 'int [256]'

(and它还打印"0")。
显然,同样值得注意的是,尽管-fsanitize工具发出的诊断信息被设计为错误消息,但这些消息并不代表"致命"错误。目标是使此类信息性消息成为程序正常行为的补充,而不是替代或破坏正常行为。特别是,当检测到不正确的内存访问时,-fsanitize不中止程序,尽管在某些情况下,某些其他机制可能会导致此类故障。
在这个答案的原始版本发布后,这个问题被修改以呈现不同的情况。关于我写这篇文章时的当前版本,相关的是,"隐藏"或"模拟" -fsanitizebounds-strict模式所依赖的类型信息相对容易。问题中当前提供的示例代码通过在声明的数组和数组访问之间插入函数调用接口来实现这一点。
在这方面,我认为在

#define n 1000

typedef double array[n];

,此函数声明...

double sum(array a, long offset)

100%等同于

double sum(double *a, long offset)

尽管有typedef,但它并不传达关于在它是其成员的数组中*a之后还有多少元素的任何信息(如果有的话)。该函数实际上必须接受指向任意长度的数组的指针,因此不仅没有bounds-strict用于生成插装的信息,它被探测以假定特定的数组长度将是不正确的。
与此变体相反:

double access_flat(double *p, int i) {
    return p[i];
}

double access_dim200(double (*p)[200], int i) {
    return (*p)[i];
}

double access_dim100(double (*p)[100], int i) {
    return (*p)[i];
}
 
int main(void) {
    double *p = calloc(200, sizeof *p);
    double q[2][100] = {0};

    printf("p, flat:   %lf\n", access_flat(p, 100));
    printf("p, dim100: %lf\n", access_dim100((double (*)[100]) p, 100));
    printf("p, dim200: %lf\n\n", access_dim200((double (*)[200]) p, 100));

    printf("q, flat:   %lf\n", access_flat((double *) q, 100));
    printf("q, dim100: %lf\n", access_dim100(q, 100));
    printf("q, dim200: %lf\n", access_dim200((double (*)[200]) q, 100));
}

使用-fsanitize=bounds-strict编译时,此程序的输出为:

p, flat:   0.000000
santest.c:14:16: runtime error: index 100 out of bounds for type 'double [100]'
p, dim100: 0.000000
p, dim200: 0.000000

q, flat:   0.000000
q, dim100: 0.000000
q, dim200: 0.000000

这说明了几件事,其中

  • bounds-strict确实使用了用于访问的左值类型来生成插装,并且
  • 我的GCC中的-fsanitize版本(v8.5.0)将自动分配(和静态分配;未示出)不同于动态分配的阵列,以及
  • 我的GCC中的-fsanitize=bounds-strict版本在自动分配(和静态分配;未示出)数组,未能报告某些数组边界溢出。

尽管有bug,bounds-check模式确实检查了address模式没有检查的一些东西。对我来说,bounds-check还需要一个额外的库libubsan,而address模式本身没有。这两种模式有重叠的应用领域,但它们的设计是不同的,每种模式都检测到一些其他模式没有检测到的问题。

相关问题