Index: lib/Transforms/Instrumentation/AddressSanitizer.cpp =================================================================== --- lib/Transforms/Instrumentation/AddressSanitizer.cpp +++ lib/Transforms/Instrumentation/AddressSanitizer.cpp @@ -98,6 +98,8 @@ static const char *const kAsanOptionDetectUAR = "__asan_option_detect_stack_use_after_return"; +static const char *const kAsanPoisonAlloca = "__asan_poison_alloca"; +static const char *const kAsanUnpoisonAlloca = "__asan_unpoison_alloca"; #ifndef NDEBUG static const int kAsanStackAfterReturnMagic = 0xf5; @@ -106,6 +108,8 @@ // Accesses sizes are powers of two: 1, 2, 4, 8, 16. static const size_t kNumberOfAccessSizes = 5; +const unsigned int kAllocaRedzoneSize = 32; + // Command-line flags. // This flag may need to be replaced with -f[no-]asan-reads. @@ -162,6 +166,8 @@ "asan-memory-access-callback-prefix", cl::desc("Prefix for memory access callbacks"), cl::Hidden, cl::init("__asan_")); +static cl::opt ClInstrumentAllocas("asan-instrument-allocas", + cl::desc("instrument dynamic allocas"), cl::Hidden, cl::init(false)); // This is an experimental feature that will allow to choose between // instrumented and non-instrumented code at link-time. @@ -473,11 +479,13 @@ SmallVector AllocaVec; SmallVector RetVec; + SmallVector DynamicAllocaVec; unsigned StackAlignment; Function *AsanStackMallocFunc[kMaxAsanStackMallocSizeClass + 1], *AsanStackFreeFunc[kMaxAsanStackMallocSizeClass + 1]; - Function *AsanPoisonStackMemoryFunc, *AsanUnpoisonStackMemoryFunc; + Function *AsanPoisonStackMemoryFunc, *AsanUnpoisonStackMemoryFunc, + *AsanPoisonAlloca, *AsanUnpoisonAlloca; // Stores a place and arguments of poisoning/unpoisoning call for alloca. struct AllocaPoisonCall { @@ -504,7 +512,7 @@ for (BasicBlock *BB : depth_first(&F.getEntryBlock())) visit(*BB); - if (AllocaVec.empty()) return false; + if (AllocaVec.empty() && DynamicAllocaVec.empty()) return false; initializeCallbacks(*F.getParent()); @@ -527,12 +535,66 @@ RetVec.push_back(&RI); } + // Inserts __asan_unpoison_alloca for given nonstatic alloca before each ret + // instruction. + void insertUnpoisonDynamicAlloca(AllocaInst *AI) { + for (auto Ret : RetVec) { + IRBuilder<> IRBRet(Ret); + IRBRet.CreateCall2(AsanUnpoisonAlloca, AI, AI->getArraySize()); + } + } + + void handleDynamicAllocaCall(AllocaInst *AI) { + IRBuilder<> IRBefore(AI); + // If alloca is last instruction in basic block, insert at the end + // of this BB. + IRBuilder<> IRAfter(AI->getParent()); + if (Instruction *NextInstruction = AI->getNextNode()) + IRAfter.SetInsertPoint(NextInstruction); + + Value *OldSize = AI->getOperand(0); + unsigned AllocaAlign = AI->getAlignment(); + unsigned Align = std::max(kAllocaRedzoneSize, AllocaAlign); + const unsigned kPartialRedzoneSizeMax = kAllocaRedzoneSize - 1; + + AI->setAlignment(Align); + + // Replace alloca SIZE with NEWSIZE = Align + kPartialRedzoneSizeMax + + // kAllocaRedzoneSize. + Value *SizeAddition = ConstantInt::get(IntptrTy, Align + + kPartialRedzoneSizeMax + + kAllocaRedzoneSize); + Value *NewSize = IRBefore.CreateAdd(OldSize, SizeAddition); + + AI->setOperand(0, NewSize); + + // NewAddress = Address + Align + Value *NewAddress = IRAfter.CreateAdd(AI, + ConstantInt::get(IntptrTy, Align)); + Value *NewAddressPtr = IRAfter.CreatePointerCast(NewAddress, AI->getType()); + + // For now just insert the call to ASan runtime. + IRAfter.CreateCall2(AsanPoisonAlloca, NewAddressPtr, OldSize); + // Replace all uses of AddessReturnedByAlloca with NewAddress. + AI->replaceAllUsesWith(NewAddressPtr); + // replaceAllUsesWith replaced OldAddress with NewAddress not only for + // following instructions, but also for previous ones in particular in + // instruction located right after alloca call calculating NewSize. So + // we need to change it's first operand (which used to be OldAddress, but + // became NewAddress) back manually. + assert(isa(NewAddress)); + cast(NewAddress)->setOperand(0, AI); + } + /// \brief Collect Alloca instructions we want (and can) handle. void visitAllocaInst(AllocaInst &AI) { if (!isInterestingAlloca(AI)) return; StackAlignment = std::max(StackAlignment, AI.getAlignment()); - AllocaVec.push_back(&AI); + if (isDynamicAlloca(AI)) + DynamicAllocaVec.push_back(&AI); + else + AllocaVec.push_back(&AI); } /// \brief Collect lifetime intrinsic calls to check for use-after-scope @@ -564,10 +626,13 @@ // ---------------------- Helpers. void initializeCallbacks(Module &M); + bool isDynamicAlloca(AllocaInst &AI) const { + return AI.isArrayAllocation() || !AI.isStaticAlloca(); + } + // Check if we want (and can) handle this alloca. bool isInterestingAlloca(AllocaInst &AI) const { - return (!AI.isArrayAllocation() && AI.isStaticAlloca() && - AI.getAllocatedType()->isSized() && + return (AI.getAllocatedType()->isSized() && // alloca() may be called with 0 size, ignore it. getAllocaSizeInBytes(&AI) > 0); } @@ -1584,6 +1649,10 @@ kAsanPoisonStackMemoryName, IRB.getVoidTy(), IntptrTy, IntptrTy, NULL)); AsanUnpoisonStackMemoryFunc = checkInterfaceFunction(M.getOrInsertFunction( kAsanUnpoisonStackMemoryName, IRB.getVoidTy(), IntptrTy, IntptrTy, NULL)); + AsanPoisonAlloca = checkInterfaceFunction(M.getOrInsertFunction( + kAsanPoisonAlloca, IRB.getVoidTy(), IntptrTy, IntptrTy, NULL)); + AsanUnpoisonAlloca = checkInterfaceFunction(M.getOrInsertFunction( + kAsanUnpoisonAlloca, IRB.getVoidTy(), IntptrTy, IntptrTy, NULL)); } void @@ -1649,10 +1718,20 @@ } void FunctionStackPoisoner::poisonStack() { + assert(AllocaVec.size() > 0 || DynamicAllocaVec.size() > 0); + + if (ClInstrumentAllocas) + // Handle dynamic allocas. + for (auto AI : DynamicAllocaVec) { + handleDynamicAllocaCall(AI); + insertUnpoisonDynamicAlloca(AI); + } + + if (AllocaVec.size() == 0) return; + int StackMallocIdx = -1; DebugLoc EntryDebugLocation = getFunctionEntryDebugLocation(F); - assert(AllocaVec.size() > 0); Instruction *InsBefore = AllocaVec[0]; IRBuilder<> IRB(InsBefore); IRB.SetCurrentDebugLocation(EntryDebugLocation); Index: lib/asan/asan_fake_stack.cc =================================================================== --- lib/asan/asan_fake_stack.cc +++ lib/asan/asan_fake_stack.cc @@ -237,6 +237,33 @@ SANITIZER_INTERFACE_ATTRIBUTE void *__asan_get_current_fake_stack() { return GetFakeStackFast(); } +static ALWAYS_INLINE void AllocaPoison(const uptr addr, const uptr size, + bool doPoison) { + uptr LeftRedzoneAddr = addr - kAllocaRedzoneSize; + uptr PartialRzAddr = addr + size; + uptr RightRzAddr = (PartialRzAddr + kAllocaRedzoneMask) + & ~kAllocaRedzoneMask; + uptr PartialRzAligned = PartialRzAddr & ~(SHADOW_GRANULARITY - 1); + char leftMagic = doPoison ? kAsanAllocaLeftMagic : 0; + char rightMagic = doPoison ? kAsanAllocaRightMagic : 0; + FastPoisonShadow(LeftRedzoneAddr, kAllocaRedzoneSize, leftMagic); + FastPoisonShadowPartialRightRedzone(PartialRzAligned, + PartialRzAddr % SHADOW_GRANULARITY, + RightRzAddr - PartialRzAligned, + rightMagic); + FastPoisonShadow(RightRzAddr, kAllocaRedzoneSize, rightMagic); +} + +SANITIZER_INTERFACE_ATTRIBUTE +void __asan_poison_alloca(const uptr addr, const uptr size) { + AllocaPoison(addr, size, true); +} + +SANITIZER_INTERFACE_ATTRIBUTE +void __asan_unpoison_alloca(const uptr addr, const uptr size) { + AllocaPoison(addr, size, false); +} + SANITIZER_INTERFACE_ATTRIBUTE void *__asan_addr_is_in_fake_stack(void *fake_stack, void *addr, void **beg, void **end) { Index: lib/asan/asan_interface_internal.h =================================================================== --- lib/asan/asan_interface_internal.h +++ lib/asan/asan_interface_internal.h @@ -177,6 +177,16 @@ void __asan_poison_intra_object_redzone(uptr p, uptr size); SANITIZER_INTERFACE_ATTRIBUTE void __asan_unpoison_intra_object_redzone(uptr p, uptr size); + + // Runtime calls this function to poison redzones for dynamic allocas. Addr + // is base address of user memory, size is size of user memory. + SANITIZER_INTERFACE_ATTRIBUTE + void __asan_poison_alloca(const uptr address, const uptr size); + + // The same as __asan_poison_alloca, but unpoison redzones for dynamic + // allocas. + SANITIZER_INTERFACE_ATTRIBUTE + void __asan_unpoison_alloca(const uptr address, const uptr size); } // extern "C" #endif // ASAN_INTERFACE_INTERNAL_H Index: lib/asan/asan_internal.h =================================================================== --- lib/asan/asan_internal.h +++ lib/asan/asan_internal.h @@ -136,10 +136,14 @@ const int kAsanInternalHeapMagic = 0xfe; const int kAsanArrayCookieMagic = 0xac; const int kAsanIntraObjectRedzone = 0xbb; +const int kAsanAllocaLeftMagic = 0xca; +const int kAsanAllocaRightMagic = 0xcb; static const uptr kCurrentStackFrameMagic = 0x41B58AB3; static const uptr kRetiredStackFrameMagic = 0x45E0360E; +static const uptr kAllocaRedzoneSize = 32; +static const uptr kAllocaRedzoneMask = kAllocaRedzoneSize - 1; } // namespace __asan #endif // ASAN_INTERNAL_H Index: lib/asan/asan_report.cc =================================================================== --- lib/asan/asan_report.cc +++ lib/asan/asan_report.cc @@ -87,6 +87,8 @@ return Cyan(); case kAsanUserPoisonedMemoryMagic: case kAsanContiguousContainerOOBMagic: + case kAsanAllocaLeftMagic: + case kAsanAllocaRightMagic: return Blue(); case kAsanStackUseAfterScopeMagic: return Magenta(); @@ -173,6 +175,8 @@ PrintShadowByte(str, " Intra object redzone: ", kAsanIntraObjectRedzone); PrintShadowByte(str, " ASan internal: ", kAsanInternalHeapMagic); + PrintShadowByte(str, " Left alloca redzone: ", kAsanAllocaLeftMagic); + PrintShadowByte(str, " Right alloca redzone: ", kAsanAllocaRightMagic); } void MaybeDumpInstructionBytes(uptr pc) { @@ -982,6 +986,10 @@ case kAsanIntraObjectRedzone: bug_descr = "intra-object-overflow"; break; + case kAsanAllocaLeftMagic: + case kAsanAllocaRightMagic: + bug_descr = "dynamic-stack-buffer-overflow"; + break; } } Index: test/asan/TestCases/Linux/interface_symbols_linux.c =================================================================== --- test/asan/TestCases/Linux/interface_symbols_linux.c +++ test/asan/TestCases/Linux/interface_symbols_linux.c @@ -26,6 +26,8 @@ // RUN: echo __asan_report_store_n >> %t.interface // RUN: echo __asan_get_current_fake_stack >> %t.interface // RUN: echo __asan_addr_is_in_fake_stack >> %t.interface +// RUN: echo __asan_poison_alloca >> %t.interface +// RUN: echo __asan_unpoison_alloca >> %t.interface // RUN: cat %t.interface | sort -u | diff %t.symbols - // FIXME: nm -D on powerpc somewhy shows ASan interface symbols residing Index: test/asan/TestCases/alloca_big_alignment.cc =================================================================== --- /dev/null +++ test/asan/TestCases/alloca_big_alignment.cc @@ -0,0 +1,17 @@ +// RUN: %clangxx_asan -O2 -mllvm -asan-instrument-allocas %s -o %t +// RUN: not %run %t 2>&1 | FileCheck %s +// + +#include +__attribute__((noinline)) void foo(int index, int len) { + volatile char str[len] __attribute__((aligned(128))); + assert(!(reinterpret_cast(str) & 127L)); + str[index] = '1'; // BOOM +// CHECK: ERROR: AddressSanitizer: dynamic-stack-buffer-overflow on address [[ADDR:0x[0-9a-f]+]] +// CHECK: WRITE of size 1 at [[ADDR]] thread T0 +} + +int main(int argc, char **argv) { + foo(10, 10); + return 0; +} Index: test/asan/TestCases/alloca_overflow_partial.cc =================================================================== --- /dev/null +++ test/asan/TestCases/alloca_overflow_partial.cc @@ -0,0 +1,15 @@ +// RUN: %clangxx_asan -O2 -mllvm -asan-instrument-allocas %s -o %t +// RUN: not %run %t 2>&1 | FileCheck %s +// + +__attribute__((noinline)) void foo(int index, int len) { + volatile char str[len]; + str[index] = '1'; // BOOM +// CHECK: ERROR: AddressSanitizer: dynamic-stack-buffer-overflow on address [[ADDR:0x[0-9a-f]+]] +// CHECK: WRITE of size 1 at [[ADDR]] thread T0 +} + +int main(int argc, char **argv) { + foo(10, 10); + return 0; +} Index: test/asan/TestCases/alloca_overflow_right.cc =================================================================== --- /dev/null +++ test/asan/TestCases/alloca_overflow_right.cc @@ -0,0 +1,15 @@ +// RUN: %clangxx_asan -O2 -mllvm -asan-instrument-allocas %s -o %t +// RUN: not %run %t 2>&1 | FileCheck %s +// + +__attribute__((noinline)) void foo(int index, int len) { + volatile char str[len]; + str[index] = '1'; // BOOM +// CHECK: ERROR: AddressSanitizer: dynamic-stack-buffer-overflow on address [[ADDR:0x[0-9a-f]+]] +// CHECK: WRITE of size 1 at [[ADDR]] thread T0 +} + +int main(int argc, char **argv) { + foo(33, 10); + return 0; +} Index: test/asan/TestCases/alloca_underflow_left.cc =================================================================== --- /dev/null +++ test/asan/TestCases/alloca_underflow_left.cc @@ -0,0 +1,15 @@ +// RUN: %clangxx_asan -O2 -mllvm -asan-instrument-allocas %s -o %t +// RUN: not %run %t 2>&1 | FileCheck %s +// + +__attribute__((noinline)) void foo(int index, int len) { + volatile char str[len]; + str[index] = '1'; // BOOM +// CHECK: ERROR: AddressSanitizer: dynamic-stack-buffer-overflow on address [[ADDR:0x[0-9a-f]+]] +// CHECK: WRITE of size 1 at [[ADDR]] thread T0 +} + +int main(int argc, char **argv) { + foo(-1, 10); + return 0; +} Index: test/asan/TestCases/alloca_unpoison_stack.cc =================================================================== --- /dev/null +++ test/asan/TestCases/alloca_unpoison_stack.cc @@ -0,0 +1,14 @@ +// RUN: %clangxx_asan -O2 -mllvm -asan-instrument-allocas %s -o %t +// RUN: %run %t 2>&1 +// + +__attribute__((noinline)) void foo(int index, int len) { + volatile char str[len]; + str[index] = '1'; +} + +int main(int argc, char **argv) { + foo(4, 5); + foo(9, 10); + return 0; +}