繁体   English   中英

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

[英]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严格检查数组的sizeof ,也不会进行单一演员甚至打开所有警告,即使是简单的越界数组写入! 或者我错过了什么?

C99,第6.7.5.2节似乎是给出相关规则的地方。 特别是,

第6行:

要使两个数组类型兼容,两者都应具有兼容的元素类型,如果两个大小说明符都存在,并且是整数常量表达式,则两个大小说明符应具有相同的常量值。 如果在要求它们兼容的上下文中使用这两种数组类型,则如果两个大小说明符计算为不相等的值,则它是未定义的行为。

先前已删除的答案也引用了第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的一个合理读数),在运行时变量维度与编译时常量维度不同。 并且不是未定义的行为,无论如何你会期望什么? 为什么要提出这个问题呢?

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

#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的程度,正如您的代码所示。

但是,编译器通常无法静态确定是否/何时错误地调用foo()。 似乎如果你这样做,那么编译器会说“好吧,我会允许你传递你想要的任何东西作为d,只要它的类型是一个双重索引的字符数组。指针d上的操作将被确定c。祝你好运!“

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM