9

我很想知道是否可以将 C 中的变量显式污染为未初始化。

伪代码...

{
    int *array;
    array = some_alloc();
    b = array[0];
    some_free(array);
    TAINT_MACRO(array);

    /* the compiler should raise an uninitialized warning here */
    b = array[0];
}

这是污染变量的一种方法的一个示例,但是当为“a”分配未初始化的变量时,GCC 会发出警告,而不是第二次使用“a”。

{
    int a = 10;
    printf("first %d\n", a);
    do {
        int b;
        a = b;
    } while(0);
    printf("second %d\n", a);
}

我能想出的唯一解决方案是用未初始化的变量显式隐藏变量,(添加了空格,因此没有未使用的警告)。

#define TAINT_MACRO_BEGIN(array) (void)(array); { void **array; (void)array;
#define TAINT_MACRO_END(array) } (void)(array);
{
    int *array;
    array = some_alloc();
    b = array[0];
    some_free(array);
    TAINT_MACRO_BEGIN(array);

    /* the compiler should raise an uninitialized warning here */
    b = array[0];
    TAINT_MACRO_END(array);
}

这种方法增加了太多的开销,无法包含在现有代码中(增加了很多噪音和烦人的维护),所以我想知道是否有其他方法可以告诉编译器变量未初始化。

我知道有静态检查器并且我确实使用了这些,但我正在寻找可以在编译时发出警告并且没有误报的东西,我相信在这种情况下这是可能的并且可以避免某些类别的错误。

4

3 回答 3

3

我在 GCC 列表上发送了一个答案,但由于我自己首先使用 SO...

在现代 C 和 C++ 中,我希望程序员使用有限的变量范围来控制这种暴露。

例如,我认为您想要这样的东西(请注意,我使用的属性实际上并不存在,我只是想解释您的请求)。

int x = 1; // initialized 
int y;     // uninitialized 

x = y;     // use of uninitialized value 'y' 

y = 2;     // no longer uninitialized 
x = y;     // fine 

y = ((__attr__ uninitialized))0; // tell gcc it's uninitialized again 

x = y;    // warn here please. 

如果是这样,我会在 C99(或更高版本)或 C++ 中使用额外的范围(很确定它至少从 1993 年的 ARM 开始就“在使用点声明”......):

int x = 1; // initialized 

{ 
    int y; // uninitialized 
    x = y; // warn here 
    y = 2; // ok, now it's initialized 
    x = y; // fine, no warning 
} 

{ 
    int y; // uninitialized again! 
    x = y; // warns here 
} 

额外的作用域有点令人反感,但我在 C++ 中非常习惯它们(由于大量使用 RAII 技术。)

由于主流语言对此有答案,因此我认为不值得将其添加到编译器中。

查看您的示例,您关心的是数组。这应该与额外的范围一样工作,并且不应该有额外的运行时成本,因为整个堆栈帧是在函数入口(至少是 SFAIK)上分配的。

于 2013-05-27T04:01:44.940 回答
2

根据对不同问题的回答,您可以使用setjmpandlongjmp使更改的局部变量具有不确定的值。

#define TAINT(x)                             \
        do {                                 \
            static jmp_buf jb;               \
            if (setjmp(jb) == 0) {           \
                memset(&x, '\0', sizeof(x)); \
                longjmp(jb, 1);              \
            }                                \
        } while (0)

如果x是一个局部变量,它的值将在TAINT应用到它之后的代码行中是不确定的。这是因为 C.11 §7.13.2 ¶3(强调我的):

所有可访问对象都有值,并且抽象机器的所有其他组件都有状态,截至longjmp调用函数时,除了自动存储持续时间的对象的值,这些对象的值对于包含调用相应setjmp宏的函数来说是本地的没有 volatile 限定的类型,并且在setjmp调用和longjmp 调用之间已更改是indeterminate

请注意,使用受污染的变量不需要诊断。但是,编译器编写者正在积极检测未定义的行为以增强优化,因此如果这永远无法诊断,我会感到惊讶。

于 2017-02-08T01:53:24.677 回答
1

我会反过来,将污点宏包装在分配和释放函数周围。这就是我的想法:

#ifdef O_TAINT
volatile int taint_me;
#define TAINT(x, m) \
    if (taint_me) { goto taint_end_##x; } else {} x = m
#define free(x) free(x); taint_end_##x: (void)0
#else
#define TAINT(x, m) x = m
#endif

因此,您的示例将如下所示:

int *array;
int b;

TAINT(array, malloc(sizeof(int)));
b = array[0];
printf("%d\n", b);
free(array);

/* the compiler should raise an uninitialized warning here */
b = array[0];
printf("%d\n", b);

这并不完美。每个受污染的变量只能调用free()一次,因为goto标签与变量名称相关联。如果跳转跳过其他初始化,您可能会得到其他误报。如果分配发生在一个函数中,而内存在另一个函数中释放,则它不起作用。

但是,它提供了您要求示例的行为。正常编译时,不会出现任何警告。如果用 编译-DO_TAINT,第二次赋值时会出现警告b


我确实制定了一个相当通用的解决方案,但它涉及使用开始/结束宏将整个函数括起来,并且依赖于 GCC 扩展typeof运算符。解决方案最终看起来像这样:

void foo (int *array, char *buf)
{
    TAINT_BEGIN2(array, buf);
    int b;

    puts(buf);
    b = array[0];
    printf("%d\n", b);

    free(array);
    free(buf);

    /* the compiler should raise an uninitialized warning here */
    puts(buf);
    b = array[0];
    printf("%d\n", b);

    TAINT_END;
}

在这里,TAINT_BEGIN2用于声明将获得污点处理的两个函数参数。不幸的是,宏有点乱,但很容易扩展:

#ifdef O_TAINT
volatile int taint_me;
#define TAINT(x, m) \
    if (taint_me) { goto taint_end_##x; } else {} x = m
#define TAINT1(x) \
    if (taint_me) { goto taint_end_##x; } else {} x = x##_taint
#define TAINT_BEGIN(v1) \
    typeof(v1) v1##_taint = v1; do { \
    typeof(v1##_taint) v1; TAINT1(v1)
#define TAINT_BEGIN2(v1, ...) \
    typeof(v1) v1##_taint = v1; TAINT_BEGIN(__VA_ARGS__); \
    typeof(v1##_taint) v1; TAINT1(v1)
#define TAINT_BEGIN3(v1, ...) \
    typeof(v1) v1##_taint = v1; TAINT_BEGIN2(__VA_ARGS__); \
    typeof(v1##_taint) v1; TAINT1(v1)
#define TAINT_END } while(0)
#define free(x) free(x); taint_end_##x: (void)0
#else
#define TAINT_BEGIN(x) (void)0
#define TAINT_BEGIN2(...) (void)0
#define TAINT_BEGIN3(...) (void)0
#define TAINT_END (void)0
#define TAINT1(x) (void)0
#define TAINT(x, m) x = m
#endif
于 2013-05-25T04:29:27.840 回答