This leads to a statistically significant improvement when using -hwasan-instrument-stack=0: https://bit.ly/3AZUIKI.
When enabling stack instrumentation, the data appears gets better but not statistically significantly so. This is consistent
with the very moderate improvements I have seen for stack safety otherwise, so I expect it to improve when the underlying
issue of that is resolved.
Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
Unit Tests
Event Timeline
I don't like the new flag. If we want this as a mitigation, IMHO it's a bad idea to allow overflows from stack. Even constant sized ones. Well, maybe reasonably small constant overflows are ok.
Consider adopting AddressSanitizer::isSafeAccess instead.
In face, isSafeAccess seems pretty limited. Surely ScalarEvolution (similar to how it is used in StackSafetyAnalysis) can capture more cases, including a non-constant offset that can be proven to be within range?
Done. PTAL.
What I do right now is not emit any checks when we can we disable stack instrumentation and we can trace back an operation to an alloca. While we could potentially overflow towards other regions, I think it is not unexpected that we do not catch this if stack instrumentation is disabled. What do you think?
This is pretty cool, I thought it would be more complicated.
This change needs comprehensive tests in llvm/test/Analysis/StackSafetyAnalysis. Update the print() method to show safe/unsafe instructions (or maybe only list known safe instruction).
What if an instruction may access either stack or heap?
i32 *p = flag ? p_heap_i16 : &stack_i32; *p = 42;
The analysis will say "safe" because it is only scanning from the stack roots.
This should probably be fixed in hwasan by tracking the underlying alloca.
Ah yes, I did handle this but then accidentally lost that when I refactored around some stuff. Put that back and added an IR test.
Thinking again I remembered why I removed the explicit case for this during the refactoring: in this case, SCEV will not be able to calculate an in-range offset between the operant of the store and the alloca, so it will not be judged a safe access
EDIT: I ran pdfium benchmarks and had some results here, but I spoke too early and need to run some more.
Ideas for more analysis tests:
- unsafe alloca with a mix of safe and unsafe accesses
- memcpy that is safe on one side and unsafe on the other. Either between two allocas, or within the same (memmove?). Or between alloca and non-stack memory.
Right, of course. Any reachable instruction we would require to be *always* within the alloca range.
llvm/lib/Analysis/StackSafetyAnalysis.cpp | ||
---|---|---|
836 | This affect compilation time and memory. Ideally we would not do it if the client can not use this info (ex. MTE). | |
llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp | ||
787 | if (findAllocaForValue(Ptr)) return true; |
still missing test cases for combinations of mixed safe/unsafe accesses
llvm/include/llvm/Analysis/StackSafetyAnalysis.h | ||
---|---|---|
68 | you want either DenseMap or SmallPtrSet here. std::map is unnecessarily ordered, logarithmic, and wastes memory |
llvm/include/llvm/Analysis/StackSafetyAnalysis.h | ||
---|---|---|
67 | why do we need bool if missing instruction is equivalent to false? | |
67 | just std::map<const Instruction *, bool>Accesses; InfoTy in unique_ptr to avoid exposing it into the header. | |
68 | std set/map seems fine | |
68 | I don't see value in ::getAccesses when accessIsSafe can access the field. | |
llvm/lib/Analysis/StackSafetyAnalysis.cpp | ||
135–138 | Less boilerplate this way: | |
140–142 | the point of <it, bool> result that you can update if insert failed and avoid the second lookup | |
456 | may I ask you to extract two NFC patches which you can land without review:
void addRange(I, R) { // the rest you will add in D108457 updateRange(R); } I assume also not tests should be affected. | |
814 | this is a separate NFC patch | |
827 | so we lazily construct this on the request | |
836 | I am a little bit skeptical that we can measure a difference. I'd rather keep it simple. | |
llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp | ||
290 | HWAsan and related tests should be in a separate patch |
I split the stack safety change to D109503 and added more tests to that.
llvm/include/llvm/Analysis/StackSafetyAnalysis.h | ||
---|---|---|
67 | While I build this up, there are two cases: the instruction hadn't been considered yet | |
67 | I reverted to calculating this in getInfo and putting it into the InfoTy. @eugenis is that okay? | |
llvm/lib/Analysis/StackSafetyAnalysis.cpp | ||
140–142 | That doesn't work, because it's not move constructible (I think, I don't fully remember what exactly it complained about when I did that). | |
456 | Actually no one looks at the return value, so I just made it void. | |
836 | I reverted it to what it was before for simplicity. We can change it again later if it becomes a problem. |
LGTM
llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp | ||
---|---|---|
786 | I'm still on the fence about this. A stack pointer can be used to access heap if the offset is attacker controlled, but that sounds a bit exotic. But let's land it like this for now. One thing I'd like to explore is applying the same SCEV computation as in StackSafetyAnalysis and excluding instrumentation for anything with offset provably within 32 bits or less - that should be reasonably common (indices are often int, not long) and safe (heap is unlikely to be within 4Gb from stack on 64-bit). |
why do we need bool if missing instruction is equivalent to false?
std::set?