diff --git a/clang/docs/AddressSanitizer.rst b/clang/docs/AddressSanitizer.rst index ed59afa1e0af5..dc0ba79f66cc2 100644 --- a/clang/docs/AddressSanitizer.rst +++ b/clang/docs/AddressSanitizer.rst @@ -254,6 +254,40 @@ AddressSanitizer also supports works similarly to ``__attribute__((no_sanitize("address")))``, but it also prevents instrumentation performed by other sanitizers. +Conditional Sanitizer Checks with ``__builtin_allow_sanitize_check`` +-------------------------------------------------------------------- + +The ``__builtin_allow_sanitize_check("address")`` builtin can be used to +conditionally execute code only when AddressSanitizer is active for the current +function (after inlining). This is particularly useful for inserting explicit, +sanitizer-specific checks around operations like syscalls or inline assembly, +which might otherwise be unchecked by the sanitizer. + +Example: + +.. code-block:: c + + inline __attribute__((always_inline)) + void copy_to_device(void *addr, size_t size) { + if (__builtin_allow_sanitize_check("address")) { + // Custom checks that address range is valid. + } + // ... actual device memory copy logic, potentially a syscall ... + } + + void instrumented_function() { + ... + copy_to_device(buf, sizeof(buf)); // checks are active + ... + } + + __attribute__((no_sanitize("address"))) + void uninstrumented_function() { + ... + copy_to_device(buf, sizeof(buf)); // checks are skipped + ... + } + Disabling container overflow checks ----------------------------------- diff --git a/clang/docs/MemorySanitizer.rst b/clang/docs/MemorySanitizer.rst index 4f581427c36af..54f88d076d36e 100644 --- a/clang/docs/MemorySanitizer.rst +++ b/clang/docs/MemorySanitizer.rst @@ -101,6 +101,40 @@ positives and therefore should be used with care, and only if absolutely required; for example for certain code that cannot tolerate any instrumentation and resulting side-effects. This attribute overrides ``no_sanitize("memory")``. +Conditional Sanitizer Checks with ``__builtin_allow_sanitize_check`` +-------------------------------------------------------------------- + +The ``__builtin_allow_sanitize_check("memory")`` builtin can be used to +conditionally execute code only when MemorySanitizer is active for the current +function (after inlining). This is particularly useful for inserting explicit, +sanitizer-specific checks around operations like syscalls or inline assembly, +which might otherwise be unchecked by the sanitizer. + +Example: + +.. code-block:: c + + inline __attribute__((always_inline)) + void copy_to_device(void *addr, size_t size) { + if (__builtin_allow_sanitize_check("memory")) { + // Custom checks if `data` is initialized. + } + // ... actual device memory copy logic, potentially a syscall ... + } + + void instrumented_function() { + ... + copy_to_device(buf, sizeof(buf)); // checks are active + ... + } + + __attribute__((no_sanitize("memory"))) + void uninstrumented_function() { + ... + copy_to_device(buf, sizeof(buf)); // checks are skipped + ... + } + Ignorelist ---------- diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 59abb0b27cfbe..1756a43d14267 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -798,6 +798,13 @@ Moved checkers Sanitizers ---------- - Improved documentation for legacy ``no_sanitize`` attributes. +- Added ``__builtin_allow_sanitize_check("name")`` that returns true if the + specified sanitizer is enabled for the current function (after inlining). + This allows for conditional code execution based on sanitizer enablement, + respecting ``no_sanitize`` attributes. It currently supports sanitizers: + "address", "kernel-address", "hwaddress", "kernel-hwaddress", "memory", + "kernel-memory", and "thread". + Python Binding Changes ---------------------- diff --git a/clang/docs/ThreadSanitizer.rst b/clang/docs/ThreadSanitizer.rst index 5dc78fa5a7a56..c90d668600ec9 100644 --- a/clang/docs/ThreadSanitizer.rst +++ b/clang/docs/ThreadSanitizer.rst @@ -110,6 +110,40 @@ and only if absolutely required; for example for certain code that cannot tolerate any instrumentation and resulting side-effects. This attribute overrides ``no_sanitize("thread")``. +Conditional Sanitizer Checks with ``__builtin_allow_sanitize_check`` +-------------------------------------------------------------------- + +The ``__builtin_allow_sanitize_check("thread")`` builtin can be used to +conditionally execute code only when ThreadSanitizer is active for the current +function (after inlining). This is particularly useful for inserting explicit, +sanitizer-specific checks around operations like syscalls or inline assembly, +which might otherwise be unchecked by the sanitizer. + +Example: + +.. code-block:: c + + inline __attribute__((always_inline)) + void copy_to_device(void *addr, size_t size) { + if (__builtin_allow_sanitize_check("thread")) { + // Custom checks that `data` is not concurrently modified. + } + // ... actual device memory copy logic, potentially a syscall ... + } + + void instrumented_function() { + ... + copy_to_device(&shared_data, size); // checks are active + ... + } + + __attribute__((no_sanitize("thread"))) + void uninstrumented_function() { + ... + copy_to_device(&shared_data, size); // checks are skipped + ... + } + Ignorelist ---------- diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 98fb2debad06d..ef312a4e02e7f 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -1226,6 +1226,12 @@ def AllowRuntimeCheck : Builtin { let Prototype = "bool(char const*)"; } +def BuiltinAllowSanitizeCheck : Builtin { + let Spellings = ["__builtin_allow_sanitize_check"]; + let Attributes = [NoThrow, Const, CustomTypeChecking]; + let Prototype = "bool(char const*)"; +} + def ShuffleVector : Builtin { let Spellings = ["__builtin_shufflevector"]; let Attributes = [NoThrow, Const, CustomTypeChecking, Constexpr]; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 381d1fb063eba..03a38fa1a35e8 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -838,6 +838,8 @@ def warn_redecl_library_builtin : Warning< InGroup>; def err_builtin_definition : Error<"definition of builtin function %0">; def err_builtin_redeclare : Error<"cannot redeclare builtin function %0">; +def err_invalid_builtin_argument : Error<"invalid argument '%0' to %1">; + def err_arm_invalid_specialreg : Error<"invalid special register for builtin">; def err_arm_invalid_coproc : Error<"coprocessor %0 must be configured as " "%select{GCP|CDE}1">; diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp index 0f60af616aa7b..99c83cd710045 100644 --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -843,15 +843,25 @@ static void addSanitizers(const Triple &TargetTriple, // LastEP does not need GlobalsAA. PB.registerOptimizerLastEPCallback(SanitizersCallback); } +} +void addLowerAllowCheckPass(const CodeGenOptions &CodeGenOpts, + const LangOptions &LangOpts, PassBuilder &PB) { // SanitizeSkipHotCutoffs: doubles with range [0, 1] // Opts.cutoffs: unsigned ints with range [0, 1000000] auto ScaledCutoffs = CodeGenOpts.SanitizeSkipHotCutoffs.getAllScaled(1000000); uint64_t AllowRuntimeCheckSkipHotCutoff = CodeGenOpts.AllowRuntimeCheckSkipHotCutoff.value_or(0.0) * 1000000; + bool LowerAllowSanitize = LangOpts.Sanitize.hasOneOf( + SanitizerKind::Address | SanitizerKind::KernelAddress | + SanitizerKind::Thread | SanitizerKind::Memory | + SanitizerKind::KernelMemory | SanitizerKind::HWAddress | + SanitizerKind::KernelHWAddress); + // TODO: remove IsRequested() if (LowerAllowCheckPass::IsRequested() || ScaledCutoffs.has_value() || - CodeGenOpts.AllowRuntimeCheckSkipHotCutoff.has_value()) { + CodeGenOpts.AllowRuntimeCheckSkipHotCutoff.has_value() || + LowerAllowSanitize) { // We want to call it after inline, which is about OptimizerEarlyEPCallback. PB.registerOptimizerEarlyEPCallback( [ScaledCutoffs, AllowRuntimeCheckSkipHotCutoff]( @@ -1124,6 +1134,7 @@ void EmitAssemblyHelper::RunOptimizationPipeline( // Most sanitizers only run during PreLink stage. addSanitizers(TargetTriple, CodeGenOpts, LangOpts, PB); addKCFIPass(TargetTriple, LangOpts, PB); + addLowerAllowCheckPass(CodeGenOpts, LangOpts, PB); PB.registerPipelineStartEPCallback( [&](ModulePassManager &MPM, OptimizationLevel Level) { diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index 01be374422d93..98d80620b44a5 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -3549,6 +3549,38 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, llvm::MetadataAsValue::get(Ctx, llvm::MDString::get(Ctx, Kind))); return RValue::get(Allow); } + case Builtin::BI__builtin_allow_sanitize_check: { + Intrinsic::ID IntrID = Intrinsic::not_intrinsic; + StringRef SanitizerName = + cast(E->getArg(0)->IgnoreParenCasts())->getString(); + + if (SanitizerName == "address" || SanitizerName == "kernel-address") { + if (CGM.getLangOpts().Sanitize.hasOneOf(SanitizerKind::Address | + SanitizerKind::KernelAddress)) + IntrID = Intrinsic::allow_sanitize_address; + } else if (SanitizerName == "thread") { + if (CGM.getLangOpts().Sanitize.has(SanitizerKind::Thread)) + IntrID = Intrinsic::allow_sanitize_thread; + } else if (SanitizerName == "memory" || SanitizerName == "kernel-memory") { + if (CGM.getLangOpts().Sanitize.hasOneOf(SanitizerKind::Memory | + SanitizerKind::KernelMemory)) + IntrID = Intrinsic::allow_sanitize_memory; + } else if (SanitizerName == "hwaddress" || + SanitizerName == "kernel-hwaddress") { + if (CGM.getLangOpts().Sanitize.hasOneOf(SanitizerKind::HWAddress | + SanitizerKind::KernelHWAddress)) + IntrID = Intrinsic::allow_sanitize_hwaddress; + } + + if (IntrID != Intrinsic::not_intrinsic) { + llvm::Value *Allow = Builder.CreateCall(CGM.getIntrinsic(IntrID)); + return RValue::get(Allow); + } + // If the checked sanitizer is not enabled, we can safely lower to false + // right away. This is also more efficient, since the LowerAllowCheckPass + // must not always be enabled if none of the above sanitizers are enabled. + return RValue::get(Builder.getFalse()); + } case Builtin::BI__arithmetic_fence: { // Create the builtin call if FastMath is selected, and the target // supports the builtin, otherwise just return the argument. diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 67482f1d56da1..1f1cdd0a4c7ed 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -3583,6 +3583,30 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID, } break; } + + case Builtin::BI__builtin_allow_sanitize_check: { + Expr *Arg = TheCall->getArg(0); + // Check if the argument is a string literal. + const StringLiteral *SanitizerName = + dyn_cast(Arg->IgnoreParenImpCasts()); + if (!SanitizerName) { + Diag(TheCall->getBeginLoc(), diag::err_expr_not_string_literal) + << Arg->getSourceRange(); + return ExprError(); + } + // Validate the sanitizer name. + if (!llvm::StringSwitch(SanitizerName->getString()) + .Cases({"address", "thread", "memory", "hwaddress", + "kernel-address", "kernel-memory", "kernel-hwaddress"}, + true) + .Default(false)) { + Diag(TheCall->getBeginLoc(), diag::err_invalid_builtin_argument) + << SanitizerName->getString() << "__builtin_allow_sanitize_check" + << Arg->getSourceRange(); + return ExprError(); + } + break; + } case Builtin::BI__builtin_counted_by_ref: if (BuiltinCountedByRef(TheCall)) return ExprError(); diff --git a/clang/test/CodeGen/builtin-allow-sanitize-check-lower.c b/clang/test/CodeGen/builtin-allow-sanitize-check-lower.c new file mode 100644 index 0000000000000..5e52f77f55573 --- /dev/null +++ b/clang/test/CodeGen/builtin-allow-sanitize-check-lower.c @@ -0,0 +1,23 @@ +// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=address -emit-llvm -O0 -o - %s | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=address -emit-llvm -O1 -o - %s | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=address -emit-llvm -O2 -o - %s | FileCheck %s + +// CHECK-NOT: call{{.*}} @llvm.allow.sanitize.address + +__attribute__((always_inline)) +_Bool check() { + return __builtin_allow_sanitize_check("address"); +} + +// CHECK-LABEL: @test_sanitize +// CHECK: ret i1 true +_Bool test_sanitize() { + return check(); +} + +// CHECK-LABEL: @test_no_sanitize +// CHECK: ret i1 false +__attribute__((no_sanitize("address"))) +_Bool test_no_sanitize() { + return check(); +} diff --git a/clang/test/CodeGen/builtin-allow-sanitize-check.c b/clang/test/CodeGen/builtin-allow-sanitize-check.c new file mode 100644 index 0000000000000..d535885bd2ddb --- /dev/null +++ b/clang/test/CodeGen/builtin-allow-sanitize-check.c @@ -0,0 +1,78 @@ +// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,NONE +// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=address -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,ASAN +// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=kernel-address -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,ASAN +// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=thread -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,TSAN +// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=memory -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,MSAN +// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=kernel-memory -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,MSAN +// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=hwaddress -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,HWASAN +// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=kernel-hwaddress -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,HWASAN + +// CHECK-LABEL: @test_address +// NONE: ret i1 false +// ASAN: call i1 @llvm.allow.sanitize.address() +// TSAN: ret i1 false +// MSAN: ret i1 false +// HWASAN: ret i1 false +_Bool test_address() { + return __builtin_allow_sanitize_check("address"); +} + +// CHECK-LABEL: @test_kernel_address +// NONE: ret i1 false +// ASAN: call i1 @llvm.allow.sanitize.address() +// TSAN: ret i1 false +// MSAN: ret i1 false +// HWASAN: ret i1 false +_Bool test_kernel_address() { + return __builtin_allow_sanitize_check("kernel-address"); +} + +// CHECK-LABEL: @test_thread +// NONE: ret i1 false +// ASAN: ret i1 false +// TSAN: call i1 @llvm.allow.sanitize.thread() +// MSAN: ret i1 false +// HWASAN: ret i1 false +_Bool test_thread() { + return __builtin_allow_sanitize_check("thread"); +} + +// CHECK-LABEL: @test_memory +// NONE: ret i1 false +// ASAN: ret i1 false +// TSAN: ret i1 false +// MSAN: call i1 @llvm.allow.sanitize.memory() +// HWASAN: ret i1 false +_Bool test_memory() { + return __builtin_allow_sanitize_check("memory"); +} + +// CHECK-LABEL: @test_kernel_memory +// NONE: ret i1 false +// ASAN: ret i1 false +// TSAN: ret i1 false +// MSAN: call i1 @llvm.allow.sanitize.memory() +// HWASAN: ret i1 false +_Bool test_kernel_memory() { + return __builtin_allow_sanitize_check("kernel-memory"); +} + +// CHECK-LABEL: @test_hwaddress +// NONE: ret i1 false +// ASAN: ret i1 false +// TSAN: ret i1 false +// MSAN: ret i1 false +// HWASAN: call i1 @llvm.allow.sanitize.hwaddress() +_Bool test_hwaddress() { + return __builtin_allow_sanitize_check("hwaddress"); +} + +// CHECK-LABEL: @test_kernel_hwaddress +// NONE: ret i1 false +// ASAN: ret i1 false +// TSAN: ret i1 false +// MSAN: ret i1 false +// HWASAN: call i1 @llvm.allow.sanitize.hwaddress() +_Bool test_kernel_hwaddress() { + return __builtin_allow_sanitize_check("kernel-hwaddress"); +} diff --git a/clang/test/Sema/builtin-allow-sanitize-check.c b/clang/test/Sema/builtin-allow-sanitize-check.c new file mode 100644 index 0000000000000..94deb16dd89f9 --- /dev/null +++ b/clang/test/Sema/builtin-allow-sanitize-check.c @@ -0,0 +1,24 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +void test_builtin_allow_sanitize_check() { + // Test with non-string literal argument + char str[] = "address"; + (void)__builtin_allow_sanitize_check(str); // expected-error {{expression is not a string literal}} + (void)__builtin_allow_sanitize_check(123); // expected-error {{expression is not a string literal}} + + // Test with unsupported sanitizer name + (void)__builtin_allow_sanitize_check("unsupported"); // expected-error {{invalid argument 'unsupported' to __builtin_allow_sanitize_check}} + + // Test with supported sanitizer names + (void)__builtin_allow_sanitize_check("address"); + (void)__builtin_allow_sanitize_check("thread"); + (void)__builtin_allow_sanitize_check("memory"); + (void)__builtin_allow_sanitize_check("hwaddress"); + (void)__builtin_allow_sanitize_check("kernel-address"); + (void)__builtin_allow_sanitize_check("kernel-memory"); + (void)__builtin_allow_sanitize_check("kernel-hwaddress"); +} + +#if !__has_builtin(__builtin_allow_sanitize_check) +#error "missing __builtin_allow_sanitize_check" +#endif