Merge changes Ifbb55372,I26f5833b,I19ec542e,Ibe203918 into main
* changes:
Implement deterministic MTE globals for dlext RELRO sharing
Fix dlext tests for MTE globals
Re-land tests for MTE globals
Re-land linker support for MTE globals
diff --git a/libc/bionic/__libc_init_main_thread.cpp b/libc/bionic/__libc_init_main_thread.cpp
index 1b539f2..0d557f1 100644
--- a/libc/bionic/__libc_init_main_thread.cpp
+++ b/libc/bionic/__libc_init_main_thread.cpp
@@ -44,7 +44,7 @@
// Declared in "private/bionic_ssp.h".
uintptr_t __stack_chk_guard = 0;
-static pthread_internal_t main_thread;
+BIONIC_USED_BEFORE_LINKER_RELOCATES static pthread_internal_t main_thread;
// Setup for the main thread. For dynamic executables, this is called by the
// linker _before_ libc is mapped in memory. This means that all writes to
diff --git a/libc/bionic/bionic_call_ifunc_resolver.cpp b/libc/bionic/bionic_call_ifunc_resolver.cpp
index e44d998..d5a812c 100644
--- a/libc/bionic/bionic_call_ifunc_resolver.cpp
+++ b/libc/bionic/bionic_call_ifunc_resolver.cpp
@@ -31,6 +31,7 @@
#include <sys/hwprobe.h>
#include <sys/ifunc.h>
+#include "bionic/macros.h"
#include "private/bionic_auxv.h"
// This code is called in the linker before it has been relocated, so minimize calls into other
@@ -40,8 +41,8 @@
ElfW(Addr) __bionic_call_ifunc_resolver(ElfW(Addr) resolver_addr) {
#if defined(__aarch64__)
typedef ElfW(Addr) (*ifunc_resolver_t)(uint64_t, __ifunc_arg_t*);
- static __ifunc_arg_t arg;
- static bool initialized = false;
+ BIONIC_USED_BEFORE_LINKER_RELOCATES static __ifunc_arg_t arg;
+ BIONIC_USED_BEFORE_LINKER_RELOCATES static bool initialized = false;
if (!initialized) {
initialized = true;
arg._size = sizeof(__ifunc_arg_t);
diff --git a/libc/bionic/libc_init_static.cpp b/libc/bionic/libc_init_static.cpp
index 7c46113..ae86da1 100644
--- a/libc/bionic/libc_init_static.cpp
+++ b/libc/bionic/libc_init_static.cpp
@@ -344,19 +344,12 @@
// This function is called from the linker before the main executable is relocated.
__attribute__((no_sanitize("hwaddress", "memtag"))) void __libc_init_mte(
const memtag_dynamic_entries_t* memtag_dynamic_entries, const void* phdr_start, size_t phdr_ct,
- uintptr_t load_bias, void* stack_top) {
+ uintptr_t load_bias) {
bool memtag_stack = false;
HeapTaggingLevel level =
__get_tagging_level(memtag_dynamic_entries, phdr_start, phdr_ct, load_bias, &memtag_stack);
- // initial_memtag_stack is used by the linker (in linker.cpp) to communicate than any library
- // linked by this executable enables memtag-stack.
- // memtag_stack is also set for static executables if they request memtag stack via the note,
- // in which case it will differ from initial_memtag_stack.
- if (__libc_shared_globals()->initial_memtag_stack || memtag_stack) {
- memtag_stack = true;
- __libc_shared_globals()->initial_memtag_stack_abi = true;
- __get_bionic_tcb()->tls_slot(TLS_SLOT_STACK_MTE) = __allocate_stack_mte_ringbuffer(0, nullptr);
- }
+ __libc_shared_globals()->initial_memtag_stack = memtag_stack;
+
if (int64_t timed_upgrade = __get_memtag_upgrade_secs()) {
if (level == M_HEAP_TAGGING_LEVEL_ASYNC) {
async_safe_format_log(ANDROID_LOG_INFO, "libc",
@@ -380,15 +373,7 @@
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* pg_start =
- reinterpret_cast<void*>(page_start(reinterpret_cast<uintptr_t>(stack_top)));
- if (mprotect(pg_start, page_size(), PROT_READ | PROT_WRITE | PROT_MTE | PROT_GROWSDOWN)) {
- async_safe_fatal("error: failed to set PROT_MTE on main thread stack: %m");
- }
- }
struct sigaction action = {};
action.sa_flags = SA_SIGINFO | SA_RESTART;
action.sa_sigaction = __enable_mte_signal_handler;
@@ -404,11 +389,36 @@
}
// We did not enable MTE, so we do not need to arm the upgrade timer.
__libc_shared_globals()->heap_tagging_upgrade_timer_sec = 0;
- // We also didn't enable memtag_stack.
- __libc_shared_globals()->initial_memtag_stack = false;
}
+
+// Figure out whether we need to map the stack as PROT_MTE.
+// For dynamic executables, this has to be called after loading all
+// DT_NEEDED libraries, in case one of them needs stack MTE.
+__attribute__((no_sanitize("hwaddress", "memtag"))) void __libc_init_mte_stack(void* stack_top) {
+ if (!__libc_shared_globals()->initial_memtag_stack) {
+ return;
+ }
+
+ // Even if the device doesn't support MTE, we have to allocate stack
+ // history buffers for code compiled for stack MTE. That is because the
+ // codegen expects a buffer to be present in TLS_SLOT_STACK_MTE either
+ // way.
+ __libc_shared_globals()->initial_memtag_stack_abi = true;
+ __get_bionic_tcb()->tls_slot(TLS_SLOT_STACK_MTE) = __allocate_stack_mte_ringbuffer(0, nullptr);
+
+ if (!__libc_mte_enabled()) {
+ __libc_shared_globals()->initial_memtag_stack = false;
+ return;
+ }
+ void* pg_start = reinterpret_cast<void*>(page_start(reinterpret_cast<uintptr_t>(stack_top)));
+ if (mprotect(pg_start, page_size(), PROT_READ | PROT_WRITE | PROT_MTE | PROT_GROWSDOWN)) {
+ async_safe_fatal("error: failed to set PROT_MTE on main thread stack: %m");
+ }
+}
+
#else // __aarch64__
-void __libc_init_mte(const memtag_dynamic_entries_t*, const void*, size_t, uintptr_t, void*) {}
+void __libc_init_mte(const memtag_dynamic_entries_t*, const void*, size_t, uintptr_t) {}
+void __libc_init_mte_stack(void*) {}
#endif // __aarch64__
void __libc_init_profiling_handlers() {
@@ -436,7 +446,8 @@
__libc_init_common();
__libc_init_mte(/*memtag_dynamic_entries=*/nullptr,
reinterpret_cast<ElfW(Phdr)*>(getauxval(AT_PHDR)), getauxval(AT_PHNUM),
- /*load_bias = */ 0, /*stack_top = */ raw_args);
+ /*load_bias = */ 0);
+ __libc_init_mte_stack(/*stack_top = */ raw_args);
__libc_init_scudo();
__libc_init_profiling_handlers();
__libc_init_fork_handler();
@@ -508,6 +519,11 @@
// compiled with -ffreestanding to avoid implicit string.h function calls. (It shouldn't strictly
// be necessary, though.)
__LIBC_HIDDEN__ libc_shared_globals* __libc_shared_globals() {
- static libc_shared_globals globals;
+ BIONIC_USED_BEFORE_LINKER_RELOCATES static libc_shared_globals globals;
return &globals;
}
+
+__LIBC_HIDDEN__ bool __libc_mte_enabled() {
+ HeapTaggingLevel lvl = __libc_shared_globals()->initial_heap_tagging_level;
+ return lvl == M_HEAP_TAGGING_LEVEL_SYNC || lvl == M_HEAP_TAGGING_LEVEL_ASYNC;
+}
diff --git a/libc/platform/bionic/macros.h b/libc/platform/bionic/macros.h
index 93268c1..837583e 100644
--- a/libc/platform/bionic/macros.h
+++ b/libc/platform/bionic/macros.h
@@ -97,3 +97,26 @@
static inline T* _Nonnull untag_address(T* _Nonnull p) {
return reinterpret_cast<T*>(untag_address(reinterpret_cast<uintptr_t>(p)));
}
+
+// MTE globals protects internal and external global variables. One of the main
+// things that MTE globals does is force all global variable accesses to go
+// through the GOT. In the linker though, some global variables are accessed (or
+// address-taken) prior to relocations being processed. Because relocations
+// haven't run yet, the GOT entry hasn't been populated, and this leads to
+// crashes. Thus, any globals used by the linker prior to relocation should be
+// annotated with this attribute, which suppresses tagging of this global
+// variable, restoring the pc-relative address computation.
+//
+// A way to find global variables that need this attribute is to build the
+// linker/libc with `SANITIZE_TARGET=memtag_globals`, push them onto a device
+// (it doesn't have to be MTE capable), and then run an executable using
+// LD_LIBRARY_PATH and using the linker in interpreter mode (e.g.
+// `LD_LIBRARY_PATH=/data/tmp/ /data/tmp/linker64 /data/tmp/my_binary`). A
+// good heuristic is that the global variable is in a file that should be
+// compiled with `-ffreestanding` (but there are global variables there that
+// don't need thisattribute).
+#if __has_feature(memtag_globals)
+#define BIONIC_USED_BEFORE_LINKER_RELOCATES __attribute__((no_sanitize("memtag")))
+#else // __has_feature(memtag_globals)
+#define BIONIC_USED_BEFORE_LINKER_RELOCATES
+#endif // __has_feature(memtag_globals)
diff --git a/libc/platform/bionic/mte.h b/libc/platform/bionic/mte.h
index 98b3d27..423ed0f 100644
--- a/libc/platform/bionic/mte.h
+++ b/libc/platform/bionic/mte.h
@@ -28,6 +28,7 @@
#pragma once
+#include <stddef.h>
#include <sys/auxv.h>
#include <sys/mman.h>
#include <sys/prctl.h>
@@ -49,6 +50,36 @@
return supported;
}
+inline void* get_tagged_address(const void* ptr) {
+#if defined(__aarch64__)
+ if (mte_supported()) {
+ __asm__ __volatile__(".arch_extension mte; ldg %0, [%0]" : "+r"(ptr));
+ }
+#endif // aarch64
+ return const_cast<void*>(ptr);
+}
+
+// Inserts a random tag tag to `ptr`, using any of the set lower 16 bits in
+// `mask` to exclude the corresponding tag from being generated. Note: This does
+// not tag memory. This generates a pointer to be used with set_memory_tag.
+inline void* insert_random_tag(const void* ptr, __attribute__((unused)) uint64_t mask = 0) {
+#if defined(__aarch64__)
+ if (mte_supported() && ptr) {
+ __asm__ __volatile__(".arch_extension mte; irg %0, %0, %1" : "+r"(ptr) : "r"(mask));
+ }
+#endif // aarch64
+ return const_cast<void*>(ptr);
+}
+
+// Stores the address tag in `ptr` to memory, at `ptr`.
+inline void set_memory_tag(__attribute__((unused)) void* ptr) {
+#if defined(__aarch64__)
+ if (mte_supported()) {
+ __asm__ __volatile__(".arch_extension mte; stg %0, [%0]" : "+r"(ptr));
+ }
+#endif // aarch64
+}
+
#ifdef __aarch64__
class ScopedDisableMTE {
size_t prev_tco_;
diff --git a/libc/private/bionic_globals.h b/libc/private/bionic_globals.h
index a1bebda..02713fc 100644
--- a/libc/private/bionic_globals.h
+++ b/libc/private/bionic_globals.h
@@ -157,6 +157,7 @@
};
__LIBC_HIDDEN__ libc_shared_globals* __libc_shared_globals();
+__LIBC_HIDDEN__ bool __libc_mte_enabled();
__LIBC_HIDDEN__ void __libc_init_fdsan();
__LIBC_HIDDEN__ void __libc_init_fdtrack();
__LIBC_HIDDEN__ void __libc_init_profiling_handlers();
diff --git a/linker/Android.bp b/linker/Android.bp
index a06ca29..847a9b2 100644
--- a/linker/Android.bp
+++ b/linker/Android.bp
@@ -108,6 +108,12 @@
// We need to access Bionic private headers in the linker.
include_dirs: ["bionic/libc"],
+
+ sanitize: {
+ // Supporting memtag_globals in the linker would be tricky,
+ // because it relocates itself very early.
+ memtag_globals: false,
+ },
}
// ========================================================
diff --git a/linker/dlfcn.cpp b/linker/dlfcn.cpp
index fee19f4..82f2728 100644
--- a/linker/dlfcn.cpp
+++ b/linker/dlfcn.cpp
@@ -331,6 +331,7 @@
__libdl_info->gnu_bloom_filter_ = linker_si.gnu_bloom_filter_;
__libdl_info->gnu_bucket_ = linker_si.gnu_bucket_;
__libdl_info->gnu_chain_ = linker_si.gnu_chain_;
+ __libdl_info->memtag_dynamic_entries_ = linker_si.memtag_dynamic_entries_;
__libdl_info->ref_count_ = 1;
__libdl_info->strtab_size_ = linker_si.strtab_size_;
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 88d02dc..ed3f121 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -51,6 +51,7 @@
#include <android-base/scopeguard.h>
#include <async_safe/log.h>
#include <bionic/pthread_internal.h>
+#include <platform/bionic/mte.h>
// Private C library headers.
@@ -1697,11 +1698,19 @@
}
}
+ // The WebView loader uses RELRO sharing in order to promote page sharing of the large RELRO
+ // segment, as it's full of C++ vtables. Because MTE globals, by default, applies random tags to
+ // each global variable, the RELRO segment is polluted and unique for each process. In order to
+ // allow sharing, but still provide some protection, we use deterministic global tagging schemes
+ // for DSOs that are loaded through android_dlopen_ext, such as those loaded by WebView.
+ bool deterministic_memtag_globals =
+ extinfo && extinfo->flags & (ANDROID_DLEXT_WRITE_RELRO | ANDROID_DLEXT_USE_RELRO);
+
// Step 3: pre-link all DT_NEEDED libraries in breadth first order.
bool any_memtag_stack = false;
for (auto&& task : load_tasks) {
soinfo* si = task->get_soinfo();
- if (!si->is_linked() && !si->prelink_image()) {
+ if (!si->is_linked() && !si->prelink_image(deterministic_memtag_globals)) {
return false;
}
// si->memtag_stack() needs to be called after si->prelink_image() which populates
@@ -2361,7 +2370,7 @@
void* tls_block = get_tls_block_for_this_thread(tls_module, /*should_alloc=*/true);
*symbol = static_cast<char*>(tls_block) + sym->st_value;
} else {
- *symbol = reinterpret_cast<void*>(found->resolve_symbol_address(sym));
+ *symbol = get_tagged_address(reinterpret_cast<void*>(found->resolve_symbol_address(sym)));
}
failure_guard.Disable();
LD_LOG(kLogDlsym,
@@ -2791,15 +2800,25 @@
return true;
}
-static void apply_relr_reloc(ElfW(Addr) offset, ElfW(Addr) load_bias) {
- ElfW(Addr) address = offset + load_bias;
- *reinterpret_cast<ElfW(Addr)*>(address) += load_bias;
+static void apply_relr_reloc(ElfW(Addr) offset, ElfW(Addr) load_bias, bool has_memtag_globals) {
+ ElfW(Addr) destination = offset + load_bias;
+ if (!has_memtag_globals) {
+ *reinterpret_cast<ElfW(Addr)*>(destination) += load_bias;
+ return;
+ }
+
+ ElfW(Addr)* tagged_destination =
+ reinterpret_cast<ElfW(Addr)*>(get_tagged_address(reinterpret_cast<void*>(destination)));
+ ElfW(Addr) tagged_value = reinterpret_cast<ElfW(Addr)>(
+ get_tagged_address(reinterpret_cast<void*>(*tagged_destination + load_bias)));
+ *tagged_destination = tagged_value;
}
// Process relocations in SHT_RELR section (experimental).
// Details of the encoding are described in this post:
// https://groups.google.com/d/msg/generic-abi/bX460iggiKg/Pi9aSwwABgAJ
-bool relocate_relr(const ElfW(Relr)* begin, const ElfW(Relr)* end, ElfW(Addr) load_bias) {
+bool relocate_relr(const ElfW(Relr) * begin, const ElfW(Relr) * end, ElfW(Addr) load_bias,
+ bool has_memtag_globals) {
constexpr size_t wordsize = sizeof(ElfW(Addr));
ElfW(Addr) base = 0;
@@ -2810,7 +2829,7 @@
if ((entry&1) == 0) {
// Even entry: encodes the offset for next relocation.
offset = static_cast<ElfW(Addr)>(entry);
- apply_relr_reloc(offset, load_bias);
+ apply_relr_reloc(offset, load_bias, has_memtag_globals);
// Set base offset for subsequent bitmap entries.
base = offset + wordsize;
continue;
@@ -2821,7 +2840,7 @@
while (entry != 0) {
entry >>= 1;
if ((entry&1) != 0) {
- apply_relr_reloc(offset, load_bias);
+ apply_relr_reloc(offset, load_bias, has_memtag_globals);
}
offset += wordsize;
}
@@ -2836,7 +2855,7 @@
// An empty list of soinfos
static soinfo_list_t g_empty_list;
-bool soinfo::prelink_image() {
+bool soinfo::prelink_image(bool deterministic_memtag_globals) {
if (flags_ & FLAG_PRELINKED) return true;
/* Extract dynamic section */
ElfW(Word) dynamic_flags = 0;
@@ -3325,6 +3344,18 @@
// it each time we look up a symbol with a version.
if (!validate_verdef_section(this)) return false;
+ // MTE globals requires remapping data segments with PROT_MTE as anonymous mappings, because file
+ // based mappings may not be backed by tag-capable memory (see "MAP_ANONYMOUS" on
+ // https://www.kernel.org/doc/html/latest/arch/arm64/memory-tagging-extension.html). This is only
+ // done if the binary has MTE globals (evidenced by the dynamic table entries), as it destroys
+ // page sharing. It's also only done on devices that support MTE, because the act of remapping
+ // pages is unnecessary on non-MTE devices (where we might still run MTE-globals enabled code).
+ if (should_tag_memtag_globals() &&
+ remap_memtag_globals_segments(phdr, phnum, base) == 0) {
+ tag_globals(deterministic_memtag_globals);
+ protect_memtag_globals_ro_segments(phdr, phnum, base);
+ }
+
flags_ |= FLAG_PRELINKED;
return true;
}
@@ -3397,6 +3428,10 @@
return false;
}
+ if (should_tag_memtag_globals()) {
+ name_memtag_globals_segments(phdr, phnum, base, get_realpath(), vma_names_);
+ }
+
/* Handle serializing/sharing the RELRO segment */
if (extinfo && (extinfo->flags & ANDROID_DLEXT_WRITE_RELRO)) {
if (phdr_table_serialize_gnu_relro(phdr, phnum, load_bias,
@@ -3435,6 +3470,54 @@
return true;
}
+// https://github.com/ARM-software/abi-aa/blob/main/memtagabielf64/memtagabielf64.rst#global-variable-tagging
+void soinfo::tag_globals(bool deterministic_memtag_globals) {
+ if (is_linked()) return;
+ if (flags_ & FLAG_GLOBALS_TAGGED) return;
+ flags_ |= FLAG_GLOBALS_TAGGED;
+
+ constexpr size_t kTagGranuleSize = 16;
+ const uint8_t* descriptor_stream = reinterpret_cast<const uint8_t*>(memtag_globals());
+
+ if (memtag_globalssz() == 0) {
+ DL_ERR("Invalid memtag descriptor pool size: %zu", memtag_globalssz());
+ }
+
+ uint64_t addr = load_bias;
+ uleb128_decoder decoder(descriptor_stream, memtag_globalssz());
+ // Don't ever generate tag zero, to easily distinguish between tagged and
+ // untagged globals in register/tag dumps.
+ uint64_t last_tag_mask = 1;
+ uint64_t last_tag = 1;
+ constexpr uint64_t kDistanceReservedBits = 3;
+
+ while (decoder.has_bytes()) {
+ uint64_t value = decoder.pop_front();
+ uint64_t distance = (value >> kDistanceReservedBits) * kTagGranuleSize;
+ uint64_t ngranules = value & ((1 << kDistanceReservedBits) - 1);
+ if (ngranules == 0) {
+ ngranules = decoder.pop_front() + 1;
+ }
+
+ addr += distance;
+ void* tagged_addr;
+ if (deterministic_memtag_globals) {
+ tagged_addr = reinterpret_cast<void*>(addr | (last_tag++ << 56));
+ if (last_tag > (1 << kTagGranuleSize)) last_tag = 1;
+ } else {
+ tagged_addr = insert_random_tag(reinterpret_cast<void*>(addr), last_tag_mask);
+ uint64_t tag = (reinterpret_cast<uint64_t>(tagged_addr) >> 56) & 0x0f;
+ last_tag_mask = 1 | (1 << tag);
+ }
+
+ for (size_t k = 0; k < ngranules; k++) {
+ auto* granule = static_cast<uint8_t*>(tagged_addr) + k * kTagGranuleSize;
+ set_memory_tag(static_cast<void*>(granule));
+ }
+ addr += ngranules * kTagGranuleSize;
+ }
+}
+
static std::vector<android_namespace_t*> init_default_namespace_no_config(bool is_asan, bool is_hwasan) {
g_default_namespace.set_isolated(false);
auto default_ld_paths = is_asan ? kAsanDefaultLdPaths : (
diff --git a/linker/linker.h b/linker/linker.h
index ac2222d..b696fd9 100644
--- a/linker/linker.h
+++ b/linker/linker.h
@@ -179,7 +179,8 @@
int get_application_target_sdk_version();
ElfW(Versym) find_verdef_version_index(const soinfo* si, const version_info* vi);
bool validate_verdef_section(const soinfo* si);
-bool relocate_relr(const ElfW(Relr)* begin, const ElfW(Relr)* end, ElfW(Addr) load_bias);
+bool relocate_relr(const ElfW(Relr) * begin, const ElfW(Relr) * end, ElfW(Addr) load_bias,
+ bool has_memtag_globals);
struct platform_properties {
#if defined(__aarch64__)
diff --git a/linker/linker_main.cpp b/linker/linker_main.cpp
index 48ed723..f65f82d 100644
--- a/linker/linker_main.cpp
+++ b/linker/linker_main.cpp
@@ -46,6 +46,7 @@
#include "linker_tls.h"
#include "linker_utils.h"
+#include "platform/bionic/macros.h"
#include "private/KernelArgumentBlock.h"
#include "private/bionic_call_ifunc_resolver.h"
#include "private/bionic_globals.h"
@@ -71,7 +72,9 @@
static void set_bss_vma_name(soinfo* si);
void __libc_init_mte(const memtag_dynamic_entries_t* memtag_dynamic_entries, const void* phdr_start,
- size_t phdr_count, uintptr_t load_bias, void* stack_top);
+ size_t phdr_count, uintptr_t load_bias);
+
+void __libc_init_mte_stack(void* stack_top);
static void __linker_cannot_link(const char* argv0) {
__linker_error("CANNOT LINK EXECUTABLE \"%s\": %s", argv0, linker_get_error_buffer());
@@ -365,6 +368,8 @@
init_link_map_head(*solinker);
#if defined(__aarch64__)
+ __libc_init_mte(somain->memtag_dynamic_entries(), somain->phdr, somain->phnum, somain->load_bias);
+
if (exe_to_load == nullptr) {
// Kernel does not add PROT_BTI to executable pages of the loaded ELF.
// Apply appropriate protections here if it is needed.
@@ -465,8 +470,7 @@
#if defined(__aarch64__)
// This has to happen after the find_libraries, which will have collected any possible
// libraries that request memtag_stack in the dynamic section.
- __libc_init_mte(somain->memtag_dynamic_entries(), somain->phdr, somain->phnum, somain->load_bias,
- args.argv);
+ __libc_init_mte_stack(args.argv);
#endif
linker_finalize_static_tls();
@@ -625,8 +629,13 @@
// Apply RELR relocations first so that the GOT is initialized for ifunc
// resolvers.
if (relr && relrsz) {
+ // Nothing has tagged the memtag globals here, so it is pointless either
+ // way to handle them, the tags will be zero anyway.
+ // That is moot though, because the linker does not use memtag_globals
+ // in the first place.
relocate_relr(reinterpret_cast<ElfW(Relr*)>(ehdr + relr),
- reinterpret_cast<ElfW(Relr*)>(ehdr + relr + relrsz), ehdr);
+ reinterpret_cast<ElfW(Relr*)>(ehdr + relr + relrsz), ehdr,
+ /*has_memtag_globals=*/ false);
}
if (pltrel && pltrelsz) {
call_ifunc_resolvers_for_section(reinterpret_cast<RelType*>(ehdr + pltrel),
@@ -646,6 +655,16 @@
}
}
+// Remapping MTE globals segments happens before the linker relocates itself, and so can't use
+// memcpy() from string.h. This function is compiled with -ffreestanding.
+void linker_memcpy(void* dst, const void* src, size_t n) {
+ char* dst_bytes = reinterpret_cast<char*>(dst);
+ const char* src_bytes = reinterpret_cast<const char*>(src);
+ for (size_t i = 0; i < n; ++i) {
+ dst_bytes[i] = src_bytes[i];
+ }
+}
+
// Detect an attempt to run the linker on itself. e.g.:
// /system/bin/linker64 /system/bin/linker64
// Use priority-1 to run this constructor before other constructors.
diff --git a/linker/linker_main.h b/linker/linker_main.h
index 724f43c..ffbcf0f 100644
--- a/linker/linker_main.h
+++ b/linker/linker_main.h
@@ -70,3 +70,5 @@
soinfo* solist_get_head();
soinfo* solist_get_somain();
soinfo* solist_get_vdso();
+
+void linker_memcpy(void* dst, const void* src, size_t n);
diff --git a/linker/linker_phdr.cpp b/linker/linker_phdr.cpp
index 7691031..2bdd7f8 100644
--- a/linker/linker_phdr.cpp
+++ b/linker/linker_phdr.cpp
@@ -37,9 +37,12 @@
#include <unistd.h>
#include "linker.h"
+#include "linker_debug.h"
#include "linker_dlwarning.h"
#include "linker_globals.h"
-#include "linker_debug.h"
+#include "linker_logger.h"
+#include "linker_main.h"
+#include "linker_soinfo.h"
#include "linker_utils.h"
#include "private/bionic_asm_note.h"
@@ -1172,6 +1175,125 @@
should_use_16kib_app_compat);
}
+static bool segment_needs_memtag_globals_remapping(const ElfW(Phdr) * phdr) {
+ // For now, MTE globals is only supported on writeable data segments.
+ return phdr->p_type == PT_LOAD && !(phdr->p_flags & PF_X) && (phdr->p_flags & PF_W);
+}
+
+/* When MTE globals are requested by the binary, and when the hardware supports
+ * it, remap the executable's PT_LOAD data pages to have PROT_MTE.
+ *
+ * Returns 0 on success, -1 on failure (error code in errno).
+ */
+int remap_memtag_globals_segments(const ElfW(Phdr) * phdr_table __unused,
+ size_t phdr_count __unused, ElfW(Addr) load_bias __unused) {
+#if defined(__aarch64__)
+ for (const ElfW(Phdr)* phdr = phdr_table; phdr < phdr_table + phdr_count; phdr++) {
+ if (!segment_needs_memtag_globals_remapping(phdr)) {
+ continue;
+ }
+
+ uintptr_t seg_page_start = page_start(phdr->p_vaddr) + load_bias;
+ uintptr_t seg_page_end = page_end(phdr->p_vaddr + phdr->p_memsz) + load_bias;
+ size_t seg_page_aligned_size = seg_page_end - seg_page_start;
+
+ int prot = PFLAGS_TO_PROT(phdr->p_flags);
+ // For anonymous private mappings, it may be possible to simply mprotect()
+ // the PROT_MTE flag over the top. For file-based mappings, this will fail,
+ // and we'll need to fall back. We also allow PROT_WRITE here to allow
+ // writing memory tags (in `soinfo::tag_globals()`), and set these sections
+ // back to read-only after tags are applied (similar to RELRO).
+ prot |= PROT_MTE;
+ if (mprotect(reinterpret_cast<void*>(seg_page_start), seg_page_aligned_size,
+ prot | PROT_WRITE) == 0) {
+ continue;
+ }
+
+ void* mapping_copy = mmap(nullptr, seg_page_aligned_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ linker_memcpy(mapping_copy, reinterpret_cast<void*>(seg_page_start), seg_page_aligned_size);
+
+ void* seg_addr = mmap(reinterpret_cast<void*>(seg_page_start), seg_page_aligned_size,
+ prot | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (seg_addr == MAP_FAILED) return -1;
+
+ linker_memcpy(seg_addr, mapping_copy, seg_page_aligned_size);
+ munmap(mapping_copy, seg_page_aligned_size);
+ }
+#endif // defined(__aarch64__)
+ return 0;
+}
+
+void protect_memtag_globals_ro_segments(const ElfW(Phdr) * phdr_table __unused,
+ size_t phdr_count __unused, ElfW(Addr) load_bias __unused) {
+#if defined(__aarch64__)
+ for (const ElfW(Phdr)* phdr = phdr_table; phdr < phdr_table + phdr_count; phdr++) {
+ int prot = PFLAGS_TO_PROT(phdr->p_flags);
+ if (!segment_needs_memtag_globals_remapping(phdr) || (prot & PROT_WRITE)) {
+ continue;
+ }
+
+ prot |= PROT_MTE;
+
+ uintptr_t seg_page_start = page_start(phdr->p_vaddr) + load_bias;
+ uintptr_t seg_page_end = page_end(phdr->p_vaddr + phdr->p_memsz) + load_bias;
+ size_t seg_page_aligned_size = seg_page_end - seg_page_start;
+ mprotect(reinterpret_cast<void*>(seg_page_start), seg_page_aligned_size, prot);
+ }
+#endif // defined(__aarch64__)
+}
+
+void name_memtag_globals_segments(const ElfW(Phdr) * phdr_table, size_t phdr_count,
+ ElfW(Addr) load_bias, const char* soname,
+ std::list<std::string>& vma_names) {
+ for (const ElfW(Phdr)* phdr = phdr_table; phdr < phdr_table + phdr_count; phdr++) {
+ if (!segment_needs_memtag_globals_remapping(phdr)) {
+ continue;
+ }
+
+ uintptr_t seg_page_start = page_start(phdr->p_vaddr) + load_bias;
+ uintptr_t seg_page_end = page_end(phdr->p_vaddr + phdr->p_memsz) + load_bias;
+ size_t seg_page_aligned_size = seg_page_end - seg_page_start;
+
+ // For file-based mappings that we're now forcing to be anonymous mappings, set the VMA name to
+ // make debugging easier.
+ // Once we are targeting only devices that run kernel 5.10 or newer (and thus include
+ // https://android-review.git.corp.google.com/c/kernel/common/+/1934723 which causes the
+ // VMA_ANON_NAME to be copied into the kernel), we can get rid of the storage here.
+ // For now, that is not the case:
+ // https://source.android.com/docs/core/architecture/kernel/android-common#compatibility-matrix
+ constexpr int kVmaNameLimit = 80;
+ std::string& vma_name = vma_names.emplace_back('\0', kVmaNameLimit);
+ int full_vma_length =
+ async_safe_format_buffer(vma_name.data(), kVmaNameLimit, "mt:%s+%" PRIxPTR, soname,
+ page_start(phdr->p_vaddr)) +
+ /* include the null terminator */ 1;
+ // There's an upper limit of 80 characters, including the null terminator, in the anonymous VMA
+ // name. If we run over that limit, we end up truncating the segment offset and parts of the
+ // DSO's name, starting on the right hand side of the basename. Because the basename is the most
+ // important thing, chop off the soname from the left hand side first.
+ //
+ // Example (with '#' as the null terminator):
+ // - "mt:/data/nativetest64/bionic-unit-tests/bionic-loader-test-libs/libdlext_test.so+e000#"
+ // is a `full_vma_length` == 86.
+ //
+ // We need to left-truncate (86 - 80) 6 characters from the soname, plus the
+ // `vma_truncation_prefix`, so 9 characters total.
+ if (full_vma_length > kVmaNameLimit) {
+ const char vma_truncation_prefix[] = "...";
+ int soname_truncated_bytes =
+ full_vma_length - kVmaNameLimit + sizeof(vma_truncation_prefix) - 1;
+ async_safe_format_buffer(vma_name.data(), kVmaNameLimit, "mt:%s%s+%" PRIxPTR,
+ vma_truncation_prefix, soname + soname_truncated_bytes,
+ page_start(phdr->p_vaddr));
+ }
+ if (prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, reinterpret_cast<void*>(seg_page_start),
+ seg_page_aligned_size, vma_name.data()) != 0) {
+ DL_WARN("Failed to re-name memtag global segment.");
+ }
+ }
+}
+
/* Change the protection of all loaded segments in memory to writable.
* This is useful before performing relocations. Once completed, you
* will have to call phdr_table_protect_segments to restore the original
diff --git a/linker/linker_phdr.h b/linker/linker_phdr.h
index 2f159f3..353970c 100644
--- a/linker/linker_phdr.h
+++ b/linker/linker_phdr.h
@@ -39,6 +39,8 @@
#include "linker_mapped_file_fragment.h"
#include "linker_note_gnu_property.h"
+#include <list>
+
#define MAYBE_MAP_FLAG(x, from, to) (((x) & (from)) ? (to) : 0)
#define PFLAGS_TO_PROT(x) (MAYBE_MAP_FLAG((x), PF_X, PROT_EXEC) | \
MAYBE_MAP_FLAG((x), PF_R, PROT_READ) | \
@@ -188,3 +190,13 @@
ElfW(Addr) load_bias);
bool page_size_migration_supported();
+
+int remap_memtag_globals_segments(const ElfW(Phdr) * phdr_table, size_t phdr_count,
+ ElfW(Addr) load_bias);
+
+void protect_memtag_globals_ro_segments(const ElfW(Phdr) * phdr_table, size_t phdr_count,
+ ElfW(Addr) load_bias);
+
+void name_memtag_globals_segments(const ElfW(Phdr) * phdr_table, size_t phdr_count,
+ ElfW(Addr) load_bias, const char* soname,
+ std::list<std::string>& vma_names);
diff --git a/linker/linker_relocate.cpp b/linker/linker_relocate.cpp
index 0470f87..bbf8359 100644
--- a/linker/linker_relocate.cpp
+++ b/linker/linker_relocate.cpp
@@ -44,6 +44,8 @@
#include "linker_soinfo.h"
#include "private/bionic_globals.h"
+#include <platform/bionic/mte.h>
+
static bool is_tls_reloc(ElfW(Word) type) {
switch (type) {
case R_GENERIC_TLS_DTPMOD:
@@ -163,7 +165,8 @@
static bool process_relocation_impl(Relocator& relocator, const rel_t& reloc) {
constexpr bool IsGeneral = Mode == RelocMode::General;
- void* const rel_target = reinterpret_cast<void*>(reloc.r_offset + relocator.si->load_bias);
+ void* const rel_target = reinterpret_cast<void*>(
+ relocator.si->apply_memtag_if_mte_globals(reloc.r_offset + relocator.si->load_bias));
const uint32_t r_type = ELFW(R_TYPE)(reloc.r_info);
const uint32_t r_sym = ELFW(R_SYM)(reloc.r_info);
@@ -316,6 +319,7 @@
// common in non-platform binaries.
if (r_type == R_GENERIC_ABSOLUTE) {
count_relocation_if<IsGeneral>(kRelocAbsolute);
+ if (found_in) sym_addr = found_in->apply_memtag_if_mte_globals(sym_addr);
const ElfW(Addr) result = sym_addr + get_addend_rel();
LD_DEBUG(reloc && IsGeneral, "RELO ABSOLUTE %16p <- %16p %s",
rel_target, reinterpret_cast<void*>(result), sym_name);
@@ -326,6 +330,7 @@
// document (IHI0044F) specifies that R_ARM_GLOB_DAT has an addend, but Bionic isn't adding
// it.
count_relocation_if<IsGeneral>(kRelocAbsolute);
+ if (found_in) sym_addr = found_in->apply_memtag_if_mte_globals(sym_addr);
const ElfW(Addr) result = sym_addr + get_addend_norel();
LD_DEBUG(reloc && IsGeneral, "RELO GLOB_DAT %16p <- %16p %s",
rel_target, reinterpret_cast<void*>(result), sym_name);
@@ -335,7 +340,18 @@
// In practice, r_sym is always zero, but if it weren't, the linker would still look up the
// referenced symbol (and abort if the symbol isn't found), even though it isn't used.
count_relocation_if<IsGeneral>(kRelocRelative);
- const ElfW(Addr) result = relocator.si->load_bias + get_addend_rel();
+ ElfW(Addr) result = relocator.si->load_bias + get_addend_rel();
+ // MTE globals reuses the place bits for additional tag-derivation metadata for
+ // R_AARCH64_RELATIVE relocations, which makes it incompatible with
+ // `-Wl,--apply-dynamic-relocs`. This is enforced by lld, however there's nothing stopping
+ // Android binaries (particularly prebuilts) from building with this linker flag if they're
+ // not built with MTE globals. Thus, don't use the new relocation semantics if this DSO
+ // doesn't have MTE globals.
+ if (relocator.si->should_tag_memtag_globals()) {
+ int64_t* place = static_cast<int64_t*>(rel_target);
+ int64_t offset = *place;
+ result = relocator.si->apply_memtag_if_mte_globals(result + offset) - offset;
+ }
LD_DEBUG(reloc && IsGeneral, "RELO RELATIVE %16p <- %16p",
rel_target, reinterpret_cast<void*>(result));
*static_cast<ElfW(Addr)*>(rel_target) = result;
@@ -600,7 +616,7 @@
LD_DEBUG(reloc, "[ relocating %s relr ]", get_realpath());
const ElfW(Relr)* begin = relr_;
const ElfW(Relr)* end = relr_ + relr_count_;
- if (!relocate_relr(begin, end, load_bias)) {
+ if (!relocate_relr(begin, end, load_bias, should_tag_memtag_globals())) {
return false;
}
}
diff --git a/linker/linker_sleb128.h b/linker/linker_sleb128.h
index 6bb3199..f48fda8 100644
--- a/linker/linker_sleb128.h
+++ b/linker/linker_sleb128.h
@@ -69,3 +69,32 @@
const uint8_t* current_;
const uint8_t* const end_;
};
+
+class uleb128_decoder {
+ public:
+ uleb128_decoder(const uint8_t* buffer, size_t count) : current_(buffer), end_(buffer + count) {}
+
+ uint64_t pop_front() {
+ uint64_t value = 0;
+
+ size_t shift = 0;
+ uint8_t byte;
+
+ do {
+ if (current_ >= end_) {
+ async_safe_fatal("uleb128_decoder ran out of bounds");
+ }
+ byte = *current_++;
+ value |= (static_cast<size_t>(byte & 127) << shift);
+ shift += 7;
+ } while (byte & 128);
+
+ return value;
+ }
+
+ bool has_bytes() { return current_ < end_; }
+
+ private:
+ const uint8_t* current_;
+ const uint8_t* const end_;
+};
diff --git a/linker/linker_soinfo.cpp b/linker/linker_soinfo.cpp
index 0549d36..176c133 100644
--- a/linker/linker_soinfo.cpp
+++ b/linker/linker_soinfo.cpp
@@ -44,6 +44,8 @@
#include "linker_logger.h"
#include "linker_relocate.h"
#include "linker_utils.h"
+#include "platform/bionic/mte.h"
+#include "private/bionic_globals.h"
SymbolLookupList::SymbolLookupList(soinfo* si)
: sole_lib_(si->get_lookup_lib()), begin_(&sole_lib_), end_(&sole_lib_ + 1) {
@@ -304,6 +306,12 @@
return is_gnu_hash() ? gnu_lookup(symbol_name, vi) : elf_lookup(symbol_name, vi);
}
+ElfW(Addr) soinfo::apply_memtag_if_mte_globals(ElfW(Addr) sym_addr) const {
+ if (!should_tag_memtag_globals()) return sym_addr;
+ if (sym_addr == 0) return sym_addr; // Handle undefined weak symbols.
+ return reinterpret_cast<ElfW(Addr)>(get_tagged_address(reinterpret_cast<void*>(sym_addr)));
+}
+
const ElfW(Sym)* soinfo::gnu_lookup(SymbolName& symbol_name, const version_info* vi) const {
const uint32_t hash = symbol_name.gnu_hash();
diff --git a/linker/linker_soinfo.h b/linker/linker_soinfo.h
index a776c1f..64f8aea 100644
--- a/linker/linker_soinfo.h
+++ b/linker/linker_soinfo.h
@@ -30,6 +30,7 @@
#include <link.h>
+#include <list>
#include <memory>
#include <string>
#include <vector>
@@ -66,9 +67,10 @@
// soinfo is executed and this flag is
// unset.
#define FLAG_PRELINKED 0x00000400 // prelink_image has successfully processed this soinfo
+#define FLAG_GLOBALS_TAGGED 0x00000800 // globals have been tagged by MTE.
#define FLAG_NEW_SOINFO 0x40000000 // new soinfo format
-#define SOINFO_VERSION 6
+#define SOINFO_VERSION 7
ElfW(Addr) call_ifunc_resolver(ElfW(Addr) resolver_addr);
@@ -252,11 +254,14 @@
void call_constructors();
void call_destructors();
void call_pre_init_constructors();
- bool prelink_image();
+ bool prelink_image(bool deterministic_memtag_globals = false);
bool link_image(const SymbolLookupList& lookup_list, soinfo* local_group_root,
const android_dlextinfo* extinfo, size_t* relro_fd_offset);
bool protect_relro();
+ void tag_globals(bool deterministic_memtag_globals);
+ ElfW(Addr) apply_memtag_if_mte_globals(ElfW(Addr) sym_addr) const;
+
void add_child(soinfo* child);
void remove_all_links();
@@ -368,6 +373,10 @@
should_pad_segments_ = should_pad_segments;
}
bool should_pad_segments() const { return should_pad_segments_; }
+ bool should_tag_memtag_globals() const {
+ return !is_linker() && memtag_globals() && memtag_globalssz() > 0 && __libc_mte_enabled();
+ }
+ std::list<std::string>& vma_names() { return vma_names_; };
void set_should_use_16kib_app_compat(bool should_use_16kib_app_compat) {
should_use_16kib_app_compat_ = should_use_16kib_app_compat;
@@ -464,6 +473,8 @@
// version >= 7
memtag_dynamic_entries_t memtag_dynamic_entries_;
+ std::list<std::string> vma_names_;
+
// Pad gaps between segments when memory mapping?
bool should_pad_segments_ = false;
diff --git a/tests/Android.bp b/tests/Android.bp
index f5f3e31..ea03c08 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -429,6 +429,7 @@
"math_test.cpp",
"membarrier_test.cpp",
"memtag_stack_test.cpp",
+ "memtag_globals_test.cpp",
"mntent_test.cpp",
"mte_test.cpp",
"netdb_test.cpp",
@@ -896,6 +897,11 @@
"preinit_syscall_test_helper",
"thread_exit_cb_helper",
"tls_properties_helper",
+ "memtag_globals_binary",
+ "memtag_globals_binary_static",
+ "mte_globals_relr_regression_test_b_314038442",
+ "mte_globals_relr_regression_test_b_314038442_mte",
+ "memtag_globals_dso",
],
data_libs: [
"libatest_simple_zip",
@@ -1286,6 +1292,11 @@
"heap_tagging_static_disabled_helper",
"heap_tagging_static_sync_helper",
"heap_tagging_sync_helper",
+ "memtag_globals_binary",
+ "memtag_globals_binary_static",
+ "memtag_globals_dso",
+ "mte_globals_relr_regression_test_b_314038442",
+ "mte_globals_relr_regression_test_b_314038442_mte",
"stack_tagging_helper",
"stack_tagging_static_helper",
],
diff --git a/tests/dlext_test.cpp b/tests/dlext_test.cpp
index 570da2a..8b26cb0 100644
--- a/tests/dlext_test.cpp
+++ b/tests/dlext_test.cpp
@@ -21,6 +21,7 @@
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
+#include <link.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
@@ -40,11 +41,13 @@
#include <procinfo/process_map.h>
#include <ziparchive/zip_archive.h>
+#include "bionic/mte.h"
+#include "bionic/page.h"
#include "core_shared_libs.h"
-#include "gtest_globals.h"
-#include "utils.h"
#include "dlext_private.h"
#include "dlfcn_symlink_support.h"
+#include "gtest_globals.h"
+#include "utils.h"
#define ASSERT_DL_NOTNULL(ptr) \
ASSERT_TRUE((ptr) != nullptr) << "dlerror: " << dlerror()
@@ -1958,6 +1961,14 @@
dlclose(ns_a_handle3);
}
+static inline int MapPflagsToProtFlags(uint32_t flags) {
+ int prot_flags = 0;
+ if (PF_X & flags) prot_flags |= PROT_EXEC;
+ if (PF_W & flags) prot_flags |= PROT_WRITE;
+ if (PF_R & flags) prot_flags |= PROT_READ;
+ return prot_flags;
+}
+
TEST(dlext, ns_anonymous) {
static const char* root_lib = "libnstest_root.so";
std::string shared_libs = g_core_shared_libs + ":" + g_public_lib;
@@ -1999,30 +2010,45 @@
typedef const char* (*fn_t)();
fn_t ns_get_dlopened_string_private = reinterpret_cast<fn_t>(ns_get_dlopened_string_addr);
- std::vector<map_record> maps;
- Maps::parse_maps(&maps);
-
+ Dl_info private_library_info;
+ ASSERT_NE(dladdr(reinterpret_cast<void*>(ns_get_dlopened_string_addr), &private_library_info), 0)
+ << dlerror();
+ std::vector<map_record> maps_to_copy;
+ bool has_executable_segment = false;
uintptr_t addr_start = 0;
uintptr_t addr_end = 0;
- bool has_executable_segment = false;
- std::vector<map_record> maps_to_copy;
+ std::tuple dl_iterate_arg = {&private_library_info, &maps_to_copy, &has_executable_segment,
+ &addr_start, &addr_end};
+ ASSERT_EQ(
+ 1, dl_iterate_phdr(
+ [](dl_phdr_info* info, size_t /*size*/, void* data) -> int {
+ auto [private_library_info, maps_to_copy, has_executable_segment, addr_start,
+ addr_end] = *reinterpret_cast<decltype(dl_iterate_arg)*>(data);
+ if (info->dlpi_addr != reinterpret_cast<ElfW(Addr)>(private_library_info->dli_fbase))
+ return 0;
- for (const auto& rec : maps) {
- if (rec.pathname == private_library_absolute_path) {
- if (addr_start == 0) {
- addr_start = rec.addr_start;
- }
- addr_end = rec.addr_end;
- has_executable_segment = has_executable_segment || (rec.perms & PROT_EXEC) != 0;
-
- maps_to_copy.push_back(rec);
- }
- }
+ for (size_t i = 0; i < info->dlpi_phnum; ++i) {
+ const ElfW(Phdr)* phdr = info->dlpi_phdr + i;
+ if (phdr->p_type != PT_LOAD) continue;
+ *has_executable_segment |= phdr->p_flags & PF_X;
+ uintptr_t mapping_start = page_start(info->dlpi_addr + phdr->p_vaddr);
+ uintptr_t mapping_end = page_end(info->dlpi_addr + phdr->p_vaddr + phdr->p_memsz);
+ if (*addr_start == 0 || mapping_start < *addr_start) *addr_start = mapping_start;
+ if (*addr_end == 0 || mapping_end > *addr_end) *addr_end = mapping_end;
+ maps_to_copy->push_back({
+ .addr_start = mapping_start,
+ .addr_end = mapping_end,
+ .perms = MapPflagsToProtFlags(phdr->p_flags),
+ });
+ }
+ return 1;
+ },
+ &dl_iterate_arg));
// Some validity checks.
+ ASSERT_NE(maps_to_copy.size(), 0u);
ASSERT_TRUE(addr_start > 0);
ASSERT_TRUE(addr_end > 0);
- ASSERT_TRUE(maps_to_copy.size() > 0);
ASSERT_TRUE(ns_get_dlopened_string_addr > addr_start);
ASSERT_TRUE(ns_get_dlopened_string_addr < addr_end);
@@ -2052,19 +2078,26 @@
ASSERT_EQ(ret, 0) << "Failed to stat library";
size_t file_size = file_stat.st_size;
- for (const auto& rec : maps_to_copy) {
- uintptr_t offset = rec.addr_start - addr_start;
- size_t size = rec.addr_end - rec.addr_start;
- void* addr = reinterpret_cast<void*>(reserved_addr + offset);
- void* map = mmap(addr, size, PROT_READ | PROT_WRITE,
- MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
- ASSERT_TRUE(map != MAP_FAILED);
- // Attempting the below memcpy from a portion of the map that is off the end of
- // the backing file will cause the kernel to throw a SIGBUS
- size_t _size = ::android::procinfo::MappedFileSize(rec.addr_start, rec.addr_end,
- rec.offset, file_size);
- memcpy(map, reinterpret_cast<void*>(rec.addr_start), _size);
- mprotect(map, size, rec.perms);
+ {
+ // Disable MTE while copying the PROT_MTE-protected global variables from
+ // the existing mappings. We don't really care about turning on PROT_MTE for
+ // the new copy of the mappings, as this isn't the behaviour under test and
+ // tags will be ignored. This only applies for MTE-enabled devices.
+ ScopedDisableMTE disable_mte_for_copying_global_variables;
+ for (const auto& rec : maps_to_copy) {
+ uintptr_t offset = rec.addr_start - addr_start;
+ size_t size = rec.addr_end - rec.addr_start;
+ void* addr = reinterpret_cast<void*>(reserved_addr + offset);
+ void* map =
+ mmap(addr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
+ ASSERT_TRUE(map != MAP_FAILED);
+ // Attempting the below memcpy from a portion of the map that is off the end of
+ // the backing file will cause the kernel to throw a SIGBUS
+ size_t _size =
+ ::android::procinfo::MappedFileSize(rec.addr_start, rec.addr_end, rec.offset, file_size);
+ memcpy(map, reinterpret_cast<void*>(rec.addr_start), _size);
+ mprotect(map, size, rec.perms);
+ }
}
// call the function copy
diff --git a/tests/libs/Android.bp b/tests/libs/Android.bp
index d13ee60..5b86e78 100644
--- a/tests/libs/Android.bp
+++ b/tests/libs/Android.bp
@@ -1903,3 +1903,89 @@
" $(location soong_zip) -o $(out).unaligned -L 0 -C $(genDir)/zipdir -D $(genDir)/zipdir &&" +
" $(location bionic_tests_zipalign) 16384 $(out).unaligned $(out)",
}
+
+cc_defaults {
+ name: "memtag_globals_defaults",
+ defaults: [
+ "bionic_testlib_defaults",
+ "bionic_targets_only"
+ ],
+ cflags: [
+ "-Wno-array-bounds",
+ "-Wno-unused-variable",
+ ],
+ header_libs: ["bionic_libc_platform_headers"],
+ sanitize: {
+ hwaddress: false,
+ memtag_heap: true,
+ memtag_globals: true,
+ diag: {
+ memtag_heap: true,
+ }
+ },
+}
+
+cc_test_library {
+ name: "memtag_globals_dso",
+ defaults: [ "memtag_globals_defaults" ],
+ srcs: ["memtag_globals_dso.cpp"],
+}
+
+cc_test {
+ name: "memtag_globals_binary",
+ defaults: [ "memtag_globals_defaults" ],
+ srcs: ["memtag_globals_binary.cpp"],
+ shared_libs: [ "memtag_globals_dso" ],
+ // This binary is used in the bionic-unit-tests as a data dependency, and is
+ // in the same folder as memtag_globals_dso. But, the default cc_test rules
+ // make this binary (when just explicitly built and shoved in
+ // /data/nativetest64/) end up in a subfolder called
+ // 'memtag_globals_binary'. When this happens, the explicit build fails to
+ // find the DSO because the default rpath is just ${ORIGIN}, and because we
+ // want this to be usable both from bionic-unit-tests and explicit builds,
+ // let's just not put it in a subdirectory.
+ no_named_install_directory: true,
+}
+
+cc_test {
+ name: "memtag_globals_binary_static",
+ defaults: [ "memtag_globals_defaults" ],
+ srcs: ["memtag_globals_binary.cpp"],
+ static_libs: [ "memtag_globals_dso" ],
+ no_named_install_directory: true,
+ static_executable: true,
+}
+
+// This is a regression test for b/314038442, where binaries built *without* MTE
+// globals would have out-of-bounds RELR relocations, which where then `ldg`'d,
+// which resulted in linker crashes.
+cc_test {
+ name: "mte_globals_relr_regression_test_b_314038442",
+ defaults: [
+ "bionic_testlib_defaults",
+ "bionic_targets_only"
+ ],
+ cflags: [ "-Wno-array-bounds" ],
+ ldflags: [ "-Wl,--pack-dyn-relocs=relr" ],
+ srcs: ["mte_globals_relr_regression_test_b_314038442.cpp"],
+ no_named_install_directory: true,
+ sanitize: {
+ memtag_globals: false,
+ },
+}
+
+// Same test as above, but also for MTE globals, just for the sake of it.
+cc_test {
+ name: "mte_globals_relr_regression_test_b_314038442_mte",
+ defaults: [
+ "bionic_testlib_defaults",
+ "bionic_targets_only"
+ ],
+ cflags: [ "-Wno-array-bounds" ],
+ ldflags: [ "-Wl,--pack-dyn-relocs=relr" ],
+ srcs: ["mte_globals_relr_regression_test_b_314038442.cpp"],
+ no_named_install_directory: true,
+ sanitize: {
+ memtag_globals: true,
+ },
+}
diff --git a/tests/libs/memtag_globals.h b/tests/libs/memtag_globals.h
new file mode 100644
index 0000000..a03abae
--- /dev/null
+++ b/tests/libs/memtag_globals.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <utility>
+#include <vector>
+
+void check_tagged(const void* a);
+void check_untagged(const void* a);
+void check_matching_tags(const void* a, const void* b);
+void check_eq(const void* a, const void* b);
+
+void dso_check_assertions(bool enforce_tagged);
+void dso_print_variables();
+
+void print_variable_address(const char* name, const void* ptr);
+void print_variables(const char* header,
+ const std::vector<std::pair<const char*, const void*>>& tagged_variables,
+ const std::vector<std::pair<const char*, const void*>>& untagged_variables);
diff --git a/tests/libs/memtag_globals_binary.cpp b/tests/libs/memtag_globals_binary.cpp
new file mode 100644
index 0000000..9248728
--- /dev/null
+++ b/tests/libs/memtag_globals_binary.cpp
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <string>
+#include <vector>
+
+#include "memtag_globals.h"
+
+// Adapted from the LLD test suite: lld/test/ELF/Inputs/aarch64-memtag-globals.s
+
+/// Global variables defined here, of various semantics.
+char global[30] = {};
+__attribute__((no_sanitize("memtag"))) int global_untagged = 0;
+const int const_global = 0;
+static const int hidden_const_global = 0;
+static char hidden_global[12] = {};
+__attribute__((visibility("hidden"))) int hidden_attr_global = 0;
+__attribute__((visibility("hidden"))) const int hidden_attr_const_global = 0;
+
+/// Should be untagged.
+__thread int tls_global;
+__thread static int hidden_tls_global;
+
+/// Tagged, from the other file.
+extern int global_extern;
+/// Untagged, from the other file.
+extern __attribute__((no_sanitize("memtag"))) int global_extern_untagged;
+/// Tagged here, but untagged in the definition found in the sister objfile
+/// (explicitly).
+extern int global_extern_untagged_definition_but_tagged_import;
+
+/// ABS64 relocations. Also, forces symtab entries for local and external
+/// globals.
+char* pointer_to_global = &global[0];
+char* pointer_inside_global = &global[17];
+char* pointer_to_global_end = &global[30];
+char* pointer_past_global_end = &global[48];
+int* pointer_to_global_untagged = &global_untagged;
+const int* pointer_to_const_global = &const_global;
+/// RELATIVE relocations.
+const int* pointer_to_hidden_const_global = &hidden_const_global;
+char* pointer_to_hidden_global = &hidden_global[0];
+int* pointer_to_hidden_attr_global = &hidden_attr_global;
+const int* pointer_to_hidden_attr_const_global = &hidden_attr_const_global;
+/// RELATIVE relocations with special AArch64 MemtagABI semantics, with the
+/// offset ('12' or '16') encoded in the place.
+char* pointer_to_hidden_global_end = &hidden_global[12];
+char* pointer_past_hidden_global_end = &hidden_global[16];
+/// ABS64 relocations.
+int* pointer_to_global_extern = &global_extern;
+int* pointer_to_global_extern_untagged = &global_extern_untagged;
+int* pointer_to_global_extern_untagged_definition_but_tagged_import =
+ &global_extern_untagged_definition_but_tagged_import;
+
+// Force materialization of these globals into the symtab.
+int* get_address_to_tls_global() {
+ return &tls_global;
+}
+int* get_address_to_hidden_tls_global() {
+ return &hidden_tls_global;
+}
+
+static const std::vector<std::pair<const char*, const void*>>& get_expected_tagged_vars() {
+ static std::vector<std::pair<const char*, const void*>> expected_tagged_vars = {
+ {"global", &global},
+ {"pointer_inside_global", pointer_inside_global},
+ {"pointer_to_global_end", pointer_to_global_end},
+ {"pointer_past_global_end", pointer_past_global_end},
+ {"hidden_global", &hidden_global},
+ {"hidden_attr_global", &hidden_attr_global},
+ {"global_extern", &global_extern},
+ };
+ return expected_tagged_vars;
+}
+
+static const std::vector<std::pair<const char*, const void*>>& get_expected_untagged_vars() {
+ static std::vector<std::pair<const char*, const void*>> expected_untagged_vars = {
+ {"global_extern_untagged", &global_extern_untagged},
+ {"global_extern_untagged_definition_but_tagged_import",
+ &global_extern_untagged_definition_but_tagged_import},
+ {"global_untagged", &global_untagged},
+ {"const_global", &const_global},
+ {"hidden_const_global", &hidden_const_global},
+ {"hidden_attr_const_global", &hidden_attr_const_global},
+ {"tls_global", &tls_global},
+ {"hidden_tls_global", &hidden_tls_global},
+ };
+ return expected_untagged_vars;
+}
+
+void exe_print_variables() {
+ print_variables(" Variables accessible from the binary:\n", get_expected_tagged_vars(),
+ get_expected_untagged_vars());
+}
+
+// Dump the addresses of the global variables to stderr
+void dso_print();
+void dso_print_others();
+
+void exe_check_assertions(bool check_pointers_are_tagged) {
+ // Check that non-const variables are writeable.
+ *pointer_to_global = 0;
+ *pointer_inside_global = 0;
+ *(pointer_to_global_end - 1) = 0;
+ *pointer_to_global_untagged = 0;
+ *pointer_to_hidden_global = 0;
+ *pointer_to_hidden_attr_global = 0;
+ *(pointer_to_hidden_global_end - 1) = 0;
+ *pointer_to_global_extern = 0;
+ *pointer_to_global_extern_untagged = 0;
+ *pointer_to_global_extern_untagged_definition_but_tagged_import = 0;
+
+ if (check_pointers_are_tagged) {
+ for (const auto& [_, pointer] : get_expected_tagged_vars()) {
+ check_tagged(pointer);
+ }
+ }
+
+ for (const auto& [_, pointer] : get_expected_untagged_vars()) {
+ check_untagged(pointer);
+ }
+
+ check_matching_tags(pointer_to_global, pointer_inside_global);
+ check_matching_tags(pointer_to_global, pointer_to_global_end);
+ check_matching_tags(pointer_to_global, pointer_past_global_end);
+ check_eq(pointer_inside_global, pointer_to_global + 17);
+ check_eq(pointer_to_global_end, pointer_to_global + 30);
+ check_eq(pointer_past_global_end, pointer_to_global + 48);
+
+ check_matching_tags(pointer_to_hidden_global, pointer_to_hidden_global_end);
+ check_matching_tags(pointer_to_hidden_global, pointer_past_hidden_global_end);
+ check_eq(pointer_to_hidden_global_end, pointer_to_hidden_global + 12);
+ check_eq(pointer_past_hidden_global_end, pointer_to_hidden_global + 16);
+}
+
+void crash() {
+ *pointer_past_global_end = 0;
+}
+
+int main(int argc, char** argv) {
+ bool check_pointers_are_tagged = false;
+ // For an MTE-capable device, provide argv[1] == '1' to enable the assertions
+ // that pointers should be tagged.
+ if (argc >= 2 && argv[1][0] == '1') {
+ check_pointers_are_tagged = true;
+ }
+
+ char* heap_ptr = static_cast<char*>(malloc(1));
+ print_variable_address("heap address", heap_ptr);
+ *heap_ptr = 0;
+ if (check_pointers_are_tagged) check_tagged(heap_ptr);
+ free(heap_ptr);
+
+ exe_print_variables();
+ dso_print_variables();
+
+ exe_check_assertions(check_pointers_are_tagged);
+ dso_check_assertions(check_pointers_are_tagged);
+
+ printf("Assertions were passed. Now doing a global-buffer-overflow.\n");
+ fflush(stdout);
+ crash();
+ printf("global-buffer-overflow went uncaught.\n");
+ return 0;
+}
diff --git a/tests/libs/memtag_globals_dso.cpp b/tests/libs/memtag_globals_dso.cpp
new file mode 100644
index 0000000..9ed264e
--- /dev/null
+++ b/tests/libs/memtag_globals_dso.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <vector>
+
+#include "memtag_globals.h"
+
+// Adapted from the LLD test suite: lld/test/ELF/Inputs/aarch64-memtag-globals.s
+
+int global_extern;
+static int global_extern_hidden;
+__attribute__((no_sanitize("memtag"))) int global_extern_untagged;
+__attribute__((no_sanitize("memtag"))) int global_extern_untagged_definition_but_tagged_import;
+
+void assertion_failure() {
+ exit(1);
+}
+
+void check_tagged(const void* a) {
+ uintptr_t a_uptr = reinterpret_cast<uintptr_t>(a);
+#if defined(__aarch64__)
+ if ((a_uptr >> 56) == 0) {
+ fprintf(stderr, "**********************************\n");
+ fprintf(stderr, "Failed assertion:\n");
+ fprintf(stderr, " tag(0x%zx) != 0\n", a_uptr);
+ fprintf(stderr, "**********************************\n");
+
+ assertion_failure();
+ }
+#endif // defined(__aarch64__)
+}
+
+void check_untagged(const void* a) {
+ uintptr_t a_uptr = reinterpret_cast<uintptr_t>(a);
+#if defined(__aarch64__)
+ if ((a_uptr >> 56) != 0) {
+ fprintf(stderr, "**********************************\n");
+ fprintf(stderr, "Failed assertion:\n");
+ fprintf(stderr, " tag(0x%zx) == 0\n", a_uptr);
+ fprintf(stderr, "**********************************\n");
+
+ assertion_failure();
+ }
+#endif // defined(__aarch64__)
+}
+
+void check_matching_tags(const void* a, const void* b) {
+ uintptr_t a_uptr = reinterpret_cast<uintptr_t>(a);
+ uintptr_t b_uptr = reinterpret_cast<uintptr_t>(b);
+#if defined(__aarch64__)
+ if (a_uptr >> 56 != b_uptr >> 56) {
+ fprintf(stderr, "**********************************\n");
+ fprintf(stderr, "Failed assertion:\n");
+ fprintf(stderr, " tag(0x%zx) != tag(0x%zx)\n", a_uptr, b_uptr);
+ fprintf(stderr, "**********************************\n");
+
+ assertion_failure();
+ }
+#endif // defined(__aarch64__)
+}
+
+void check_eq(const void* a, const void* b) {
+ if (a != b) {
+ fprintf(stderr, "**********************************\n");
+ fprintf(stderr, "Failed assertion:\n");
+ fprintf(stderr, " %p != %p\n", a, b);
+ fprintf(stderr, "**********************************\n");
+
+ assertion_failure();
+ }
+}
+
+#define LONGEST_VARIABLE_NAME "51"
+void print_variable_address(const char* name, const void* ptr) {
+ printf("%" LONGEST_VARIABLE_NAME "s: %16p\n", name, ptr);
+}
+
+static const std::vector<std::pair<const char*, const void*>>& get_expected_tagged_vars() {
+ static std::vector<std::pair<const char*, const void*>> expected_tagged_vars = {
+ {"global_extern", &global_extern},
+ {"global_extern_hidden", &global_extern_hidden},
+ };
+ return expected_tagged_vars;
+}
+
+static const std::vector<std::pair<const char*, const void*>>& get_expected_untagged_vars() {
+ static std::vector<std::pair<const char*, const void*>> expected_untagged_vars = {
+ {"global_extern_untagged", &global_extern_untagged},
+ {"global_extern_untagged_definition_but_tagged_import",
+ &global_extern_untagged_definition_but_tagged_import},
+ };
+ return expected_untagged_vars;
+}
+
+void dso_print_variables() {
+ print_variables(" Variables declared in the DSO:\n", get_expected_tagged_vars(),
+ get_expected_untagged_vars());
+}
+
+void print_variables(const char* header,
+ const std::vector<std::pair<const char*, const void*>>& tagged_variables,
+ const std::vector<std::pair<const char*, const void*>>& untagged_variables) {
+ printf("==========================================================\n");
+ printf("%s", header);
+ printf("==========================================================\n");
+ printf(" Variables expected to be tagged:\n");
+ printf("----------------------------------------------------------\n");
+ for (const auto& [name, pointer] : tagged_variables) {
+ print_variable_address(name, pointer);
+ }
+
+ printf("\n----------------------------------------------------------\n");
+ printf(" Variables expected to be untagged:\n");
+ printf("----------------------------------------------------------\n");
+ for (const auto& [name, pointer] : untagged_variables) {
+ print_variable_address(name, pointer);
+ }
+ printf("\n");
+}
+
+void dso_check_assertions(bool check_pointers_are_tagged) {
+ // Check that non-const variables are writeable.
+ global_extern = 0;
+ global_extern_hidden = 0;
+ global_extern_untagged = 0;
+ global_extern_untagged_definition_but_tagged_import = 0;
+
+ if (check_pointers_are_tagged) {
+ for (const auto& [_, pointer] : get_expected_tagged_vars()) {
+ check_tagged(pointer);
+ }
+ }
+
+ for (const auto& [_, pointer] : get_expected_untagged_vars()) {
+ check_untagged(pointer);
+ }
+}
diff --git a/tests/libs/mte_globals_relr_regression_test_b_314038442.cpp b/tests/libs/mte_globals_relr_regression_test_b_314038442.cpp
new file mode 100644
index 0000000..20bbba9
--- /dev/null
+++ b/tests/libs/mte_globals_relr_regression_test_b_314038442.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+
+static volatile char array[0x10000];
+volatile char* volatile oob_ptr = &array[0x111111111];
+
+unsigned char get_tag(__attribute__((unused)) volatile void* ptr) {
+#if defined(__aarch64__)
+ return static_cast<unsigned char>(reinterpret_cast<uintptr_t>(ptr) >> 56) & 0xf;
+#else // !defined(__aarch64__)
+ return 0;
+#endif // defined(__aarch64__)
+}
+
+int main() {
+ printf("Program loaded successfully. %p %p. ", array, oob_ptr);
+ if (get_tag(array) != get_tag(oob_ptr)) {
+ printf("Tags are mismatched!\n");
+ return 1;
+ }
+ if (get_tag(array) == 0) {
+ printf("Tags are zero!\n");
+ } else {
+ printf("Tags are non-zero\n");
+ }
+ return 0;
+}
diff --git a/tests/memtag_globals_test.cpp b/tests/memtag_globals_test.cpp
new file mode 100644
index 0000000..ff93e7b
--- /dev/null
+++ b/tests/memtag_globals_test.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <gtest/gtest.h>
+
+#if defined(__BIONIC__)
+#include "gtest_globals.h"
+#include "utils.h"
+#endif // defined(__BIONIC__)
+
+#include <android-base/test_utils.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string>
+#include <tuple>
+
+#include "platform/bionic/mte.h"
+
+class MemtagGlobalsTest : public testing::TestWithParam<bool> {};
+
+TEST_P(MemtagGlobalsTest, test) {
+ SKIP_WITH_HWASAN << "MTE globals tests are incompatible with HWASan";
+#if defined(__BIONIC__) && defined(__aarch64__)
+ std::string binary = GetTestLibRoot() + "/memtag_globals_binary";
+ bool is_static = MemtagGlobalsTest::GetParam();
+ if (is_static) {
+ binary += "_static";
+ }
+
+ chmod(binary.c_str(), 0755);
+ ExecTestHelper eth;
+ eth.SetArgs({binary.c_str(), nullptr});
+ eth.Run(
+ [&]() {
+ execve(binary.c_str(), eth.GetArgs(), eth.GetEnv());
+ GTEST_FAIL() << "Failed to execve: " << strerror(errno) << " " << binary.c_str();
+ },
+ // We catch the global-buffer-overflow and crash only when MTE globals is
+ // supported. Note that MTE globals is unsupported for fully static
+ // executables, but we should still make sure the binary passes its
+ // assertions, just that global variables won't be tagged.
+ (mte_supported() && !is_static) ? -SIGSEGV : 0, "Assertions were passed");
+#else
+ GTEST_SKIP() << "bionic/arm64 only";
+#endif
+}
+
+INSTANTIATE_TEST_SUITE_P(MemtagGlobalsTest, MemtagGlobalsTest, testing::Bool(),
+ [](const ::testing::TestParamInfo<MemtagGlobalsTest::ParamType>& info) {
+ if (info.param) return "MemtagGlobalsTest_static";
+ return "MemtagGlobalsTest";
+ });
+
+TEST(MemtagGlobalsTest, RelrRegressionTestForb314038442) {
+ SKIP_WITH_HWASAN << "MTE globals tests are incompatible with HWASan";
+#if defined(__BIONIC__) && defined(__aarch64__)
+ std::string binary = GetTestLibRoot() + "/mte_globals_relr_regression_test_b_314038442";
+ chmod(binary.c_str(), 0755);
+ ExecTestHelper eth;
+ eth.SetArgs({binary.c_str(), nullptr});
+ eth.Run(
+ [&]() {
+ execve(binary.c_str(), eth.GetArgs(), eth.GetEnv());
+ GTEST_FAIL() << "Failed to execve: " << strerror(errno) << " " << binary.c_str();
+ },
+ /* exit code */ 0, "Program loaded successfully.*Tags are zero!");
+#else
+ GTEST_SKIP() << "bionic/arm64 only";
+#endif
+}
+
+TEST(MemtagGlobalsTest, RelrRegressionTestForb314038442WithMteGlobals) {
+ if (!mte_supported()) GTEST_SKIP() << "Must have MTE support.";
+#if defined(__BIONIC__) && defined(__aarch64__)
+ std::string binary = GetTestLibRoot() + "/mte_globals_relr_regression_test_b_314038442_mte";
+ chmod(binary.c_str(), 0755);
+ ExecTestHelper eth;
+ eth.SetArgs({binary.c_str(), nullptr});
+ eth.Run(
+ [&]() {
+ execve(binary.c_str(), eth.GetArgs(), eth.GetEnv());
+ GTEST_FAIL() << "Failed to execve: " << strerror(errno) << " " << binary.c_str();
+ },
+ /* exit code */ 0, "Program loaded successfully.*Tags are non-zero");
+#else
+ GTEST_SKIP() << "bionic/arm64 only";
+#endif
+}