Address Sanitizer Internals
blog.gistre.epita.fr
blog.gistre.epita.fr
So I learned something new from the OP (that this type of false negative is possible).
He’s saying that with a compiler flag you can get the same kind of safety as in Ada or Rust. Later downthread he clarifies to say that you only get this safety if you stop using pointers, which he considers “trivial” to do.
This is the same guy who commented a few months back saying the following:
“All decent C compilers have compilation options so that at run-time any undefined actions, including integer overflow and out-of-bounds accesses, will be trapped.”
There is, put simply, no flag that will trap on “any undefined actions”, period. That’s not what sanitizers are capable of. That’s the kind of overhyped comment that isn’t helpful when trying to understand what sanitizers are actually useful for.
Checking out-of-bounds accesses must detect any out-of-bounds access done at run time. It is done by comparing the pointers or indices used for access with the array bounds.
(At least for gcc: "Initializers of variables with static storage are not instrumented.")
Out-of-bounds access checking and integer overflow detection should normally be enabled by default by any C/C++ developer, excluding only those functions where it has been determined experimentally that disabling the checks improves measurably the performance and which have been verified very carefully to prove that such exceptions cannot occur.
Unfortunately, in gcc and clang there is a large number of compilation options related to sanitizers. Most of them should always be enabled, to remove all problems created by the laxity of the C/C++ standards. Instead of listing on the command line the humongous number of such options, many of the most useful are included in "-fsanitize=undefined", but the compiler documentation must be studied to see what else may need to be added. At least for release builds, "-fsanitize-trap=all" is usually also desirable.
C or C++ w/o ASan and UBSan is like skydiving w/o a parachute.
In that case there are no false positives or negatives.
There is nothing magic about this option. With it C or C++ is compiled exactly like any other programming language where array bounds checking is implicit.
That means that whenever a new value is computed for a pointer or index that will be used to access an array, the value is compared with the bounds associated with that array.
Such a comparison cannot give false positives or negatives, any address is either within bounds or outside bounds.
This is something that is completely independent of the programming language. Out-of-bounds checking has nothing to do with the syntax or with any explicit features of a programming language. It is just a compilation technique, which can be applied or it can be omitted at the compilation of any programming language, regardless whether the language is C or C++ or Ada or Rust.
The only difference is that in better programming language specifications it is required that any conforming compiler must by default insert bounds checks, while in the C and C++ standards the behavior of the compiler is unspecified and the existing compilers have a bad default behavior, so it is the responsibility of the programmer to use the right compiler options.
#include <stdio.h>
int main(int argc, char **argv) {
printf("%s\n", argv[-8]);
}
(https://godbolt.org/z/ef8KMGnce - feel free to try other values to dump out environment variables and other stack crap)Oh, but maybe that's because the compiler has no model of how `argv` works. Fine, try this?
#include <stdio.h>
int foo(volatile char *x) {
return x[0] + x[-64];
}
int main(int argc, char **argv) {
char x[1] = {42};
printf("%d\n", foo(x));
}
(https://godbolt.org/z/TMzehfGah)Happily loads way out of bounds, no problem whatsoever - no runtime error, no sanitizer complaints, nothing.
Sanitizers are great, but they are not perfect. They are not a replacement for real array bounds checking; other languages that do real bounds checking do so by carrying the bounds with the object which is categorically impossible under the standard C ABIs.
The C language indeed allows the use of pointers having arbitrary values that cannot be checked in any way.
However it is trivial to avoid the use of such pointers and any decent programmer will never use such pointers, because they are never needed.
Unfortunately, it is difficult to forbid the use of such pointers, because there are too many legacy programs.
That however cannot be an excuse for any programmer who is writing a new program. If someone uses pointers in such a way, that cannot happen through an unwilling mistake, so it is their fault and they have no right to blame the programming language.
Unfortunately the documentation for the great number of sanitizers available in gcc and clang is both voluminous and incomplete.
For all of them their internals should have been very clearly documented, like in this article about ASAN.
https://news.ycombinator.com/item?id=37479651
“All decent C compilers have compilation options so that at run-time any undefined actions, including integer overflow and out-of-bounds accesses, will be trapped.”
“Despite the hype, by default Rust is not safer than C compiled with the right options, because the default for Rust releases is also to omit many run-time checks.”
https://news.ycombinator.com/item?id=25922430
Upthread someone asks for a “-safe” flag which makes C a safe language. The reply is “It's called AddressSanitizer. You enable it with the compiler flag -fsanitize=address.” Several replies ensue pointing out how wrong this is.
Does anyone know of a good explanation of HWAddress Sanitizer internals?
One for ARMv8 with Top-Byte-Ignore: you can use the top byte of memory addresses to store a tag.
When you allocate memory you return the "tagged" pointer and internally store "this region has this tag".
When you dereference a pointer, you check that the tag matches what you expect in your internal data structure.
With memory tagging extensions you can do something similar but the checks are performed by the processor.
Basic C understanding (Memory, Stack, Heap, Syscall)."
Obviously, since C doesn't prescribe any kind of heap, stack or syscall behavior (or if they even exist), I assume the author meant something like "Basic understanding of how C is often implemented on certain operating systems and hardware".
If you didn't have pain you'd still get the same damage to the body, you just wouldn't be aware.