diff --git a/llvm/include/llvm/Analysis/StackSafetyAnalysis.h b/llvm/include/llvm/Analysis/StackSafetyAnalysis.h --- a/llvm/include/llvm/Analysis/StackSafetyAnalysis.h +++ b/llvm/include/llvm/Analysis/StackSafetyAnalysis.h @@ -76,6 +76,7 @@ ~StackSafetyGlobalInfo(); bool isSafe(const AllocaInst &AI) const; + bool accessIsSafe(const Instruction *I) const; void print(raw_ostream &O) const; void dump() const; }; diff --git a/llvm/lib/Analysis/StackSafetyAnalysis.cpp b/llvm/lib/Analysis/StackSafetyAnalysis.cpp --- a/llvm/lib/Analysis/StackSafetyAnalysis.cpp +++ b/llvm/lib/Analysis/StackSafetyAnalysis.cpp @@ -30,6 +30,7 @@ #include "llvm/Support/raw_ostream.h" #include #include +#include using namespace llvm; @@ -116,6 +117,7 @@ // Access range if the address (alloca or parameters). // It is allowed to be empty-set when there are no known accesses. ConstantRange Range; + std::map Accesses; // List of calls which pass address as an argument. // Value is offset range of address from base address (alloca or calling @@ -129,6 +131,18 @@ UseInfo(unsigned PointerSize) : Range{PointerSize, false} {} void updateRange(const ConstantRange &R) { Range = unionNoWrap(Range, R); } + void addRange(const Instruction *I, const ConstantRange &R) { + bool Inserted; + std::map::iterator It; + std::tie(It, Inserted) = Accesses.emplace(I, R); + if (!Inserted) { + auto Union = unionNoWrap(It->second, R); + Accesses.erase(It); + std::tie(It, Inserted) = Accesses.emplace(I, Union); + assert(Inserted); + } + updateRange(R); + } }; template @@ -223,6 +237,7 @@ struct StackSafetyGlobalInfo::InfoTy { GVToSSI Info; SmallPtrSet SafeAllocas; + std::map Accesses; }; namespace { @@ -349,11 +364,11 @@ switch (I->getOpcode()) { case Instruction::Load: { if (AI && !SL.isAliveAfter(AI, I)) { - US.updateRange(UnknownRange); - return false; + US.addRange(I, UnknownRange); + break; } - US.updateRange( - getAccessRange(UI, Ptr, DL.getTypeStoreSize(I->getType()))); + US.addRange(I, + getAccessRange(UI, Ptr, DL.getTypeStoreSize(I->getType()))); break; } @@ -363,15 +378,16 @@ case Instruction::Store: { if (V == I->getOperand(0)) { // Stored the pointer - conservatively assume it may be unsafe. - US.updateRange(UnknownRange); - return false; + US.addRange(I, UnknownRange); + break; } if (AI && !SL.isAliveAfter(AI, I)) { - US.updateRange(UnknownRange); - return false; + US.addRange(I, UnknownRange); + break; } - US.updateRange(getAccessRange( - UI, Ptr, DL.getTypeStoreSize(I->getOperand(0)->getType()))); + US.addRange( + I, getAccessRange( + UI, Ptr, DL.getTypeStoreSize(I->getOperand(0)->getType()))); break; } @@ -379,8 +395,8 @@ // Information leak. // FIXME: Process parameters correctly. This is a leak only if we return // alloca. - US.updateRange(UnknownRange); - return false; + US.addRange(I, UnknownRange); + break; case Instruction::Call: case Instruction::Invoke: { @@ -388,25 +404,26 @@ break; if (AI && !SL.isAliveAfter(AI, I)) { - US.updateRange(UnknownRange); - return false; + US.addRange(I, UnknownRange); + break; } if (const MemIntrinsic *MI = dyn_cast(I)) { - US.updateRange(getMemIntrinsicAccessRange(MI, UI, Ptr)); + US.addRange(I, getMemIntrinsicAccessRange(MI, UI, Ptr)); break; } const auto &CB = cast(*I); if (!CB.isArgOperand(&UI)) { - US.updateRange(UnknownRange); - return false; + US.addRange(I, UnknownRange); + break; } unsigned ArgNo = CB.getArgOperandNo(&UI); if (CB.isByValArgument(ArgNo)) { - US.updateRange(getAccessRange( - UI, Ptr, DL.getTypeStoreSize(CB.getParamByValType(ArgNo)))); + US.addRange(I, getAccessRange( + UI, Ptr, + DL.getTypeStoreSize(CB.getParamByValType(ArgNo)))); break; } @@ -416,8 +433,8 @@ const GlobalValue *Callee = dyn_cast(CB.getCalledOperand()->stripPointerCasts()); if (!Callee) { - US.updateRange(UnknownRange); - return false; + US.addRange(I, UnknownRange); + break; } assert(isa(Callee) || isa(Callee)); @@ -436,7 +453,7 @@ } } - return true; + return !US.Range.isFullSet(); } FunctionInfo StackSafetyLocalAnalysis::run() { @@ -806,15 +823,24 @@ } } Info.reset(new InfoTy{ - createGlobalStackSafetyInfo(std::move(Functions), Index), {}}); + createGlobalStackSafetyInfo(std::move(Functions), Index), {}, {}}); for (auto &FnKV : Info->Info) { for (auto &KV : FnKV.second.Allocas) { ++NumAllocaTotal; const AllocaInst *AI = KV.first; - if (getStaticAllocaSizeRange(*AI).contains(KV.second.Range)) { + auto AIRange = getStaticAllocaSizeRange(*AI); + if (AIRange.contains(KV.second.Range)) { Info->SafeAllocas.insert(AI); ++NumAllocaStackSafe; } + for (const auto &A : KV.second.Accesses) { + bool IsSafe = AIRange.contains(A.second); + bool Inserted; + std::map::iterator It; + std::tie(It, Inserted) = Info->Accesses.emplace(A.first, IsSafe); + if (!Inserted) + It->second = It->second && IsSafe; + } } } if (StackSafetyPrint) @@ -886,6 +912,15 @@ return Info.SafeAllocas.count(&AI); } +bool StackSafetyGlobalInfo::accessIsSafe(const Instruction *I) const { + const auto &Info = getInfo(); + auto It = Info.Accesses.find(I); + if (It != Info.Accesses.end()) { + return It->second; + } + return false; +} + void StackSafetyGlobalInfo::print(raw_ostream &O) const { auto &SSI = getInfo().Info; if (SSI.empty()) diff --git a/llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp --- a/llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp +++ b/llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp @@ -287,7 +287,7 @@ Instruction *InsertBefore); void instrumentMemIntrinsic(MemIntrinsic *MI); bool instrumentMemAccess(InterestingMemoryOperand &O); - bool ignoreAccess(Value *Ptr); + bool ignoreAccess(Instruction *Inst, Value *Ptr); void getInterestingMemoryOperands( Instruction *I, SmallVectorImpl &Interesting); @@ -768,7 +768,7 @@ } } -bool HWAddressSanitizer::ignoreAccess(Value *Ptr) { +bool HWAddressSanitizer::ignoreAccess(Instruction *Inst, Value *Ptr) { // Do not instrument acesses from different address spaces; we cannot deal // with them. Type *PtrTy = cast(Ptr->getType()->getScalarType()); @@ -782,6 +782,13 @@ if (Ptr->isSwiftError()) return true; + if (!InstrumentStack) { + const AllocaInst *AI = findAllocaForValue(Ptr); + if (AI) + return true; + } else if (SSI && SSI->accessIsSafe(Inst)) { + return true; + } return false; } @@ -796,29 +803,29 @@ return; if (LoadInst *LI = dyn_cast(I)) { - if (!ClInstrumentReads || ignoreAccess(LI->getPointerOperand())) + if (!ClInstrumentReads || ignoreAccess(I, LI->getPointerOperand())) return; Interesting.emplace_back(I, LI->getPointerOperandIndex(), false, LI->getType(), LI->getAlign()); } else if (StoreInst *SI = dyn_cast(I)) { - if (!ClInstrumentWrites || ignoreAccess(SI->getPointerOperand())) + if (!ClInstrumentWrites || ignoreAccess(I, SI->getPointerOperand())) return; Interesting.emplace_back(I, SI->getPointerOperandIndex(), true, SI->getValueOperand()->getType(), SI->getAlign()); } else if (AtomicRMWInst *RMW = dyn_cast(I)) { - if (!ClInstrumentAtomics || ignoreAccess(RMW->getPointerOperand())) + if (!ClInstrumentAtomics || ignoreAccess(I, RMW->getPointerOperand())) return; Interesting.emplace_back(I, RMW->getPointerOperandIndex(), true, RMW->getValOperand()->getType(), None); } else if (AtomicCmpXchgInst *XCHG = dyn_cast(I)) { - if (!ClInstrumentAtomics || ignoreAccess(XCHG->getPointerOperand())) + if (!ClInstrumentAtomics || ignoreAccess(I, XCHG->getPointerOperand())) return; Interesting.emplace_back(I, XCHG->getPointerOperandIndex(), true, XCHG->getCompareOperand()->getType(), None); } else if (auto CI = dyn_cast(I)) { for (unsigned ArgNo = 0; ArgNo < CI->getNumArgOperands(); ArgNo++) { if (!ClInstrumentByval || !CI->isByValArgument(ArgNo) || - ignoreAccess(CI->getArgOperand(ArgNo))) + ignoreAccess(I, CI->getArgOperand(ArgNo))) continue; Type *Ty = CI->getParamByValType(ArgNo); Interesting.emplace_back(I, ArgNo, false, Ty, Align(1)); diff --git a/llvm/test/Instrumentation/HWAddressSanitizer/memaccess-clobber.ll b/llvm/test/Instrumentation/HWAddressSanitizer/memaccess-clobber.ll --- a/llvm/test/Instrumentation/HWAddressSanitizer/memaccess-clobber.ll +++ b/llvm/test/Instrumentation/HWAddressSanitizer/memaccess-clobber.ll @@ -1,6 +1,6 @@ ; Make sure memaccess checks preceed the following reads. ; -; RUN: opt < %s -S -enable-new-pm=0 -hwasan -basic-aa -memdep -print-memdeps -analyze -mtriple aarch64-linux-android30 | FileCheck %s +; RUN: opt < %s -S -enable-new-pm=0 -hwasan -hwasan-use-stack-safety=0 -basic-aa -memdep -print-memdeps -analyze -mtriple aarch64-linux-android30 | FileCheck %s target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128" target triple = "aarch64--linux-android10000" diff --git a/llvm/test/Instrumentation/HWAddressSanitizer/stack-safety-analysis.ll b/llvm/test/Instrumentation/HWAddressSanitizer/stack-safety-analysis.ll --- a/llvm/test/Instrumentation/HWAddressSanitizer/stack-safety-analysis.ll +++ b/llvm/test/Instrumentation/HWAddressSanitizer/stack-safety-analysis.ll @@ -1,15 +1,21 @@ -; RUN: opt -hwasan -hwasan-use-stack-safety=1 -hwasan-generate-tags-with-calls -S < %s | FileCheck %s --check-prefixes=SAFETY -; RUN: opt -hwasan -hwasan-use-stack-safety=0 -hwasan-generate-tags-with-calls -S < %s | FileCheck %s --check-prefixes=NOSAFETY -; RUN: opt -hwasan -hwasan-generate-tags-with-calls -S < %s | FileCheck %s --check-prefixes=SAFETY +; RUN: opt -hwasan -hwasan-instrument-with-calls -hwasan-use-stack-safety=1 -hwasan-generate-tags-with-calls -S < %s | FileCheck %s --check-prefixes=SAFETY,CHECK +; RUN: opt -hwasan -hwasan-instrument-with-calls -hwasan-use-stack-safety=0 -hwasan-generate-tags-with-calls -S < %s | FileCheck %s --check-prefixes=NOSAFETY,CHECK +; RUN: opt -hwasan -hwasan-instrument-with-calls -hwasan-generate-tags-with-calls -S < %s | FileCheck %s --check-prefixes=SAFETY,CHECK +; RUN: opt -hwasan -hwasan-instrument-stack=0 -hwasan-instrument-with-calls -hwasan-generate-tags-with-calls -S < %s | FileCheck %s --check-prefixes=NOSTACK,CHECK target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128" target triple = "aarch64-unknown-linux-gnu" ; Check a safe alloca to ensure it does not get a tag. -define i32 @test_load(i32* %a) sanitize_hwaddress { +define i32 @test_simple(i32* %a) sanitize_hwaddress { entry: + ; CHECK-LABEL: @test_simple ; NOSAFETY: call {{.*}}__hwasan_generate_tag + ; NOSAFETY: call {{.*}}__hwasan_store ; SAFETY-NOT: call {{.*}}__hwasan_generate_tag + ; SAFETY-NOT: call {{.*}}__hwasan_store + ; NOSTACK-NOT: call {{.*}}__hwasan_generate_tag + ; NOSTACK-NOT: call {{.*}}__hwasan_store %buf.sroa.0 = alloca i8, align 4 call void @llvm.lifetime.start.p0i8(i64 1, i8* nonnull %buf.sroa.0) store volatile i8 0, i8* %buf.sroa.0, align 4, !tbaa !8 @@ -20,8 +26,13 @@ ; Check a non-safe alloca to ensure it gets a tag. define i32 @test_use(i32* %a) sanitize_hwaddress { entry: + ; CHECK-LABEL: @test_use ; NOSAFETY: call {{.*}}__hwasan_generate_tag + ; NOSAFETY: call {{.*}}__hwasan_store ; SAFETY: call {{.*}}__hwasan_generate_tag + ; SAFETY-NOT: call {{.*}}__hwasan_store + ; NOSTACK-NOT: call {{.*}}__hwasan_generate_tag + ; NOSTACK-NOT: call {{.*}}__hwasan_store %buf.sroa.0 = alloca i8, align 4 call void @use(i8* nonnull %buf.sroa.0) call void @llvm.lifetime.start.p0i8(i64 1, i8* nonnull %buf.sroa.0) @@ -30,6 +41,101 @@ ret i32 0 } +; Check an alloca with in range GEP to ensure it does not get a tag or check. +define i32 @test_in_range(i32* %a) sanitize_hwaddress { +entry: + ; CHECK-LABEL: @test_in_range + ; NOSAFETY: call {{.*}}__hwasan_generate_tag + ; NOSAFETY: call {{.*}}__hwasan_store + ; SAFETY-NOT: call {{.*}}__hwasan_generate_tag + ; SAFETY-NOT: call {{.*}}__hwasan_store + ; NOSTACK-NOT: call {{.*}}__hwasan_generate_tag + ; NOSTACK-NOT: call {{.*}}__hwasan_store + %buf.sroa.0 = alloca [10 x i8], align 4 + %ptr = getelementptr [10 x i8], [10 x i8]* %buf.sroa.0, i32 0, i32 0 + call void @llvm.lifetime.start.p0i8(i64 10, i8* nonnull %ptr) + store volatile i8 0, i8* %ptr, align 4, !tbaa !8 + call void @llvm.lifetime.end.p0i8(i64 10, i8* nonnull %ptr) + ret i32 0 +} + +; Check an alloca with in range GEP to ensure it does not get a tag or check. +define i32 @test_in_range2(i32* %a) sanitize_hwaddress { +entry: + ; CHECK-LABEL: @test_in_range2 + ; NOSAFETY: call {{.*}}__hwasan_generate_tag + ; NOSAFETY: call {{.*}}__hwasan_store + ; SAFETY-NOT: call {{.*}}__hwasan_generate_tag + ; SAFETY-NOT: call {{.*}}__hwasan_store + ; NOSTACK-NOT: call {{.*}}__hwasan_generate_tag + ; NOSTACK-NOT: call {{.*}}__hwasan_store + %buf.sroa.0 = alloca [10 x i8], align 4 + %ptr = getelementptr [10 x i8], [10 x i8]* %buf.sroa.0, i32 0, i32 9 + %x = bitcast [10 x i8]* %buf.sroa.0 to i8* + call void @llvm.lifetime.start.p0i8(i64 10, i8* nonnull %x) + store volatile i8 0, i8* %ptr, align 4, !tbaa !8 + call void @llvm.lifetime.end.p0i8(i64 10, i8* nonnull %x) + ret i32 0 +} + +; Check an alloca with out of range GEP to ensure it gets a tag and check. +define i32 @test_out_of_range(i32* %a) sanitize_hwaddress { +entry: + ; CHECK-LABEL: @test_out_of_range + ; NOSAFETY: call {{.*}}__hwasan_generate_tag + ; NOSAFETY: call {{.*}}__hwasan_store + ; SAFETY: call {{.*}}__hwasan_generate_tag + ; SAFETY: call {{.*}}__hwasan_store + ; NOSTACK-NOT: call {{.*}}__hwasan_generate_tag + ; NOSTACK-NOT: call {{.*}}__hwasan_store + %buf.sroa.0 = alloca [10 x i8], align 4 + %ptr = getelementptr [10 x i8], [10 x i8]* %buf.sroa.0, i32 0, i32 10 + %x = bitcast [10 x i8]* %buf.sroa.0 to i8* + call void @llvm.lifetime.start.p0i8(i64 10, i8* nonnull %x) + store volatile i8 0, i8* %ptr, align 4, !tbaa !8 + call void @llvm.lifetime.end.p0i8(i64 10, i8* nonnull %x) + ret i32 0 +} + +; Check an alloca with potentially out of range GEP to ensure it gets a tag and +; check. +define i32 @test_potentially_out_of_range(i32* %a) sanitize_hwaddress { +entry: + ; CHECK-LABEL: @test_potentially_out_of_range + ; NOSAFETY: call {{.*}}__hwasan_generate_tag + ; NOSAFETY: call {{.*}}__hwasan_store + ; SAFETY: call {{.*}}__hwasan_generate_tag + ; SAFETY: call {{.*}}__hwasan_store + ; NOSTACK-NOT: call {{.*}}__hwasan_generate_tag + ; NOSTACK-NOT: call {{.*}}__hwasan_store + %buf.sroa.0 = alloca [10 x i8], align 4 + %off = call i32 @getoffset() + %ptr = getelementptr [10 x i8], [10 x i8]* %buf.sroa.0, i32 0, i32 %off + call void @llvm.lifetime.start.p0i8(i64 10, i8* nonnull %ptr) + store volatile i8 0, i8* %ptr, align 4, !tbaa !8 + call void @llvm.lifetime.end.p0i8(i64 10, i8* nonnull %ptr) + ret i32 0 +} + +; Check an alloca with potentially out of range GEP to ensure it gets a tag and +; check. +define i32 @test_unclear(i32* %a) sanitize_hwaddress { +entry: + ; CHECK-LABEL: @test_unclear + ; NOSAFETY: call {{.*}}__hwasan_generate_tag + ; NOSAFETY: call {{.*}}__hwasan_store + ; SAFETY: call {{.*}}__hwasan_generate_tag + ; SAFETY: call {{.*}}__hwasan_store + ; NOSTACK-NOT: call {{.*}}__hwasan_generate_tag + ; NOSTACK: call {{.*}}__hwasan_store + %buf.sroa.0 = alloca i8, align 4 + %ptr = call i8* @getptr(i8* %buf.sroa.0) + call void @llvm.lifetime.start.p0i8(i64 10, i8* nonnull %ptr) + store volatile i8 0, i8* %ptr, align 4, !tbaa !8 + call void @llvm.lifetime.end.p0i8(i64 10, i8* nonnull %ptr) + ret i32 0 +} + ; Function Attrs: argmemonly mustprogress nofree nosync nounwind willreturn declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) @@ -37,6 +143,8 @@ declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) declare void @use(i8* nocapture) +declare i32 @getoffset() +declare i8* @getptr(i8* nocapture) !8 = !{!9, !9, i64 0} !9 = !{!"omnipotent char", !10, i64 0}