可变修改类型的兼容性及其安全隐患

Variably-modified types compatibility and its security implications

我对 C99 的可变修改类型系统产生了浓厚的兴趣。这个问题的灵感来自 .

检查这个问题的代码,我发现了一些有趣的东西。考虑这段代码:

int myFunc(int, int, int, int[][100]);

int myFunc(int a, int b, int c, int d[][200]) {
    /* Some code here... */
}

这显然不会(也不会)编译。但是,这段代码:

int myFunc(int, int, int, int[][100]);

int myFunc(int a, int b, int c, int d[][c]) {
    /* Some code here... */
}

编译甚至没有警告(在 gcc 上)。

这似乎暗示可变修改数组类型与任何非可变修改数组类型兼容!

但这还不是全部。您希望可变修改类型至少会考虑使用哪个变量来设置其大小。但它似乎并没有这样做!

int myFunc(int, int b, int, int[][b]);

int myFunc(int a, int b, int c, int d[][c]) {
    return 0;
}

同样编译无误。

所以,我的问题是:这是正确的标准化行为吗?

此外,如果可变修改的数组类型确实与具有相同维度的任何数组兼容,这是否意味着严重的安全问题?例如,考虑以下代码:

int myFunc(int a, int b, int c, int d[][c]) {
    printf("%d\n", sizeof(*d) / sizeof((*d)[0]));
    return 0;
}

int main(){
    int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    myFunc(0, 0, 100, &arr);

    return 0;
}

编译并输出 100,没有错误或警告,什么都没有。正如我所见,这意味着即使您通过 sizeof 不执行单个转换 甚至严格检查数组的大小,也很容易越界写入数组已打开所有警告!还是我遗漏了什么?

#include <stdio.h>

void foo(int c, char d[][c])
{
  fprintf(stdout, "c = %d; d = %p; d + 1 = %p\n", c, d, d + 1);
}

int main()
{
  char x[2][4];
  char y[3][16];
  char (*z)[4] = y;  /* Warning: incompatible types */

  foo(4, x);
  foo(16, y);
  foo(16, x);        /* We are lying about x. What can / should the compiler / code do? */
  foo(4, y);         /* We are lying about y. What can / should the compiler / code do? */

  return 0;
}

输出:

c = 4; d = 0x7fff5b295b70; d + 1 = 0x7fff5b295b74
c = 16; d = 0x7fff5b295b40; d + 1 = 0x7fff5b295b50
c = 16; d = 0x7fff5b295b70; d + 1 = 0x7fff5b295b80
c = 4; d = 0x7fff5b295b40; d + 1 = 0x7fff5b295b44

因此,foo() 会动态计算出基于 c 将 d 推进多远,正如您的代码所演示的那样。

但是,编译器通常不可能静态地确定if/when您错误地调用了 foo()。似乎如果你这样做,那么编译器会说 "OK, I'll allow you to pass whatever you want as d, so long as its type is a doubly indexed array of chars. Operations on the pointer d will be determined by c. Good luck!"

也就是说,是的,编译器通常无法对这些类型的参数进行静态类型检查,因此该标准几乎肯定不会强制编译器捕获所有可能静态确定类型不兼容的情况。

C99,section 6.7.5.2 好像是给出相关规则的地方。特别是,

第 6 行:

For two array types to be compatible, both shall have compatible element types, and if both size specifiers are present, and are integer constant expressions, then both size specifiers shall have the same constant value. If the two array types are used in a context which requires them to be compatible, it is undefined behavior if the two size specifiers evaluate to unequal values.

之前的一个现已删除的答案也引用了第 6 行。对该答案的评论认为第二句受第一句末尾的条件约束,但这似乎不太可能解读。该部分的示例 3 可能会阐明(摘录):

int c[n][n][6][m];
int (*r)[n][n][n+1];
r=c;   // compatible, but defined behavior only if
       // n == 6 and m == n+1

这似乎与问题中的示例相当:两种数组类型,一种具有常量维度,另一种具有相应的可变维度,并且要求兼容。当运行时变量维度与编译时常量维度不同时,行为未定义(根据示例 3 中的注释和 6.7.5.2/6 的合理阅读)。无论如何,未定义的行为不是您所期望的吗?不然为什么提这个问题?

假设我们可以同意在发生这种不匹配时行为是未定义的,我观察到编译器通常不需要识别未定义或可能未定义的行为,也不需要发出任何类型的诊断(如果它们确实识别出此类行为) .在这种情况下,我希望编译器能够警告可能未定义的行为,但它必须成功编译代码,因为它在语法上是正确的并且满足所有适用的约束。请注意,默认情况下

能够警告此类用途的编译器可能不会这样做