diff --git a/clang/docs/ClangCommandLineReference.rst b/clang/docs/ClangCommandLineReference.rst --- a/clang/docs/ClangCommandLineReference.rst +++ b/clang/docs/ClangCommandLineReference.rst @@ -870,6 +870,17 @@ Enable ODR indicator globals to avoid false ODR violation reports in partially sanitized programs at the cost of an increase in binary size +.. option:: -fsanitize-address-destructor-kind= + +Set the kind of module destructors emitted by AddressSanitizer instrumentation. +These destructors are emitted to unregister instrumented global variables when +code is unloaded (e.g. via `dlclose()`). + +Valid options are: + +* ``global`` - Emit module destructors that are called via a platform specific array (see `llvm.global_dtors`). +* ``none`` - Do not emit module destructors. + .. option:: -fsanitize-blacklist= Path to blacklist file for sanitizers diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h --- a/clang/include/clang/Basic/CodeGenOptions.h +++ b/clang/include/clang/Basic/CodeGenOptions.h @@ -20,6 +20,7 @@ #include "llvm/Support/CodeGen.h" #include "llvm/Support/Regex.h" #include "llvm/Target/TargetOptions.h" +#include "llvm/Transforms/Instrumentation/AddressSanitizerOptions.h" #include #include #include diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -214,6 +214,9 @@ CODEGENOPT(SanitizeAddressUseOdrIndicator, 1, 0) ///< Enable ODR indicator globals CODEGENOPT(SanitizeMemoryTrackOrigins, 2, 0) ///< Enable tracking origins in ///< MemorySanitizer +ENUM_CODEGENOPT(SanitizeAddressDtorKind, llvm::AsanDtorKind, 2, + llvm::AsanDtorKind::Global) ///< Set how ASan global + ///< destructors are emitted. CODEGENOPT(SanitizeMemoryUseAfterDtor, 1, 0) ///< Enable use-after-delete detection ///< in MemorySanitizer CODEGENOPT(SanitizeCfiCrossDso, 1, 0) ///< Enable cross-dso support in CFI. diff --git a/clang/include/clang/Basic/Sanitizers.h b/clang/include/clang/Basic/Sanitizers.h --- a/clang/include/clang/Basic/Sanitizers.h +++ b/clang/include/clang/Basic/Sanitizers.h @@ -17,6 +17,7 @@ #include "clang/Basic/LLVM.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/MathExtras.h" +#include "llvm/Transforms/Instrumentation/AddressSanitizerOptions.h" #include #include @@ -193,6 +194,10 @@ SanitizerKind::Undefined | SanitizerKind::FloatDivideByZero; } +StringRef AsanDtorKindToString(llvm::AsanDtorKind kind); + +llvm::AsanDtorKind AsanDtorKindFromString(StringRef kind); + } // namespace clang #endif // LLVM_CLANG_BASIC_SANITIZERS_H diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1500,6 +1500,11 @@ " reports in partially sanitized programs at the cost of an increase in binary size">, NegFlag>, Group; +def sanitize_address_destructor_kind_EQ : Joined<["-"], "fsanitize-address-destructor-kind=">, + MetaVarName<"">, + Flags<[CC1Option]>, + HelpText<"Set destructor type used in ASan instrumentation">, + Group; // Note: This flag was introduced when it was necessary to distinguish between // ABI for correct codegen. This is no longer needed, but the flag is // not removed since targeting either ABI will behave the same. diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h --- a/clang/include/clang/Driver/SanitizerArgs.h +++ b/clang/include/clang/Driver/SanitizerArgs.h @@ -43,6 +43,7 @@ bool AsanUseOdrIndicator = false; bool AsanInvalidPointerCmp = false; bool AsanInvalidPointerSub = false; + llvm::AsanDtorKind AsanDtorKind = llvm::AsanDtorKind::Invalid; std::string HwasanAbi; bool LinkRuntimes = true; bool LinkCXXRuntimes = false; diff --git a/clang/lib/Basic/Sanitizers.cpp b/clang/lib/Basic/Sanitizers.cpp --- a/clang/lib/Basic/Sanitizers.cpp +++ b/clang/lib/Basic/Sanitizers.cpp @@ -60,4 +60,23 @@ llvm::hash_code hash_value(const clang::SanitizerMask &Arg) { return Arg.hash_value(); } + +StringRef AsanDtorKindToString(llvm::AsanDtorKind kind) { + switch (kind) { + case llvm::AsanDtorKind::None: + return "none"; + case llvm::AsanDtorKind::Global: + return "global"; + case llvm::AsanDtorKind::Invalid: + return "invalid"; + } +} + +llvm::AsanDtorKind AsanDtorKindFromString(StringRef kindStr) { + return llvm::StringSwitch(kindStr) + .Case("none", llvm::AsanDtorKind::None) + .Case("global", llvm::AsanDtorKind::Global) + .Default(llvm::AsanDtorKind::Invalid); +} + } // namespace clang diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -287,10 +287,12 @@ bool UseAfterScope = CGOpts.SanitizeAddressUseAfterScope; bool UseOdrIndicator = CGOpts.SanitizeAddressUseOdrIndicator; bool UseGlobalsGC = asanUseGlobalsGC(T, CGOpts); + llvm::AsanDtorKind DestructorKind = CGOpts.getSanitizeAddressDtorKind(); PM.add(createAddressSanitizerFunctionPass(/*CompileKernel*/ false, Recover, UseAfterScope)); PM.add(createModuleAddressSanitizerLegacyPassPass( - /*CompileKernel*/ false, Recover, UseGlobalsGC, UseOdrIndicator)); + /*CompileKernel*/ false, Recover, UseGlobalsGC, UseOdrIndicator, + DestructorKind)); } static void addKernelAddressSanitizerPasses(const PassManagerBuilder &Builder, @@ -1111,9 +1113,12 @@ bool UseAfterScope = CodeGenOpts.SanitizeAddressUseAfterScope; bool ModuleUseAfterScope = asanUseGlobalsGC(TargetTriple, CodeGenOpts); bool UseOdrIndicator = CodeGenOpts.SanitizeAddressUseOdrIndicator; + llvm::AsanDtorKind DestructorKind = + CodeGenOpts.getSanitizeAddressDtorKind(); MPM.addPass(RequireAnalysisPass()); MPM.addPass(ModuleAddressSanitizerPass( - CompileKernel, Recover, ModuleUseAfterScope, UseOdrIndicator)); + CompileKernel, Recover, ModuleUseAfterScope, UseOdrIndicator, + DestructorKind)); MPM.addPass(createModuleToFunctionPassAdaptor( AddressSanitizerPass(CompileKernel, Recover, UseAfterScope))); } diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp --- a/clang/lib/Driver/SanitizerArgs.cpp +++ b/clang/lib/Driver/SanitizerArgs.cpp @@ -825,6 +825,16 @@ AsanInvalidPointerSub = true; } + if (const auto *Arg = + Args.getLastArg(options::OPT_sanitize_address_destructor_kind_EQ)) { + auto parsedAsanDtorKind = AsanDtorKindFromString(Arg->getValue()); + if (parsedAsanDtorKind == llvm::AsanDtorKind::Invalid) { + TC.getDriver().Diag(clang::diag::err_drv_unsupported_option_argument) + << Arg->getOption().getName() << Arg->getValue(); + } + AsanDtorKind = parsedAsanDtorKind; + } + } else { AsanUseAfterScope = false; // -fsanitize=pointer-compare/pointer-subtract requires -fsanitize=address. @@ -1079,6 +1089,13 @@ CmdArgs.push_back("-asan-detect-invalid-pointer-sub"); } + // Only pass the option to the frontend if the user requested, + // otherwise the frontend will just use the codegen default. + if (AsanDtorKind != llvm::AsanDtorKind::Invalid) { + CmdArgs.push_back(Args.MakeArgString("-fsanitize-address-destructor-kind=" + + AsanDtorKindToString(AsanDtorKind))); + } + if (!HwasanAbi.empty()) { CmdArgs.push_back("-default-function-attr"); CmdArgs.push_back(Args.MakeArgString("hwasan-abi=" + HwasanAbi)); diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -1937,6 +1937,19 @@ Opts.EmitVersionIdentMetadata = Args.hasFlag(OPT_Qy, OPT_Qn, true); + if (LangOptsRef.Sanitize.has(SanitizerKind::Address)) { + if (Arg *A = + Args.getLastArg(options::OPT_sanitize_address_destructor_kind_EQ)) { + auto destructorKind = AsanDtorKindFromString(A->getValue()); + if (destructorKind == llvm::AsanDtorKind::Invalid) { + Diags.Report(clang::diag::err_drv_unsupported_option_argument) + << A->getOption().getName() << A->getValue(); + } else { + Opts.setSanitizeAddressDtorKind(destructorKind); + } + } + } + if (Args.hasArg(options::OPT_ffinite_loops)) Opts.FiniteLoops = CodeGenOptions::FiniteLoopsKind::Always; else if (Args.hasArg(options::OPT_fno_finite_loops)) diff --git a/clang/test/CodeGen/asan-destructor-kind.cpp b/clang/test/CodeGen/asan-destructor-kind.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/asan-destructor-kind.cpp @@ -0,0 +1,49 @@ +// Frontend rejects invalid option +// RUN: not %clang_cc1 -fsanitize=address \ +// RUN: -fsanitize-address-destructor-kind=bad_arg -emit-llvm -o - \ +// RUN: -triple x86_64-apple-macosx10.15 %s 2>&1 | \ +// RUN: FileCheck %s --check-prefixes=CHECK-BAD-ARG +// CHECK-BAD-ARG: unsupported argument 'bad_arg' to option 'fsanitize-address-destructor-kind=' + +// Default is global dtor +// RUN: %clang_cc1 -fsanitize=address -emit-llvm -o - -triple x86_64-apple-macosx10.15 \ +// RUN: -fno-legacy-pass-manager %s \ +// RUN: | FileCheck %s --check-prefixes=CHECK-GLOBAL-DTOR +// +// RUN: %clang_cc1 -fsanitize=address -emit-llvm -o - -triple x86_64-apple-macosx10.15 \ +// RUN: -flegacy-pass-manager %s \ +// RUN: | FileCheck %s --check-prefixes=CHECK-GLOBAL-DTOR + +// Explictly ask for global dtor +// RUN: %clang_cc1 -fsanitize=address \ +// RUN: -fsanitize-address-destructor-kind=global -emit-llvm -o - \ +// RUN: -triple x86_64-apple-macosx10.15 -fno-legacy-pass-manager %s | \ +// RUN: FileCheck %s --check-prefixes=CHECK-GLOBAL-DTOR +// +// RUN: %clang_cc1 -fsanitize=address \ +// RUN: -fsanitize-address-destructor-kind=global -emit-llvm -o - \ +// RUN: -triple x86_64-apple-macosx10.15 -flegacy-pass-manager %s | \ +// RUN: FileCheck %s --check-prefixes=CHECK-GLOBAL-DTOR + +// CHECK-GLOBAL-DTOR: llvm.global_dtor{{.+}}asan.module_dtor +// CHECK-GLOBAL-DTOR: define internal void @asan.module_dtor + +// Explictly ask for no dtors +// RUN: %clang_cc1 -fsanitize=address \ +// RUN: -fsanitize-address-destructor-kind=none -emit-llvm -o - \ +// RUN: -triple x86_64-apple-macosx10.15 -fno-legacy-pass-manager %s | \ +// RUN: FileCheck %s --check-prefixes=CHECK-NONE-DTOR +// +// RUN: %clang_cc1 -fsanitize=address \ +// RUN: -fsanitize-address-destructor-kind=none -emit-llvm -o - \ +// RUN: -triple x86_64-apple-macosx10.15 -flegacy-pass-manager %s | \ +// RUN: FileCheck %s --check-prefixes=CHECK-NONE-DTOR + +int global; + +int main() { + return global; +} + +// CHECK-NONE-DTOR-NOT: llvm.global_dtor{{.+}}asan.module_dtor +// CHECK-NONE-DTOR-NOT: define internal void @asan.module_dtor diff --git a/clang/test/Driver/fsanitize-address-destructor-kind.c b/clang/test/Driver/fsanitize-address-destructor-kind.c new file mode 100644 --- /dev/null +++ b/clang/test/Driver/fsanitize-address-destructor-kind.c @@ -0,0 +1,20 @@ +// Option should not be passed to the frontend by default. +// RUN: %clang -target x86_64-apple-macosx10.15-gnu -fsanitize=address %s \ +// RUN: -### 2>&1 | \ +// RUN: FileCheck %s +// CHECK-NOT: -fsanitize-address-destructor-kind + +// RUN: %clang -target x86_64-apple-macosx10.15-gnu -fsanitize=address \ +// RUN: -fsanitize-address-destructor-kind=none %s -### 2>&1 | \ +// RUN: FileCheck -check-prefix=CHECK-NONE-ARG %s +// CHECK-NONE-ARG: "-fsanitize-address-destructor-kind=none" + +// RUN: %clang -target x86_64-apple-macosx10.15-gnu -fsanitize=address \ +// RUN: -fsanitize-address-destructor-kind=global %s -### 2>&1 | \ +// RUN: FileCheck -check-prefix=CHECK-GLOBAL-ARG %s +// CHECK-GLOBAL-ARG: "-fsanitize-address-destructor-kind=global" + +// RUN: %clang -target x86_64-apple-macosx10.15-gnu -fsanitize=address \ +// RUN: -fsanitize-address-destructor-kind=bad_arg %s -### 2>&1 | \ +// RUN: FileCheck -check-prefix=CHECK-INVALID-ARG %s +// CHECK-INVALID-ARG: error: unsupported argument 'bad_arg' to option 'fsanitize-address-destructor-kind='