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_LEVEL_NONE (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,27 @@ return arg->getValue(); } +static int getMemtagMode(opt::InputArgList &args) { + StringRef memtagModeArg = args.getLastArgValue(OPT_memtag_mode); + if (!config->memtagHeap && !config->memtagStack) { + if (!memtagModeArg.empty()) + error("when using --memtag-mode, at least one of --memtag-heap or " + "--memtag-stack is required"); + return ELF::NT_MEMTAG_LEVEL_NONE; + } + + if (memtagModeArg == "sync" || memtagModeArg.empty()) + return ELF::NT_MEMTAG_LEVEL_SYNC; + if (memtagModeArg == "async") + return ELF::NT_MEMTAG_LEVEL_ASYNC; + if (memtagModeArg == "none") + return ELF::NT_MEMTAG_LEVEL_NONE; + + error("--memtag-mode value of \"" + memtagModeArg + + "\" invalid, should be one of {async, sync, none}"); + return ELF::NT_MEMTAG_LEVEL_NONE; +} + 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 +1087,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: BB<"memtag-stack", + "Instruct the dynamic loader to prepare for MTE stack instrumentation", "">; +defm memtag_heap: BB<"memtag-heap", + "Instruct the dynamic loader to enable MTE protection for the heap", "">; +defm memtag_mode: EEq<"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_NONE) { + 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,75 @@ +# REQUIRES: aarch64 + +## 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,NOSTACK,ASYNC + +# RUN: ld.lld --memtag-mode=sync --memtag-heap %t.o -o %t +# RUN: llvm-readelf -n %t | \ +# RUN: FileCheck %s --check-prefixes=NOTE,HEAP,NOSTACK,SYNC + +# RUN: ld.lld --memtag-mode=async --memtag-stack %t.o -o %t +# RUN: llvm-readelf -n %t | \ +# RUN: FileCheck %s --check-prefixes=NOTE,NOHEAP,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,NOHEAP,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,HEAP,STACK,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,HEAP,STACK,SYNC + +# RUN: ld.lld --memtag-heap %t.o -o %t +# RUN: llvm-readelf -n %t | \ +# RUN: FileCheck %s --check-prefixes=NOTE,HEAP,NOSTACK,SYNC + +# RUN: ld.lld --memtag-stack %t.o -o %t +# RUN: llvm-readelf -n %t | \ +# RUN: FileCheck %s --check-prefixes=NOTE,NOHEAP,STACK,SYNC + +# RUN: ld.lld --memtag-heap --memtag-stack %t.o -o %t +# RUN: llvm-readelf -n %t | \ +# RUN: FileCheck %s --check-prefixes=NOTE,HEAP,STACK,SYNC + +# NOTE: .note.android.memtag +# NOTE-NEXT: Owner +# NOTE-NEXT: Android 0x00000004 NT_ANDROID_TYPE_MEMTAG (Android memory tagging +# NOTE-SAME: information) +# ASYNC-NEXT: Tagging Mode: ASYNC +# SYNC-NEXT: Tagging Mode: SYNC +# HEAP-NEXT: Heap: Enabled +# NOHEAP-NEXT: Heap: Disabled +## 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-NEXT: Stack: Enabled +# NOSTACK-NEXT: Stack: Disabled + +# RUN: not ld.lld --memtag-mode=asymm --memtag-heap 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