Index: compiler-rt/test/asan/TestCases/Windows/issue64990.cpp =================================================================== --- /dev/null +++ compiler-rt/test/asan/TestCases/Windows/issue64990.cpp @@ -0,0 +1,18 @@ +// Repro for the issue #64990: Asan with Windows EH generates __asan_xxx runtime calls without required funclet tokens +// RUN: %clang_cl_asan %Od %s -EHsc %Fe%t +// RUN: not %run %t 2>&1 | FileCheck %s + +char buff1[6] = "hello"; +char buff2[6] = "hello"; + +int main(int argc, char **argv) { + try { + throw 1; + } catch (...) { + // Make asan generate call to __asan_memcpy inside the EH pad. + __builtin_memcpy(buff1, buff2 + 3, 6); + } + return 0; +} + +// CHECK: SUMMARY: AddressSanitizer: global-buffer-overflow {{.*}} in __asan_memcpy Index: llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp =================================================================== --- llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp +++ llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp @@ -43,6 +43,7 @@ #include "llvm/IR/DebugInfoMetadata.h" #include "llvm/IR/DebugLoc.h" #include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/EHPersonalities.h" #include "llvm/IR/Function.h" #include "llvm/IR/GlobalAlias.h" #include "llvm/IR/GlobalValue.h" @@ -640,6 +641,88 @@ } namespace { +/// Helper class to keep track of the inserted runtime calls during a pass on a +/// single Function. At the end of that pass, calling finalize() will detect and +/// add the corresponding funclet OpBundle to the inserted calls. +class RuntimeCallInserter { + Function *OwnerFn = nullptr; + bool TrackInsertedCalls = false; + std::vector InsertedCalls; + + void reset() { + OwnerFn = nullptr; + TrackInsertedCalls = false; + InsertedCalls.clear(); + } + +public: + ~RuntimeCallInserter() { assert(!isInitialized()); } + + bool isInitialized() const { return OwnerFn != nullptr; } + + void initialize(Function &Fn) { + assert(!isInitialized() && !TrackInsertedCalls); + OwnerFn = &Fn; + if (Fn.hasPersonalityFn()) { + auto Personality = classifyEHPersonality(Fn.getPersonalityFn()); + if (isScopedEHPersonality(Personality)) + TrackInsertedCalls = true; + } + } + + CallInst *createRuntimeCall(IRBuilder<> &IRB, FunctionCallee Callee, + ArrayRef Args = {}, + const Twine &Name = "") { + assert(isInitialized()); + assert(IRB.GetInsertBlock()->getParent() == OwnerFn); + + CallInst *Inst = IRB.CreateCall(Callee, Args, Name, nullptr); + if (TrackInsertedCalls) { + auto Fn = dyn_cast(Callee.getCallee()); + bool IsIntrinsicCallee = Fn && Fn->isIntrinsic(); + if (!IsIntrinsicCallee) + InsertedCalls.push_back(Inst); + } + return Inst; + } + + void finalize() { + assert(isInitialized()); + if (!TrackInsertedCalls || InsertedCalls.empty()) { + reset(); + return; + } + + DenseMap BlockColors = colorEHFunclets(*OwnerFn); + for (CallInst *CI : InsertedCalls) { + BasicBlock *BB = CI->getParent(); + assert(BB && "Instruction doesn't belong to a BasicBlock"); + assert(BB->getParent() == OwnerFn && + "Instruction doesn't belong to the expected Function!"); + + ColorVector &Colors = BlockColors[BB]; + // A BB could be colorless when it is unreachable, in which case it will + // be removed by a later pass anyway. + if (Colors.empty()) + continue; + assert(Colors.size() == 1 && "Expected monochromatic BB!"); + + BasicBlock *Color = Colors.front(); + Instruction *EHPad = Color->getFirstNonPHI(); + + if (EHPad && EHPad->isEHPad()) { + // Replace CI with a clone with funclet OperandBundle + OperandBundleDef OB("funclet", EHPad); + auto *NewCall = + CallBase::addOperandBundle(CI, LLVMContext::OB_funclet, OB, CI); + NewCall->copyMetadata(*CI); + CI->replaceAllUsesWith(NewCall); + CI->eraseFromParent(); + } + } + reset(); + } +}; /// AddressSanitizer: instrument the code in module to find memory bugs. struct AddressSanitizer { @@ -716,6 +799,12 @@ bool maybeInsertDynamicShadowAtFunctionEntry(Function &F); void markEscapedLocalAllocas(Function &F); + CallInst *createRuntimeCall(IRBuilder<> &IRB, FunctionCallee Callee, + ArrayRef Args = {}, + const Twine &Name = "") { + return RtCallInserter.createRuntimeCall(IRB, Callee, Args, Name); + } + private: friend struct FunctionStackPoisoner; @@ -739,6 +828,7 @@ ~FunctionStateRAII() { Pass->LocalDynamicShadow = nullptr; Pass->ProcessedAllocas.clear(); + assert(!Pass->RtCallInserter.isInitialized()); } }; @@ -758,6 +848,8 @@ FunctionCallee AsanPtrCmpFunction, AsanPtrSubFunction; Constant *AsanShadowGlobal; + RuntimeCallInserter RtCallInserter; + // These arrays is indexed by AccessIsWrite, Experiment and log2(AccessSize). FunctionCallee AsanErrorCallback[2][2][kNumberOfAccessSizes]; FunctionCallee AsanMemoryAccessCallback[2][2][kNumberOfAccessSizes]; @@ -1012,8 +1104,8 @@ DynamicAreaOffset); } - IRB.CreateCall( - AsanAllocasUnpoisonFunc, + ASan.createRuntimeCall( + IRB, AsanAllocasUnpoisonFunc, {IRB.CreateLoad(IntptrTy, DynamicAllocaLayout), DynamicAreaPtr}); } @@ -1232,14 +1324,14 @@ void AddressSanitizer::instrumentMemIntrinsic(MemIntrinsic *MI) { InstrumentationIRBuilder IRB(MI); if (isa(MI)) { - IRB.CreateCall( - isa(MI) ? AsanMemmove : AsanMemcpy, + createRuntimeCall( + IRB, isa(MI) ? AsanMemmove : AsanMemcpy, {IRB.CreatePointerCast(MI->getOperand(0), IRB.getInt8PtrTy()), IRB.CreatePointerCast(MI->getOperand(1), IRB.getInt8PtrTy()), IRB.CreateIntCast(MI->getOperand(2), IntptrTy, false)}); } else if (isa(MI)) { - IRB.CreateCall( - AsanMemset, + createRuntimeCall( + IRB, AsanMemset, {IRB.CreatePointerCast(MI->getOperand(0), IRB.getInt8PtrTy()), IRB.CreateIntCast(MI->getOperand(1), IRB.getInt32Ty(), false), IRB.CreateIntCast(MI->getOperand(2), IntptrTy, false)}); @@ -1485,7 +1577,7 @@ if (i->getType()->isPointerTy()) i = IRB.CreatePointerCast(i, IntptrTy); } - IRB.CreateCall(F, Param); + createRuntimeCall(IRB, F, Param); } static void doInstrumentAddress(AddressSanitizer *Pass, Instruction *I, @@ -1647,18 +1739,18 @@ CallInst *Call = nullptr; if (SizeArgument) { if (Exp == 0) - Call = IRB.CreateCall(AsanErrorCallbackSized[IsWrite][0], - {Addr, SizeArgument}); + Call = createRuntimeCall(IRB, AsanErrorCallbackSized[IsWrite][0], + {Addr, SizeArgument}); else - Call = IRB.CreateCall(AsanErrorCallbackSized[IsWrite][1], - {Addr, SizeArgument, ExpVal}); + Call = createRuntimeCall(IRB, AsanErrorCallbackSized[IsWrite][1], + {Addr, SizeArgument, ExpVal}); } else { if (Exp == 0) - Call = - IRB.CreateCall(AsanErrorCallback[IsWrite][0][AccessSizeIndex], Addr); + Call = createRuntimeCall( + IRB, AsanErrorCallback[IsWrite][0][AccessSizeIndex], Addr); else - Call = IRB.CreateCall(AsanErrorCallback[IsWrite][1][AccessSizeIndex], - {Addr, ExpVal}); + Call = createRuntimeCall( + IRB, AsanErrorCallback[IsWrite][1][AccessSizeIndex], {Addr, ExpVal}); } Call->setCannotMerge(); @@ -1736,11 +1828,12 @@ Value *AddrLong = IRB.CreatePointerCast(Addr, IntptrTy); if (UseCalls) { if (Exp == 0) - IRB.CreateCall(AsanMemoryAccessCallback[IsWrite][0][AccessSizeIndex], - AddrLong); + createRuntimeCall( + IRB, AsanMemoryAccessCallback[IsWrite][0][AccessSizeIndex], AddrLong); else - IRB.CreateCall(AsanMemoryAccessCallback[IsWrite][1][AccessSizeIndex], - {AddrLong, ConstantInt::get(IRB.getInt32Ty(), Exp)}); + createRuntimeCall(IRB, + AsanMemoryAccessCallback[IsWrite][1][AccessSizeIndex], + {AddrLong, ConstantInt::get(IRB.getInt32Ty(), Exp)}); return; } @@ -1799,11 +1892,12 @@ Value *AddrLong = IRB.CreatePointerCast(Addr, IntptrTy); if (UseCalls) { if (Exp == 0) - IRB.CreateCall(AsanMemoryAccessCallbackSized[IsWrite][0], - {AddrLong, Size}); + createRuntimeCall(IRB, AsanMemoryAccessCallbackSized[IsWrite][0], + {AddrLong, Size}); else - IRB.CreateCall(AsanMemoryAccessCallbackSized[IsWrite][1], - {AddrLong, Size, ConstantInt::get(IRB.getInt32Ty(), Exp)}); + createRuntimeCall( + IRB, AsanMemoryAccessCallbackSized[IsWrite][1], + {AddrLong, Size, ConstantInt::get(IRB.getInt32Ty(), Exp)}); } else { Value *SizeMinusOne = IRB.CreateSub(Size, ConstantInt::get(IntptrTy, 1)); Value *LastByte = IRB.CreateIntToPtr( @@ -2821,6 +2915,8 @@ FunctionStateRAII CleanupObj(this); + RtCallInserter.initialize(F); + FunctionModified |= maybeInsertDynamicShadowAtFunctionEntry(F); // We can't instrument allocas used with llvm.localescape. Only static allocas @@ -2842,7 +2938,10 @@ TempsToInstrument.clear(); int NumInsnsPerBB = 0; for (auto &Inst : BB) { - if (LooksLikeCodeInBug11395(&Inst)) return false; + if (LooksLikeCodeInBug11395(&Inst)) { + RtCallInserter.finalize(); + return false; + } // Skip instructions inserted by another instrumentation. if (Inst.hasMetadata(LLVMContext::MD_nosanitize)) continue; @@ -2919,7 +3018,7 @@ // See e.g. https://github.com/google/sanitizers/issues/37 for (auto *CI : NoReturnCalls) { IRBuilder<> IRB(CI); - IRB.CreateCall(AsanHandleNoReturnFunc, {}); + createRuntimeCall(IRB, AsanHandleNoReturnFunc, {}); } for (auto *Inst : PointerComparisonsOrSubtracts) { @@ -2930,6 +3029,8 @@ if (ChangedStack || !NoReturnCalls.empty()) FunctionModified = true; + RtCallInserter.finalize(); + LLVM_DEBUG(dbgs() << "ASAN done instrumenting: " << FunctionModified << " " << F << "\n"); @@ -3068,9 +3169,10 @@ if (j - i >= ClMaxInlinePoisoningSize) { copyToShadowInline(ShadowMask, ShadowBytes, Done, i, IRB, ShadowBase); - IRB.CreateCall(AsanSetShadowFunc[Val], - {IRB.CreateAdd(ShadowBase, ConstantInt::get(IntptrTy, i)), - ConstantInt::get(IntptrTy, j - i)}); + ASan.createRuntimeCall( + IRB, AsanSetShadowFunc[Val], + {IRB.CreateAdd(ShadowBase, ConstantInt::get(IntptrTy, i)), + ConstantInt::get(IntptrTy, j - i)}); Done = j; } } @@ -3357,8 +3459,8 @@ StackMallocIdx = StackMallocSizeClass(LocalStackSize); assert(StackMallocIdx <= kMaxAsanStackMallocSizeClass); Value *FakeStackValue = - IRBIf.CreateCall(AsanStackMallocFunc[StackMallocIdx], - ConstantInt::get(IntptrTy, LocalStackSize)); + ASan.createRuntimeCall(IRBIf, AsanStackMallocFunc[StackMallocIdx], + ConstantInt::get(IntptrTy, LocalStackSize)); IRB.SetInsertPoint(InsBefore); FakeStack = createPHI(IRB, UseAfterReturnIsEnabled, FakeStackValue, Term, ConstantInt::get(IntptrTy, 0)); @@ -3368,7 +3470,8 @@ // void *LocalStackBase = (FakeStack) ? FakeStack : // alloca(LocalStackSize); StackMallocIdx = StackMallocSizeClass(LocalStackSize); - FakeStack = IRB.CreateCall(AsanStackMallocFunc[StackMallocIdx], + FakeStack = + ASan.createRuntimeCall(IRB, AsanStackMallocFunc[StackMallocIdx], ConstantInt::get(IntptrTy, LocalStackSize)); } Value *NoFakeStack = @@ -3503,8 +3606,8 @@ IRBPoison.CreateIntToPtr(SavedFlagPtr, IRBPoison.getInt8PtrTy())); } else { // For larger frames call __asan_stack_free_*. - IRBPoison.CreateCall( - AsanStackFreeFunc[StackMallocIdx], + ASan.createRuntimeCall( + IRBPoison, AsanStackFreeFunc[StackMallocIdx], {FakeStack, ConstantInt::get(IntptrTy, LocalStackSize)}); } @@ -3525,8 +3628,8 @@ // For now just insert the call to ASan runtime. Value *AddrArg = IRB.CreatePointerCast(V, IntptrTy); Value *SizeArg = ConstantInt::get(IntptrTy, Size); - IRB.CreateCall( - DoPoison ? AsanPoisonStackMemoryFunc : AsanUnpoisonStackMemoryFunc, + ASan.createRuntimeCall( + IRB, DoPoison ? AsanPoisonStackMemoryFunc : AsanUnpoisonStackMemoryFunc, {AddrArg, SizeArg}); } @@ -3587,7 +3690,7 @@ ConstantInt::get(IntptrTy, Alignment.value())); // Insert __asan_alloca_poison call for new created alloca. - IRB.CreateCall(AsanAllocaPoisonFunc, {NewAddress, OldSize}); + ASan.createRuntimeCall(IRB, AsanAllocaPoisonFunc, {NewAddress, OldSize}); // Store the last alloca's address to DynamicAllocaLayout. We'll need this // for unpoisoning stuff. Index: llvm/test/Instrumentation/AddressSanitizer/asan-funclet.ll =================================================================== --- /dev/null +++ llvm/test/Instrumentation/AddressSanitizer/asan-funclet.ll @@ -0,0 +1,128 @@ +; Test appropriate tagging of funclet for function calls generated by asan. +; RUN: opt -S -passes=asan -asan-max-inline-poisoning-size=0 \ +; RUN: -asan-detect-invalid-pointer-cmp -asan-detect-invalid-pointer-sub -asan-use-after-scope < %s | FileCheck %s --check-prefixes=CHECK,CHECK-INLINE +; RUN: opt -S -passes=asan -asan-max-inline-poisoning-size=0 -asan-instrumentation-with-call-threshold=0 \ +; RUN: -asan-detect-invalid-pointer-cmp -asan-detect-invalid-pointer-sub -asan-use-after-scope < %s | FileCheck %s --check-prefixes=CHECK,CHECK-OUTLINE + +; REQUIRES: x86-registered-target + +target triple = "x86_64-pc-windows-msvc" + +declare void @DeInit(ptr) +declare void @MayThrowFunc() +declare void @NoReturn() noreturn + +declare void @llvm.memmove.p0.p0.i64(ptr nocapture, ptr nocapture readonly, i64, i1) +declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1) +declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1) +declare void @llvm.lifetime.start.p0(i64, ptr nocapture) nounwind +declare void @llvm.lifetime.end.p0(i64, ptr nocapture) nounwind + +declare i32 @__CxxFrameHandler3(...) +declare i32 @dummyPersonality(...) + +define void @FuncletPersonality(ptr %ptrParam) sanitize_address personality ptr @__CxxFrameHandler3 { +; CHECK-LABEL: @FuncletPersonality +; CHECK: ehcleanup: +; CHECK: [[CleanupPad1:%[^ ]+]] = cleanuppad within none [] +; CHECK-INLINE: call void @__asan_unpoison_stack_memory{{.*}} [ "funclet"(token [[CleanupPad1]]) ] +; CHECK-INLINE: call void @__asan_report_store1{{.*}} [ "funclet"(token [[CleanupPad1]]) ] +; CHECK-INLINE: call void @__asan_poison_stack_memory{{.*}} [ "funclet"(token [[CleanupPad1]]) ] +; CHECK: call void @DeInit({{.*}}) [ "funclet"(token [[CleanupPad1]]) ] +; CHECK: call ptr @__asan_memset{{.*}} [ "funclet"(token [[CleanupPad1]]) ] +; CHECK: call ptr @__asan_memcpy{{.*}} [ "funclet"(token [[CleanupPad1]]) ] +; CHECK: call ptr @__asan_memmove{{.*}} [ "funclet"(token [[CleanupPad1]]) ] +; CHECK: call void @__sanitizer_ptr_cmp{{.*}} [ "funclet"(token [[CleanupPad1]]) ] +; CHECK: call void @__sanitizer_ptr_sub{{.*}} [ "funclet"(token [[CleanupPad1]]) ] + +; CHECK-OUTLINE: call void @__asan_storeN{{.*}} [ "funclet"(token [[CleanupPad1]]) ] + +; CHECK: noreturncall: +; CHECK: call void @__asan_handle_no_return{{.*}} [ "funclet"(token [[CleanupPad1]]) ] +; CHECK: ehexit: +; CHECK: call void @__asan_allocas_unpoison{{.*}} [ "funclet"(token [[CleanupPad1]]) ] +; CHECK: call void @__asan_stack_free_{{.*}} [ "funclet"(token [[CleanupPad1]]) ] +; CHECK: call void @__asan_set_shadow_{{.*}} [ "funclet"(token [[CleanupPad1]]) ] +entry: + ; Large enough local alloca to have asan generate a __asan_stack_free_#() call + %largeObj = alloca [2048 x i32], align 16 + %tmpInt1 = alloca i32, align 4 + %tmpInt2 = alloca i32, align 4 + %tmpInt3 = alloca i32, align 4 + + ; Creating %lifetimeInt and %lifetimeArr, and managing their lifetimes + ; to make asan generate stack poisoning calls + %lifetimeInt = alloca i32, align 4 + call void @llvm.lifetime.start.p0(i64 4, ptr %lifetimeInt) + store volatile i8 0, ptr %lifetimeInt + call void @llvm.lifetime.end.p0(i64 4, ptr %lifetimeInt) + %lifetimeArr = alloca i32, align 4 + + ; Dynamic alloca to generate a @__asan_allocas_unpoison call in ehcleanup + %tmpVolatilei64 = alloca i64, align 8 + store volatile i64 0, ptr %tmpVolatilei64, align 8 + %tmpCopyi64 = load i64, ptr %tmpVolatilei64, align 8 + %tmpVolatilei8 = alloca i8, i64 %tmpCopyi64, align 32 + store volatile i8 0, ptr %tmpVolatilei8 + + invoke void @MayThrowFunc() + to label %invoke.cont unwind label %ehcleanup +invoke.cont: ; preds = %entry + call void @DeInit(ptr %largeObj) + ret void + +ehcleanup: ; preds = %entry + %0 = cleanuppad within none [] + + ; Make asan add a call to __asan_unpoison_stack_memory + call void @llvm.lifetime.start.p0(i64 4, ptr %lifetimeArr) + ; Make asan add a call to __asan_report_store1 + store volatile i8 0, ptr %lifetimeArr + ; Make asan add a call to __asan_poison_stack_memory + call void @llvm.lifetime.end.p0(i64 4, ptr %lifetimeArr) + + call void @DeInit(ptr %largeObj) [ "funclet"(token %0) ] + call void @llvm.memset.p0.i64(ptr align 4 %tmpInt1, i8 0, i64 4, i1 false) + call void @llvm.memcpy.p0.p0.i64(ptr align 4 %tmpInt2, ptr align 4 %tmpInt1, i64 4, i1 false) + call void @llvm.memmove.p0.p0.i64(ptr align 4 %tmpInt3, ptr align 4 %tmpInt1, i64 4, i1 false) + %cmpAddr = icmp ule ptr %tmpInt1, %tmpInt2 + %addr1 = ptrtoint ptr %tmpInt1 to i64 + %addr2 = ptrtoint ptr %tmpInt2 to i64 + %subAddr = sub i64 %addr1, %addr2 + + store i64 0, ptr %ptrParam, align 1 + + %cmp = icmp ne i64 %subAddr, 0 + br i1 %cmp, label %ehexit, label %noreturncall + +noreturncall: + call void @NoReturn(ptr null, ptr null) noreturn [ "funclet"(token %0) ] + unreachable + +ehexit: + cleanupret from %0 unwind to caller + +; Ensure unreachable basic block doesn't make the compiler assert, as it's a special case for coloring computation. +nopredecessor: + call void @llvm.memset.p0.i64(ptr align 4 %tmpInt1, i8 0, i64 4, i1 false) + unreachable +} + +; Non-Windows personality, ensure no funclet gets attached to asan runtime call. +define void @OtherPersonality(ptr %ptrParam) sanitize_address personality ptr @dummyPersonality { +; CHECK-LABEL: @OtherPersonality +; CHECK: ehcleanup: +; CHECK: call ptr @__asan_memset +; CHECK-NOT: funclet +entry: + %tmpInt = alloca i32, align 4 + invoke void @MayThrowFunc() + to label %invoke.cont unwind label %ehcleanup +invoke.cont: ; preds = %entry + ret void + +ehcleanup: ; preds = %entry + %0 = cleanuppad within none [] + call void @llvm.memset.p0.i64(ptr align 4 %tmpInt, i8 0, i64 4, i1 false) + cleanupret from %0 unwind to caller +} Index: llvm/test/Instrumentation/AddressSanitizer/localescape.ll =================================================================== --- llvm/test/Instrumentation/AddressSanitizer/localescape.ll +++ llvm/test/Instrumentation/AddressSanitizer/localescape.ll @@ -14,10 +14,10 @@ declare ptr @llvm.localrecover(ptr, ptr, i32) declare void @llvm.localescape(...) #1 -declare i32 @_except_handler3(...) +declare i32 @__gcc_personality_v0(...) declare void @may_throw(ptr %r) -define i32 @main() sanitize_address personality ptr @_except_handler3 { +define i32 @main() sanitize_address personality ptr @__gcc_personality_v0 { entry: %r = alloca i32, align 4 %__exception_code = alloca i32, align 4