[compiler-rt][asan] Add asan checks for __builtin_assume_dereferencable#190871
[compiler-rt][asan] Add asan checks for __builtin_assume_dereferencable#190871
Conversation
This checks that the range covered by this intrinsic is dereferencable. Specifically it checks for `llvm.assume` intrinsics using the `dereferencable` operator bundle and just asserts the shadow for this range is zero. The bulk of this PR was made by gemini but it was thoroughly edited and reviewed to the best of my ability.
|
In hindsight it probably would've been better to make an RFC but I don't think I wasted a lot of time making this. |
|
@llvm/pr-subscribers-llvm-transforms Author: PiJoules ChangesThis checks that the range covered by this intrinsic is dereferencable. Specifically it checks for The bulk of this PR was made by gemini but it was thoroughly edited and reviewed to the best of my ability. Full diff: https://github.com/llvm/llvm-project/pull/190871.diff 13 Files Affected:
diff --git a/compiler-rt/lib/asan/AIX/asan.link_with_main_exec.txt b/compiler-rt/lib/asan/AIX/asan.link_with_main_exec.txt
index 5efc48c262369..d3b8e98bd020f 100644
--- a/compiler-rt/lib/asan/AIX/asan.link_with_main_exec.txt
+++ b/compiler-rt/lib/asan/AIX/asan.link_with_main_exec.txt
@@ -1,5 +1,7 @@
#! .
__asan_report_load_n
+__asan_report_assume_dereferenceable
+__asan_report_assume_dereferenceable_noabort
__asan_loadN
__asan_report_load1
__asan_load1
diff --git a/compiler-rt/lib/asan/asan_errors.cpp b/compiler-rt/lib/asan/asan_errors.cpp
index c4100cf7244e3..91941fb18dafb 100644
--- a/compiler-rt/lib/asan/asan_errors.cpp
+++ b/compiler-rt/lib/asan/asan_errors.cpp
@@ -713,4 +713,59 @@ void ErrorGeneric::Print() {
CheckPoisonRecords(addr);
}
+ErrorAssumeDereferenceable::ErrorAssumeDereferenceable(u32 tid, uptr pc,
+ uptr bp, uptr sp,
+ uptr addr,
+ uptr dereferencable_size)
+ : ErrorBase(tid),
+ addr_description(addr, dereferencable_size,
+ /*shouldLockThreadRegistry=*/false),
+ pc(pc),
+ bp(bp),
+ sp(sp),
+ dereferencable_size(dereferencable_size) {
+ scariness.Clear();
+ scariness.Scare(10, "assume-dereferenceable");
+}
+
+void ErrorAssumeDereferenceable::Print() {
+ Decorator d;
+ Printf("%s", d.Error());
+ uptr addr = addr_description.Address();
+ Report(
+ "ERROR: AddressSanitizer: assume-dereferenceable-failed on address %p at "
+ "pc %p bp %p sp %p\n",
+ (void*)addr, (void*)pc, (void*)bp, (void*)sp);
+ Printf("%s", d.Default());
+
+ Printf("%sASSUMPTION of size %zu at %p thread %s%s\n", d.Access(),
+ dereferencable_size, (void*)addr, AsanThreadIdAndName(tid).c_str(),
+ d.Default());
+
+ uptr range_start = addr;
+ bool current_poisoned = AddressIsPoisoned(range_start);
+ for (uptr i = 1; i <= dereferencable_size; ++i) {
+ bool poisoned = (i < dereferencable_size) ? AddressIsPoisoned(addr + i)
+ : !current_poisoned;
+ if (poisoned != current_poisoned) {
+ Printf("%s range [%p, %p) is %s%s\n", d.Default(), (void*)range_start,
+ (void*)(addr + i),
+ current_poisoned ? "NOT dereferenceable" : "dereferenceable",
+ d.Default());
+ if (i < dereferencable_size) {
+ range_start = addr + i;
+ current_poisoned = poisoned;
+ }
+ }
+ }
+
+ scariness.Print();
+ GET_STACK_TRACE_FATAL(pc, bp);
+ stack.Print();
+
+ addr_description.Print("assume-dereferenceable-failed");
+ ReportErrorSummary("assume-dereferenceable-failed", &stack);
+ PrintShadowMemoryForAddress(addr);
+}
+
} // namespace __asan
diff --git a/compiler-rt/lib/asan/asan_errors.h b/compiler-rt/lib/asan/asan_errors.h
index 9c6843774f376..660250246f91b 100644
--- a/compiler-rt/lib/asan/asan_errors.h
+++ b/compiler-rt/lib/asan/asan_errors.h
@@ -436,6 +436,17 @@ struct ErrorGeneric : ErrorBase {
void Print();
};
+struct ErrorAssumeDereferenceable : ErrorBase {
+ AddressDescription addr_description;
+ uptr pc, bp, sp;
+ uptr dereferencable_size;
+
+ ErrorAssumeDereferenceable() = default; // (*)
+ ErrorAssumeDereferenceable(u32 tid, uptr pc, uptr bp, uptr sp, uptr addr,
+ uptr dereferencable_size);
+ void Print();
+};
+
// clang-format off
#define ASAN_FOR_EACH_ERROR_KIND(macro) \
macro(DeadlySignal) \
@@ -462,6 +473,7 @@ struct ErrorGeneric : ErrorBase {
macro(BadParamsToCopyContiguousContainerAnnotations) \
macro(ODRViolation) \
macro(InvalidPointerPair) \
+ macro(AssumeDereferenceable) \
macro(Generic)
// clang-format on
diff --git a/compiler-rt/lib/asan/asan_interface.inc b/compiler-rt/lib/asan/asan_interface.inc
index b465356b1e443..3c794376fa959 100644
--- a/compiler-rt/lib/asan/asan_interface.inc
+++ b/compiler-rt/lib/asan/asan_interface.inc
@@ -93,6 +93,8 @@ INTERFACE_FUNCTION(__asan_report_load8_noabort)
INTERFACE_FUNCTION(__asan_report_load16_noabort)
INTERFACE_FUNCTION(__asan_report_load_n_noabort)
INTERFACE_FUNCTION(__asan_report_present)
+INTERFACE_FUNCTION(__asan_report_assume_dereferenceable)
+INTERFACE_FUNCTION(__asan_report_assume_dereferenceable_noabort)
INTERFACE_FUNCTION(__asan_report_store1)
INTERFACE_FUNCTION(__asan_report_store2)
INTERFACE_FUNCTION(__asan_report_store4)
diff --git a/compiler-rt/lib/asan/asan_report.cpp b/compiler-rt/lib/asan/asan_report.cpp
index e50faf0223212..fbdada1849d45 100644
--- a/compiler-rt/lib/asan/asan_report.cpp
+++ b/compiler-rt/lib/asan/asan_report.cpp
@@ -543,6 +543,18 @@ void ReportGenericError(uptr pc, uptr bp, uptr sp, uptr addr, bool is_write,
in_report.ReportError(error);
}
+void ReportAssumeDereferenceableError(uptr pc, uptr bp, uptr sp, uptr addr,
+ uptr dereferencable_size, bool fatal) {
+ if (!fatal && SuppressErrorReport(pc))
+ return;
+ ENABLE_FRAME_POINTER;
+
+ ScopedInErrorReport in_report(fatal);
+ ErrorAssumeDereferenceable error(GetCurrentTidOrInvalid(), pc, bp, sp, addr,
+ dereferencable_size);
+ in_report.ReportError(error);
+}
+
} // namespace __asan
// --------------------------- Interface --------------------- {{{1
diff --git a/compiler-rt/lib/asan/asan_report.h b/compiler-rt/lib/asan/asan_report.h
index b181849b10f92..a0d16e50d02fb 100644
--- a/compiler-rt/lib/asan/asan_report.h
+++ b/compiler-rt/lib/asan/asan_report.h
@@ -49,6 +49,8 @@ bool ParseFrameDescription(const char *frame_descr,
// Different kinds of error reports.
void ReportGenericError(uptr pc, uptr bp, uptr sp, uptr addr, bool is_write,
uptr access_size, u32 exp, bool fatal);
+void ReportAssumeDereferenceableError(uptr pc, uptr bp, uptr sp, uptr addr,
+ uptr dereferencable_size, bool fatal);
void ReportDeadlySignal(const SignalContext &sig);
void ReportNewDeleteTypeMismatch(uptr addr, uptr delete_size,
uptr delete_alignment,
diff --git a/compiler-rt/lib/asan/asan_rtl.cpp b/compiler-rt/lib/asan/asan_rtl.cpp
index c036a13a11029..76e6b90b79524 100644
--- a/compiler-rt/lib/asan/asan_rtl.cpp
+++ b/compiler-rt/lib/asan/asan_rtl.cpp
@@ -156,6 +156,22 @@ void __asan_report_ ## type ## _n_noabort(uptr addr, uptr size) { \
ASAN_REPORT_ERROR_N(load, false)
ASAN_REPORT_ERROR_N(store, true)
+extern "C" NOINLINE INTERFACE_ATTRIBUTE
+void __asan_report_assume_dereferenceable(uptr addr, uptr size) {
+ if (__asan_region_is_poisoned(addr, size)) {
+ GET_CALLER_PC_BP_SP;
+ ReportAssumeDereferenceableError(pc, bp, sp, addr, size, true);
+ }
+}
+
+extern "C" NOINLINE INTERFACE_ATTRIBUTE
+void __asan_report_assume_dereferenceable_noabort(uptr addr, uptr size) {
+ if (__asan_region_is_poisoned(addr, size)) {
+ GET_CALLER_PC_BP_SP;
+ ReportAssumeDereferenceableError(pc, bp, sp, addr, size, false);
+ }
+}
+
#define ASAN_MEMORY_ACCESS_CALLBACK_BODY(type, is_write, size, exp_arg, fatal) \
uptr sp = MEM_TO_SHADOW(addr); \
uptr s = size <= ASAN_SHADOW_GRANULARITY ? *reinterpret_cast<u8 *>(sp) \
diff --git a/compiler-rt/test/asan/TestCases/assume_dereferenceable.cpp b/compiler-rt/test/asan/TestCases/assume_dereferenceable.cpp
new file mode 100644
index 0000000000000..f32e6d47c7971
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/assume_dereferenceable.cpp
@@ -0,0 +1,96 @@
+// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-assume-dereferenceable=1 -fsanitize-recover=address %s -o %t && %env_asan_opts=halt_on_error=0 %run %t 2>&1 | FileCheck %s
+
+#include <stdio.h>
+#include <stdlib.h>
+
+void test_malloc_fully_oob() {
+ char *p = (char *)malloc(10);
+ fprintf(stderr, "test_malloc_fully_oob: %p\n", p);
+ // CHECK: test_malloc_fully_oob: [[PTR1:0x[0-9a-f]+]]
+ // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR1]]
+ // CHECK: ASSUMPTION of size 20 at [[PTR1]] thread T0
+ // CHECK-NEXT: range [[[PTR1]], 0x{{.*}}) is dereferenceable
+ // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is NOT dereferenceable
+ __builtin_assume_dereferenceable(p, 20);
+ free(p);
+}
+
+void test_malloc_partial_right() {
+ char *p = (char *)malloc(10);
+ fprintf(stderr, "test_malloc_partial_right: %p\n", p + 5);
+ // CHECK: test_malloc_partial_right: [[PTR2:0x[0-9a-f]+]]
+ // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR2]]
+ // CHECK: ASSUMPTION of size 10 at [[PTR2]] thread T0
+ // CHECK-NEXT: range [[[PTR2]], 0x{{.*}}) is dereferenceable
+ // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is NOT dereferenceable
+ __builtin_assume_dereferenceable(p + 5, 10);
+ free(p);
+}
+
+void test_malloc_partial_left() {
+ char *p = (char *)malloc(10);
+ fprintf(stderr, "test_malloc_partial_left: %p\n", p - 5);
+ // CHECK: test_malloc_partial_left: [[PTR3:0x[0-9a-f]+]]
+ // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR3]]
+ // CHECK: ASSUMPTION of size 10 at [[PTR3]] thread T0
+ // CHECK-NEXT: range [[[PTR3]], 0x{{.*}}) is NOT dereferenceable
+ // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is dereferenceable
+ __builtin_assume_dereferenceable(p - 5, 10);
+ free(p);
+}
+
+void test_stack_fully_oob() {
+ char p[10];
+ fprintf(stderr, "test_stack_fully_oob: %p\n", p);
+ // CHECK: test_stack_fully_oob: [[PTR4:0x[0-9a-f]+]]
+ // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR4]]
+ // CHECK: ASSUMPTION of size 20 at [[PTR4]] thread T0
+ // CHECK-NEXT: range [[[PTR4]], 0x{{.*}}) is dereferenceable
+ // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is NOT dereferenceable
+ __builtin_assume_dereferenceable(p, 20);
+}
+
+void test_stack_partial_right() {
+ char p[10];
+ fprintf(stderr, "test_stack_partial_right: %p\n", p + 5);
+ // CHECK: test_stack_partial_right: [[PTR5:0x[0-9a-f]+]]
+ // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR5]]
+ // CHECK: ASSUMPTION of size 10 at [[PTR5]] thread T0
+ // CHECK-NEXT: range [[[PTR5]], 0x{{.*}}) is dereferenceable
+ // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is NOT dereferenceable
+ __builtin_assume_dereferenceable(p + 5, 10);
+}
+
+void test_stack_partial_left() {
+ char p[10];
+ fprintf(stderr, "test_stack_partial_left: %p\n", p - 5);
+ // CHECK: test_stack_partial_left: [[PTR6:0x[0-9a-f]+]]
+ // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR6]]
+ // CHECK: ASSUMPTION of size 10 at [[PTR6]] thread T0
+ // CHECK-NEXT: range [[[PTR6]], 0x{{.*}}) is NOT dereferenceable
+ // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is dereferenceable
+ __builtin_assume_dereferenceable(p - 5, 10);
+}
+
+void test_malloc_completely_poisoned() {
+ char *p = (char *)malloc(10);
+ free(p);
+ fprintf(stderr, "test_malloc_completely_poisoned: %p\n", p);
+ // CHECK: test_malloc_completely_poisoned: [[PTR7:0x[0-9a-f]+]]
+ // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR7]]
+ // CHECK: ASSUMPTION of size 10 at [[PTR7]] thread T0
+ // CHECK-NEXT: range [[[PTR7]], 0x{{.*}}) is NOT dereferenceable
+ // CHECK-NOT: is dereferenceable
+ __builtin_assume_dereferenceable(p, 10);
+}
+
+int main() {
+ test_malloc_fully_oob();
+ test_malloc_partial_right();
+ test_malloc_partial_left();
+ test_stack_fully_oob();
+ test_stack_partial_right();
+ test_stack_partial_left();
+ test_malloc_completely_poisoned();
+ return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/assume_dereferenceable_fully_poisoned.cpp b/compiler-rt/test/asan/TestCases/assume_dereferenceable_fully_poisoned.cpp
new file mode 100644
index 0000000000000..a1abf837619b1
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/assume_dereferenceable_fully_poisoned.cpp
@@ -0,0 +1,14 @@
+// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-assume-dereferenceable=1 %s -o %t && not %run %t 2>&1 | FileCheck %s
+
+#include <stdlib.h>
+
+int main() {
+ char *p = (char *)malloc(10);
+ free(p);
+ // CHECK: AddressSanitizer: assume-dereferenceable-failed
+ // CHECK: ASSUMPTION of size 10
+ // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is NOT dereferenceable
+ // CHECK-NOT: is dereferenceable
+ __builtin_assume_dereferenceable(p, 10);
+ return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/assume_dereferenceable_halt_on_error.cpp b/compiler-rt/test/asan/TestCases/assume_dereferenceable_halt_on_error.cpp
new file mode 100644
index 0000000000000..86cd92580ce38
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/assume_dereferenceable_halt_on_error.cpp
@@ -0,0 +1,24 @@
+// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-assume-dereferenceable=1 -fsanitize-recover=address %s -o %t
+// RUN: %env_asan_opts=halt_on_error=1 not %run %t 2>&1 | FileCheck %s
+// RUN: %env_asan_opts=halt_on_error=0 %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-RECOVER
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int main() {
+ char *p = (char *)malloc(10);
+ fprintf(stderr, "PTR: %p\n", p);
+ // CHECK: PTR: [[PTR:0x[0-9a-f]+]]
+ // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR]]
+
+ // CHECK-RECOVER: PTR: [[PTR:0x[0-9a-f]+]]
+ // CHECK-RECOVER: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR]]
+ __builtin_assume_dereferenceable(p, 20);
+ free(p);
+
+ fprintf(stderr, "EXECUTED AFTER ERROR\n");
+ // CHECK-NOT: EXECUTED AFTER ERROR
+ // CHECK-RECOVER: EXECUTED AFTER ERROR
+
+ return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/assume_dereferenceable_pass.cpp b/compiler-rt/test/asan/TestCases/assume_dereferenceable_pass.cpp
new file mode 100644
index 0000000000000..9f5a65888df3b
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/assume_dereferenceable_pass.cpp
@@ -0,0 +1,35 @@
+// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-assume-dereferenceable=1 %s -o %t && %run %t
+
+#include <stdlib.h>
+
+void test_pass_1() {
+ char *p = (char *)malloc(20);
+ __builtin_assume_dereferenceable(p, 10);
+ __builtin_assume_dereferenceable(p, 20);
+ free(p);
+}
+
+void test_pass_2() {
+ char *p = (char *)malloc(10);
+ __builtin_assume_dereferenceable(p, 0);
+ free(p);
+}
+
+void test_stack_pass_1() {
+ char p[20];
+ __builtin_assume_dereferenceable(p, 10);
+ __builtin_assume_dereferenceable(p, 20);
+}
+
+void test_stack_pass_2() {
+ char p[10];
+ __builtin_assume_dereferenceable(p, 0);
+}
+
+int main() {
+ test_pass_1();
+ test_pass_2();
+ test_stack_pass_1();
+ test_stack_pass_2();
+ return 0;
+}
diff --git a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
index 8b5969ffb3ca0..4d575b3defecf 100644
--- a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
@@ -299,6 +299,11 @@ static cl::opt<bool> ClRedzoneByvalArgs("asan-redzone-byval-args",
"required)"), cl::Hidden,
cl::init(true));
+static cl::opt<bool> ClInstrumentAssumeDereferenceable(
+ "asan-instrument-assume-dereferenceable",
+ cl::desc("instrument llvm.assume(dereferenceable)"), cl::Hidden,
+ cl::init(true));
+
static cl::opt<bool> ClUseAfterScope("asan-use-after-scope",
cl::desc("Check stack-use-after-scope"),
cl::Hidden, cl::init(false));
@@ -924,6 +929,7 @@ struct AddressSanitizer {
// These arrays is indexed by AccessIsWrite and Experiment.
FunctionCallee AsanErrorCallbackSized[2][2];
FunctionCallee AsanMemoryAccessCallbackSized[2][2];
+ FunctionCallee AsanAssumeDereferenceableCallback;
FunctionCallee AsanMemmove, AsanMemcpy, AsanMemset;
Value *LocalDynamicShadow = nullptr;
@@ -2881,6 +2887,12 @@ bool ModuleAddressSanitizer::instrumentModule() {
void AddressSanitizer::initializeCallbacks(const TargetLibraryInfo *TLI) {
IRBuilder<> IRB(*C);
+
+ const std::string EndingStr = Recover ? "_noabort" : "";
+ AsanAssumeDereferenceableCallback = M.getOrInsertFunction(
+ "__asan_report_assume_dereferenceable" + EndingStr,
+ FunctionType::get(IRB.getVoidTy(), {IntptrTy, IntptrTy}, false));
+
// Create __asan_report* callbacks.
// IsWrite, TypeSize and Exp are encoded in the function name.
for (int Exp = 0; Exp < 2; Exp++) {
@@ -3099,6 +3111,7 @@ bool AddressSanitizer::instrumentFunction(Function &F,
SmallVector<Instruction *, 8> NoReturnCalls;
SmallVector<BasicBlock *, 16> AllBlocks;
SmallVector<Instruction *, 16> PointerComparisonsOrSubtracts;
+ SmallVector<AssumeInst *, 8> DerefAssumptions;
// Fill the set of memory operations to instrument.
for (auto &BB : F) {
@@ -3140,6 +3153,12 @@ bool AddressSanitizer::instrumentFunction(Function &F,
// ok, take it.
IntrinToInstrument.push_back(MI);
NumInsnsPerBB++;
+ } else if (ClInstrumentAssumeDereferenceable && isa<AssumeInst>(&Inst)) {
+ auto *AI = cast<AssumeInst>(&Inst);
+ if (AI->getOperandBundle("dereferenceable")) {
+ DerefAssumptions.push_back(AI);
+ NumInsnsPerBB++;
+ }
} else {
if (auto *CB = dyn_cast<CallBase>(&Inst)) {
// A call inside BB.
@@ -3189,6 +3208,27 @@ bool AddressSanitizer::instrumentFunction(Function &F,
FunctionModified = true;
}
+ for (auto *AI : DerefAssumptions) {
+ if (auto Bundle = AI->getOperandBundle("dereferenceable")) {
+ Value *Ptr = Bundle->Inputs[0];
+ Value *Size = Bundle->Inputs[1];
+
+ InstrumentationIRBuilder IRB(AI);
+ Value *AddrLong = IRB.CreatePointerCast(Ptr, IntptrTy);
+ Value *SizeExt = IRB.CreateZExtOrTrunc(Size, IntptrTy);
+
+ // Skip if size is exactly 0.
+ if (ConstantInt *CI = dyn_cast<ConstantInt>(SizeExt)) {
+ if (CI->isZero())
+ continue;
+ }
+
+ RTCI.createRuntimeCall(IRB, AsanAssumeDereferenceableCallback, {AddrLong, SizeExt});
+
+ FunctionModified = true;
+ }
+ }
+
if (ChangedStack || !NoReturnCalls.empty())
FunctionModified = true;
diff --git a/llvm/test/Instrumentation/AddressSanitizer/assume-dereferenceable.ll b/llvm/test/Instrumentation/AddressSanitizer/assume-dereferenceable.ll
new file mode 100644
index 0000000000000..cb0d69da60639
--- /dev/null
+++ b/llvm/test/Instrumentation/AddressSanitizer/assume-dereferenceable.ll
@@ -0,0 +1,18 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=asan -asan-instrument-assume-dereferenceable=1 -S | FileCheck %s
+
+target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+define void @test(ptr %p, i64 %size) sanitize_address {
+; CHECK-LABEL: @test(
+; CHECK-NEXT: [[TMP17:%.*]] = ptrtoint ptr [[TMP4:%.*]] to i64
+; CHECK-NEXT: call void @__asan_report_assume_dereferenceable(i64 [[TMP17]], i64 [[SIZE:%.*]])
+; CHECK-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[TMP4]], i64 [[SIZE]]) ]
+; CHECK-NEXT: ret void
+;
+ call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %p, i64 %size) ]
+ ret void
+}
+
+declare void @llvm.assume(i1)
|
|
@llvm/pr-subscribers-compiler-rt-sanitizer Author: PiJoules ChangesThis checks that the range covered by this intrinsic is dereferencable. Specifically it checks for The bulk of this PR was made by gemini but it was thoroughly edited and reviewed to the best of my ability. Full diff: https://github.com/llvm/llvm-project/pull/190871.diff 13 Files Affected:
diff --git a/compiler-rt/lib/asan/AIX/asan.link_with_main_exec.txt b/compiler-rt/lib/asan/AIX/asan.link_with_main_exec.txt
index 5efc48c262369..d3b8e98bd020f 100644
--- a/compiler-rt/lib/asan/AIX/asan.link_with_main_exec.txt
+++ b/compiler-rt/lib/asan/AIX/asan.link_with_main_exec.txt
@@ -1,5 +1,7 @@
#! .
__asan_report_load_n
+__asan_report_assume_dereferenceable
+__asan_report_assume_dereferenceable_noabort
__asan_loadN
__asan_report_load1
__asan_load1
diff --git a/compiler-rt/lib/asan/asan_errors.cpp b/compiler-rt/lib/asan/asan_errors.cpp
index c4100cf7244e3..91941fb18dafb 100644
--- a/compiler-rt/lib/asan/asan_errors.cpp
+++ b/compiler-rt/lib/asan/asan_errors.cpp
@@ -713,4 +713,59 @@ void ErrorGeneric::Print() {
CheckPoisonRecords(addr);
}
+ErrorAssumeDereferenceable::ErrorAssumeDereferenceable(u32 tid, uptr pc,
+ uptr bp, uptr sp,
+ uptr addr,
+ uptr dereferencable_size)
+ : ErrorBase(tid),
+ addr_description(addr, dereferencable_size,
+ /*shouldLockThreadRegistry=*/false),
+ pc(pc),
+ bp(bp),
+ sp(sp),
+ dereferencable_size(dereferencable_size) {
+ scariness.Clear();
+ scariness.Scare(10, "assume-dereferenceable");
+}
+
+void ErrorAssumeDereferenceable::Print() {
+ Decorator d;
+ Printf("%s", d.Error());
+ uptr addr = addr_description.Address();
+ Report(
+ "ERROR: AddressSanitizer: assume-dereferenceable-failed on address %p at "
+ "pc %p bp %p sp %p\n",
+ (void*)addr, (void*)pc, (void*)bp, (void*)sp);
+ Printf("%s", d.Default());
+
+ Printf("%sASSUMPTION of size %zu at %p thread %s%s\n", d.Access(),
+ dereferencable_size, (void*)addr, AsanThreadIdAndName(tid).c_str(),
+ d.Default());
+
+ uptr range_start = addr;
+ bool current_poisoned = AddressIsPoisoned(range_start);
+ for (uptr i = 1; i <= dereferencable_size; ++i) {
+ bool poisoned = (i < dereferencable_size) ? AddressIsPoisoned(addr + i)
+ : !current_poisoned;
+ if (poisoned != current_poisoned) {
+ Printf("%s range [%p, %p) is %s%s\n", d.Default(), (void*)range_start,
+ (void*)(addr + i),
+ current_poisoned ? "NOT dereferenceable" : "dereferenceable",
+ d.Default());
+ if (i < dereferencable_size) {
+ range_start = addr + i;
+ current_poisoned = poisoned;
+ }
+ }
+ }
+
+ scariness.Print();
+ GET_STACK_TRACE_FATAL(pc, bp);
+ stack.Print();
+
+ addr_description.Print("assume-dereferenceable-failed");
+ ReportErrorSummary("assume-dereferenceable-failed", &stack);
+ PrintShadowMemoryForAddress(addr);
+}
+
} // namespace __asan
diff --git a/compiler-rt/lib/asan/asan_errors.h b/compiler-rt/lib/asan/asan_errors.h
index 9c6843774f376..660250246f91b 100644
--- a/compiler-rt/lib/asan/asan_errors.h
+++ b/compiler-rt/lib/asan/asan_errors.h
@@ -436,6 +436,17 @@ struct ErrorGeneric : ErrorBase {
void Print();
};
+struct ErrorAssumeDereferenceable : ErrorBase {
+ AddressDescription addr_description;
+ uptr pc, bp, sp;
+ uptr dereferencable_size;
+
+ ErrorAssumeDereferenceable() = default; // (*)
+ ErrorAssumeDereferenceable(u32 tid, uptr pc, uptr bp, uptr sp, uptr addr,
+ uptr dereferencable_size);
+ void Print();
+};
+
// clang-format off
#define ASAN_FOR_EACH_ERROR_KIND(macro) \
macro(DeadlySignal) \
@@ -462,6 +473,7 @@ struct ErrorGeneric : ErrorBase {
macro(BadParamsToCopyContiguousContainerAnnotations) \
macro(ODRViolation) \
macro(InvalidPointerPair) \
+ macro(AssumeDereferenceable) \
macro(Generic)
// clang-format on
diff --git a/compiler-rt/lib/asan/asan_interface.inc b/compiler-rt/lib/asan/asan_interface.inc
index b465356b1e443..3c794376fa959 100644
--- a/compiler-rt/lib/asan/asan_interface.inc
+++ b/compiler-rt/lib/asan/asan_interface.inc
@@ -93,6 +93,8 @@ INTERFACE_FUNCTION(__asan_report_load8_noabort)
INTERFACE_FUNCTION(__asan_report_load16_noabort)
INTERFACE_FUNCTION(__asan_report_load_n_noabort)
INTERFACE_FUNCTION(__asan_report_present)
+INTERFACE_FUNCTION(__asan_report_assume_dereferenceable)
+INTERFACE_FUNCTION(__asan_report_assume_dereferenceable_noabort)
INTERFACE_FUNCTION(__asan_report_store1)
INTERFACE_FUNCTION(__asan_report_store2)
INTERFACE_FUNCTION(__asan_report_store4)
diff --git a/compiler-rt/lib/asan/asan_report.cpp b/compiler-rt/lib/asan/asan_report.cpp
index e50faf0223212..fbdada1849d45 100644
--- a/compiler-rt/lib/asan/asan_report.cpp
+++ b/compiler-rt/lib/asan/asan_report.cpp
@@ -543,6 +543,18 @@ void ReportGenericError(uptr pc, uptr bp, uptr sp, uptr addr, bool is_write,
in_report.ReportError(error);
}
+void ReportAssumeDereferenceableError(uptr pc, uptr bp, uptr sp, uptr addr,
+ uptr dereferencable_size, bool fatal) {
+ if (!fatal && SuppressErrorReport(pc))
+ return;
+ ENABLE_FRAME_POINTER;
+
+ ScopedInErrorReport in_report(fatal);
+ ErrorAssumeDereferenceable error(GetCurrentTidOrInvalid(), pc, bp, sp, addr,
+ dereferencable_size);
+ in_report.ReportError(error);
+}
+
} // namespace __asan
// --------------------------- Interface --------------------- {{{1
diff --git a/compiler-rt/lib/asan/asan_report.h b/compiler-rt/lib/asan/asan_report.h
index b181849b10f92..a0d16e50d02fb 100644
--- a/compiler-rt/lib/asan/asan_report.h
+++ b/compiler-rt/lib/asan/asan_report.h
@@ -49,6 +49,8 @@ bool ParseFrameDescription(const char *frame_descr,
// Different kinds of error reports.
void ReportGenericError(uptr pc, uptr bp, uptr sp, uptr addr, bool is_write,
uptr access_size, u32 exp, bool fatal);
+void ReportAssumeDereferenceableError(uptr pc, uptr bp, uptr sp, uptr addr,
+ uptr dereferencable_size, bool fatal);
void ReportDeadlySignal(const SignalContext &sig);
void ReportNewDeleteTypeMismatch(uptr addr, uptr delete_size,
uptr delete_alignment,
diff --git a/compiler-rt/lib/asan/asan_rtl.cpp b/compiler-rt/lib/asan/asan_rtl.cpp
index c036a13a11029..76e6b90b79524 100644
--- a/compiler-rt/lib/asan/asan_rtl.cpp
+++ b/compiler-rt/lib/asan/asan_rtl.cpp
@@ -156,6 +156,22 @@ void __asan_report_ ## type ## _n_noabort(uptr addr, uptr size) { \
ASAN_REPORT_ERROR_N(load, false)
ASAN_REPORT_ERROR_N(store, true)
+extern "C" NOINLINE INTERFACE_ATTRIBUTE
+void __asan_report_assume_dereferenceable(uptr addr, uptr size) {
+ if (__asan_region_is_poisoned(addr, size)) {
+ GET_CALLER_PC_BP_SP;
+ ReportAssumeDereferenceableError(pc, bp, sp, addr, size, true);
+ }
+}
+
+extern "C" NOINLINE INTERFACE_ATTRIBUTE
+void __asan_report_assume_dereferenceable_noabort(uptr addr, uptr size) {
+ if (__asan_region_is_poisoned(addr, size)) {
+ GET_CALLER_PC_BP_SP;
+ ReportAssumeDereferenceableError(pc, bp, sp, addr, size, false);
+ }
+}
+
#define ASAN_MEMORY_ACCESS_CALLBACK_BODY(type, is_write, size, exp_arg, fatal) \
uptr sp = MEM_TO_SHADOW(addr); \
uptr s = size <= ASAN_SHADOW_GRANULARITY ? *reinterpret_cast<u8 *>(sp) \
diff --git a/compiler-rt/test/asan/TestCases/assume_dereferenceable.cpp b/compiler-rt/test/asan/TestCases/assume_dereferenceable.cpp
new file mode 100644
index 0000000000000..f32e6d47c7971
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/assume_dereferenceable.cpp
@@ -0,0 +1,96 @@
+// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-assume-dereferenceable=1 -fsanitize-recover=address %s -o %t && %env_asan_opts=halt_on_error=0 %run %t 2>&1 | FileCheck %s
+
+#include <stdio.h>
+#include <stdlib.h>
+
+void test_malloc_fully_oob() {
+ char *p = (char *)malloc(10);
+ fprintf(stderr, "test_malloc_fully_oob: %p\n", p);
+ // CHECK: test_malloc_fully_oob: [[PTR1:0x[0-9a-f]+]]
+ // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR1]]
+ // CHECK: ASSUMPTION of size 20 at [[PTR1]] thread T0
+ // CHECK-NEXT: range [[[PTR1]], 0x{{.*}}) is dereferenceable
+ // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is NOT dereferenceable
+ __builtin_assume_dereferenceable(p, 20);
+ free(p);
+}
+
+void test_malloc_partial_right() {
+ char *p = (char *)malloc(10);
+ fprintf(stderr, "test_malloc_partial_right: %p\n", p + 5);
+ // CHECK: test_malloc_partial_right: [[PTR2:0x[0-9a-f]+]]
+ // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR2]]
+ // CHECK: ASSUMPTION of size 10 at [[PTR2]] thread T0
+ // CHECK-NEXT: range [[[PTR2]], 0x{{.*}}) is dereferenceable
+ // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is NOT dereferenceable
+ __builtin_assume_dereferenceable(p + 5, 10);
+ free(p);
+}
+
+void test_malloc_partial_left() {
+ char *p = (char *)malloc(10);
+ fprintf(stderr, "test_malloc_partial_left: %p\n", p - 5);
+ // CHECK: test_malloc_partial_left: [[PTR3:0x[0-9a-f]+]]
+ // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR3]]
+ // CHECK: ASSUMPTION of size 10 at [[PTR3]] thread T0
+ // CHECK-NEXT: range [[[PTR3]], 0x{{.*}}) is NOT dereferenceable
+ // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is dereferenceable
+ __builtin_assume_dereferenceable(p - 5, 10);
+ free(p);
+}
+
+void test_stack_fully_oob() {
+ char p[10];
+ fprintf(stderr, "test_stack_fully_oob: %p\n", p);
+ // CHECK: test_stack_fully_oob: [[PTR4:0x[0-9a-f]+]]
+ // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR4]]
+ // CHECK: ASSUMPTION of size 20 at [[PTR4]] thread T0
+ // CHECK-NEXT: range [[[PTR4]], 0x{{.*}}) is dereferenceable
+ // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is NOT dereferenceable
+ __builtin_assume_dereferenceable(p, 20);
+}
+
+void test_stack_partial_right() {
+ char p[10];
+ fprintf(stderr, "test_stack_partial_right: %p\n", p + 5);
+ // CHECK: test_stack_partial_right: [[PTR5:0x[0-9a-f]+]]
+ // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR5]]
+ // CHECK: ASSUMPTION of size 10 at [[PTR5]] thread T0
+ // CHECK-NEXT: range [[[PTR5]], 0x{{.*}}) is dereferenceable
+ // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is NOT dereferenceable
+ __builtin_assume_dereferenceable(p + 5, 10);
+}
+
+void test_stack_partial_left() {
+ char p[10];
+ fprintf(stderr, "test_stack_partial_left: %p\n", p - 5);
+ // CHECK: test_stack_partial_left: [[PTR6:0x[0-9a-f]+]]
+ // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR6]]
+ // CHECK: ASSUMPTION of size 10 at [[PTR6]] thread T0
+ // CHECK-NEXT: range [[[PTR6]], 0x{{.*}}) is NOT dereferenceable
+ // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is dereferenceable
+ __builtin_assume_dereferenceable(p - 5, 10);
+}
+
+void test_malloc_completely_poisoned() {
+ char *p = (char *)malloc(10);
+ free(p);
+ fprintf(stderr, "test_malloc_completely_poisoned: %p\n", p);
+ // CHECK: test_malloc_completely_poisoned: [[PTR7:0x[0-9a-f]+]]
+ // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR7]]
+ // CHECK: ASSUMPTION of size 10 at [[PTR7]] thread T0
+ // CHECK-NEXT: range [[[PTR7]], 0x{{.*}}) is NOT dereferenceable
+ // CHECK-NOT: is dereferenceable
+ __builtin_assume_dereferenceable(p, 10);
+}
+
+int main() {
+ test_malloc_fully_oob();
+ test_malloc_partial_right();
+ test_malloc_partial_left();
+ test_stack_fully_oob();
+ test_stack_partial_right();
+ test_stack_partial_left();
+ test_malloc_completely_poisoned();
+ return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/assume_dereferenceable_fully_poisoned.cpp b/compiler-rt/test/asan/TestCases/assume_dereferenceable_fully_poisoned.cpp
new file mode 100644
index 0000000000000..a1abf837619b1
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/assume_dereferenceable_fully_poisoned.cpp
@@ -0,0 +1,14 @@
+// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-assume-dereferenceable=1 %s -o %t && not %run %t 2>&1 | FileCheck %s
+
+#include <stdlib.h>
+
+int main() {
+ char *p = (char *)malloc(10);
+ free(p);
+ // CHECK: AddressSanitizer: assume-dereferenceable-failed
+ // CHECK: ASSUMPTION of size 10
+ // CHECK-NEXT: range [0x{{.*}}, 0x{{.*}}) is NOT dereferenceable
+ // CHECK-NOT: is dereferenceable
+ __builtin_assume_dereferenceable(p, 10);
+ return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/assume_dereferenceable_halt_on_error.cpp b/compiler-rt/test/asan/TestCases/assume_dereferenceable_halt_on_error.cpp
new file mode 100644
index 0000000000000..86cd92580ce38
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/assume_dereferenceable_halt_on_error.cpp
@@ -0,0 +1,24 @@
+// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-assume-dereferenceable=1 -fsanitize-recover=address %s -o %t
+// RUN: %env_asan_opts=halt_on_error=1 not %run %t 2>&1 | FileCheck %s
+// RUN: %env_asan_opts=halt_on_error=0 %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-RECOVER
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int main() {
+ char *p = (char *)malloc(10);
+ fprintf(stderr, "PTR: %p\n", p);
+ // CHECK: PTR: [[PTR:0x[0-9a-f]+]]
+ // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR]]
+
+ // CHECK-RECOVER: PTR: [[PTR:0x[0-9a-f]+]]
+ // CHECK-RECOVER: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR]]
+ __builtin_assume_dereferenceable(p, 20);
+ free(p);
+
+ fprintf(stderr, "EXECUTED AFTER ERROR\n");
+ // CHECK-NOT: EXECUTED AFTER ERROR
+ // CHECK-RECOVER: EXECUTED AFTER ERROR
+
+ return 0;
+}
diff --git a/compiler-rt/test/asan/TestCases/assume_dereferenceable_pass.cpp b/compiler-rt/test/asan/TestCases/assume_dereferenceable_pass.cpp
new file mode 100644
index 0000000000000..9f5a65888df3b
--- /dev/null
+++ b/compiler-rt/test/asan/TestCases/assume_dereferenceable_pass.cpp
@@ -0,0 +1,35 @@
+// RUN: %clangxx_asan -O0 -mllvm -asan-instrument-assume-dereferenceable=1 %s -o %t && %run %t
+
+#include <stdlib.h>
+
+void test_pass_1() {
+ char *p = (char *)malloc(20);
+ __builtin_assume_dereferenceable(p, 10);
+ __builtin_assume_dereferenceable(p, 20);
+ free(p);
+}
+
+void test_pass_2() {
+ char *p = (char *)malloc(10);
+ __builtin_assume_dereferenceable(p, 0);
+ free(p);
+}
+
+void test_stack_pass_1() {
+ char p[20];
+ __builtin_assume_dereferenceable(p, 10);
+ __builtin_assume_dereferenceable(p, 20);
+}
+
+void test_stack_pass_2() {
+ char p[10];
+ __builtin_assume_dereferenceable(p, 0);
+}
+
+int main() {
+ test_pass_1();
+ test_pass_2();
+ test_stack_pass_1();
+ test_stack_pass_2();
+ return 0;
+}
diff --git a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
index 8b5969ffb3ca0..4d575b3defecf 100644
--- a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
@@ -299,6 +299,11 @@ static cl::opt<bool> ClRedzoneByvalArgs("asan-redzone-byval-args",
"required)"), cl::Hidden,
cl::init(true));
+static cl::opt<bool> ClInstrumentAssumeDereferenceable(
+ "asan-instrument-assume-dereferenceable",
+ cl::desc("instrument llvm.assume(dereferenceable)"), cl::Hidden,
+ cl::init(true));
+
static cl::opt<bool> ClUseAfterScope("asan-use-after-scope",
cl::desc("Check stack-use-after-scope"),
cl::Hidden, cl::init(false));
@@ -924,6 +929,7 @@ struct AddressSanitizer {
// These arrays is indexed by AccessIsWrite and Experiment.
FunctionCallee AsanErrorCallbackSized[2][2];
FunctionCallee AsanMemoryAccessCallbackSized[2][2];
+ FunctionCallee AsanAssumeDereferenceableCallback;
FunctionCallee AsanMemmove, AsanMemcpy, AsanMemset;
Value *LocalDynamicShadow = nullptr;
@@ -2881,6 +2887,12 @@ bool ModuleAddressSanitizer::instrumentModule() {
void AddressSanitizer::initializeCallbacks(const TargetLibraryInfo *TLI) {
IRBuilder<> IRB(*C);
+
+ const std::string EndingStr = Recover ? "_noabort" : "";
+ AsanAssumeDereferenceableCallback = M.getOrInsertFunction(
+ "__asan_report_assume_dereferenceable" + EndingStr,
+ FunctionType::get(IRB.getVoidTy(), {IntptrTy, IntptrTy}, false));
+
// Create __asan_report* callbacks.
// IsWrite, TypeSize and Exp are encoded in the function name.
for (int Exp = 0; Exp < 2; Exp++) {
@@ -3099,6 +3111,7 @@ bool AddressSanitizer::instrumentFunction(Function &F,
SmallVector<Instruction *, 8> NoReturnCalls;
SmallVector<BasicBlock *, 16> AllBlocks;
SmallVector<Instruction *, 16> PointerComparisonsOrSubtracts;
+ SmallVector<AssumeInst *, 8> DerefAssumptions;
// Fill the set of memory operations to instrument.
for (auto &BB : F) {
@@ -3140,6 +3153,12 @@ bool AddressSanitizer::instrumentFunction(Function &F,
// ok, take it.
IntrinToInstrument.push_back(MI);
NumInsnsPerBB++;
+ } else if (ClInstrumentAssumeDereferenceable && isa<AssumeInst>(&Inst)) {
+ auto *AI = cast<AssumeInst>(&Inst);
+ if (AI->getOperandBundle("dereferenceable")) {
+ DerefAssumptions.push_back(AI);
+ NumInsnsPerBB++;
+ }
} else {
if (auto *CB = dyn_cast<CallBase>(&Inst)) {
// A call inside BB.
@@ -3189,6 +3208,27 @@ bool AddressSanitizer::instrumentFunction(Function &F,
FunctionModified = true;
}
+ for (auto *AI : DerefAssumptions) {
+ if (auto Bundle = AI->getOperandBundle("dereferenceable")) {
+ Value *Ptr = Bundle->Inputs[0];
+ Value *Size = Bundle->Inputs[1];
+
+ InstrumentationIRBuilder IRB(AI);
+ Value *AddrLong = IRB.CreatePointerCast(Ptr, IntptrTy);
+ Value *SizeExt = IRB.CreateZExtOrTrunc(Size, IntptrTy);
+
+ // Skip if size is exactly 0.
+ if (ConstantInt *CI = dyn_cast<ConstantInt>(SizeExt)) {
+ if (CI->isZero())
+ continue;
+ }
+
+ RTCI.createRuntimeCall(IRB, AsanAssumeDereferenceableCallback, {AddrLong, SizeExt});
+
+ FunctionModified = true;
+ }
+ }
+
if (ChangedStack || !NoReturnCalls.empty())
FunctionModified = true;
diff --git a/llvm/test/Instrumentation/AddressSanitizer/assume-dereferenceable.ll b/llvm/test/Instrumentation/AddressSanitizer/assume-dereferenceable.ll
new file mode 100644
index 0000000000000..cb0d69da60639
--- /dev/null
+++ b/llvm/test/Instrumentation/AddressSanitizer/assume-dereferenceable.ll
@@ -0,0 +1,18 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=asan -asan-instrument-assume-dereferenceable=1 -S | FileCheck %s
+
+target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+define void @test(ptr %p, i64 %size) sanitize_address {
+; CHECK-LABEL: @test(
+; CHECK-NEXT: [[TMP17:%.*]] = ptrtoint ptr [[TMP4:%.*]] to i64
+; CHECK-NEXT: call void @__asan_report_assume_dereferenceable(i64 [[TMP17]], i64 [[SIZE:%.*]])
+; CHECK-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr [[TMP4]], i64 [[SIZE]]) ]
+; CHECK-NEXT: ret void
+;
+ call void @llvm.assume(i1 true) [ "dereferenceable"(ptr %p, i64 %size) ]
+ ret void
+}
+
+declare void @llvm.assume(i1)
|
You can test this locally with the following command:git-clang-format --diff origin/main HEAD --extensions inc,h,cpp -- compiler-rt/test/asan/TestCases/assume_dereferenceable.cpp compiler-rt/test/asan/TestCases/assume_dereferenceable_fully_poisoned.cpp compiler-rt/test/asan/TestCases/assume_dereferenceable_halt_on_error.cpp compiler-rt/test/asan/TestCases/assume_dereferenceable_pass.cpp compiler-rt/lib/asan/asan_errors.cpp compiler-rt/lib/asan/asan_errors.h compiler-rt/lib/asan/asan_interface.inc compiler-rt/lib/asan/asan_report.cpp compiler-rt/lib/asan/asan_report.h compiler-rt/lib/asan/asan_rtl.cpp llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp --diff_from_common_commit
View the diff from clang-format here.diff --git a/compiler-rt/lib/asan/asan_rtl.cpp b/compiler-rt/lib/asan/asan_rtl.cpp
index 76e6b90b7..9726f8f44 100644
--- a/compiler-rt/lib/asan/asan_rtl.cpp
+++ b/compiler-rt/lib/asan/asan_rtl.cpp
@@ -156,16 +156,16 @@ void __asan_report_ ## type ## _n_noabort(uptr addr, uptr size) { \
ASAN_REPORT_ERROR_N(load, false)
ASAN_REPORT_ERROR_N(store, true)
-extern "C" NOINLINE INTERFACE_ATTRIBUTE
-void __asan_report_assume_dereferenceable(uptr addr, uptr size) {
+extern "C" NOINLINE INTERFACE_ATTRIBUTE void
+__asan_report_assume_dereferenceable(uptr addr, uptr size) {
if (__asan_region_is_poisoned(addr, size)) {
GET_CALLER_PC_BP_SP;
ReportAssumeDereferenceableError(pc, bp, sp, addr, size, true);
}
}
-extern "C" NOINLINE INTERFACE_ATTRIBUTE
-void __asan_report_assume_dereferenceable_noabort(uptr addr, uptr size) {
+extern "C" NOINLINE INTERFACE_ATTRIBUTE void
+__asan_report_assume_dereferenceable_noabort(uptr addr, uptr size) {
if (__asan_region_is_poisoned(addr, size)) {
GET_CALLER_PC_BP_SP;
ReportAssumeDereferenceableError(pc, bp, sp, addr, size, false);
diff --git a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
index 4d575b3de..60996ca07 100644
--- a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
@@ -3223,7 +3223,8 @@ bool AddressSanitizer::instrumentFunction(Function &F,
continue;
}
- RTCI.createRuntimeCall(IRB, AsanAssumeDereferenceableCallback, {AddrLong, SizeExt});
+ RTCI.createRuntimeCall(IRB, AsanAssumeDereferenceableCallback,
+ {AddrLong, SizeExt});
FunctionModified = true;
}
|
🪟 Windows x64 Test Results
Failed Tests(click on a test name to see its output) AddressSanitizer-x86_64-windowsAddressSanitizer-x86_64-windows.TestCases/assume_dereferenceable.cppAddressSanitizer-x86_64-windows.TestCases/assume_dereferenceable_halt_on_error.cppAddressSanitizer-x86_64-windows-dynamicAddressSanitizer-x86_64-windows-dynamic.TestCases/assume_dereferenceable.cppAddressSanitizer-x86_64-windows-dynamic.TestCases/assume_dereferenceable_halt_on_error.cppIf these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the |
| IntrinToInstrument.push_back(MI); | ||
| NumInsnsPerBB++; | ||
| } else if (ClInstrumentAssumeDereferenceable && isa<AssumeInst>(&Inst)) { | ||
| auto *AI = cast<AssumeInst>(&Inst); |
There was a problem hiding this comment.
nit:
if (auto *AI = cast<AssumeInst>(&Inst))| fprintf(stderr, "test_malloc_fully_oob: %p\n", p); | ||
| // CHECK: test_malloc_fully_oob: [[PTR1:0x[0-9a-f]+]] | ||
| // CHECK: ERROR: AddressSanitizer: assume-dereferenceable-failed on address [[PTR1]] | ||
| // CHECK: ASSUMPTION of size 20 at [[PTR1]] thread T0 |
There was a problem hiding this comment.
ASSUMPTION of size 20 is not very meaningful. Maybe DEREFERENCABLE ASSUMPTION?
| Printf("%sASSUMPTION of size %zu at %p thread %s%s\n", d.Access(), | ||
| dereferencable_size, (void*)addr, AsanThreadIdAndName(tid).c_str(), | ||
| d.Default()); | ||
|
|
There was a problem hiding this comment.
Can we pull this into a helper?
| (void*)(addr + i), | ||
| current_poisoned ? "NOT dereferenceable" : "dereferenceable", | ||
| d.Default()); | ||
| if (i < dereferencable_size) { |
There was a problem hiding this comment.
Wouldn't this loop be better if we jumped over the range here?
| Printf("%s", d.Error()); | ||
| uptr addr = addr_description.Address(); | ||
| Report( | ||
| "ERROR: AddressSanitizer: assume-dereferenceable-failed on address %p at " |
There was a problem hiding this comment.
optional nit, just an idea: dereferencable-assumption-violation?
|
Please fix code formatting and tests |
| CheckPoisonRecords(addr); | ||
| } | ||
|
|
||
| ErrorAssumeDereferenceable::ErrorAssumeDereferenceable(u32 tid, uptr pc, |
There was a problem hiding this comment.
I think this one matches ErrorGeneric, we have other errors only for those which don't fit into ErrorGeneric at all
There was a problem hiding this comment.
There is nice helpers to access generic error from user program _asan_get_report*
so I'd like to avoid either losing them or complicating them for a small customization
Please try to fit it ErrorGeneric case
| void ReportGenericError(uptr pc, uptr bp, uptr sp, uptr addr, bool is_write, | ||
| uptr access_size, u32 exp, bool fatal); | ||
| void ReportAssumeDereferenceableError(uptr pc, uptr bp, uptr sp, uptr addr, | ||
| uptr dereferencable_size, bool fatal); |
There was a problem hiding this comment.
typo dereferencable_size -> dereferenceable_size
| struct ErrorAssumeDereferenceable : ErrorBase { | ||
| AddressDescription addr_description; | ||
| uptr pc, bp, sp; | ||
| uptr dereferencable_size; |
There was a problem hiding this comment.
typo dereferencable_size -> dereferenceable_size
This checks that the range covered by this intrinsic is dereferencable. Specifically it checks for
llvm.assumeintrinsics using thedereferencableoperator bundle and just asserts the shadow for this range is zero.The bulk of this PR was made by gemini but it was thoroughly edited and reviewed to the best of my ability.