From 3b48753a2f3952996ec1857fd85fd42595d6a0e6 Mon Sep 17 00:00:00 2001 From: Evgeniy Stepanov Date: Fri, 7 Jul 2017 00:48:25 +0000 Subject: [PATCH] Copy arguments passed by value into explicit allocas for ASan. ASan determines the stack layout from alloca instructions. Since arguments marked as "byval" do not have an explicit alloca instruction, ASan does not produce red zones for them. This commit produces an explicit alloca instruction and copies the byval argument into the allocated memory so that red zones are produced. Patch by Matt Morehouse. Differential revision: https://reviews.llvm.org/D34789 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@307342 91177308-0d34-0410-b5e6-96231b3b80d8 --- .../Instrumentation/AddressSanitizer.cpp | 37 ++++++++++++++ .../stack-poisoning-byval-args.ll | 48 +++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 test/Instrumentation/AddressSanitizer/stack-poisoning-byval-args.ll diff --git a/lib/Transforms/Instrumentation/AddressSanitizer.cpp b/lib/Transforms/Instrumentation/AddressSanitizer.cpp index 184940b7ea5..5f98cd566f3 100644 --- a/lib/Transforms/Instrumentation/AddressSanitizer.cpp +++ b/lib/Transforms/Instrumentation/AddressSanitizer.cpp @@ -22,9 +22,11 @@ #include "llvm/ADT/Statistic.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/Triple.h" +#include "llvm/ADT/Twine.h" #include "llvm/Analysis/MemoryBuiltins.h" #include "llvm/Analysis/TargetLibraryInfo.h" #include "llvm/Analysis/ValueTracking.h" +#include "llvm/IR/Argument.h" #include "llvm/IR/CallSite.h" #include "llvm/IR/DIBuilder.h" #include "llvm/IR/DataLayout.h" @@ -192,6 +194,11 @@ static cl::opt ClMaxInlinePoisoningSize( static cl::opt ClUseAfterReturn("asan-use-after-return", cl::desc("Check stack-use-after-return"), cl::Hidden, cl::init(true)); +static cl::opt ClRedzoneByvalArgs("asan-redzone-byval-args", + cl::desc("Create redzones for byval " + "arguments (extra copy " + "required)"), cl::Hidden, + cl::init(true)); static cl::opt ClUseAfterScope("asan-use-after-scope", cl::desc("Check stack-use-after-scope"), cl::Hidden, cl::init(false)); @@ -747,6 +754,9 @@ struct FunctionStackPoisoner : public InstVisitor { bool runOnFunction() { if (!ClStack) return false; + + if (ClRedzoneByvalArgs) copyArgsPassedByValToAllocas(); + // Collect alloca, ret, lifetime instructions etc. for (BasicBlock *BB : depth_first(&F.getEntryBlock())) visit(*BB); @@ -763,6 +773,11 @@ struct FunctionStackPoisoner : public InstVisitor { return true; } + // Arguments marked with the "byval" attribute are implicitly copied without + // using an alloca instruction. To produce redzones for those arguments, we + // copy them a second time into memory allocated with an alloca instruction. + void copyArgsPassedByValToAllocas(); + // Finds all Alloca instructions and puts // poisoned red zones around all of them. // Then unpoison everything back before the function returns. @@ -2528,6 +2543,28 @@ static int StackMallocSizeClass(uint64_t LocalStackSize) { llvm_unreachable("impossible LocalStackSize"); } +void FunctionStackPoisoner::copyArgsPassedByValToAllocas() { + BasicBlock &FirstBB = *F.begin(); + IRBuilder<> IRB(&FirstBB, FirstBB.getFirstInsertionPt()); + const DataLayout &DL = F.getParent()->getDataLayout(); + for (Argument &Arg : F.args()) { + if (Arg.hasByValAttr()) { + Type *Ty = Arg.getType()->getPointerElementType(); + unsigned Align = Arg.getParamAlignment(); + if (Align == 0) Align = DL.getABITypeAlignment(Ty); + + const std::string &Name = Arg.hasName() ? Arg.getName().str() : + "Arg" + std::to_string(Arg.getArgNo()); + AllocaInst *AI = IRB.CreateAlloca(Ty, nullptr, Twine(Name) + ".byval"); + AI->setAlignment(Align); + Arg.replaceAllUsesWith(AI); + + uint64_t AllocSize = DL.getTypeAllocSize(Ty); + IRB.CreateMemCpy(AI, &Arg, AllocSize, Align); + } + } +} + PHINode *FunctionStackPoisoner::createPHI(IRBuilder<> &IRB, Value *Cond, Value *ValueIfTrue, Instruction *ThenTerm, diff --git a/test/Instrumentation/AddressSanitizer/stack-poisoning-byval-args.ll b/test/Instrumentation/AddressSanitizer/stack-poisoning-byval-args.ll new file mode 100644 index 00000000000..32610ce3b81 --- /dev/null +++ b/test/Instrumentation/AddressSanitizer/stack-poisoning-byval-args.ll @@ -0,0 +1,48 @@ +; This check verifies that arguments passed by value get redzones. +; RUN: opt < %s -asan -asan-realign-stack=32 -S | FileCheck %s + +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" + +%struct.A = type { [8 x i32] } + +declare i32 @bar(%struct.A*) + +; Test behavior for named argument with explicit alignment. The memcpy and +; alloca alignments should match the explicit alignment of 64. +define void @foo(%struct.A* byval align 64 %a) sanitize_address { +entry: +; CHECK-LABEL: foo +; CHECK: call i64 @__asan_stack_malloc +; CHECK: alloca i8, i64 {{.*}} align 64 +; CHECK: [[copyPtr:%[^ \t]+]] = inttoptr i64 %{{[^ \t]+}} to %struct.A* +; CHECK: [[copyBytePtr:%[^ \t]+]] = bitcast %struct.A* [[copyPtr]] +; CHECK: [[aBytePtr:%[^ \t]+]] = bitcast %struct.A* %a +; CHECK: call void @llvm.memcpy{{[^%]+}}[[copyBytePtr]]{{[^%]+}}[[aBytePtr]],{{[^,]+}}, i32 64 +; CHECK: call i32 @bar(%struct.A* [[copyPtr]]) +; CHECK: ret void + + %call = call i32 @bar(%struct.A* %a) + ret void +} + +; Test behavior for unnamed argument without explicit alignment. In this case, +; the first argument is referenced by the identifier %0 and the ABI requires a +; minimum alignment of 4 bytes since struct.A contains i32s which have 4-byte +; alignment. However, the alloca alignment will be 32 since that is the value +; passed via the -asan-realign-stack option, which is greater than 4. +define void @baz(%struct.A* byval) sanitize_address { +entry: +; CHECK-LABEL: baz +; CHECK: call i64 @__asan_stack_malloc +; CHECK: alloca i8, i64 {{.*}} align 32 +; CHECK: [[copyPtr:%[^ \t]+]] = inttoptr i64 %{{[^ \t]+}} to %struct.A* +; CHECK: [[copyBytePtr:%[^ \t]+]] = bitcast %struct.A* [[copyPtr]] +; CHECK: [[aBytePtr:%[^ \t]+]] = bitcast %struct.A* %0 +; CHECK: call void @llvm.memcpy{{[^%]+}}[[copyBytePtr]]{{[^%]+}}[[aBytePtr]],{{[^,]+}}, i32 4 +; CHECK: call i32 @bar(%struct.A* [[copyPtr]]) +; CHECK: ret void + + %call = call i32 @bar(%struct.A* %0) + ret void +}