The GCC manual says:
-fsanitize=bounds-strict
This option enables strict instrumentation of array bounds. Most out of bounds accesses are detected, including flexible array members and flexible array member-like arrays. Initializers of variables with static storage are not instrumented.
However, here, sum can access its argument out-of-bounds, despite the use of -fsanitize=address,undefined,bounds-strict (when offset is chosen to reach into the wrong array):
#include <stdio.h>
#include <stdlib.h>
#define n 1000
typedef double array[n];
double sum(array a, long offset)
{
double acc = 0;
for(int i = 0; i < n; ++i)
acc += a[i + offset];
return acc;
}
int main()
{
array a;
array b;
for(int i = 0; i < n; ++i) a[i] = 1;
for(int i = 0; i < n; ++i) b[i] = 2;
long offset = b - a;
printf("%ld\n", offset);
printf("%f\n", sum(a, offset));
}
Compiling this with GCC 10.2 gives me:
$ gcc -pedantic -Wall -std=c17 -fsanitize=address,undefined,bounds-strict memory_errors_two_arrays.c && ./a.out
1032
2000.000000
So the code dereferenced a double[1000] with 2031 and didn't even blink.
Does -fsanitize=bounds-strict check anything that -fsanitize=address does not?
Yes.
-fsanitize=bounds-strictis about checking array bounds, not the bounds of general objects. It uses compile-time information about array lengths, drawn largely from the types of the lvalues used for access, to generate instrumentation. That is orthogonal to the allocation size of dynamically-allocated objects, and the kind of information used is unavailable from many of the common idioms for creating and accessing dynamically allocated objects, such as the one in the original version of this question.Consider this, for example:
If I compile with
-fsanitze=addressand run the result, it just prints "0".If I compile with
-fsanitize=bounds-strictand run it, I get this report:(and it also prints "0").
Apparently, it is also notable that although the diagnostic information emitted by
-fsanitizeinstrumentation is styled as error messages, these do not represent fatal errors. The objective is for such informational messages to be in addition to the normal behavior of the program, not to replace or subvert the normal behavior. In particular,-fsanitizedoes not abort the program when an incorrect memory access is detected, though some other mechanism might cause such a failure in some cases.After the original version of this answer was posted, the question was modified to present a different situation. With respect to the version that is current as I write this, it is relevant that it is relatively easy to hide or moot the type information on which
-fsanitize'sbounds-strictmode relies. The example code currently presented in the question does this by interposing a function call interface between a declared array and the array access.In this context, I observe that in the scope of
, this function declaration ...
is 100% equivalent to
. The
typedefnotwithstanding, it does not convey any information about how many more elements follow*ain the array, if any, of which it is a member. This function must in fact accept pointers into arrays of arbitrary length, so not only is there is no information forbounds-strictto use for generating instrumentation, it would be incorrect for it to be instrumented to assume a particular array length.Contrast with this variation:
When compiled with
-fsanitize=bounds-strict, the output of this program is:This shows several things, among them that
bounds-strictis indeed using the type of the lvalue used for access to generate instrumentation, and-fsanitizein my GCC (v8.5.0) treats automatically allocated (and statically allocated; not shown) arrays differently than dynamically allocated ones, and-fsanitize=bounds-strictin my GCC is buggy for automatically allocated (and statically allocated; not shown) arrays, failing to report some array-bounds overruns.Bugs notwithstanding, again yes,
bounds-checkmode does check some things thataddressmode does not. For me,bounds-checkalso requires an additional library,libubsan, thataddressmode by itself does not. The two modes have overlapping area of application, but their design is different, and each detects some issues that the other does not.