diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td --- a/clang/include/clang/Basic/DiagnosticDriverKinds.td +++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -472,7 +472,8 @@ "ROPI is not compatible with c++">; def err_stack_tagging_requires_hardware_feature : Error< - "'-fsanitize=memtag' requires hardware support (+memtag)">; + "'-fsanitize=memtag-stack' requires hardware support (+memtag). For Armv8, " + "try compiling with -march=armv8a+memtag.">; def err_cmse_pi_are_incompatible : Error< "cmse is not compatible with %select{RWPI|ROPI}0">; diff --git a/clang/include/clang/Basic/Features.def b/clang/include/clang/Basic/Features.def --- a/clang/include/clang/Basic/Features.def +++ b/clang/include/clang/Basic/Features.def @@ -45,7 +45,10 @@ FEATURE(hwaddress_sanitizer, LangOpts.Sanitize.hasOneOf(SanitizerKind::HWAddress | SanitizerKind::KernelHWAddress)) -FEATURE(memtag_sanitizer, LangOpts.Sanitize.has(SanitizerKind::MemTag)) +FEATURE(memtag_stack, + LangOpts.Sanitize.has(SanitizerKind::MemtagStack)) +FEATURE(memtag_heap, + LangOpts.Sanitize.has(SanitizerKind::MemtagHeap)) FEATURE(xray_instrument, LangOpts.XRayInstrument) FEATURE(undefined_behavior_sanitizer, LangOpts.Sanitize.hasOneOf(SanitizerKind::Undefined)) diff --git a/clang/include/clang/Basic/Sanitizers.def b/clang/include/clang/Basic/Sanitizers.def --- a/clang/include/clang/Basic/Sanitizers.def +++ b/clang/include/clang/Basic/Sanitizers.def @@ -56,7 +56,9 @@ SANITIZER("kernel-hwaddress", KernelHWAddress) // A variant of AddressSanitizer using AArch64 MTE extension. -SANITIZER("memtag", MemTag) +SANITIZER("memtag-stack", MemtagStack) +SANITIZER("memtag-heap", MemtagHeap) +SANITIZER_GROUP("memtag", MemTag, MemtagStack | MemtagHeap) // MemorySanitizer SANITIZER("memory", Memory) 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 @@ -1614,6 +1614,9 @@ def fno_sanitize_address_outline_instrumentation : Flag<["-"], "fno-sanitize-address-outline-instrumentation">, Group, HelpText<"Use default code inlining logic for the address sanitizer">; +def fsanitize_memtag_mode_EQ : Joined<["-"], "fsanitize-memtag-mode=">, + Group, + HelpText<"Set default MTE mode to 'async' (default) or 'sync'.">; def fsanitize_hwaddress_experimental_aliasing : Flag<["-"], "fsanitize-hwaddress-experimental-aliasing">, Group, 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 @@ -64,6 +64,8 @@ llvm::AsanDetectStackUseAfterReturnMode AsanUseAfterReturn = llvm::AsanDetectStackUseAfterReturnMode::Invalid; + std::string MemtagMode = ""; + public: /// Parses the sanitizer arguments from an argument list. SanitizerArgs(const ToolChain &TC, const llvm::opt::ArgList &Args, @@ -97,6 +99,15 @@ bool needsStatsRt() const { return Stats; } bool needsScudoRt() const { return Sanitizers.has(SanitizerKind::Scudo); } + bool hasMemTag() const { return hasMemtagHeap() || hasMemtagStack(); } + bool hasMemtagHeap() const { + return Sanitizers.has(SanitizerKind::MemtagHeap); + } + bool hasMemtagStack() const { + return Sanitizers.has(SanitizerKind::MemtagStack); + } + const std::string &getMemtagMode() const { return MemtagMode; } + bool requiresPIE() const; bool needsUnwindTables() const; bool needsLTO() const; diff --git a/clang/lib/CodeGen/CGDeclCXX.cpp b/clang/lib/CodeGen/CGDeclCXX.cpp --- a/clang/lib/CodeGen/CGDeclCXX.cpp +++ b/clang/lib/CodeGen/CGDeclCXX.cpp @@ -458,8 +458,8 @@ !isInNoSanitizeList(SanitizerKind::KernelHWAddress, Fn, Loc)) Fn->addFnAttr(llvm::Attribute::SanitizeHWAddress); - if (getLangOpts().Sanitize.has(SanitizerKind::MemTag) && - !isInNoSanitizeList(SanitizerKind::MemTag, Fn, Loc)) + if (getLangOpts().Sanitize.has(SanitizerKind::MemtagStack) && + !isInNoSanitizeList(SanitizerKind::MemtagStack, Fn, Loc)) Fn->addFnAttr(llvm::Attribute::SanitizeMemTag); if (getLangOpts().Sanitize.has(SanitizerKind::Thread) && diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -772,7 +772,7 @@ Fn->addFnAttr(llvm::Attribute::SanitizeAddress); if (SanOpts.hasOneOf(SanitizerKind::HWAddress | SanitizerKind::KernelHWAddress)) Fn->addFnAttr(llvm::Attribute::SanitizeHWAddress); - if (SanOpts.has(SanitizerKind::MemTag)) + if (SanOpts.has(SanitizerKind::MemtagStack)) Fn->addFnAttr(llvm::Attribute::SanitizeMemTag); if (SanOpts.has(SanitizerKind::Thread)) Fn->addFnAttr(llvm::Attribute::SanitizeThread); diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -726,7 +726,7 @@ LangOptions::SignReturnAddressScopeKind::None) getModule().addModuleFlag(llvm::Module::Override, "sign-return-address-buildattr", 1); - if (LangOpts.Sanitize.has(SanitizerKind::MemTag)) + if (LangOpts.Sanitize.has(SanitizerKind::MemtagStack)) getModule().addModuleFlag(llvm::Module::Override, "tag-stack-memory-buildattr", 1); 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 @@ -44,8 +44,8 @@ static const SanitizerMask SupportsCoverage = SanitizerKind::Address | SanitizerKind::HWAddress | SanitizerKind::KernelAddress | SanitizerKind::KernelHWAddress | - SanitizerKind::MemTag | SanitizerKind::Memory | - SanitizerKind::KernelMemory | SanitizerKind::Leak | + SanitizerKind::MemtagStack | SanitizerKind::MemtagHeap | + SanitizerKind::Memory | SanitizerKind::KernelMemory | SanitizerKind::Leak | SanitizerKind::Undefined | SanitizerKind::Integer | SanitizerKind::Bounds | SanitizerKind::ImplicitConversion | SanitizerKind::Nullability | SanitizerKind::DataFlow | SanitizerKind::Fuzzer | @@ -73,7 +73,7 @@ SanitizerKind::CFIUnrelatedCast; static const SanitizerMask CompatibleWithMinimalRuntime = TrappingSupported | SanitizerKind::Scudo | SanitizerKind::ShadowCallStack | - SanitizerKind::MemTag; + SanitizerKind::MemtagStack | SanitizerKind::MemtagHeap; enum CoverageFeature { CoverageFunc = 1 << 0, @@ -652,6 +652,17 @@ MsanParamRetval = false; } + if (AllAddedKinds & SanitizerKind::MemTag) { + StringRef S = + Args.getLastArgValue(options::OPT_fsanitize_memtag_mode_EQ, "async"); + if (S.equals("async") || S.equals("sync")) { + MemtagMode = S.str(); + } else { + D.Diag(clang::diag::err_drv_invalid_value_with_suggestion) + << "-fsanitize-memtag-mode=" << S << "{async, sync}"; + } + } + if (AllAddedKinds & SanitizerKind::Thread) { TsanMemoryAccess = Args.hasFlag( options::OPT_fsanitize_thread_memory_access, @@ -1230,7 +1241,8 @@ << "-fvisibility="; } - if (Sanitizers.has(SanitizerKind::MemTag) && !hasTargetFeatureMTE(CmdArgs)) + if (Sanitizers.has(SanitizerKind::MemtagStack) && + !hasTargetFeatureMTE(CmdArgs)) TC.getDriver().Diag(diag::err_stack_tagging_requires_hardware_feature); } diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp --- a/clang/lib/Driver/ToolChains/CommonArgs.cpp +++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp @@ -972,6 +972,15 @@ if (SanArgs.hasCrossDsoCfi() && !AddExportDynamic) CmdArgs.push_back("--export-dynamic-symbol=__cfi_check"); + if (SanArgs.hasMemTag() && TC.getTriple().isAndroid()) { + CmdArgs.push_back( + Args.MakeArgString("-memtag-mode=" + SanArgs.getMemtagMode())); + if (SanArgs.hasMemtagHeap()) + CmdArgs.push_back("-memtag-heap"); + if (SanArgs.hasMemtagStack()) + CmdArgs.push_back("-memtag-stack"); + } + return !StaticRuntimes.empty() || !NonWholeStaticRuntimes.empty(); } diff --git a/clang/test/CodeGen/memtag-attr.cpp b/clang/test/CodeGen/memtag-attr.cpp --- a/clang/test/CodeGen/memtag-attr.cpp +++ b/clang/test/CodeGen/memtag-attr.cpp @@ -4,16 +4,27 @@ // RUN: %clang_cc1 -triple aarch64-unknown-linux -disable-O0-optnone \ // RUN: -emit-llvm -o - %s | FileCheck -check-prefix=CHECK-NO %s -// RUN: %clang_cc1 -triple aarch64-unknown-linux -fsanitize=memtag \ +// RUN: %clang_cc1 -triple aarch64-unknown-linux -fsanitize=memtag-stack \ // RUN: -disable-O0-optnone -emit-llvm -o - %s | \ // RUN: FileCheck -check-prefix=CHECK-MEMTAG %s +// RUN: %clang --target=aarch64-unknown-linux -march=armv8a+memtag \ +// RUN: -fsanitize=memtag -disable-O0-optnone -S -emit-llvm -o - %s | \ +// RUN: FileCheck -check-prefix=CHECK-MEMTAG %s + int HasSanitizeMemTag() { return 1; } -// CHECK-NO: {{Function Attrs: mustprogress noinline nounwind$}} -// CHECK-MEMTAG: Function Attrs: mustprogress noinline nounwind sanitize_memtag +// CHECK-NO: Function Attrs +// CHECK-NO-NOT: sanitize_memtag +// CHECK-NO-NEXT: define {{.*}}HasSanitizeMemTag +// CHECK-MEMTAG: Function Attrs: {{.*}} sanitize_memtag +// CHECK-MEMTAG-NEXT: define {{.*}}HasSanitizeMemTag __attribute__((no_sanitize("memtag"))) int NoSanitizeQuoteAddress() { return 0; } -// CHECK-NO: {{Function Attrs: mustprogress noinline nounwind$}} -// CHECK-MEMTAG: {{Function Attrs: mustprogress noinline nounwind$}} +// CHECK-NO: Function Attrs +// CHECK-NO-NOT: sanitize_memtag +// CHECK-NO-NEXT: define {{.*}}NoSanitizeQuoteAddress +// CHECK-MEMTAG: Function Attrs +// CHECK-MEMTAG-NOT: sanitize_memtag +// CHECK-MEMTAG-NEXT: define {{.*}}NoSanitizeQuoteAddress diff --git a/clang/test/Driver/fsanitize.c b/clang/test/Driver/fsanitize.c --- a/clang/test/Driver/fsanitize.c +++ b/clang/test/Driver/fsanitize.c @@ -196,13 +196,13 @@ // RUN: %clang -target aarch64-linux -fsanitize=memtag -march=armv8-a+memtag %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANMT-MT // CHECK-SANMT-MT: "-target-feature" "+mte" -// CHECK-SANMT-MT-SAME: "-fsanitize=memtag" +// CHECK-SANMT-MT-SAME: "-fsanitize=memtag-stack,memtag-heap" // RUN: %clang -target aarch64-linux -fsanitize=memtag %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANMT-NOMT-0 -// CHECK-SANMT-NOMT-0: '-fsanitize=memtag' requires hardware support (+memtag) +// CHECK-SANMT-NOMT-0: '-fsanitize=memtag-stack' requires hardware support (+memtag) // RUN: %clang -target aarch64-linux -fsanitize=memtag -I +mte %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANMT-NOMT-1 -// CHECK-SANMT-NOMT-1: '-fsanitize=memtag' requires hardware support (+memtag) +// CHECK-SANMT-NOMT-1: '-fsanitize=memtag-stack' requires hardware support (+memtag) // RUN: %clang -target x86_64-linux-gnu -fsanitize=address -fsanitize-address-use-after-scope %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-USE-AFTER-SCOPE // RUN: %clang_cl --target=x86_64-windows -fsanitize=address -fsanitize-address-use-after-scope -### -- %s 2>&1 | FileCheck %s --check-prefix=CHECK-USE-AFTER-SCOPE @@ -822,7 +822,7 @@ // CHECK-INTSAN-MINIMAL: "-fsanitize-minimal-runtime" // RUN: %clang -target aarch64-linux-android -march=armv8-a+memtag -fsanitize=memtag -fsanitize-minimal-runtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-MEMTAG-MINIMAL -// CHECK-MEMTAG-MINIMAL: "-fsanitize=memtag" +// CHECK-MEMTAG-MINIMAL: "-fsanitize=memtag-stack,memtag-heap" // CHECK-MEMTAG-MINIMAL: "-fsanitize-minimal-runtime" // RUN: %clang -target x86_64-linux-gnu -fsanitize=undefined -fsanitize=function -fsanitize-minimal-runtime %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UBSAN-FUNCTION-MINIMAL diff --git a/clang/test/Driver/memtag-ld.c b/clang/test/Driver/memtag-ld.c new file mode 100644 --- /dev/null +++ b/clang/test/Driver/memtag-ld.c @@ -0,0 +1,46 @@ +// REQUIRES: aarch64-registered-target + +// RUN: %clang -### --target=aarch64-linux-android -march=armv8+memtag \ +// RUN: -fsanitize=memtag %s 2>&1 \ +// RUN: | FileCheck %s --check-prefixes=CHECK-ASYNC,CHECK-HEAP,CHECK-STACK + +// RUN: %clang -### --target=aarch64-linux-android -march=armv8+memtag \ +// RUN: -fsanitize=memtag-stack %s 2>&1 \ +// RUN: | FileCheck %s --check-prefixes=CHECK-ASYNC,CHECK-NO-HEAP,CHECK-STACK + +// RUN: %clang -### --target=aarch64-linux-android -march=armv8+memtag \ +// RUN: -fsanitize=memtag-heap %s 2>&1 \ +// RUN: | FileCheck %s --check-prefixes=CHECK-ASYNC,CHECK-HEAP,CHECK-NO-STACK + +// RUN: %clang -### --target=aarch64-linux-android -march=armv8+memtag \ +// RUN: -fsanitize=memtag -fsanitize-memtag-mode=sync %s 2>&1 \ +// RUN: | FileCheck %s --check-prefixes=CHECK-SYNC,CHECK-HEAP,CHECK-STACK + +// RUN: %clang -### --target=aarch64-linux-android -march=armv8+memtag \ +// RUN: -fsanitize=memtag-stack -fsanitize-memtag-mode=sync %s 2>&1 \ +// RUN: | FileCheck %s --check-prefixes=CHECK-SYNC,CHECK-NO-HEAP,CHECK-STACK + +// RUN: %clang -### --target=aarch64-linux-android -march=armv8+memtag \ +// RUN: -fsanitize=memtag-heap -fsanitize-memtag-mode=sync %s 2>&1 \ +// RUN: | FileCheck %s --check-prefixes=CHECK-SYNC,CHECK-HEAP,CHECK-NO-STACK + +// RUN: %clang -### --target=aarch64-linux-android -march=armv8+memtag \ +// RUN: -fsanitize=memtag-heap -fsanitize-memtag-mode=asymm %s 2>&1 \ +// RUN: | FileCheck %s --check-prefixes=CHECK-INVALID-MODE + +// RUN: %clang -### --target=aarch64-linux-android -march=armv8+memtag \ +// RUN: -fsanitize=memtag-stack -fsanitize=memtag-heap \ +// RUN: -fsanitize-memtag-mode=asymm -fno-sanitize=memtag %s 2>&1 \ +// RUN: | FileCheck %s --check-prefixes=CHECK-NONE + +// CHECK-ASYNC: ld{{.*}} "-memtag-mode=async" +// CHECK-SYNC: ld{{.*}} "-memtag-mode=sync" +// CHECK-HEAP: "-memtag-heap" +// CHECK-NO-HEAP-NOT: "-memtag-heap" +// CHECK-STACK: "-memtag-stack" +// CHECK-NO-STACK-NOT: "-memtag-stack" +// CHECK-INVALID-MODE: memtag-mode +// CHECK-INVALID-MODE-SAME: expected one of: {async, sync} +// CHECK-NONE-NOT: ld{{.*}} "-memtag + +void f() {} diff --git a/clang/test/Driver/memtag.c b/clang/test/Driver/memtag-stack.c rename from clang/test/Driver/memtag.c rename to clang/test/Driver/memtag-stack.c diff --git a/clang/test/Lexer/has_feature_memtag_heap.cpp b/clang/test/Lexer/has_feature_memtag_heap.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Lexer/has_feature_memtag_heap.cpp @@ -0,0 +1,13 @@ +// RUN: %clang_cc1 -E -fsanitize=memtag-heap %s -o - | FileCheck --check-prefix=CHECK-MEMTAG %s +// RUN: %clang -E -fsanitize=memtag --target=aarch64-unknown-linux -march=armv8a+memtag %s -o - \ +// RUN: | FileCheck --check-prefix=CHECK-MEMTAG %s +// RUN: %clang_cc1 -E %s -o - | FileCheck --check-prefix=CHECK-NO-MEMTAG %s + +#if __has_feature(memtag_heap) +int MemTagSanitizerEnabled(); +#else +int MemTagSanitizerDisabled(); +#endif + +// CHECK-MEMTAG: MemTagSanitizerEnabled +// CHECK-NO-MEMTAG: MemTagSanitizerDisabled diff --git a/clang/test/Lexer/has_feature_memtag_sanitizer.cpp b/clang/test/Lexer/has_feature_memtag_sanitizer.cpp deleted file mode 100644 --- a/clang/test/Lexer/has_feature_memtag_sanitizer.cpp +++ /dev/null @@ -1,11 +0,0 @@ -// RUN: %clang_cc1 -E -fsanitize=memtag %s -o - | FileCheck --check-prefix=CHECK-MEMTAG %s -// RUN: %clang_cc1 -E %s -o - | FileCheck --check-prefix=CHECK-NO-MEMTAG %s - -#if __has_feature(memtag_sanitizer) -int MemTagSanitizerEnabled(); -#else -int MemTagSanitizerDisabled(); -#endif - -// CHECK-MEMTAG: MemTagSanitizerEnabled -// CHECK-NO-MEMTAG: MemTagSanitizerDisabled diff --git a/clang/test/Lexer/has_feature_memtag_stack.cpp b/clang/test/Lexer/has_feature_memtag_stack.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Lexer/has_feature_memtag_stack.cpp @@ -0,0 +1,13 @@ +// RUN: %clang_cc1 -E -fsanitize=memtag-stack %s -o - | FileCheck --check-prefix=CHECK-MEMTAG %s +// RUN: %clang -E -fsanitize=memtag --target=aarch64-unknown-linux -march=armv8a+memtag %s -o - \ +// RUN: | FileCheck --check-prefix=CHECK-MEMTAG %s +// RUN: %clang_cc1 -E %s -o - | FileCheck --check-prefix=CHECK-NO-MEMTAG %s + +#if __has_feature(memtag_stack) +int MemTagSanitizerEnabled(); +#else +int MemTagSanitizerDisabled(); +#endif + +// CHECK-MEMTAG: MemTagSanitizerEnabled +// CHECK-NO-MEMTAG: MemTagSanitizerDisabled diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -341,6 +341,18 @@ // 4 for ELF32, 8 for ELF64. int wordsize; + + // Mode of MTE to write to the ELF note. Should be one of NT_MEMTAG_ASYNC (for + // async), NT_MEMTAG_SYNC (for sync), or NT_MEMTAG_DEFAULT (for none). If + // async or sync is enabled, write the ELF note specifying the default MTE + // mode. + int memtagMode; + // Signal to the dynamic loader to enable heap MTE. + bool memtagHeap; + // Signal to the dynamic loader that this binary expects stack MTE. Generally, + // this means to map the primary and thread stacks as PROT_MTE. Note: This is + // not supported on Android 11 & 12. + bool memtagStack; }; // The only instance of Configuration struct. diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -678,6 +678,28 @@ return arg->getValue(); } +static int getMemtagMode(opt::InputArgList &args) { + StringRef memtagModeArg = args.getLastArgValue(OPT_memtag_mode); + int memtagMode = ELF::NT_MEMTAG_LEVEL_DEFAULT; + if (memtagModeArg == "async") { + memtagMode = ELF::NT_MEMTAG_LEVEL_ASYNC; + } else if (memtagModeArg == "sync") { + memtagMode = ELF::NT_MEMTAG_LEVEL_SYNC; + } else if (memtagModeArg == "none" || memtagModeArg.empty()) { + if (config->memtagStack || config->memtagHeap) + error("When using --memtag-stack or --memtag-heap, a --memtag-mode value " + "is required."); + } else { + error("--memtag-mode value of \"" + memtagModeArg + + "\" invalid. Should be one of {async, sync, none}."); + } + if (memtagMode != ELF::NT_MEMTAG_LEVEL_DEFAULT && + !(config->memtagHeap || config->memtagStack)) + error("When using --memtag-mode, at least one of --memtag-heap or " + "--memtag-stack is required."); + return memtagMode; +} + static ICFLevel getICF(opt::InputArgList &args) { auto *arg = args.getLastArg(OPT_icf_none, OPT_icf_safe, OPT_icf_all); if (!arg || arg->getOption().getID() == OPT_icf_none) @@ -1066,6 +1088,10 @@ args.hasFlag(OPT_lto_unique_basic_block_section_names, OPT_no_lto_unique_basic_block_section_names, false); config->mapFile = args.getLastArgValue(OPT_Map); + config->memtagHeap = args.hasFlag(OPT_memtag_heap, OPT_no_memtag_heap, false); + config->memtagStack = + args.hasFlag(OPT_memtag_stack, OPT_no_memtag_stack, false); + config->memtagMode = getMemtagMode(args); config->mipsGotSize = args::getInteger(args, OPT_mips_got_size, 0xfff0); config->mergeArmExidx = args.hasFlag(OPT_merge_exidx_entries, OPT_no_merge_exidx_entries, true); diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -719,3 +719,15 @@ "Perform additional validation of the written dynamic relocations", "Do not perform additional validation of the written dynamic relocations">, Flags<[HelpHidden]>; + +// Hidden options, used by clang's -fsanitize=memtag-* options to emit an ELF +// note to designate what kinds of memory (stack/heap) should be protected using +// ARM's MTE on armv8.5+. A binary's desire for stack MTE can't be obtained +// implicitly, so we have a specific bit in the note to signal to the loader to +// remap the stack as PROT_MTE. +defm memtag_stack: B<"memtag-stack", + "Instruct the dynamic loader to prepare for MTE stack instrumentation", "">; +defm memtag_heap: B<"memtag-heap", + "Instruct the dynamic loader to enable MTE protection for the heap", "">; +defm memtag_mode: Eq<"memtag-mode", + "Instruct the dynamic loader to start under MTE mode {async, sync, none}">; diff --git a/lld/ELF/SyntheticSections.h b/lld/ELF/SyntheticSections.h --- a/lld/ELF/SyntheticSections.h +++ b/lld/ELF/SyntheticSections.h @@ -1188,6 +1188,15 @@ void writeTo(uint8_t *buf) override; }; +class MemtagAndroidNote : public SyntheticSection { +public: + MemtagAndroidNote() + : SyntheticSection(llvm::ELF::SHF_ALLOC, llvm::ELF::SHT_NOTE, + /* alignment=*/4, ".note.android.memtag") {} + void writeTo(uint8_t *buf) override; + size_t getSize() const override; +}; + InputSection *createInterpSection(); MergeInputSection *createCommentSection(); template void splitSections(); @@ -1218,6 +1227,7 @@ std::unique_ptr ehFrame; std::unique_ptr gnuHashTab; std::unique_ptr hashTab; + std::unique_ptr memtagAndroidNote; std::unique_ptr relaDyn; std::unique_ptr relrDyn; std::unique_ptr verDef; diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp --- a/lld/ELF/SyntheticSections.cpp +++ b/lld/ELF/SyntheticSections.cpp @@ -29,6 +29,7 @@ #include "llvm/ADT/SetOperations.h" #include "llvm/ADT/StringExtras.h" #include "llvm/BinaryFormat/Dwarf.h" +#include "llvm/BinaryFormat/ELF.h" #include "llvm/DebugInfo/DWARF/DWARFDebugPubTable.h" #include "llvm/Object/ELFObjectFile.h" #include "llvm/Support/Compression.h" @@ -3838,6 +3839,36 @@ symTabShndx.reset(); } +constexpr char kMemtagAndroidNoteName[] = "Android"; +void MemtagAndroidNote::writeTo(uint8_t *buf) { + assert(sizeof(kMemtagAndroidNoteName) == 8); // ABI check for Android 11 & 12. + assert((config->memtagStack || config->memtagHeap) && + "Should only be synthesizing a note if heap || stack is enabled."); + + write32(buf, sizeof(kMemtagAndroidNoteName)); // nhdr.n_namesz + write32(buf + 4, sizeof(uint32_t)); // nhdr.n_descsz + write32(buf + 8, llvm::ELF::NT_ANDROID_TYPE_MEMTAG); // nhdr.n_type + memcpy(buf + 12, kMemtagAndroidNoteName, // name string + sizeof(kMemtagAndroidNoteName)); + buf += 12 + sizeof(kMemtagAndroidNoteName); + + uint32_t value = 0; + value |= config->memtagMode; + if (config->memtagHeap) + value |= llvm::ELF::NT_MEMTAG_HEAP; + // Note, MTE stack is an ABI break. Attempting to run an MTE stack-enabled + // binary on Android 11 or 12 will result in a checkfail in the loader. + if (config->memtagStack) + value |= llvm::ELF::NT_MEMTAG_STACK; + write32(buf, value); // note value +} + +size_t MemtagAndroidNote::getSize() const { + return sizeof(llvm::ELF::Elf64_Nhdr) + + /* namesz=*/sizeof(kMemtagAndroidNoteName) + + /* descsz=*/sizeof(uint32_t); +} + InStruct elf::in; std::vector elf::partitions; diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp --- a/lld/ELF/Writer.cpp +++ b/lld/ELF/Writer.cpp @@ -362,6 +362,13 @@ part.dynSymTab = std::make_unique>(*part.dynStrTab); part.dynamic = std::make_unique>(); + + if (config->emachine == EM_AARCH64 && + config->memtagMode != ELF::NT_MEMTAG_LEVEL_DEFAULT) { + part.memtagAndroidNote = std::make_unique(); + add(*part.memtagAndroidNote); + } + if (config->androidPackDynRelocs) part.relaDyn = std::make_unique>(relaDynName); diff --git a/lld/test/ELF/aarch64-memtag-android-abi.s b/lld/test/ELF/aarch64-memtag-android-abi.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/aarch64-memtag-android-abi.s @@ -0,0 +1,78 @@ +# Old versions of Android (Android 11 & 12) have very strict parsing logic on +# the layout of the ELF note. This test serves as a "hey, you're going to break +# the ABI" check. Basically below is the copied version of the Android parsing +# logic. We create a no-op aarch64 binary with an elf note, and then consume it +# using the parsing logic on the host system. Because we don't pull in any +# libraries or headers, this should be runnable on any system that uses linux +# (technically, any system that can parse ELF, but I'm not rewriting it in +# python to run on Windows...). Note that MTE stack is an ABI break, so we +# expect it to crash under this parsing logic. + +# RUN: llvm-mc --filetype=obj -triple=aarch64-linux-none-gnu %s -o %t.o +# RUN: ld.lld --memtag-mode=async --memtag-heap %t.o -o %t +# RUN: llvm-readelf -n %t | \ +# RUN: FileCheck %s --check-prefixes=NOTE,HEAP-ASYNC-NOTE + +# RUN: ld.lld --memtag-mode=sync --memtag-heap %t.o -o %t +# RUN: llvm-readelf -n %t | \ +# RUN: FileCheck %s --check-prefixes=NOTE,HEAP-SYNC-NOTE + +# RUN: ld.lld --memtag-mode=async --memtag-stack %t.o -o %t +# RUN: llvm-readelf -n %t | \ +# RUN: FileCheck %s --check-prefixes=NOTE,STACK-ASYNC + +# RUN: ld.lld --memtag-mode=sync --memtag-stack %t.o -o %t +# RUN: llvm-readelf -n %t | \ +# RUN: FileCheck %s --check-prefixes=NOTE,STACK-SYNC + +# RUN: ld.lld --memtag-mode=async --memtag-heap --memtag-stack %t.o -o %t +# RUN: llvm-readelf -n %t | \ +# RUN: FileCheck %s --check-prefixes=NOTE,BOTH-ASYNC + +# RUN: ld.lld --memtag-mode=sync --memtag-heap --memtag-stack %t.o -o %t +# RUN: llvm-readelf -n %t | \ +# RUN: FileCheck %s --check-prefixes=NOTE,BOTH-SYNC + +# NOTE: .note.android.memtag +# NOTE-NEXT: Owner +# TODO(hctim): Update llvm-readelf to understand this note type. +# NOTE-NEXT: Android 0x00000004 Unknown note type: (0x00000004) + +# The next few lines are an ABI enforcement for Android 11 and 12. If you're +# compiling with just heap MTE (i.e just --memtag-mode), then these lines MUST +# be unchanging. +# HEAP-ASYNC-NOTE-NEXT: description data: 05 00 00 00 +# HEAP-SYNC-NOTE-NEXT: description data: 06 00 00 00 + +# Stack MTE is, as of Android 12, unimplemented. However, we pre-emptively emit +# a bit that signifies to the dynamic loader to map the primary and thread +# stacks as PROT_MTE, in preparation for the bionic support. +# STACK-ASYNC-NEXT: description data: 09 00 00 00 +# STACK-SYNC-NEXT: description data: 0a 00 00 00 + +# Stack MTE is, as of Android 12, unimplemented. However, we pre-emptively emit +# a bit that signifies to the dynamic loader to map the primary and thread +# stacks as PROT_MTE, in preparation for the bionic support. +# BOTH-ASYNC-NEXT: description data: 0d 00 00 00 +# BOTH-SYNC-NEXT: description data: 0e 00 00 00 + +# RUN: not ld.lld --memtag-stack 2>&1 | \ +# RUN: FileCheck %s --check-prefix=MISSING-MODE +# RUN: not ld.lld --memtag-heap 2>&1 | \ +# RUN: FileCheck %s --check-prefix=MISSING-MODE + +# MISSING-MODE: When using --memtag-stack or --memtag-heap, +# MISSING-MODE-SAME: a --memtag-mode value is required. + +# RUN: not ld.lld --memtag-mode=asymm 2>&1 | \ +# RUN: FileCheck %s --check-prefix=BAD-MODE +# BAD-MODE: --memtag-mode value of "asymm" invalid + +# RUN: not ld.lld --memtag-mode=async 2>&1 | \ +# RUN: FileCheck %s --check-prefix=MISSING-STACK-OR-HEAP +# MISSING-STACK-OR-HEAP: When using --memtag-mode, at least one of +# MISSING-STACK-OR-HEAP-SAME: --memtag-heap or --memtag-stack is required. + +.globl _start +_start: + ret diff --git a/llvm/include/llvm/BinaryFormat/ELF.h b/llvm/include/llvm/BinaryFormat/ELF.h --- a/llvm/include/llvm/BinaryFormat/ELF.h +++ b/llvm/include/llvm/BinaryFormat/ELF.h @@ -1531,6 +1531,22 @@ NT_GNU_PROPERTY_TYPE_0 = 5, }; +// Android note types. +enum { + NT_ANDROID_TYPE_IDENT = 1, + NT_ANDROID_TYPE_KUSER = 3, + NT_ANDROID_TYPE_MEMTAG = 4, +}; + +enum { + NT_MEMTAG_LEVEL_DEFAULT = 0, + NT_MEMTAG_LEVEL_ASYNC = 1, + NT_MEMTAG_LEVEL_SYNC = 2, + NT_MEMTAG_LEVEL_MASK = 3, + NT_MEMTAG_HEAP = 4, + NT_MEMTAG_STACK = 8, +}; + // Property types used in GNU_PROPERTY_TYPE_0 notes. enum : unsigned { GNU_PROPERTY_STACK_SIZE = 1,