Index: lib/Transforms/Instrumentation/AddressSanitizer.cpp =================================================================== --- lib/Transforms/Instrumentation/AddressSanitizer.cpp +++ lib/Transforms/Instrumentation/AddressSanitizer.cpp @@ -40,6 +40,7 @@ #include "llvm/Support/DataTypes.h" #include "llvm/Support/Debug.h" #include "llvm/Support/Endian.h" +#include "llvm/Support/SwapByteOrder.h" #include "llvm/Transforms/Scalar.h" #include "llvm/Transforms/Utils/ASanStackFrameLayout.h" #include "llvm/Transforms/Utils/BasicBlockUtils.h" @@ -104,6 +105,8 @@ // Accesses sizes are powers of two: 1, 2, 4, 8, 16. static const size_t kNumberOfAccessSizes = 5; +const unsigned kAllocaRzSize = 32; + // Command-line flags. // This flag may need to be replaced with -f[no-]asan-reads. @@ -151,6 +154,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. @@ -464,6 +469,22 @@ }; SmallVector AllocaPoisonCallVec; + // Stores left, middle and right redzone shadow addresses for dynamic alloca. + struct DynamicAllocaCall { + AllocaInst *AI; + Value *LeftRzAddr; + Value *PartialRzAddr; + Value *RightRzAddr; + explicit DynamicAllocaCall(AllocaInst *AI, + Value *LeftRzAddr = nullptr, + Value *PartialRzAddr= nullptr, + Value *RightRzAddr = nullptr) + : AI(AI), LeftRzAddr(LeftRzAddr), PartialRzAddr(PartialRzAddr), + RightRzAddr(RightRzAddr) + {} + }; + SmallVector DynamicAllocaVec; + // Maps Value to an AllocaInst from which the Value is originated. typedef DenseMap AllocaForValueMapTy; AllocaForValueMapTy AllocaForValue; @@ -480,7 +501,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()); @@ -503,12 +524,163 @@ RetVec.push_back(&RI); } + // (Un)poison memory starting on Address with value Val. + void poisonAddrForAlloca(Value *Address, IRBuilder<> &IRB, + const unsigned Val) { + PointerType *Int32PtrTy = PointerType::getUnqual(IRB.getInt32Ty()); + Value *ShadowBase = IRB.CreateIntToPtr(ASan.memToShadow(Address, IRB), + Int32PtrTy); + IRB.CreateStore(ConstantInt::get(IRB.getInt32Ty(), Val), ShadowBase); + } + + // Inserts __asan_unpoison_alloca for given nonstatic alloca before each ret + // instruction. + void unpoisonDynamicAlloca(DynamicAllocaCall &AllocaCall, + IRBuilder<> IRBRet) { + poisonAddrForAlloca(AllocaCall.LeftRzAddr, IRBRet, 0); + poisonAddrForAlloca(AllocaCall.PartialRzAddr, IRBRet, 0); + poisonAddrForAlloca(AllocaCall.RightRzAddr, IRBRet, 0); + } + + // Right shift for BigEndian and left shift for LittleEndian. + inline Instruction *shiftAllocaMagic(Value *Val, IRBuilder<> &IRB, + Value *Shift) { + if (ASan.DL->isLittleEndian()) + return cast(IRB.CreateShl(Val, Shift)); + else + return cast(IRB.CreateLShr(Val, Shift)); + } + + // Calculate PartialRzMagic. + Value *calculatePartialRzMagic(Value *PartialSize, IRBuilder<> &IRB) { + PartialSize = IRB.CreateIntCast(PartialSize, IRB.getInt32Ty(), + false); + Value *Shift = IRB.CreateAnd(PartialSize, IRB.getInt32(~7)); + unsigned Val1Int = 0xcbcbcb00; + unsigned Val2Int = 0x000000cb; + if (!ASan.DL->isLittleEndian()) { + Val1Int = sys::getSwappedBytes(Val1Int); + Val2Int = sys::getSwappedBytes(Val2Int); + } + Value *Val1 = shiftAllocaMagic(IRB.getInt32(Val1Int), IRB, Shift); + Value *PartialBits = IRB.CreateAnd(PartialSize, IRB.getInt32(7)); + Value *Cond = IRB.CreateICmpNE(PartialBits, IRB.getInt32(0)); + // For BigEndian get 0x000000YZ -> 0xYZ000000 + if (ASan.DL->isBigEndian()) + PartialBits = IRB.CreateShl(PartialBits, IRB.getInt32(24)); + Instruction *Val2 = shiftAllocaMagic(PartialBits, IRB, Shift); + SelectInst *Partial = SelectInst::Create(Cond, PartialBits, + IRB.getInt32(Val2Int), + "partial_bits", Val2); + Val2->setOperand(0, Partial); + return IRB.CreateOr(Val1, Val2); + } + + void handleDynamicAllocaCall(DynamicAllocaCall &AllocaCall) { + AllocaInst *AI = AllocaCall.AI; + IRBuilder<> IRB(AI); + + PointerType *Int32PtrTy = PointerType::getUnqual(IRB.getInt32Ty()); + const unsigned Align = std::max(kAllocaRzSize, AI->getAlignment()); + const unsigned long AllocaRedzoneMask = kAllocaRzSize - 1; + + Value *Zero = ConstantInt::get(IntptrTy, 0); + Value *AllocaRzSize = ConstantInt::get(IntptrTy, kAllocaRzSize); + Value *AllocaRzMask = ConstantInt::get(IntptrTy, AllocaRedzoneMask); + Value *NotAllocaRzMask = ConstantInt::get(IntptrTy, ~AllocaRedzoneMask); + + // Since we need to extend alloca with additional memory to locate + // 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)); + + // PartialSize = OldSize % 32 + Value *PartialSize = IRB.CreateAnd(OldSize, AllocaRzMask); + + // Misalign = kAllocaRzSize - PartialSize; + Value *Misalign = IRB.CreateSub(AllocaRzSize, PartialSize); + + // PartialPadding = Misalign != kAllocaRzSize ? Misalign : 0; + Value *Cond = IRB.CreateICmpNE(Misalign, AllocaRzSize); + SelectInst *PartialPadding = SelectInst::Create(Cond, Misalign, Zero, + "additional_size", AI); + + // AdditionalChunkSize = Align + PartialPadding + kAllocaRzSize + // Align is added to locate left redzone, PartialPadding for possible + // partial redzone and kAllocaRzSize for right redzone respectively. + Value *AdditionalChunkSize = IRB.CreateAdd( + ConstantInt::get(IntptrTy, Align + kAllocaRzSize), + PartialPadding); + + Value *NewSize = IRB.CreateAdd(OldSize, AdditionalChunkSize); + + // Insert new alloca with new NewSize and Align params. + AllocaInst *NewAlloca = IRB.CreateAlloca(IRB.getInt8Ty(), NewSize); + NewAlloca->setAlignment(Align); + + // NewAddress = Address + Align + Value *NewAddress = IRB.CreateAdd(IRB.CreatePtrToInt(NewAlloca, IntptrTy), + ConstantInt::get(IntptrTy, Align)); + + Value *NewAddressPtr = IRB.CreateIntToPtr(NewAddress, AI->getType()); + + // LeftRzAddress = NewAddress - kAllocaRzSize + Value *LeftRzAddress = IRB.CreateSub(NewAddress, AllocaRzSize); + + // Poisoning left redzone. + poisonAddrForAlloca(LeftRzAddress, IRB, 0xcacacacaU); + Value *PartialRzAddr = IRB.CreateAdd(NewAddress, OldSize); + AllocaCall.LeftRzAddr = LeftRzAddress; + + // PartialRzAligned = PartialRzAddr & ~AllocaRzMask + Value *PartialRzAligned = IRB.CreateAnd(PartialRzAddr, NotAllocaRzMask); + AllocaCall.PartialRzAddr = PartialRzAligned; + + // RightRzAddress + // = (PartialRzAddr + AllocaRzMask) & ~AllocaRzMask + Value *RightRzAddress = IRB.CreateAnd(IRB.CreateAdd(PartialRzAddr, + AllocaRzMask), + NotAllocaRzMask); + + // Poisoning right redzone. + poisonAddrForAlloca(RightRzAddress, IRB, 0xcbcbcbcbU); + AllocaCall.RightRzAddr = RightRzAddress; + + Value *PartialRzShadowBase = ASan.memToShadow(PartialRzAligned, IRB); + Value *PartialRzShadowBasePtr = IRB.CreateIntToPtr(PartialRzShadowBase, + Int32PtrTy); + // if (PartialSize) { + // PartialRzMagic = calculatePartialRzMagic(PartialSize); + // ShadowBase = ASan.memToShadow(PartialRzAligned); + // *ShadowBase = PartialRzMagic; + // } + Value *Cmp = IRB.CreateICmpNE(PartialSize, Zero); + Instruction *Term = SplitBlockAndInsertIfThen(Cmp, AI, false); + IRBuilder<> IRBIf(Term); + Value *PartialRzMagic = calculatePartialRzMagic(PartialSize, IRBIf); + IRBIf.CreateStore(PartialRzMagic, PartialRzShadowBasePtr); + + // Replace all uses of AddessReturnedByAlloca with NewAddress. + AI->replaceAllUsesWith(NewAddressPtr); + + // We are done. Erase old alloca and store left, partial and right redzones + // shadow addresses for future unpoisoning. + AI->eraseFromParent(); + } + /// \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(DynamicAllocaCall(&AI)); + else + AllocaVec.push_back(&AI); } /// \brief Collect lifetime intrinsic calls to check for use-after-scope @@ -540,10 +712,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); } @@ -1500,10 +1675,18 @@ } void FunctionStackPoisoner::poisonStack() { + assert(AllocaVec.size() > 0 || DynamicAllocaVec.size() > 0); + + if (ClInstrumentAllocas) + // Handle dynamic allocas. + for (auto AllocaCall : DynamicAllocaVec) + handleDynamicAllocaCall(AllocaCall); + + 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); @@ -1653,13 +1836,20 @@ IRBuilder<> IRBElse(ElseTerm); poisonRedZones(L.ShadowBytes, IRBElse, ShadowBase, false); - } else if (HavePoisonedAllocas) { - // If we poisoned some allocas in llvm.lifetime analysis, - // unpoison whole stack frame now. - assert(LocalStackBase == OrigStackBase); - poisonAlloca(LocalStackBase, LocalStackSize, IRBRet, false); } else { - poisonRedZones(L.ShadowBytes, IRBRet, ShadowBase, false); + if (ClInstrumentAllocas) + // Unpoison dynamic allocas. + for (auto AllocaCall : DynamicAllocaVec) + unpoisonDynamicAlloca(AllocaCall, IRBRet); + + if (HavePoisonedAllocas) { + // If we poisoned some allocas in llvm.lifetime analysis, + // unpoison whole stack frame now. + assert(LocalStackBase == OrigStackBase); + poisonAlloca(LocalStackBase, LocalStackSize, IRBRet, false); + } else { + poisonRedZones(L.ShadowBytes, IRBRet, ShadowBase, false); + } } } Index: lib/asan/asan_internal.h =================================================================== --- lib/asan/asan_internal.h +++ lib/asan/asan_internal.h @@ -136,6 +136,8 @@ 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; 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/Instrumentation/AddressSanitizer/instrument-dynamic-allocas.ll =================================================================== --- /dev/null +++ test/Instrumentation/AddressSanitizer/instrument-dynamic-allocas.ll @@ -0,0 +1,27 @@ +; Test asan internal compiler flags: +; -asan-instrument-allocas=1 + +; RUN: opt < %s -asan -asan-module -asan-instrument-allocas=1 -S | FileCheck %s --check-prefix=CHECK-ALLOCA +; RUN: opt < %s -asan -asan-module -asan-instrument-allocas=0 -S | FileCheck %s --check-prefix=CHECK-NOALLOCA +; RUN: opt < %s -asan -asan-module -S | FileCheck %s --check-prefix=CHECK-DEFAULT +target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64" +target triple = "x86_64-unknown-linux-gnu" + +define void @foo(i32 %len) sanitize_address { +entry: +; CHECK-ALLOCA: %additional_size = select +; CHECK-ALLOCA: %partial_bits = select +; CHECK-NOALLOCA-NOT: %additional_size = select +; CHECK-NOALLOCA-NOT: %partial_bits = select +; CHECK-DEFAULT-NOT: %additional_size = select +; CHECK-DEFAULT-NOT: %partial_bits = select + %0 = alloca i32, align 4 + %1 = alloca i8* + store i32 %len, i32* %0, align 4 + %2 = load i32* %0, align 4 + %3 = zext i32 %2 to i64 + %4 = alloca i8, i64 %3, align 32 + ret void +} + + Index: test/asan/TestCases/alloca_big_alignment.cc =================================================================== --- /dev/null +++ test/asan/TestCases/alloca_big_alignment.cc @@ -0,0 +1,18 @@ +// RUN: %clangxx_asan -O0 -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_detect_custom_size_.cc =================================================================== --- /dev/null +++ test/asan/TestCases/alloca_detect_custom_size_.cc @@ -0,0 +1,23 @@ +// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-allocas %s -o %t +// RUN: not %run %t 2>&1 | FileCheck %s +// + +#include + +struct A { + char a[3]; + int b[3]; +}; + +__attribute__((noinline)) void foo(int index, int len) { + volatile struct A str[len] __attribute__((aligned(32))); + assert(!(reinterpret_cast(str) & 31L)); + str[index].a[0] = '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_instruments_all_paddings.cc =================================================================== --- /dev/null +++ test/asan/TestCases/alloca_instruments_all_paddings.cc @@ -0,0 +1,19 @@ +// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-allocas %s -o %t +// RUN: %run %t 2>&1 +// + +#include "sanitizer/asan_interface.h" +#include + +__attribute__((noinline)) void foo(int index, int len) { + volatile char str[len] __attribute__((aligned(32))); + assert(!(reinterpret_cast(str) & 31L)); + char *q = (char *)__asan_region_is_poisoned((char *)str + index, 64 - index); + assert(q && ((q - str) == index)); +} + +int main(int argc, char **argv) { + for (int i = 1; i < 33; ++i) + foo(i, i); + return 0; +} Index: test/asan/TestCases/alloca_overflow_partial.cc =================================================================== --- /dev/null +++ test/asan/TestCases/alloca_overflow_partial.cc @@ -0,0 +1,18 @@ +// RUN: %clangxx_asan -O0 -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(32))); + 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(10, 10); + return 0; +} Index: test/asan/TestCases/alloca_overflow_right.cc =================================================================== --- /dev/null +++ test/asan/TestCases/alloca_overflow_right.cc @@ -0,0 +1,18 @@ +// RUN: %clangxx_asan -O0 -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(32))); + 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/alloca_safe_access.cc =================================================================== --- /dev/null +++ test/asan/TestCases/alloca_safe_access.cc @@ -0,0 +1,17 @@ +// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-allocas %s -o %t +// RUN: %run %t 2>&1 +// + +#include + +__attribute__((noinline)) void foo(int index, int len) { + volatile char str[len] __attribute__((aligned(32))); + assert(!(reinterpret_cast(str) & 31L)); + str[index] = '1'; +} + +int main(int argc, char **argv) { + foo(4, 5); + foo(39, 40); + return 0; +} Index: test/asan/TestCases/alloca_underflow_left.cc =================================================================== --- /dev/null +++ test/asan/TestCases/alloca_underflow_left.cc @@ -0,0 +1,18 @@ +// RUN: %clangxx_asan -O0 -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(32))); + 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(-1, 10); + return 0; +}