Basic support for MTE stack tagging.

Map all stacks (primary, thread, and sigaltstack) as PROT_MTE when the
binary requests it through the ELF note.

For the reference, the note is produced by the following toolchain changes:
https://reviews.llvm.org/D118948
https://reviews.llvm.org/D119384
https://reviews.llvm.org/D119381

Bug: b/174878242
Test: fvp_mini with ToT LLVM (more tests in a separate change)

Change-Id: I04a4e21c966e7309b47b1f549a2919958d93a872
This commit is contained in:
Evgenii Stepanov 2022-05-12 15:54:38 -07:00
parent 3d425bb8fb
commit f9fa32acf3
6 changed files with 90 additions and 50 deletions

View file

@ -44,30 +44,37 @@ void SetDefaultHeapTaggingLevel() {
#if !__has_feature(hwaddress_sanitizer)
heap_tagging_level = __libc_shared_globals()->initial_heap_tagging_level;
#endif
switch (heap_tagging_level) {
case M_HEAP_TAGGING_LEVEL_TBI:
__libc_globals.mutate([](libc_globals* globals) {
__libc_globals.mutate([](libc_globals* globals) {
switch (heap_tagging_level) {
case M_HEAP_TAGGING_LEVEL_TBI:
// Arrange for us to set pointer tags to POINTER_TAG, check tags on
// deallocation and untag when passing pointers to the allocator.
globals->heap_pointer_tag = (reinterpret_cast<uintptr_t>(POINTER_TAG) << TAG_SHIFT) |
(0xffull << CHECK_SHIFT) | (0xffull << UNTAG_SHIFT);
});
#if defined(USE_SCUDO)
scudo_malloc_disable_memory_tagging();
#endif // USE_SCUDO
break;
#if defined(USE_SCUDO)
case M_HEAP_TAGGING_LEVEL_SYNC:
scudo_malloc_set_track_allocation_stacks(1);
break;
break;
case M_HEAP_TAGGING_LEVEL_SYNC:
case M_HEAP_TAGGING_LEVEL_ASYNC:
atomic_store(&globals->memtag_stack, __libc_shared_globals()->initial_memtag_stack);
break;
default:
break;
};
});
#if defined(USE_SCUDO)
switch (heap_tagging_level) {
case M_HEAP_TAGGING_LEVEL_TBI:
case M_HEAP_TAGGING_LEVEL_NONE:
scudo_malloc_disable_memory_tagging();
break;
#endif // USE_SCUDO
case M_HEAP_TAGGING_LEVEL_SYNC:
scudo_malloc_set_track_allocation_stacks(1);
break;
default:
break;
}
#endif // USE_SCUDO
#endif // aarch64
}
@ -104,16 +111,21 @@ bool SetHeapTaggingLevel(HeapTaggingLevel tag_level) {
switch (tag_level) {
case M_HEAP_TAGGING_LEVEL_NONE:
if (heap_tagging_level == M_HEAP_TAGGING_LEVEL_TBI) {
__libc_globals.mutate([](libc_globals* globals) {
__libc_globals.mutate([](libc_globals* globals) {
if (heap_tagging_level == M_HEAP_TAGGING_LEVEL_TBI) {
// Preserve the untag mask (we still want to untag pointers when passing them to the
// allocator), but clear the fixed tag and the check mask, so that pointers are no longer
// tagged and checks no longer happen.
globals->heap_pointer_tag = static_cast<uintptr_t>(0xffull << UNTAG_SHIFT);
});
} else if (!set_tcf_on_all_threads(PR_MTE_TCF_NONE)) {
error_log("SetHeapTaggingLevel: set_tcf_on_all_threads failed");
return false;
}
atomic_store(&globals->memtag_stack, false);
});
if (heap_tagging_level != M_HEAP_TAGGING_LEVEL_TBI) {
if (!set_tcf_on_all_threads(PR_MTE_TCF_NONE)) {
error_log("SetHeapTaggingLevel: set_tcf_on_all_threads failed");
return false;
}
}
#if defined(USE_SCUDO)
scudo_malloc_disable_memory_tagging();

View file

@ -259,17 +259,18 @@ static bool get_environment_memtag_setting(HeapTaggingLevel* level) {
// M_HEAP_TAGGING_LEVEL_NONE, if MTE isn't enabled for this process we enable
// M_HEAP_TAGGING_LEVEL_TBI.
static HeapTaggingLevel __get_heap_tagging_level(const void* phdr_start, size_t phdr_ct,
uintptr_t load_bias) {
uintptr_t load_bias, bool* stack) {
unsigned note_val =
__get_memtag_note(reinterpret_cast<const ElfW(Phdr)*>(phdr_start), phdr_ct, load_bias);
*stack = note_val & NT_MEMTAG_STACK;
HeapTaggingLevel level;
if (get_environment_memtag_setting(&level)) return level;
unsigned note_val =
__get_memtag_note(reinterpret_cast<const ElfW(Phdr)*>(phdr_start), phdr_ct, load_bias);
// Note, previously (in Android 12), any value outside of bits [0..3] resulted
// in a check-fail. In order to be permissive of further extensions, we
// relaxed this restriction. For now, we still only support MTE heap.
if (!(note_val & NT_MEMTAG_HEAP)) return M_HEAP_TAGGING_LEVEL_TBI;
// relaxed this restriction.
if (!(note_val & (NT_MEMTAG_HEAP | NT_MEMTAG_STACK))) return M_HEAP_TAGGING_LEVEL_TBI;
unsigned mode = note_val & NT_MEMTAG_LEVEL_MASK;
switch (mode) {
@ -295,8 +296,10 @@ static HeapTaggingLevel __get_heap_tagging_level(const void* phdr_start, size_t
// This function is called from the linker before the main executable is relocated.
__attribute__((no_sanitize("hwaddress", "memtag"))) void __libc_init_mte(const void* phdr_start,
size_t phdr_ct,
uintptr_t load_bias) {
HeapTaggingLevel level = __get_heap_tagging_level(phdr_start, phdr_ct, load_bias);
uintptr_t load_bias,
void* stack_top) {
bool memtag_stack;
HeapTaggingLevel level = __get_heap_tagging_level(phdr_start, phdr_ct, load_bias, &memtag_stack);
if (level == M_HEAP_TAGGING_LEVEL_SYNC || level == M_HEAP_TAGGING_LEVEL_ASYNC) {
unsigned long prctl_arg = PR_TAGGED_ADDR_ENABLE | PR_MTE_TAG_SET_NONZERO;
@ -308,6 +311,17 @@ __attribute__((no_sanitize("hwaddress", "memtag"))) void __libc_init_mte(const v
if (prctl(PR_SET_TAGGED_ADDR_CTRL, prctl_arg | PR_MTE_TCF_SYNC, 0, 0, 0) == 0 ||
prctl(PR_SET_TAGGED_ADDR_CTRL, prctl_arg, 0, 0, 0) == 0) {
__libc_shared_globals()->initial_heap_tagging_level = level;
__libc_shared_globals()->initial_memtag_stack = memtag_stack;
if (memtag_stack) {
void* page_start =
reinterpret_cast<void*>(PAGE_START(reinterpret_cast<uintptr_t>(stack_top)));
if (mprotect(page_start, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_MTE | PROT_GROWSDOWN)) {
async_safe_fatal("error: failed to set PROT_MTE on main thread stack: %s\n",
strerror(errno));
}
}
return;
}
}
@ -319,7 +333,7 @@ __attribute__((no_sanitize("hwaddress", "memtag"))) void __libc_init_mte(const v
}
}
#else // __aarch64__
void __libc_init_mte(const void*, size_t, uintptr_t) {}
void __libc_init_mte(const void*, size_t, uintptr_t, void*) {}
#endif // __aarch64__
void __libc_init_profiling_handlers() {
@ -331,11 +345,9 @@ void __libc_init_profiling_handlers() {
signal(BIONIC_SIGNAL_ART_PROFILER, SIG_IGN);
}
__noreturn static void __real_libc_init(void *raw_args,
void (*onexit)(void) __unused,
int (*slingshot)(int, char**, char**),
structors_array_t const * const structors,
bionic_tcb* temp_tcb) {
__attribute__((no_sanitize("memtag"))) __noreturn static void __real_libc_init(
void* raw_args, void (*onexit)(void) __unused, int (*slingshot)(int, char**, char**),
structors_array_t const* const structors, bionic_tcb* temp_tcb) {
BIONIC_STOP_UNWIND;
// Initialize TLS early so system calls and errno work.
@ -349,7 +361,7 @@ __noreturn static void __real_libc_init(void *raw_args,
__libc_init_main_thread_final();
__libc_init_common();
__libc_init_mte(reinterpret_cast<ElfW(Phdr)*>(getauxval(AT_PHDR)), getauxval(AT_PHNUM),
/*load_bias = */ 0);
/*load_bias = */ 0, /*stack_top = */ raw_args);
__libc_init_scudo();
__libc_init_profiling_handlers();
__libc_init_fork_handler();
@ -379,11 +391,9 @@ extern "C" void __hwasan_init_static();
//
// The 'structors' parameter contains pointers to various initializer
// arrays that must be run before the program's 'main' routine is launched.
__attribute__((no_sanitize("hwaddress")))
__noreturn void __libc_init(void* raw_args,
void (*onexit)(void) __unused,
int (*slingshot)(int, char**, char**),
structors_array_t const * const structors) {
__attribute__((no_sanitize("hwaddress", "memtag"))) __noreturn void __libc_init(
void* raw_args, void (*onexit)(void) __unused, int (*slingshot)(int, char**, char**),
structors_array_t const* const structors) {
bionic_tcb temp_tcb = {};
#if __has_feature(hwaddress_sanitizer)
// Install main thread TLS early. It will be initialized later in __libc_init_main_thread. For now

View file

@ -40,15 +40,16 @@
#include <async_safe/log.h>
#include "platform/bionic/macros.h"
#include "platform/bionic/mte.h"
#include "private/ErrnoRestorer.h"
#include "private/ScopedRWLock.h"
#include "private/bionic_constants.h"
#include "private/bionic_defs.h"
#include "private/bionic_globals.h"
#include "platform/bionic/macros.h"
#include "private/bionic_ssp.h"
#include "private/bionic_systrace.h"
#include "private/bionic_tls.h"
#include "private/ErrnoRestorer.h"
// x86 uses segment descriptors rather than a direct pointer to TLS.
#if defined(__i386__)
@ -88,7 +89,13 @@ void __free_temp_bionic_tls(bionic_tls* tls) {
static void __init_alternate_signal_stack(pthread_internal_t* thread) {
// Create and set an alternate signal stack.
void* stack_base = mmap(nullptr, SIGNAL_STACK_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
int prot = PROT_READ | PROT_WRITE;
#ifdef __aarch64__
if (atomic_load(&__libc_globals->memtag_stack)) {
prot |= PROT_MTE;
}
#endif
void* stack_base = mmap(nullptr, SIGNAL_STACK_SIZE, prot, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (stack_base != MAP_FAILED) {
// Create a guard to catch stack overflows in signal handlers.
if (mprotect(stack_base, PTHREAD_GUARD_SIZE, PROT_NONE) == -1) {
@ -224,12 +231,19 @@ ThreadMapping __allocate_thread_mapping(size_t stack_size, size_t stack_guard_si
return {};
}
const size_t writable_size = mmap_size - stack_guard_size - PTHREAD_GUARD_SIZE;
if (mprotect(space + stack_guard_size,
writable_size,
PROT_READ | PROT_WRITE) != 0) {
async_safe_format_log(ANDROID_LOG_WARN, "libc",
"pthread_create failed: couldn't mprotect R+W %zu-byte thread mapping region: %s",
writable_size, strerror(errno));
int prot = PROT_READ | PROT_WRITE;
const char* prot_str = "R+W";
#ifdef __aarch64__
if (atomic_load(&__libc_globals->memtag_stack)) {
prot |= PROT_MTE;
prot_str = "R+W+MTE";
}
#endif
if (mprotect(space + stack_guard_size, writable_size, prot) != 0) {
async_safe_format_log(
ANDROID_LOG_WARN, "libc",
"pthread_create failed: couldn't mprotect %s %zu-byte thread mapping region: %s", prot_str,
writable_size, strerror(errno));
munmap(space, mmap_size);
return {};
}

View file

@ -76,3 +76,4 @@
#define NT_MEMTAG_LEVEL_ASYNC 1
#define NT_MEMTAG_LEVEL_SYNC 2
#define NT_MEMTAG_HEAP 4
#define NT_MEMTAG_STACK 8

View file

@ -47,6 +47,7 @@ struct libc_globals {
vdso_entry vdso[VDSO_END];
long setjmp_cookie;
uintptr_t heap_pointer_tag;
_Atomic(bool) memtag_stack;
// In order to allow a complete switch between dispatch tables without
// the need for copying each function by function in the structure,
@ -112,6 +113,7 @@ struct libc_shared_globals {
const char* scudo_ring_buffer = nullptr;
HeapTaggingLevel initial_heap_tagging_level = M_HEAP_TAGGING_LEVEL_NONE;
bool initial_memtag_stack = false;
};
__LIBC_HIDDEN__ libc_shared_globals* __libc_shared_globals();

View file

@ -68,7 +68,8 @@ static void get_elf_base_from_phdr(const ElfW(Phdr)* phdr_table, size_t phdr_cou
static void set_bss_vma_name(soinfo* si);
void __libc_init_mte(const void* phdr_start, size_t phdr_count, uintptr_t load_bias);
void __libc_init_mte(const void* phdr_start, size_t phdr_count, uintptr_t load_bias,
void* stack_top);
// These should be preserved static to avoid emitting
// RELATIVE relocations for the part of the code running
@ -408,7 +409,7 @@ static ElfW(Addr) linker_main(KernelArgumentBlock& args, const char* exe_to_load
}
}
__libc_init_mte(somain->phdr, somain->phnum, somain->load_bias);
__libc_init_mte(somain->phdr, somain->phnum, somain->load_bias, args.argv);
#endif
// Register the main executable and the linker upfront to have