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,12 @@ // Accesses sizes are powers of two: 1, 2, 4, 8, 16. static const size_t kNumberOfAccessSizes = 5; +const unsigned kAllocaRzSize = 32; +const unsigned kAsanAllocaLeftMagic = 0xcacacacaU; +const unsigned kAsanAllocaRightMagic = 0xcbcbcbcbU; +const unsigned kAsanAllocaPartialVal1 = 0xcbcbcb00U; +const unsigned kAsanAllocaPartialVal2 = 0x000000cbU; + // Command-line flags. // This flag may need to be replaced with -f[no-]asan-reads. @@ -151,6 +158,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 +473,28 @@ }; SmallVector AllocaPoisonCallVec; + // Stores left, partial and right redzone shadow addresses for dynamic alloca + // and pointer to alloca instruction itself. + // LeftRzAddr is a shadow address for alloca left redzone. + // RightRzAddr is a shadow address for alloca right redzone. + // PartialRzAddr is a shadow address for alloca partial redzone, aligned by + // Align = max(kAllocaRzSize, AI->getAlignment()). If Align >= kAllocaRzSize, + // PartialRzAddr would be the same as RightRzAddr. + 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 +511,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 +534,154 @@ RetVec.push_back(&RI); } + // Unpoison dynamic allocas redzones. + void unpoisonDynamicAlloca(DynamicAllocaCall &AllocaCall) { + for (auto Ret : RetVec) { + IRBuilder<> IRBRet(Ret); + IRBRet.CreateStore(ConstantInt::get(IRBRet.getInt32Ty(), 0), + AllocaCall.LeftRzAddr); + IRBRet.CreateStore(ConstantInt::get(IRBRet.getInt32Ty(), 0), + AllocaCall.PartialRzAddr); + IRBRet.CreateStore(ConstantInt::get(IRBRet.getInt32Ty(), 0), + AllocaCall.RightRzAddr); + } + } + + // 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 = kAsanAllocaPartialVal1; + unsigned Val2Int = kAsanAllocaPartialVal2; + 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 uint64_t 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. + AllocaCall.LeftRzAddr = IRB.CreateIntToPtr( + ASan.memToShadow(LeftRzAddress, IRB), Int32PtrTy); + IRB.CreateStore(ConstantInt::get(IRB.getInt32Ty(), kAsanAllocaLeftMagic), + AllocaCall.LeftRzAddr); + + // PartialRzAligned = PartialRzAddr & ~AllocaRzMask + Value *PartialRzAddr = IRB.CreateAdd(NewAddress, OldSize); + Value *PartialRzAligned = IRB.CreateAnd(PartialRzAddr, NotAllocaRzMask); + + // Poisoning partial redzone. + Value *PartialRzMagic = calculatePartialRzMagic(PartialSize, IRB); + AllocaCall.PartialRzAddr = IRB.CreateIntToPtr( + ASan.memToShadow(PartialRzAligned, IRB), Int32PtrTy); + IRB.CreateStore(PartialRzMagic, AllocaCall.PartialRzAddr); + + // RightRzAddress + // = (PartialRzAddr + AllocaRzMask) & ~AllocaRzMask + Value *RightRzAddress = IRB.CreateAnd(IRB.CreateAdd(PartialRzAddr, + AllocaRzMask), + NotAllocaRzMask); + + // Poisoning right redzone. + AllocaCall.RightRzAddr = IRB.CreateIntToPtr( + ASan.memToShadow(RightRzAddress, IRB), Int32PtrTy); + IRB.CreateStore(ConstantInt::get(IRB.getInt32Ty(), kAsanAllocaRightMagic), + AllocaCall.RightRzAddr); + + + // 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 +713,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); } @@ -1502,10 +1678,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); @@ -1665,6 +1849,11 @@ } } + 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(); 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,25 @@ +; 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-NOALLOCA +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 + %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,23 @@ +// 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, 64); + assert(q && ((q - str) == index)); +} + +int main(int argc, char **argv) { + for (int i = 1; i < 33; ++i) + foo(i, i); + + 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; +}