Index: lib/Transforms/Instrumentation/AddressSanitizer.cpp =================================================================== --- lib/Transforms/Instrumentation/AddressSanitizer.cpp +++ lib/Transforms/Instrumentation/AddressSanitizer.cpp @@ -101,6 +101,9 @@ static const char *const kAsanOptionDetectUAR = "__asan_option_detect_stack_use_after_return"; +static const char *const kAsanUnrollAllocaName = + "__asan_unroll_alloca"; + #ifndef NDEBUG static const int kAsanStackAfterReturnMagic = 0xf5; #endif @@ -460,11 +463,13 @@ SmallVector AllocaVec; SmallVector RetVec; + SmallVector StackRestoreVec; unsigned StackAlignment; Function *AsanStackMallocFunc[kMaxAsanStackMallocSizeClass + 1], *AsanStackFreeFunc[kMaxAsanStackMallocSizeClass + 1]; - Function *AsanPoisonStackMemoryFunc, *AsanUnpoisonStackMemoryFunc; + Function *AsanPoisonStackMemoryFunc, *AsanUnpoisonStackMemoryFunc, + *AsanUnrollAllocaFunc; // Stores a place and arguments of poisoning/unpoisoning call for alloca. struct AllocaPoisonCall { @@ -491,6 +496,8 @@ }; SmallVector DynamicAllocaVec; + AllocaInst *DynamicAllocaLayout; + // Maps Value to an AllocaInst from which the Value is originated. typedef DenseMap AllocaForValueMapTy; AllocaForValueMapTy AllocaForValue; @@ -528,27 +535,23 @@ // Then unpoison everything back before the function returns. void poisonStack(); + void createDynamicAllocasInitStorage(Instruction *LayoutAlloca); + // ----------------------- Visitors. /// \brief Collect all Ret instructions. void visitReturnInst(ReturnInst &RI) { RetVec.push_back(&RI); } - // Unpoison dynamic allocas redzones. - void unpoisonDynamicAlloca(DynamicAllocaCall &AllocaCall) { - for (auto Ret : RetVec) { - IRBuilder<> IRBRet(Ret); - PointerType *Int32PtrTy = PointerType::getUnqual(IRBRet.getInt32Ty()); - Value *Zero = Constant::getNullValue(IRBRet.getInt32Ty()); - Value *PartialRzAddr = IRBRet.CreateSub(AllocaCall.RightRzAddr, - ConstantInt::get(IntptrTy, 4)); - IRBRet.CreateStore(Zero, IRBRet.CreateIntToPtr(AllocaCall.LeftRzAddr, - Int32PtrTy)); - IRBRet.CreateStore(Zero, IRBRet.CreateIntToPtr(PartialRzAddr, - Int32PtrTy)); - IRBRet.CreateStore(Zero, IRBRet.CreateIntToPtr(AllocaCall.RightRzAddr, - Int32PtrTy)); - } + void unpoisonDynamicAllocasBeforeInst(Instruction *I, Value *SavedSp) { + IRBuilder<> IRB(I); + Value *PrevAllocaLeftRzAddr = IRB.CreateLoad(DynamicAllocaLayout); + + Value *LastAllocaRzAddr = IRB.CreateCall2( + AsanUnrollAllocaFunc, + IRB.CreatePtrToInt(PrevAllocaLeftRzAddr, IntptrTy), + IRB.CreatePtrToInt(SavedSp, IntptrTy)); + IRB.CreateStore(LastAllocaRzAddr, DynamicAllocaLayout); } // Right shift for BigEndian and left shift for LittleEndian. @@ -557,6 +560,17 @@ : IRB.CreateLShr(Val, Shift); } + // Unpoison dynamic allocas redzones. + void unpoisonDynamicAllocas() { + for (auto &Ret : RetVec) + unpoisonDynamicAllocasBeforeInst(Ret, + Constant::getAllOnesValue((IntptrTy))); + + for (auto &StackRestoreInst : StackRestoreVec) + unpoisonDynamicAllocasBeforeInst(StackRestoreInst, + StackRestoreInst->getOperand(0)); + } + // Compute PartialRzMagic for dynamic alloca call. Since we don't know the // size of requested memory until runtime, we should compute it dynamically. // If PartialSize is 0, PartialRzMagic would contain kAsanAllocaRightMagic, @@ -595,6 +609,8 @@ /// \brief Collect lifetime intrinsic calls to check for use-after-scope /// errors. void visitIntrinsicInst(IntrinsicInst &II) { + if (II.getIntrinsicID() == Intrinsic::stackrestore) + StackRestoreVec.push_back(&II); if (!ClCheckLifetime) return; Intrinsic::ID ID = II.getIntrinsicID(); if (ID != Intrinsic::lifetime_start && @@ -1517,6 +1533,9 @@ AsanUnpoisonStackMemoryFunc = checkInterfaceFunction( M.getOrInsertFunction(kAsanUnpoisonStackMemoryName, IRB.getVoidTy(), IntptrTy, IntptrTy, nullptr)); + AsanUnrollAllocaFunc = checkInterfaceFunction( + M.getOrInsertFunction(kAsanUnrollAllocaName, IntptrTy, + IntptrTy, IntptrTy, nullptr)); } void @@ -1593,6 +1612,14 @@ return PHI; } +void FunctionStackPoisoner::createDynamicAllocasInitStorage( + Instruction *LayoutAlloca) { + IRBuilder<> IRB(LayoutAlloca); + DynamicAllocaLayout = IRB.CreateAlloca(IntptrTy, nullptr); + DynamicAllocaLayout->setAlignment(32); + IRB.CreateStore(Constant::getNullValue(IntptrTy), DynamicAllocaLayout); +} + Value *FunctionStackPoisoner::createAllocaForLayout( IRBuilder<> &IRB, const ASanStackFrameLayout &L, bool Dynamic) { AllocaInst *Alloca; @@ -1614,11 +1641,18 @@ void FunctionStackPoisoner::poisonStack() { assert(AllocaVec.size() > 0 || DynamicAllocaVec.size() > 0); - if (ClInstrumentAllocas) + if (ClInstrumentAllocas && DynamicAllocaVec.size() > 0) { // Handle dynamic allocas. + BasicBlock& FirstBB = *F.begin(); + Instruction *FirstInstruction = dyn_cast(FirstBB.begin()); + createDynamicAllocasInitStorage(FirstInstruction); + for (auto &AllocaCall : DynamicAllocaVec) handleDynamicAllocaCall(AllocaCall); + unpoisonDynamicAllocas(); + } + if (AllocaVec.size() == 0) return; int StackMallocIdx = -1; @@ -1796,11 +1830,6 @@ } } - if (ClInstrumentAllocas) - // Unpoison dynamic allocas. - for (auto &AllocaCall : DynamicAllocaVec) - unpoisonDynamicAlloca(AllocaCall); - // We are done. Remove the old unused alloca instructions. for (auto AI : AllocaVec) AI->eraseFromParent(); @@ -1919,9 +1948,10 @@ // redzones, and OldSize is number of allocated blocks with // ElementSize size, get allocated memory size in bytes by // OldSize * ElementSize. - unsigned ElementSize = ASan.DL->getTypeAllocSize(AI->getAllocatedType()); - Value *OldSize = IRB.CreateMul(AI->getArraySize(), - ConstantInt::get(IntptrTy, ElementSize)); + int ElementSize = ASan.DL->getTypeAllocSize(AI->getAllocatedType()); + Value *OldSize = + IRB.CreateMul(IRB.CreateIntCast(AI->getArraySize(), IntptrTy, false), + ConstantInt::get(IntptrTy, ElementSize)); // PartialSize = OldSize % 32 Value *PartialSize = IRB.CreateAnd(OldSize, AllocaRzMask); @@ -1979,6 +2009,32 @@ IRB.CreateStore(ConstantInt::get(IRB.getInt32Ty(), kAsanAllocaRightMagic), IRB.CreateIntToPtr(AllocaCall.RightRzAddr, Int32PtrTy)); + // Store LeftRzAddress of previous alloca and self RightRzAddress in + // left redzone. + + // Compute pointer size. + PointerType *IntptrPtrTy = PointerType::getUnqual(IntptrTy); + unsigned PtrSize = ASan.DL->getTypeAllocSize(IntptrTy); + + // Load previous alloca left redzone address. + LoadInst *PrevAllocaLeftRzAddr = IRB.CreateLoad(DynamicAllocaLayout); + + // Store previous alloca left redzone address into LeftRzAddress. + IRB.CreateStore(PrevAllocaLeftRzAddr, + IRB.CreateIntToPtr(LeftRzAddress, IntptrPtrTy)); + + // Store left redzone address into DynamicAllocaLayout. + IRB.CreateStore(LeftRzAddress, DynamicAllocaLayout); + + // Compute LeftRzAddress + sizeof(void *) to store righ redzone address there. + Value *LeftRzSecond = + IRB.CreateAdd(LeftRzAddress, ConstantInt::get(IntptrTy, PtrSize)); + + // Store current alloca right redzone address into + // LeftRzAddress + sizeof(void *). + IRB.CreateStore(RightRzAddress, + IRB.CreateIntToPtr(LeftRzSecond, IntptrPtrTy)); + // Replace all uses of AddessReturnedByAlloca with NewAddress. AI->replaceAllUsesWith(NewAddressPtr); Index: lib/asan/asan_fake_stack.cc =================================================================== --- lib/asan/asan_fake_stack.cc +++ lib/asan/asan_fake_stack.cc @@ -253,4 +253,24 @@ if (end) *end = reinterpret_cast(frame_end); return reinterpret_cast(frame->real_stack); } + +SANITIZER_INTERFACE_ATTRIBUTE +uptr __asan_unroll_alloca(uptr left_rz_addr, uptr left_rz_threshold) { + if (!left_rz_addr) return 0; + + // FIXME: Add support for stack upgrowing platforms. + do { + uptr *left_rz = reinterpret_cast(left_rz_addr); + uptr right_rz_addr = left_rz[1]; + u32 *left_rz_shadow = (u32*)MemToShadow(left_rz_addr); + left_rz_shadow[0] = 0; + u32 *right_rz_shadow = (u32*)MemToShadow(right_rz_addr); + right_rz_shadow[0] = 0; + right_rz_shadow[-1] = 0; + left_rz_addr = left_rz[0]; + } while (left_rz_addr != 0UL && + left_rz_addr < left_rz_threshold); + + return left_rz_addr; +} } // extern "C" Index: lib/asan/asan_interface_internal.h =================================================================== --- lib/asan/asan_interface_internal.h +++ lib/asan/asan_interface_internal.h @@ -177,6 +177,8 @@ void __asan_poison_intra_object_redzone(uptr p, uptr size); SANITIZER_INTERFACE_ATTRIBUTE void __asan_unpoison_intra_object_redzone(uptr p, uptr size); + SANITIZER_INTERFACE_ATTRIBUTE + uptr __asan_unroll_alloca(uptr left_rz_addr, uptr left_rz_threshold); } // extern "C" #endif // ASAN_INTERFACE_INTERNAL_H Index: test/asan/TestCases/alloca_loop_unpoisoning.cc =================================================================== --- /dev/null +++ test/asan/TestCases/alloca_loop_unpoisoning.cc @@ -0,0 +1,32 @@ +// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-allocas %s -o %t +// RUN: %run %t 2>&1 +// + +// This testcase checks that allocas and VLAs inside loop are correctly unpoisoned. + +#include +#include +#include +#include "sanitizer/asan_interface.h" + +void *top, *bot; + +__attribute__((noinline)) void foo(int len) { + char x; + top = &x; + char array[len]; // NOLINT + assert(!(reinterpret_cast(array) & 31L)); + alloca(len); + for (int i = 0; i < 32; ++i) { + char array[i]; // NOLINT + bot = alloca(i); + assert(!(reinterpret_cast(bot) & 31L)); + } +} + +int main(int argc, char **argv) { + foo(32); + void *q = __asan_region_is_poisoned(bot, (char *)top - (char *)bot); + assert(!q); + return 0; +} Index: test/asan/TestCases/alloca_vla_interact.cc =================================================================== --- /dev/null +++ test/asan/TestCases/alloca_vla_interact.cc @@ -0,0 +1,39 @@ +// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-allocas %s -o %t +// RUN: %run %t 2>&1 +// + +// This testcase checks correct interaction between VLAs and allocas. + +#include +#include +#include +#include "sanitizer/asan_interface.h" + +#define RZ 32 + +__attribute__((noinline)) void foo(int len) { + char *top, *bot; + // This alloca call should live until the end of foo. + char *alloca1 = (char *)alloca(len); + assert(!(reinterpret_cast(alloca1) & 31L)); + // This should be first poisoned address after loop. + top = alloca1 - RZ; + for (int i = 0; i < 32; ++i) { + // Check that previous alloca was unpoisoned at the end of iteration. + if (i) assert(!__asan_region_is_poisoned(bot, 96)); + // VLA is unpoisoned at the end of iteration. + volatile char array[i]; + assert(!(reinterpret_cast(array) & 31L)); + // Alloca is unpoisoned at the end of iteration, + // because dominated by VLA. + bot = (char *)alloca(i) - RZ; + } + // Check that all allocas from loop were unpoisoned correctly. + void *q = __asan_region_is_poisoned(bot, (char *)top - (char *)bot + 1); + assert(q == top); +} + +int main(int argc, char **argv) { + foo(32); + return 0; +} Index: test/asan/TestCases/vla_chrome_testcase.cc =================================================================== --- /dev/null +++ test/asan/TestCases/vla_chrome_testcase.cc @@ -0,0 +1,30 @@ +// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-allocas %s -o %t +// RUN: not %run %t 2>&1 | FileCheck %s +// + +// This is reduced testcase based on Chromium code. +// See http://reviews.llvm.org/D6055?vs=on&id=15616&whitespace=ignore-all#toc. + +#include +#include + +int a = 7; +int b; +int c; +int *p; + +__attribute__((noinline)) void fn3(int *first, int second) { +} + +int main() { + int d = b && c; + int e[a]; // NOLINT + assert(!(reinterpret_cast(e) & 31L)); + int f; + if (d) + fn3(&f, sizeof 0 * (&c - e)); + e[a] = 0; +// CHECK: ERROR: AddressSanitizer: dynamic-stack-buffer-overflow on address [[ADDR:0x[0-9a-f]+]] +// CHECK: WRITE of size 4 at [[ADDR]] thread T0 + return 0; +} Index: test/asan/TestCases/vla_condition_overflow.cc =================================================================== --- /dev/null +++ test/asan/TestCases/vla_condition_overflow.cc @@ -0,0 +1,21 @@ +// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-allocas %s -o %t +// RUN: not %run %t 2>&1 | FileCheck %s +// + +#include +#include + +__attribute__((noinline)) void foo(int index, int len) { + if (index > len) { + char str[len]; //NOLINT + assert(!(reinterpret_cast(str) & 31L)); + 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/vla_loop_overfow.cc =================================================================== --- /dev/null +++ test/asan/TestCases/vla_loop_overfow.cc @@ -0,0 +1,21 @@ +// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-allocas %s -o %t +// RUN: not %run %t 2>&1 | FileCheck %s +// + +#include +#include + +void foo(int index, int len) { + for (int i = 1; i < len; ++i) { + char array[len]; // NOLINT + assert(!(reinterpret_cast(array) & 31L)); + array[index + i] = 0; +// 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(9, 21); + return 0; +}