Skip to content

Commit 18531c2

Browse files
committed
[AddressSanitizer] Remove memory effects from functions
If left as-is, subsequent optimizations might utilize the possible memory effects and optimize-out the instrumentation. Think of the following case: ``` store i8 4, ptr %shadow call void @llvm.lifetime.start.p0(i64 4, ptr %local) %28 = call void @foo(ptr %local) store i8 -8, ptr %shadow call void @llvm.lifetime.end.p0(i64 4, ptr %local) ``` where `foo` is an external function with `memory(argmem: write)`. A pass such as DeadStoreElimination is allowed to remove the initial store, which might fail sanitizer checks within `foo`.
1 parent 6ebb065 commit 18531c2

File tree

5 files changed

+81
-47
lines changed

5 files changed

+81
-47
lines changed

llvm/include/llvm/Transforms/Instrumentation/AddressSanitizerCommon.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ void getAddressSanitizerParams(const Triple &TargetTriple, int LongSize,
5858
bool IsKasan, uint64_t *ShadowBase,
5959
int *MappingScale, bool *OrShadowOffset);
6060

61+
/// Remove memory attributes that are incompatible with the instrumentation
62+
/// added by AddressSanitizer and HWSanitizer. See more details in the
63+
/// implementation.
64+
/// \p ReadsArgMem - whether to remove memory attributes from function
65+
/// arguments as well.
66+
void removeASanIncompatibleFnAttributes(Function &F, bool ReadsArgMem);
67+
6168
} // namespace llvm
6269

6370
#endif

llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,48 @@ void getAddressSanitizerParams(const Triple &TargetTriple, int LongSize,
612612
*OrShadowOffset = Mapping.OrShadowOffset;
613613
}
614614

615+
void removeASanIncompatibleFnAttributes(Function &F, bool ReadsArgMem) {
616+
// Remove memory attributes that are invalid with ASan and HWSan.
617+
// ASan checks read from shadow, which invalidates memory(argmem: *)
618+
// Short granule checks on function arguments read from the argument memory
619+
// (last byte of the granule), which invalidates writeonly.
620+
//
621+
// This is not only true for sanitized functions, because AttrInfer can
622+
// infer those attributes on libc functions, which is not true if those
623+
// are instrumented (Android) or intercepted.
624+
//
625+
// We might want to model ASan shadow memory more opaquely to get rid of
626+
// this problem altogether, by hiding the shadow memory write in an
627+
// intrinsic, essentially like in the AArch64StackTagging pass. But that's
628+
// for another day.
629+
630+
// The API is weird. `onlyReadsMemory` actually means "does not write", and
631+
// `onlyWritesMemory` actually means "does not read". So we reconstruct
632+
// "accesses memory" && "does not read" <=> "writes".
633+
bool Changed = false;
634+
if (!F.doesNotAccessMemory()) {
635+
bool WritesMemory = !F.onlyReadsMemory();
636+
bool ReadsMemory = !F.onlyWritesMemory();
637+
if ((WritesMemory && !ReadsMemory) || F.onlyAccessesArgMemory()) {
638+
F.removeFnAttr(Attribute::Memory);
639+
Changed = true;
640+
}
641+
}
642+
if (ReadsArgMem) {
643+
for (Argument &A : F.args()) {
644+
if (A.hasAttribute(Attribute::WriteOnly)) {
645+
A.removeAttr(Attribute::WriteOnly);
646+
Changed = true;
647+
}
648+
}
649+
}
650+
if (Changed) {
651+
// nobuiltin makes sure later passes don't restore assumptions about
652+
// the function.
653+
F.addFnAttr(Attribute::NoBuiltin);
654+
}
655+
}
656+
615657
ASanAccessInfo::ASanAccessInfo(int32_t Packed)
616658
: Packed(Packed),
617659
AccessSizeIndex((Packed >> kAccessSizeIndexShift) & kAccessSizeIndexMask),
@@ -2733,6 +2775,9 @@ GlobalVariable *ModuleAddressSanitizer::getOrCreateModuleName() {
27332775
bool ModuleAddressSanitizer::instrumentModule() {
27342776
initializeCallbacks();
27352777

2778+
for (Function &F : M)
2779+
removeASanIncompatibleFnAttributes(F, /*ReadsArgMem=*/false);
2780+
27362781
// Create a module constructor. A destructor is created lazily because not all
27372782
// platforms, and not all modules need it.
27382783
if (ConstructorKind == AsanCtorKind::Global) {

llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp

Lines changed: 3 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,6 @@ class HWAddressSanitizer {
325325
FunctionAnalysisManager &FAM) const;
326326
void initializeModule();
327327
void createHwasanCtorComdat();
328-
void removeFnAttributes(Function *F);
329328

330329
void initializeCallbacks(Module &M);
331330

@@ -620,56 +619,13 @@ void HWAddressSanitizer::createHwasanCtorComdat() {
620619
appendToCompilerUsed(M, Dummy);
621620
}
622621

623-
void HWAddressSanitizer::removeFnAttributes(Function *F) {
624-
// Remove memory attributes that are invalid with HWASan.
625-
// HWASan checks read from shadow, which invalidates memory(argmem: *)
626-
// Short granule checks on function arguments read from the argument memory
627-
// (last byte of the granule), which invalidates writeonly.
628-
//
629-
// This is not only true for sanitized functions, because AttrInfer can
630-
// infer those attributes on libc functions, which is not true if those
631-
// are instrumented (Android) or intercepted.
632-
//
633-
// We might want to model HWASan shadow memory more opaquely to get rid of
634-
// this problem altogether, by hiding the shadow memory write in an
635-
// intrinsic, essentially like in the AArch64StackTagging pass. But that's
636-
// for another day.
637-
638-
// The API is weird. `onlyReadsMemory` actually means "does not write", and
639-
// `onlyWritesMemory` actually means "does not read". So we reconstruct
640-
// "accesses memory" && "does not read" <=> "writes".
641-
bool Changed = false;
642-
if (!F->doesNotAccessMemory()) {
643-
bool WritesMemory = !F->onlyReadsMemory();
644-
bool ReadsMemory = !F->onlyWritesMemory();
645-
if ((WritesMemory && !ReadsMemory) || F->onlyAccessesArgMemory()) {
646-
F->removeFnAttr(Attribute::Memory);
647-
Changed = true;
648-
}
649-
}
650-
for (Argument &A : F->args()) {
651-
if (A.hasAttribute(Attribute::WriteOnly)) {
652-
A.removeAttr(Attribute::WriteOnly);
653-
Changed = true;
654-
}
655-
}
656-
if (Changed) {
657-
// nobuiltin makes sure later passes don't restore assumptions about
658-
// the function.
659-
F->addFnAttr(Attribute::NoBuiltin);
660-
}
661-
}
662-
663622
/// Module-level initialization.
664623
///
665624
/// inserts a call to __hwasan_init to the module's constructor list.
666625
void HWAddressSanitizer::initializeModule() {
667626
LLVM_DEBUG(dbgs() << "Init " << M.getName() << "\n");
668627
TargetTriple = M.getTargetTriple();
669628

670-
for (Function &F : M.functions())
671-
removeFnAttributes(&F);
672-
673629
// x86_64 currently has two modes:
674630
// - Intel LAM (default)
675631
// - pointer aliasing (heap only)
@@ -718,6 +674,9 @@ void HWAddressSanitizer::initializeModule() {
718674
InstrumentGlobals =
719675
!CompileKernel && !UsePageAliases && optOr(ClGlobals, NewRuntime);
720676

677+
for (Function &F : M.functions())
678+
removeASanIncompatibleFnAttributes(F, /*ReadsArgMem=*/UseShortGranules);
679+
721680
if (!CompileKernel) {
722681
createHwasanCtorComdat();
723682

llvm/test/Instrumentation/AddressSanitizer/AMDGPU/asan_instrument_mem_intrinsics.ll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ declare void @llvm.memset.p5.i32(ptr addrspace(5) nocapture writeonly, i8, i32,
186186

187187
define void @test_mem_intrinsic_memcpy(ptr %dest0,ptr %src0,ptr addrspace(1) %dest1,ptr addrspace(1) %src1,ptr addrspace(2) %dest2,ptr addrspace(2) %src2,ptr addrspace(3) %dest3,ptr addrspace(3) %src3,ptr addrspace(4) %dest4,ptr addrspace(4) %src4,ptr addrspace(5) %dest5,ptr addrspace(5) %src5) #0 {
188188
entry:
189-
;CHECK: define void @test_mem_intrinsic_memcpy(ptr [[DEST0:%.*]], ptr [[SRC0:%.*]], ptr addrspace(1) [[DEST1:%.*]], ptr addrspace(1) [[SRC1:%.*]], ptr addrspace(2) [[DEST2:%.*]], ptr addrspace(2) [[SRC2:%.*]], ptr addrspace(3) [[DEST3:%.*]], ptr addrspace(3) [[SRC3:%.*]], ptr addrspace(4) [[DEST4:%.*]], ptr addrspace(4) [[SRC4:%.*]], ptr addrspace(5) [[DEST5:%.*]], ptr addrspace(5) [[SRC5:%.*]]) #2 {
189+
;CHECK: define void @test_mem_intrinsic_memcpy(ptr [[DEST0:%.*]], ptr [[SRC0:%.*]], ptr addrspace(1) [[DEST1:%.*]], ptr addrspace(1) [[SRC1:%.*]], ptr addrspace(2) [[DEST2:%.*]], ptr addrspace(2) [[SRC2:%.*]], ptr addrspace(3) [[DEST3:%.*]], ptr addrspace(3) [[SRC3:%.*]], ptr addrspace(4) [[DEST4:%.*]], ptr addrspace(4) [[SRC4:%.*]], ptr addrspace(5) [[DEST5:%.*]], ptr addrspace(5) [[SRC5:%.*]]){{.*}} {
190190
;CHECK-NEXT: entry:
191191
;CHECK-NEXT: [[VR0:%.*]] = call ptr @__asan_memcpy(ptr [[DEST0]], ptr [[SRC0]], i64 64)
192192
;CHECK-NEXT: [[VR1:%.*]] = addrspacecast ptr addrspace(1) [[SRC1]] to ptr
@@ -469,7 +469,7 @@ entry:
469469

470470
define void @test_mem_intrinsic_memmove(ptr %dest0,ptr %src0,ptr addrspace(1) %dest1,ptr addrspace(1) %src1,ptr addrspace(2) %dest2,ptr addrspace(2) %src2,ptr addrspace(3) %dest3,ptr addrspace(3) %src3,ptr addrspace(4) %dest4,ptr addrspace(4) %src4,ptr addrspace(5) %dest5,ptr addrspace(5) %src5) #0 {
471471
entry:
472-
;CHECK: define void @test_mem_intrinsic_memmove(ptr [[DEST0:%.*]], ptr [[SRC0:%.*]], ptr addrspace(1) [[DEST1:%.*]], ptr addrspace(1) [[SRC1:%.*]], ptr addrspace(2) [[DEST2:%.*]], ptr addrspace(2) [[SRC2:%.*]], ptr addrspace(3) [[DEST3:%.*]], ptr addrspace(3) [[SRC3:%.*]], ptr addrspace(4) [[DEST4:%.*]], ptr addrspace(4) [[SRC4:%.*]], ptr addrspace(5) [[DEST5:%.*]], ptr addrspace(5) [[SRC5:%.*]]) #2 {
472+
;CHECK: define void @test_mem_intrinsic_memmove(ptr [[DEST0:%.*]], ptr [[SRC0:%.*]], ptr addrspace(1) [[DEST1:%.*]], ptr addrspace(1) [[SRC1:%.*]], ptr addrspace(2) [[DEST2:%.*]], ptr addrspace(2) [[SRC2:%.*]], ptr addrspace(3) [[DEST3:%.*]], ptr addrspace(3) [[SRC3:%.*]], ptr addrspace(4) [[DEST4:%.*]], ptr addrspace(4) [[SRC4:%.*]], ptr addrspace(5) [[DEST5:%.*]], ptr addrspace(5) [[SRC5:%.*]]){{.*}} {
473473
;CHECK-NEXT: entry:
474474
;CHECK-NEXT: [[VR0:%.*]] = call ptr @__asan_memmove(ptr [[DEST0]], ptr [[SRC0]], i64 64)
475475
;CHECK-NEXT: [[VR1:%.*]] = addrspacecast ptr addrspace(1) [[SRC1]] to ptr
@@ -753,7 +753,7 @@ entry:
753753

754754
define void @test_mem_intrinsic_memset(ptr %ptr0,ptr addrspace(1) %ptr1,ptr addrspace(2) %ptr2,ptr addrspace(3) %ptr3,ptr addrspace(4) %ptr4,ptr addrspace(5) %ptr5) #0{
755755
entry:
756-
;CHECK: define void @test_mem_intrinsic_memset(ptr [[PTR0:%.*]], ptr addrspace(1) [[PTR1:%.*]], ptr addrspace(2) [[PTR2:%.*]], ptr addrspace(3) [[PTR3:%.*]], ptr addrspace(4) [[PTR4:%.*]], ptr addrspace(5) [[PTR5:%.*]]) #2 {
756+
;CHECK: define void @test_mem_intrinsic_memset(ptr [[PTR0:%.*]], ptr addrspace(1) [[PTR1:%.*]], ptr addrspace(2) [[PTR2:%.*]], ptr addrspace(3) [[PTR3:%.*]], ptr addrspace(4) [[PTR4:%.*]], ptr addrspace(5) [[PTR5:%.*]]){{.*}} {
757757
;CHECK-NEXT: entry:
758758
;CHECK-NEXT: [[VR0:%.*]] = call ptr @__asan_memset(ptr [[PTR0]], i32 1, i64 128)
759759
;CHECK-NEXT: [[VR1:%.*]] = addrspacecast ptr addrspace(1) [[PTR1]] to ptr
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
; Remove possible memory effects from functions that are invalidated by
2+
; AddressSanitizer instrumentation.
3+
4+
; RUN: opt -passes='asan<use-after-scope>' -S %s | FileCheck %s
5+
6+
target datalayout = "e-i64:64-f80:128-s:64-n8:16:32:64-S128"
7+
target triple = "x86_64-unknown-linux-gnu"
8+
9+
; CHECK: @foo(ptr writeonly) #[[ATTRS_FOO:[0-9]+]]
10+
declare void @foo(ptr writeonly) memory(argmem: write)
11+
12+
; CHECK: @bar() #[[ATTRS_BAR:[0-9]+]]
13+
define void @bar() sanitize_address {
14+
entry:
15+
%x = alloca i32, align 4
16+
call void @llvm.lifetime.start.p0(i64 4, ptr %x)
17+
call void @foo(ptr %x)
18+
call void @llvm.lifetime.end.p0(i64 4, ptr %x)
19+
ret void
20+
}
21+
22+
; CHECK: attributes #[[ATTRS_FOO]] = { nobuiltin }
23+
; CHECK: attributes #[[ATTRS_BAR]] = { sanitize_address }

0 commit comments

Comments
 (0)