Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this write to an array truly undefined behavior in C? [duplicate]

This code writes a value through a pointer if one array is one past the end of another array.

#include <stdio.h>
#include <inttypes.h>

extern int first[], second[];

#define ADDR_AFTER(ptr) ((uintptr_t)((ptr) + 1))

int test(int *an_int) {
    *second = 1;
    if (ADDR_AFTER(first) == (uintptr_t)an_int) {
        // ubsan does not like this.
        *an_int = 2;
    }
    return *second;
}

int first[1] = {0}, second[1] = {0};

int main() {
    if (ADDR_AFTER(first) == (uintptr_t)second) {
        printf("test: %d\n", test(second));
        printf("x: %d y: %d\n", *first, *second);
    }
}

At no point do I directly compare two pointers to different objects (because I convert them to uintptr_t first). I create a pointer to one-past-the-end of an array (which is legal), but I never dereference that pointer. As far as I can tell this should either print nothing, or print:

test: 2
x: 0 y: 2

Which is printed on Clang when optimization is -O1 or lower. At -O2, however, it prints:

test: 1
x: 0 y: 2

With -O2 -fsanitize=undefined it prints the this to stdout:

test: 2
x: 0 y: 2

and the following to stderr:

runtime error: store to address 0x000000cd9efc with insufficient space for an object of type 'int'
0x000000cd9efc: note: pointer points here
  00 00 00 00 01 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00

with a reference to the assignment to an_int in test. Is this actually undefined behavior, or is this a bug in Clang?

like image 207
Michael Morris Avatar asked Oct 15 '22 19:10

Michael Morris


1 Answers

There's nothing invalid about your code, the compiler is wrong. If you remove the unnecessary ADDR_AFTER check in test(), the code runs as expected with no UBSan error. If you run it with optimization enabled and without UBSan, you get the wrong output (test=1, should be 2).

Something about the ADDR_AFTER(first) == (uintptr_t)an_int code inside test() makes Clang do the wrong thing when compiling with -O2.

I tested with Apple clang version 11.0.3 (clang-1103.0.32.62) but it looks like Clang 13 and current trunk also have the bug: https://godbolt.org/z/s83ncTsbf - if you change the compiler to any version of GCC you'll see it can return 1 or 2 from main(), while Clang always returns 1 (mov eax, 1).

You should probably file a Clang bug for this.

like image 119
John Zwinck Avatar answered Oct 19 '22 03:10

John Zwinck