简体   繁体   中英

Variably-modified types compatibility and its security implications

I'm going through a surge of interest in C99's variably-modified type system. This question was inspired by this one .

Checking the code from this question, I discovered something interesting. Consider this code:

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

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

This obviously won't (and does not) compile. However, this code:

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

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

compiles without even a warning (on gcc).

That seems to imply that a variably-modified array type is compatible with any non-variably-modified array type!

But that's not all. You'd expect a variably-modified type to at least bother with which variable is used to set its size. But it doesn't seem to do so!

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

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

Also compiles without any error.

So, my question is: is this correct standardized behaviour?

Also, if a variably-modified array type would really be compatible with any array that has the same dimensions, wouldn't this mean nasty security problems? For example, consider the following code:

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;
}

Compiles and outputs 100, no errors or warnings, nothing. As I see it, that means easy out-of-bounds array write even if you are strictly checking the size of your array via sizeof , not doing a single cast and even have all warnings turned on! Or am I missing something?

C99, section 6.7.5.2 seems to be where the relevant rules are given. In particular,

Line 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.

A previous, now-deleted answer also referenced line 6. Commentary on that answer argued that the second sentence was subject to the condition at the end of the first, but that seems an unlikely reading. Example 3 of that section may clarify (excerpt):

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

That seems comparable to the example in the question: two array types, one having a constant dimension and the other having a corresponding variable dimension, and required to be compatible. Behavior is undefined (per comment in example 3 and one reasonable reading of 6.7.5.2/6) when at runtime the variable dimension differs from the compile-time constant dimension. And isn't undefined behavior what you would expect anyway? Else why raise the question?

Supposing we can agree that behavior is undefined when such a mismatch occurs, I observe that compilers are in general not required to recognize undefined or possibly-undefined behavior, nor to issue any kind of diagnostic whatsoever if they do recognize such. I'd hope in this case that the compiler would be capable of warning about the possibly-undefined behavior, but it must successfully compile the code because it is syntactically correct and satisfies all applicable constraints. Note that a compiler capable of warning about such uses might not do so by default .

#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;
}

Outputs:

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

So, foo() does dynamically figure out how far to advance d based on c, as your code also demonstrates.

However, it is often impossible for the compiler to statically determine if/when you call foo() incorrectly. It seems that if you do this, then the compiler is saying "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!"

That is, yes, the compiler often can't do static type checking on these kinds of parameters and so the standard almost certainly does not mandate compilers to catch all cases where it is possible to statically determine a type incompatibility.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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