Clang Hardening Cheat Sheet - Ten Years Later
20 points by freddyb
20 points by freddyb
This is obviously linux focused, but on macOS and iOS targeting arm64e-apple-{whatever} gets you what apple calls the pointer auth protected ABI. This is largely what the linux+ptrauth folk are also targeting adoption of. You can get this abi manually with something to the effect of
clang[++] -fptrauth-calls -fptrauth-returns -fptrauth-intrinsics -fptrauth-indirect-gotos -fptrauth-auth-traps -fptrauth-vtable-pointer-address-discrimination -fptrauth-vtable-pointer-type-discrimination
The problem of course is that this is a platform ABI, so anything that crosses an ABI boundary will break, e.g
int compare(void*, void*);
...
qsort(...., compare)
Will crash as the function pointer ABI has broken. The way this is handled on apple platforms is that while the entire system is compiled using the arm64e abi, when you launch an arm64 binary you get null keys and so the ptrauth operations all become no-ops. The biggest unavoidable ABI break is that some types that may previously have been POD types cease to be so by inclusion of address diversified values, and that impacts the calling convention. Butting getting this to happen requires manual use of the __ptrauth intrinsic as the only place ptrauth protected values with address diversified values are C++ dynamic objects where the vtable pointers are address discriminated, but they are already non-pod. However if you were to harden the common C vtable implementation, eg
struct MyType;
typedef MyType* MyTypeRef;
typedef void(*MyTypeDestructorMethodTy)(MyTypeRef);
typedef void(*MyTypeSomeOperationMethodTy)(MyTypeRef);
struct MyTypeVTable {
DestructorMethodTy destroy;
SomeOperationMethodTy someOperation;
};
struct MyType {
MyTypeVTable *VPtr;
// ...
};
You might want to protect the vtable pointer with an address diversified schema so it can't be copied from one object to another. To demonstrate how you might protect such a structure I banged out this example: https://godbolt.org/z/shGh1KM6M
Anyway if you do do this and were passing either MyType or MyTypeVTable as a value argument the calling convention changes as neither is a POD type anymore, so would get passed on the stack. Yes, even in C: a struct containing an address diversified pointer actually gets a copy constructor.
Now this particular ABI difference only matters if you expect the same generated code to run in a process containing both ptrauth and non-ptrauth aware code (as happens in the apple implementation), but it's also reasonably unlikely that you ever construct such a type manually, and if you do it's pretty clear you're doing it.
If you're even more interested the full ABI is described here https://clang.llvm.org/docs/PointerAuthentication.html#language-abi if you're curious.
Perhaps a dumb question but if these are all enabled is it actually possible to exploit the application in the presence of something like a int overflow? Is it just that most developers don't use all these features?
Absolutely - they can only protect certain cases, they can’t meaningfully protect against use after free or incorrect pointer arithmetic, etc.
These protections can only guard things that can be reasoned about.
Pointer arithmetic can be somewhat protected with -fbounds-safety but that requires a reasonably large spread of annotations, and you really need the annotations in system headers as well.
The attackability of use after free can be mitigated by things like type isolating system allocators, but that’s still a mitigation and for C (and C APIs in other languages) you’re stuck with best effort inference that requires compiler support and APIs that can be passed that information.
C++ has P2719 to support such allocators, which was approved for, approved but got dropped from C++26 due to the work required for contracts and reflection. But again type isolation is much more towards the mitigation end of the protection spectrum: it doesn’t prevent use after free and similar, it just makes it harder to turn a UaF into a type confusion.
interesting, do you have any sources that go over defeating some of these compiler options?
They’re all standard attacks, you’re always just targeting the lowest hanging fruit, for instance the moment the stack was made non executable attackers moved to the heap (which stayed executable for much longer), once the heap ceased being used executable the just switched to targeting the return address. When ASLR became a think they attacker “safe” out of bounds reads to determine the aslr slide. When people adopt safe languages they target all the unsafe code that is still in the process.
If you adopt all these flags, they just target the code that didn’t compile with these flags.
So it’s not necessarily defeating the protection in as much as the protections are all still dealing with what are fundamentally unsafe languages, so while these protections block some vectors, they don’t come close to closing all of them - even within your own code base, with those flags enabled there are still plenty of ways to have exploitable errors that aren’t able to be protected.
The basic goal is to make it prohibitively expensive to create an exploit, and the counter that is attackers are always trying to find the cheapest path to an exploit.
if these are all enabled is it actually possible to exploit the application in the presence of something like a int overflow?
It is very rarely possible to exploit an integer overflow by itself anyway (even without these mitigations enabled) - you need an associated buffer overflow. (The exception is if the integer overflow itself actually breaks a security check, and in that case the mitigations won't help at all).
Once you have a buffer overflow (or other memory error such as use-after-free) none of these mitigations can guarantee complete protection. They do however (particularly control-flow enforcement) make arbitrary code execution significantly more difficult, eg CET reduces the number of gadgets for ROP attacks by a huge amount.
Honestly the biggest threat of overflows is the compiler choosing to remove safety guards due to overflow being UB :/
I covered that.
The exception is if the integer overflow itself actually breaks a security check, and in that case the mitigations won't help at all
That’s somewhat vague about it though - the whole point there is that the compiler has decided the overflow means a path can’t happen, or the UB nature allows it to claim some check is always true.
None of that is the result of overflow.
In all honesty the overwhelming majority of exploitable integer overflows were simply bugs that led to under allocation, maybe a few incorrect bounds checks as well, but most that I recall were under allocation leading to a buffer overfow. In those cases the integer overflows is the exploit as it is responsible for creating the buffer overrun in this first place.
That said the last time we communicated you did nothing but make ad hominem attacks against me and misrepresented me when replying to others, so I am uninterested in carrying this conversation on.
Just to be clear this is not an ad hominem attack - I’m not claiming you’re wrong because of your previous behaviour, I’m just saying your behaviour is garbage and I have no interest in interacting with you.