diff --git a/compiler-rt/test/hwasan/TestCases/stack-uas.c b/compiler-rt/test/hwasan/TestCases/stack-uas.c new file mode 100644 --- /dev/null +++ b/compiler-rt/test/hwasan/TestCases/stack-uas.c @@ -0,0 +1,61 @@ +// Tests use-after-scope detection and reporting. +// RUN: %clang_hwasan -mllvm -hwasan-use-after-scope -g %s -o %t && not %run %t 2>&1 | FileCheck %s +// RUN: %clang_hwasan -mllvm -hwasan-use-after-scope -g %s -o %t && not %env_hwasan_opts=symbolize=0 %run %t 2>&1 | FileCheck %s --check-prefix=NOSYM + +// REQUIRES: stable-runtime + +// Stack histories currently are not recorded on x86. +// XFAIL: x86_64 + +void USE(void *x) { // pretend_to_do_something(void *x) + __asm__ __volatile__("" + : + : "r"(x) + : "memory"); +} + +__attribute__((noinline)) void Unrelated1() { + int A[2]; + USE(&A[0]); +} +__attribute__((noinline)) void Unrelated2() { + int BB[3]; + USE(&BB[0]); +} +__attribute__((noinline)) void Unrelated3() { + int CCC[4]; + USE(&CCC[0]); +} + +__attribute__((noinline)) char buggy() { + char *volatile p; + { + char zzz[0x1000]; + p = zzz; + } + return *p; +} + +int main() { + Unrelated1(); + Unrelated2(); + Unrelated3(); + char p = buggy(); + return p; + // CHECK: READ of size 1 at + // CHECK: #0 {{.*}} in buggy{{.*}}stack-uas.c:[[@LINE-10]] + // CHECK: Cause: stack tag-mismatch + // CHECK: is located in stack of thread + // CHECK: Potentially referenced stack objects: + // CHECK-NEXT: zzz in buggy {{.*}}stack-uas.c:[[@LINE-17]] + // CHECK-NEXT: Memory tags around the buggy address + + // NOSYM: Previously allocated frames: + // NOSYM-NEXT: record_addr:0x{{.*}} record:0x{{.*}} ({{.*}}/stack-uas.c.tmp+0x{{.*}}){{$}} + // NOSYM-NEXT: record_addr:0x{{.*}} record:0x{{.*}} ({{.*}}/stack-uas.c.tmp+0x{{.*}}){{$}} + // NOSYM-NEXT: record_addr:0x{{.*}} record:0x{{.*}} ({{.*}}/stack-uas.c.tmp+0x{{.*}}){{$}} + // NOSYM-NEXT: record_addr:0x{{.*}} record:0x{{.*}} ({{.*}}/stack-uas.c.tmp+0x{{.*}}){{$}} + // NOSYM-NEXT: Memory tags around the buggy address + + // CHECK: SUMMARY: HWAddressSanitizer: tag-mismatch {{.*}} in buggy +} diff --git a/compiler-rt/test/hwasan/TestCases/use-after-scope-capture.cpp b/compiler-rt/test/hwasan/TestCases/use-after-scope-capture.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/hwasan/TestCases/use-after-scope-capture.cpp @@ -0,0 +1,21 @@ +// This is the ASAN test of the same name ported to HWAsan. + +// RUN: %clangxx_hwasan -mllvm -hwasan-use-after-scope --std=c++11 -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s + +// REQUIRES: aarch64-target-arch + +#include + +int main() { + std::function f; + { + int x = 0; + f = [&x]() __attribute__((noinline)) { + return x; // BOOM + // CHECK: ERROR: HWAddressSanitizer: tag-mismatch + // CHECK: #0 0x{{.*}} in {{.*}}use-after-scope-capture.cpp:[[@LINE-2]] + // CHECK: Cause: stack tag-mismatch + }; + } + return f(); // BOOM +} diff --git a/compiler-rt/test/hwasan/TestCases/use-after-scope-conversion.cpp b/compiler-rt/test/hwasan/TestCases/use-after-scope-conversion.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/hwasan/TestCases/use-after-scope-conversion.cpp @@ -0,0 +1,53 @@ +// This is the ASAN test of the same name ported to HWAsan. + +// RUN: %clangxx_hwasan -mllvm -hwasan-use-after-scope -O0 %s -o %t + +// RUN: not %run %t 'A' 2>&1 | FileCheck %s +// RUN: not %run %t 'B' 2>&1 | FileCheck %s + +// Missing lifetime markers in test_a +// https://bugs.llvm.org/show_bug.cgi?id=34353 +// XFAIL: * + +struct B { + B() : p('B') {} + char p; +}; + +struct C { + const char *p; + explicit C(const char *c) : p(c) {} + explicit C(const B &b) : p(&b.p) {} +}; + +struct A { + char p; + A() : p('C') {} + const operator C() const { return C(&p); } +}; + +volatile char r; +void test_a() { + C s = A(); + r = *s.p; +} + +void test_b() { + C s = B(); + r = *s.p; +} + +int main(int argc, char **argv) { + switch (argv[1][0]) { + case 'A': + test_a(); + return 0; + case 'B': + test_b(); + return 0; + } + return 1; +} + +// CHECK: ERROR: HWAddressSanitizer: tag-mismatch +// CHECK: Cause: stack tag-mismatch \ No newline at end of file diff --git a/compiler-rt/test/hwasan/TestCases/use-after-scope-dtor-order.cpp b/compiler-rt/test/hwasan/TestCases/use-after-scope-dtor-order.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/hwasan/TestCases/use-after-scope-dtor-order.cpp @@ -0,0 +1,30 @@ +// This is the ASAN test of the same name ported to HWAsan. + +// RUN: %clangxx_hwasan -mllvm -hwasan-use-after-scope -O1 %s -o %t && \ +// RUN: not %run %t 2>&1 | FileCheck %s + +// REQUIRES: aarch64-target-arch + +#include + +struct IntHolder { + explicit IntHolder(int *val = 0) : val_(val) {} + __attribute__((noinline)) ~IntHolder() { + printf("Value: %d\n", *val_); // BOOM + // CHECK: ERROR: HWAddressSanitizer: tag-mismatch + // CHECK: #0 0x{{.*}} in IntHolder::~IntHolder{{.*}}.cpp:[[@LINE-2]] + } + void set(int *val) { val_ = val; } + int *get() { return val_; } + + int *val_; +}; + +int main(int argc, char *argv[]) { + // It is incorrect to use "x" int IntHolder destructor, because "x" is + // "destroyed" earlier as it's declared later. + IntHolder holder; + int x = argc; + holder.set(&x); + return 0; +} diff --git a/compiler-rt/test/hwasan/TestCases/use-after-scope-goto.cpp b/compiler-rt/test/hwasan/TestCases/use-after-scope-goto.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/hwasan/TestCases/use-after-scope-goto.cpp @@ -0,0 +1,77 @@ +// This is the ASAN test of the same name ported to HWAsan. + +// RUN: %clangxx_hwasan -mllvm -hwasan-use-after-scope -O0 %s -o %t && %run %t + +// Function jumps over variable initialization making lifetime analysis +// ambiguous. Asan should ignore such variable and program must not fail. + +// REQUIRES: aarch64-target-arch + +#include + +int *ptr; + +void f1(int cond) { + if (cond) + goto label; + int tmp; + +label: + ptr = &tmp; + *ptr = 5; +} + +void f2(int cond) { + switch (cond) { + case 1: { + ++cond; + int tmp; + ptr = &tmp; + exit(0); + case 2: + ptr = &tmp; + *ptr = 5; + exit(0); + } + } +} + +void f3(int cond) { + { + int tmp; + goto l2; + l1: + ptr = &tmp; + *ptr = 5; + + exit(0); + } +l2: + goto l1; +} + +void use(int *x) { + static int c = 10; + if (--c == 0) + exit(0); + (*x)++; +} + +void f4() { + { + int x; + l2: + use(&x); + goto l1; + } +l1: + goto l2; +} + +int main() { + f1(1); + f2(1); + f3(1); + f4(); + return 0; +} diff --git a/compiler-rt/test/hwasan/TestCases/use-after-scope-if.cpp b/compiler-rt/test/hwasan/TestCases/use-after-scope-if.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/hwasan/TestCases/use-after-scope-if.cpp @@ -0,0 +1,20 @@ +// This is the ASAN test of the same name ported to HWAsan. + +// RUN: %clangxx_hwasan -mllvm -hwasan-use-after-scope -O1 %s -o %t && \ +// RUN: not %run %t 2>&1 | FileCheck %s + +// REQUIRES: aarch64-target-arch + +int *p; +bool b = true; + +int main() { + if (b) { + int x[5]; + p = x + 1; + } + return *p; // BOOM + // CHECK: ERROR: HWAddressSanitizer: tag-mismatch + // CHECK: #0 0x{{.*}} in main {{.*}}.cpp:[[@LINE-2]] + // CHECK: Cause: stack tag-mismatch +} diff --git a/compiler-rt/test/hwasan/TestCases/use-after-scope-inlined.cpp b/compiler-rt/test/hwasan/TestCases/use-after-scope-inlined.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/hwasan/TestCases/use-after-scope-inlined.cpp @@ -0,0 +1,25 @@ +// This is the ASAN test of the same name ported to HWAsan. + +// Test with "-O2" only to make sure inlining (leading to use-after-scope) +// happens. "always_inline" is not enough, as Clang doesn't emit +// llvm.lifetime intrinsics at -O0. +// +// RUN: %clangxx_hwasan -mllvm -hwasan-use-after-scope -O2 %s -o %t && \ +// RUN: not %run %t 2>&1 | FileCheck %s + +// REQUIRES: aarch64-target-arch + +int *arr; +__attribute__((always_inline)) void inlined(int arg) { + int x[5]; + for (int i = 0; i < arg; i++) + x[i] = i; + arr = x; +} + +int main(int argc, char *argv[]) { + inlined(argc); + return arr[argc - 1]; // BOOM + // CHECK: ERROR: HWAddressSanitizer: tag-mismatch + // CHECK: Cause: stack tag-mismatch +} diff --git a/compiler-rt/test/hwasan/TestCases/use-after-scope-loop-bug.cpp b/compiler-rt/test/hwasan/TestCases/use-after-scope-loop-bug.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/hwasan/TestCases/use-after-scope-loop-bug.cpp @@ -0,0 +1,20 @@ +// This is the ASAN test of the same name ported to HWAsan. + +// RUN: %clangxx_hwasan -mllvm -hwasan-use-after-scope -O1 %s -o %t && \ +// RUN: not %run %t 2>&1 | FileCheck %s + +// REQUIRES: aarch64-target-arch + +volatile int *p; + +int main() { + // Variable goes in and out of scope. + for (int i = 0; i < 3; ++i) { + int x[3] = {i, i, i}; + p = x + i; + } + return *p; // BOOM + // CHECK: ERROR: HWAddressSanitizer: tag-mismatch + // CHECK: #0 0x{{.*}} in main {{.*}}use-after-scope-loop-bug.cpp:[[@LINE-2]] + // CHECK: Cause: stack tag-mismatch +} diff --git a/compiler-rt/test/hwasan/TestCases/use-after-scope-loop-removed.cpp b/compiler-rt/test/hwasan/TestCases/use-after-scope-loop-removed.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/hwasan/TestCases/use-after-scope-loop-removed.cpp @@ -0,0 +1,21 @@ +// This is the ASAN test of the same name ported to HWAsan. + +// RUN: %clangxx_hwasan -mllvm -hwasan-use-after-scope -O1 %s -o %t && \ +// RUN: not %run %t 2>&1 | FileCheck %s + +// REQUIRES: aarch64-target-arch + +#include + +int *p; + +int main() { + for (int i = 0; i < 3; i++) { + int x; + p = &x; + } + return *p; // BOOM + // CHECK: ERROR: HWAddressSanitizer: tag-mismatch + // CHECK: #0 0x{{.*}} in main {{.*}}use-after-scope-loop-removed.cpp:[[@LINE-2]] + // CHECK: Cause: stack tag-mismatch +} diff --git a/compiler-rt/test/hwasan/TestCases/use-after-scope-loop.cpp b/compiler-rt/test/hwasan/TestCases/use-after-scope-loop.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/hwasan/TestCases/use-after-scope-loop.cpp @@ -0,0 +1,19 @@ +// This is the ASAN test of the same name ported to HWAsan. + +// RUN: %clangxx_hwasan -mllvm -hwasan-use-after-scope -O1 %s -o %t && \ +// RUN: not %run %t 2>&1 | FileCheck %s + +// REQUIRES: aarch64-target-arch + +int *p[3]; + +int main() { + for (int i = 0; i < 3; i++) { + int x; + p[i] = &x; + } + return **p; // BOOM + // CHECK: ERROR: HWAddressSanitizer: tag-mismatch + // CHECK: #0 0x{{.*}} in main {{.*}}.cpp:[[@LINE-2]] + // CHECK: Cause: stack tag-mismatch +} diff --git a/compiler-rt/test/hwasan/TestCases/use-after-scope-nobug.cpp b/compiler-rt/test/hwasan/TestCases/use-after-scope-nobug.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/hwasan/TestCases/use-after-scope-nobug.cpp @@ -0,0 +1,20 @@ +// This is the ASAN test of the same name ported to HWAsan. + +// RUN: %clangxx_hwasan -mllvm -hwasan-use-after-scope -O1 %s -o %t && %run %t + +// REQUIRES: aarch64-target-arch + +#include +#include + +int *p[3]; + +int main() { + // Variable goes in and out of scope. + for (int i = 0; i < 3; i++) { + int x; + p[i] = &x; + } + printf("PASSED\n"); + return 0; +} diff --git a/compiler-rt/test/hwasan/TestCases/use-after-scope-temp.cpp b/compiler-rt/test/hwasan/TestCases/use-after-scope-temp.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/hwasan/TestCases/use-after-scope-temp.cpp @@ -0,0 +1,25 @@ +// This is the ASAN test of the same name ported to HWAsan. + +// RUN: %clangxx_hwasan -mllvm -hwasan-use-after-scope -std=c++11 -O1 %s -o %t && \ +// RUN: not %run %t 2>&1 | FileCheck %s + +// REQUIRES: aarch64-target-arch + +struct IntHolder { + int val; +}; + +const IntHolder *saved; + +__attribute__((noinline)) void save(const IntHolder &holder) { + saved = &holder; +} + +int main(int argc, char *argv[]) { + save({argc}); + int x = saved->val; // BOOM + // CHECK: ERROR: HWAddressSanitizer: tag-mismatch + // CHECK: #0 0x{{.*}} in main {{.*}}use-after-scope-temp.cpp:[[@LINE-2]] + // CHECK: Cause: stack tag-mismatch + return x; +} diff --git a/compiler-rt/test/hwasan/TestCases/use-after-scope-temp2.cpp b/compiler-rt/test/hwasan/TestCases/use-after-scope-temp2.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/hwasan/TestCases/use-after-scope-temp2.cpp @@ -0,0 +1,24 @@ +// This is the ASAN test of the same name ported to HWAsan. + +// RUN: %clangxx_hwasan -mllvm -hwasan-use-after-scope -std=c++11 -O1 %s -o %t && \ +// RUN: not %run %t 2>&1 | FileCheck %s + +// REQUIRES: aarch64-target-arch + +struct IntHolder { + __attribute__((noinline)) const IntHolder &Self() const { + return *this; + } + int val = 3; +}; + +const IntHolder *saved; + +int main(int argc, char *argv[]) { + saved = &IntHolder().Self(); + int x = saved->val; // BOOM + // CHECK: ERROR: HWAddressSanitizer: tag-mismatch + // CHECK: #0 0x{{.*}} in main {{.*}}use-after-scope-temp2.cpp:[[@LINE-2]] + // CHECK: Cause: stack tag-mismatch + return x; +} diff --git a/compiler-rt/test/hwasan/TestCases/use-after-scope-types.cpp b/compiler-rt/test/hwasan/TestCases/use-after-scope-types.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/hwasan/TestCases/use-after-scope-types.cpp @@ -0,0 +1,81 @@ +// This is the ASAN test of the same name ported to HWAsan. + +// RUN: %clangxx_hwasan -mllvm -hwasan-use-after-scope -std=c++11 -O0 %s -o %t +// RUN: not %run %t 0 2>&1 | FileCheck %s +// RUN: not %run %t 1 2>&1 | FileCheck %s +// RUN: not %run %t 2 2>&1 | FileCheck %s +// RUN: not %run %t 3 2>&1 | FileCheck %s +// RUN: not %run %t 4 2>&1 | FileCheck %s +// RUN: not %run %t 5 2>&1 | FileCheck %s +// The std::vector case is broken because of limited lifetime tracking. +// TODO(fmayer): Fix and enable. +// RUN: not %run %t 7 2>&1 | FileCheck %s +// RUN: not %run %t 8 2>&1 | FileCheck %s +// RUN: not %run %t 9 2>&1 | FileCheck %s +// RUN: not %run %t 10 2>&1 | FileCheck %s + +// REQUIRES: aarch64-target-arch + +#include +#include +#include + +template +struct Ptr { + void Store(T *ptr) { t = ptr; } + + void Access() { *t = {}; } + + T *t; +}; + +template +struct Ptr { + using Type = T[N]; + void Store(Type *ptr) { t = *ptr; } + + void Access() { *t = {}; } + + T *t; +}; + +template +__attribute__((noinline)) void test() { + Ptr ptr; + { + T x; + ptr.Store(&x); + } + + ptr.Access(); + // CHECK: ERROR: HWAddressSanitizer: tag-mismatch + // CHECK: #{{[0-9]+}} 0x{{.*}} in {{(void )?test.*\((void)?\) .*}}use-after-scope-types.cpp + // CHECK: Cause: stack tag-mismatch +} + +int main(int argc, char **argv) { + using Tests = void (*)(); + Tests tests[] = { + &test, + &test, + &test, + &test, + &test, + &test, + &test>, + &test, + &test, + &test, + &test, + }; + + int n = atoi(argv[1]); + if (n == sizeof(tests) / sizeof(tests[0])) { + for (auto te : tests) + te(); + } else { + tests[n](); + } + + return 0; +} diff --git a/compiler-rt/test/hwasan/TestCases/use-after-scope.cpp b/compiler-rt/test/hwasan/TestCases/use-after-scope.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/hwasan/TestCases/use-after-scope.cpp @@ -0,0 +1,20 @@ +// This is the ASAN test of the same name ported to HWAsan. + +// RUN: %clangxx_hwasan -mllvm -hwasan-use-after-scope -O1 %s -o %t && \ +// RUN: not %run %t 2>&1 | FileCheck %s + +// REQUIRES: aarch64-target-arch + +volatile int *p = 0; + +int main() { + { + int x = 0; + p = &x; + } + *p = 5; // BOOM + // CHECK: ERROR: HWAddressSanitizer: tag-mismatch + // CHECK: #0 0x{{.*}} in main {{.*}}use-after-scope.cpp:[[@LINE-2]] + // CHECK: Cause: stack tag-mismatch + return 0; +} diff --git a/llvm/include/llvm/Transforms/Instrumentation/AddressSanitizerCommon.h b/llvm/include/llvm/Transforms/Instrumentation/AddressSanitizerCommon.h --- a/llvm/include/llvm/Transforms/Instrumentation/AddressSanitizerCommon.h +++ b/llvm/include/llvm/Transforms/Instrumentation/AddressSanitizerCommon.h @@ -14,7 +14,11 @@ #ifndef LLVM_TRANSFORMS_INSTRUMENTATION_ADDRESSSANITIZERCOMMON_H #define LLVM_TRANSFORMS_INSTRUMENTATION_ADDRESSSANITIZERCOMMON_H +#include "llvm/Analysis/CFG.h" +#include "llvm/Analysis/PostDominators.h" +#include "llvm/IR/Dominators.h" #include "llvm/IR/Instruction.h" +#include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Module.h" namespace llvm { @@ -44,6 +48,38 @@ Value *getPtr() { return PtrUse->get(); } }; +template +void TagLifetimeEnd(DominatorTree *DT, PostDominatorTree *PDT, + IntrinsicInst *Start, IntrinsicInst *End, + SmallVectorImpl &RetVec, F UntagCallback) { + // We need to ensure that if we tag some object, we certainly untag it + // before the function exits. + if (PDT != nullptr && PDT->dominates(End, Start)) { + UntagCallback(End); + } else { + SmallVector ReachableRetVec; + unsigned NumCoveredExits = 0; + for (auto &RI : RetVec) { + if (!isPotentiallyReachable(Start, RI, nullptr, DT)) + continue; + ReachableRetVec.push_back(RI); + if (DT != nullptr && DT->dominates(End, RI)) + ++NumCoveredExits; + } + // If there's a mix of covered and non-covered exits, just put the untag + // on exits, so we avoid the redundancy of untagging twice. + if (NumCoveredExits == ReachableRetVec.size()) { + UntagCallback(End); + } else { + for (auto &RI : ReachableRetVec) + UntagCallback(RI); + // We may have inserted untag outside of the lifetime interval. + // Remove the lifetime end call for this alloca. + End->eraseFromParent(); + } + } +} + } // namespace llvm #endif diff --git a/llvm/lib/Target/AArch64/AArch64StackTagging.cpp b/llvm/lib/Target/AArch64/AArch64StackTagging.cpp --- a/llvm/lib/Target/AArch64/AArch64StackTagging.cpp +++ b/llvm/lib/Target/AArch64/AArch64StackTagging.cpp @@ -42,17 +42,18 @@ #include "llvm/IR/Dominators.h" #include "llvm/IR/Function.h" #include "llvm/IR/GetElementPtrTypeIterator.h" +#include "llvm/IR/IRBuilder.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/IntrinsicsAArch64.h" -#include "llvm/IR/IRBuilder.h" #include "llvm/IR/Metadata.h" #include "llvm/InitializePasses.h" #include "llvm/Pass.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Debug.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/Transforms/Instrumentation/AddressSanitizerCommon.h" #include "llvm/Transforms/Utils/Local.h" #include #include @@ -646,32 +647,8 @@ cast(Start->getArgOperand(0))->getZExtValue(); Size = alignTo(Size, kTagGranuleSize); tagAlloca(AI, Start->getNextNode(), Start->getArgOperand(1), Size); - // We need to ensure that if we tag some object, we certainly untag it - // before the function exits. - if (PDT != nullptr && PDT->dominates(End, Start)) { - untagAlloca(AI, End, Size); - } else { - SmallVector ReachableRetVec; - unsigned NumCoveredExits = 0; - for (auto &RI : RetVec) { - if (!isPotentiallyReachable(Start, RI, nullptr, DT)) - continue; - ReachableRetVec.push_back(RI); - if (DT != nullptr && DT->dominates(End, RI)) - ++NumCoveredExits; - } - // If there's a mix of covered and non-covered exits, just put the untag - // on exits, so we avoid the redundancy of untagging twice. - if (NumCoveredExits == ReachableRetVec.size()) { - untagAlloca(AI, End, Size); - } else { - for (auto &RI : ReachableRetVec) - untagAlloca(AI, RI, Size); - // We may have inserted untag outside of the lifetime interval. - // Remove the lifetime end call for this alloca. - End->eraseFromParent(); - } - } + TagLifetimeEnd(DT, PDT, Start, End, RetVec, + [&](Instruction *Node) { untagAlloca(AI, Node, Size); }); } else { uint64_t Size = Info.AI->getAllocationSizeInBits(*DL).getValue() / 8; Value *Ptr = IRB.CreatePointerCast(TagPCall, IRB.getInt8PtrTy()); diff --git a/llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp --- a/llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp +++ b/llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp @@ -17,6 +17,8 @@ #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Triple.h" +#include "llvm/Analysis/PostDominators.h" +#include "llvm/Analysis/ValueTracking.h" #include "llvm/BinaryFormat/ELF.h" #include "llvm/IR/Attributes.h" #include "llvm/IR/BasicBlock.h" @@ -25,6 +27,7 @@ #include "llvm/IR/DataLayout.h" #include "llvm/IR/DebugInfoMetadata.h" #include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Dominators.h" #include "llvm/IR/Function.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/InlineAsm.h" @@ -40,6 +43,7 @@ #include "llvm/IR/Value.h" #include "llvm/InitializePasses.h" #include "llvm/Pass.h" +#include "llvm/PassRegistry.h" #include "llvm/Support/Casting.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" @@ -109,6 +113,11 @@ cl::desc("instrument stack (allocas)"), cl::Hidden, cl::init(true)); +static cl::opt ClUseAfterScope( + "hwasan-use-after-scope", + cl::desc("detect use after scope within function"), + cl::Hidden, cl::init(false)); + static cl::opt ClUARRetagToZero( "hwasan-uar-retag-to-zero", cl::desc("Clear alloca tags before returning from the function to allow " @@ -195,10 +204,18 @@ /// An instrumentation pass implementing detection of addressability bugs /// using tagged pointers. class HWAddressSanitizer { +private: + struct AllocaInfo { + AllocaInst *AI; + SmallVector LifetimeStart; + SmallVector LifetimeEnd; + }; + public: explicit HWAddressSanitizer(Module &M, bool CompileKernel = false, - bool Recover = false) - : M(M) { + bool Recover = false, DominatorTree *DT = nullptr, + PostDominatorTree *PDT = nullptr) + : M(M), DT(DT), PDT(PDT) { this->Recover = ClRecover.getNumOccurrences() > 0 ? ClRecover : Recover; this->CompileKernel = ClEnableKhwasan.getNumOccurrences() > 0 ? ClEnableKhwasan @@ -230,13 +247,16 @@ Instruction *I, SmallVectorImpl &Interesting); bool isInterestingAlloca(const AllocaInst &AI); - bool tagAlloca(IRBuilder<> &IRB, AllocaInst *AI, Value *Tag, size_t Size); + void tagAlloca(IRBuilder<>& IRB, AllocaInst *AI, Value *Tag, + size_t Size); Value *tagPointer(IRBuilder<> &IRB, Type *Ty, Value *PtrLong, Value *Tag); Value *untagPointer(IRBuilder<> &IRB, Value *PtrLong); bool instrumentStack( - SmallVectorImpl &Allocas, + MapVector &AllocasToInstrument, + SmallVector &UnrecognizedLifetimes, DenseMap> &AllocaDbgMap, - SmallVectorImpl &RetVec, Value *StackTag); + SmallVectorImpl &RetVec, Value *StackTag, + DominatorTree *DT, PostDominatorTree *PDT); Value *readRegister(IRBuilder<> &IRB, StringRef Name); bool instrumentLandingPads(SmallVectorImpl &RetVec); Value *getNextTagWithCall(IRBuilder<> &IRB); @@ -259,6 +279,8 @@ private: LLVMContext *C; Module &M; + DominatorTree *DT; + PostDominatorTree *PDT; Triple TargetTriple; FunctionCallee HWAsanMemmove, HWAsanMemcpy, HWAsanMemset; FunctionCallee HWAsanHandleVfork; @@ -284,6 +306,7 @@ void init(Triple &TargetTriple, bool InstrumentWithCalls); unsigned getObjectAlignment() const { return 1U << Scale; } }; + ShadowMapping Mapping; Type *VoidTy = Type::getVoidTy(M.getContext()); @@ -338,7 +361,16 @@ StringRef getPassName() const override { return "HWAddressSanitizer"; } bool doInitialization(Module &M) override { - HWASan = std::make_unique(M, CompileKernel, Recover); + DominatorTree *DT = nullptr; + if (auto *P = getAnalysisIfAvailable()) + DT = &P->getDomTree(); + + PostDominatorTree *PDT = nullptr; + if (auto *P = getAnalysisIfAvailable()) + PDT = &P->getPostDomTree(); + + HWASan = std::make_unique(M, CompileKernel, Recover, DT, + PDT); return true; } @@ -908,8 +940,8 @@ return SizeInBytes * ArraySize; } -bool HWAddressSanitizer::tagAlloca(IRBuilder<> &IRB, AllocaInst *AI, Value *Tag, - size_t Size) { +void HWAddressSanitizer::tagAlloca(IRBuilder<>& IRB, AllocaInst *AI, + Value *Tag, size_t Size) { size_t AlignedSize = alignTo(Size, Mapping.getObjectAlignment()); if (!UseShortGranules) Size = AlignedSize; @@ -939,7 +971,6 @@ AlignedSize - 1)); } } - return true; } unsigned HWAddressSanitizer::retagMask(unsigned AllocaNo) { @@ -1172,16 +1203,22 @@ } bool HWAddressSanitizer::instrumentStack( - SmallVectorImpl &Allocas, + MapVector &AllocasToInstrument, + SmallVector &UnrecognizedLifetimes, DenseMap> &AllocaDbgMap, - SmallVectorImpl &RetVec, Value *StackTag) { + SmallVectorImpl &RetVec, Value *StackTag, DominatorTree *DT, + PostDominatorTree *PDT) { // Ideally, we want to calculate tagged stack base pointer, and rewrite all // alloca addresses using that. Unfortunately, offsets are not known yet // (unless we use ASan-style mega-alloca). Instead we keep the base tag in a // temp, shift-OR it into each alloca address and xor with the retag mask. // This generates one extra instruction per alloca use. - for (unsigned N = 0; N < Allocas.size(); ++N) { - auto *AI = Allocas[N]; + unsigned int N = 0; + + for (auto It = AllocasToInstrument.begin(); It < AllocasToInstrument.end(); + ++It, ++N) { + auto *AI = It->first; + AllocaInfo &Info = It->second; IRBuilder<> IRB(AI->getNextNode()); // Replace uses of the alloca with tagged address. @@ -1208,17 +1245,33 @@ } size_t Size = getAllocaSizeInBytes(*AI); - tagAlloca(IRB, AI, Tag, Size); - - for (auto RI : RetVec) { - IRB.SetInsertPoint(RI); - - // Re-tag alloca memory with the special UAR tag. - Value *Tag = getUARTag(IRB, StackTag); - tagAlloca(IRB, AI, Tag, alignTo(Size, Mapping.getObjectAlignment())); + size_t AlignedSize = alignTo(Size, Mapping.getObjectAlignment()); + if (ClUseAfterScope && UnrecognizedLifetimes.empty() && Info.LifetimeStart.size() == 1 && + Info.LifetimeEnd.size() == 1) { + IntrinsicInst *Start = Info.LifetimeStart[0]; + IntrinsicInst *End = Info.LifetimeEnd[0]; + IRB.SetInsertPoint(Start->getNextNode()); + tagAlloca(IRB, AI, Tag, Size); + TagLifetimeEnd(DT, PDT, Start, End, RetVec, [&](Instruction *Node) { + IRB.SetInsertPoint(End); + Value *UARTag = getUARTag(IRB, StackTag); + tagAlloca(IRB, AI, UARTag, AlignedSize); + }); + } else { + tagAlloca(IRB, AI, Tag, Size); + for (auto RI : RetVec) { + IRB.SetInsertPoint(RI); + Value *UARTag = getUARTag(IRB, StackTag); + tagAlloca(IRB, AI, UARTag, AlignedSize); + } + for (auto &II : Info.LifetimeStart) + II->eraseFromParent(); + for (auto &II : Info.LifetimeEnd) + II->eraseFromParent(); } } - + for (auto &I : UnrecognizedLifetimes) + I->eraseFromParent(); return true; } @@ -1249,18 +1302,36 @@ SmallVector OperandsToInstrument; SmallVector IntrinToInstrument; - SmallVector AllocasToInstrument; + MapVector AllocasToInstrument; SmallVector RetVec; SmallVector LandingPadVec; + SmallVector UnrecognizedLifetimes; DenseMap> AllocaDbgMap; for (auto &BB : F) { for (auto &Inst : BB) { - if (InstrumentStack) + if (InstrumentStack) { if (AllocaInst *AI = dyn_cast(&Inst)) { if (isInterestingAlloca(*AI)) - AllocasToInstrument.push_back(AI); + AllocasToInstrument.insert({AI, {}}); + continue; + } + auto *II = dyn_cast(&Inst); + if (II && (II->getIntrinsicID() == Intrinsic::lifetime_start || + II->getIntrinsicID() == Intrinsic::lifetime_end)) { + AllocaInst *AI = findAllocaForValue(II->getArgOperand(1)); + if (!AI) { + UnrecognizedLifetimes.push_back(&Inst); + continue; + } + if (!isInterestingAlloca(*AI)) + continue; + if (II->getIntrinsicID() == Intrinsic::lifetime_start) + AllocasToInstrument[AI].LifetimeStart.push_back(II); + else + AllocasToInstrument[AI].LifetimeEnd.push_back(II); continue; } + } if (isa(Inst) || isa(Inst) || isa(Inst)) @@ -1309,15 +1380,34 @@ Mapping.WithFrameRecord && !AllocasToInstrument.empty()); if (!AllocasToInstrument.empty()) { + std::unique_ptr DeleteDT; + DominatorTree *LocalDT = DT; + if (LocalDT == + nullptr) { // && !F.hasFnAttribute(Attribute::OptimizeNone)) { + DeleteDT = std::make_unique(F); + LocalDT = DeleteDT.get(); + } + + std::unique_ptr DeletePDT; + PostDominatorTree *LocalPDT = PDT; + if (LocalPDT == + nullptr) { // && !F.hasFnAttribute(Attribute::OptimizeNone)) { + DeletePDT = std::make_unique(F); + LocalPDT = DeletePDT.get(); + } + Value *StackTag = ClGenerateTagsWithCalls ? nullptr : getStackBaseTag(EntryIRB); - instrumentStack(AllocasToInstrument, AllocaDbgMap, RetVec, StackTag); + instrumentStack(AllocasToInstrument, UnrecognizedLifetimes, AllocaDbgMap, + RetVec, StackTag, LocalDT, LocalPDT); } // Pad and align each of the allocas that we instrumented to stop small // uninteresting allocas from hiding in instrumented alloca's padding and so // that we have enough space to store real tags for short granules. DenseMap AllocaToPaddedAllocaMap; - for (AllocaInst *AI : AllocasToInstrument) { + for (auto It = AllocasToInstrument.begin(); It != AllocasToInstrument.end(); + ++It) { + AllocaInst *AI = It->first; uint64_t Size = getAllocaSizeInBytes(*AI); uint64_t AlignedSize = alignTo(Size, Mapping.getObjectAlignment()); AI->setAlignment(