| # Arm Memory Tagging Extension (MTE) implementation |
| |
| AOSP supports Arm MTE to detect invalid memory accesses. The implementation is |
| spread across multiple components, both within and out of the AOSP tree. This |
| document gives an overview and pointers about how the various MTE features are |
| implemented. |
| |
| For documentation of the behavior rather than the implementation, see the |
| [SAC page on MTE] instead. For MTE for apps, see the [NDK page on MTE]. |
| |
| The relevant components are: |
| |
| * [LLVM Project] (out of AOSP tree) |
| * Stack tagging instrumentation pass |
| * Scudo memory allocator |
| * bionic |
| * libc |
| * dynamic loader |
| * Zygote |
| * debuggerd |
| * [NDK] |
| |
| ## MTE enablement |
| |
| The way MTE is requested and enabled differs between native binaries and Java |
| apps. This is necessarily so, because Java apps get forked from the Zygote, |
| while native executables get inintialized by the linker. |
| |
| ### Native binaries |
| |
| Both AOSP and the NDK allow you to compile C/C++ code that use MTE to detect |
| memory safety issues. The [NDK legacy cmake toolchain] and the |
| [NDK new cmake toolchain] both support "memtag" as an argument for |
| `ANDROID_SANITIZE`. NDK make has no specific support for MTE, but the |
| relevant flags can be passed directly as `CFLAGS` and `LDFLAGS`. |
| |
| For the OS itself, [Soong] supports "memtag_[heap|stack|globals]" as |
| `SANITIZE_TARGET and as `sanitize:` attribute in Android.bp files; |
| [Android make] supports the same environment variables as Soong. This passes |
| the appropriate flags to the clang driver for both compile and link steps. |
| |
| #### Linker |
| |
| * For **dynamic executables** LLD has support to |
| [add appropriate dynamic sections] as defined in the [ELF standard] |
| * For **static executables** and as a fallback for older devices, LLD |
| also supports [adding the Android-specific ELF note] |
| |
| Both of the above are controlled by the linker flag `--android-memtag-mode` |
| which is [passed in by the clang driver] if |
| `-fsanitize=memtag-[stack|heap|globals]` is [passed in]. |
| `-fsanitize=memtag` [enables all three] (even for API levels that don't |
| implement the runtime for globals, which means builds from old versions |
| of clang may no work with newer platform versions that support globals). |
| `-fsanitize-memtag-mode` allows to choose between ASYNC and SYNC. |
| |
| This information can be queried using `llvm-readelf --memtag`. |
| |
| This information is [picked up by libc init] to decide whether to enable MTE. |
| `-fsanitize-heap` controls both whether scudo tags allocations, and whether |
| tag checking is enabled. |
| |
| #### Runtime environment (dynamic loader, libc) |
| |
| There are two different initialization sequences for libc, both of which end up |
| calling `__libc_init_mte`. |
| |
| N.B. the linker has its own copy of libc, which is used when executing these |
| functions. That is why we have to use `__libc_shared_globals` to communicate |
| with the libc of the process we are starting. |
| |
| * **static executables** `__libc_init` is called from `crtbegin.c`, which calls |
| `__libc_init_mte` |
| * **dynamic executables** the linker calls `__libc_init_mte` |
| |
| `__libc_init_mte` figures out the appropriate MTE level that is requested by |
| the process, calls `prctl` to request this from the kernel, and stores data in |
| `__libc_shared_globals` which gets picked up later to enable MTE in scudo. |
| |
| It also does work related to stack tagging and permissive mode, which will be |
| detailed later. |
| |
| ### Apps |
| |
| Apps can request MTE be enabled for their process via the manifest attribute |
| `android:memtagMode`. This gets interpreted by Zygote, which always runs with |
| `ASYNC` MTE enabled, because MTE for a process can only be disabled after |
| it has been initialized (see [Native binaries](#native-binaries)), not enabled. |
| |
| [decideTaggingLevel] in the Zygote figures out whether to enable MTE for |
| an app, and stores it in the `runtimeFlags`, which get picked up by |
| [SpecializeCommon] after forking from the Zygote. |
| |
| ## MTE implementation |
| |
| ### Heap Tagging |
| |
| Heap tagging is implemented in the scudo allocator. On `malloc` and `free`, |
| scudo will update the memory's tags to prevent use-after-free and buffer |
| overflows. |
| |
| [scudo's memtag.h] contains helper functions to deal with MTE tag management, |
| which are used in [combined.h] and [secondary.h]. |
| |
| |
| ### Stack Tagging |
| |
| Stack tagging requires instrumenting function bodies. It is implemented as |
| an instrumentation pass in LLVM called [AArch64StackTagging], which sets |
| the tags according to the lifetime of stack objects. |
| |
| The instrumentation pass also supports recording stack history, consisting of: |
| |
| * PC |
| * Frame pointer |
| * Base tag |
| |
| This can be used to reconstruct which stack object was referred to in an |
| invalid access. The logic to reconstruct this can be found in the |
| [stack script]. |
| |
| |
| Stack tagging is enabled in one of two circumstances: |
| * at process startup, if the main binary or any of its dependencies are |
| compiled with `memtag-stack` |
| * library compiled with `memtag-stack` is `dlopen`ed later, either directly or |
| as a dependency of a `dlopen`ed library. In this case, the |
| [__pthread_internal_remap_stack_with_mte] function is used (called from |
| `memtag_stack_dlopen_callback`). Because `dlopen` |
| is handled by the linker, we have to [store a function pointer] to the |
| process's version of the function in `__libc_shared_globals`. |
| |
| Enabling stack MTE consists of two operations: |
| * Remapping the stacks as `PROT_MTE` |
| * Allocating a stack history buffer. |
| |
| The first operation is only necessary when the process is running with MTE |
| enabled. The second operation is also necessary when the process is not running |
| with MTE enabled, because the writes to the stack history buffer are |
| unconditional. |
| |
| libc keeps track of this through two globals: |
| |
| * `__libc_memtag_stack`: whether stack MTE is enabled on the process, i.e. |
| whether the stack pages are mapped with PROT\_MTE. This is always false if |
| MTE is disabled for the process (i.e. `libc_globals.memtag` is false). |
| * `__libc_memtag_stack_abi`: whether the process contains any code that was |
| compiled with memtag-stack. This is true even if the process does not have |
| MTE enabled. |
| |
| ### Globals Tagging |
| |
| TODO(fmayer): write once submitted |
| |
| ### Crash reporting |
| |
| For MTE crashes, debuggerd serializes special information into the Tombstone |
| proto: |
| |
| * Tags around fault address |
| * Scudo allocation history |
| |
| This is done in [tombstone\_proto.cpp]. The information is converted to a text |
| proto in [tombstone\_proto\_to\_text.cpp]. |
| |
| ## Bootloader control |
| |
| The bootloader API allows userspace to enable MTE on devices that do not ship |
| with MTE enabled by default. |
| |
| See [SAC MTE bootloader support] for the API definition. In AOSP, this API is |
| implemented in [system/extras/mtectrl]. mtectrl.rc handles the property |
| changes and invokes mtectrl to update the misc partition to communicate |
| with the bootloader. |
| |
| There is also an [API in Device Policy Manager] that allows the device admin |
| to enable or disable MTE under certain circumstances. |
| |
| The device can opt in or out of these APIs by a set of system properties: |
| |
| * `ro.arm64.memtag.bootctl_supported`: the system property API is supported, |
| and an option is displayed in Developer Options. |
| * `ro.arm64.memtag.bootctl_settings_toggle`: an option is displayed in the |
| normal settings. This requires `ro.arm64.memtag.bootctl_supported` to be |
| true. This implies `ro.arm64.memtag.bootctl_device_policy_manager`, if it |
| is not explicitely set. |
| * `ro.arm64.memtag.bootctl_device_policy_manager`: the Device Policy Manager |
| API is supported. |
| |
| ## Permissive MTE |
| |
| Permissive MTE refers to a mode which, instead of crashing the process on an |
| MTE fault, records a tombstone but then continues execution of the process. |
| An important caveat is that system calls with invalid pointers (where the |
| pointer tag does not match the memory tag) still return an error code. |
| |
| This mode is only available for system services, not apps. It is implemented |
| in the [debugger\_signal\_handler] by disabling MTE for the faulting thread. |
| Optionally, the user can ask for MTE to be re-enabled after some time. |
| This is achieved by arming a timer that calls [enable_mte_signal_handler] |
| upon expiry. |
| |
| ## MTE Mode Upgrade |
| |
| When a system service [crashes in ASYNC mode], we set an impossible signal |
| as an exit code (because that signal is always gracefully handled by libc), |
| and [in init] we set `BIONIC_MEMTAG_UPGRADE_SECS`, which gets handled by |
| [libc startup]. |
| |
| [SpecializeCommon]: https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/jni/com_android_internal_os_Zygote.cpp?q=f:frameworks%2Fbase%2Fcore%2Fjni%2Fcom_android_internal_os_Zygote.cpp%20%22%20mallopt(M_BIONIC_SET_HEAP_TAGGING_LEVEL,%22&ss=android%2Fplatform%2Fsuperproject%2Fmain |
| [LLVM Project]: https://github.com/llvm/llvm-project/ |
| [NDK]: https://android.googlesource.com/platform/ndk/ |
| [NDK legacy cmake toolchain]: https://android.googlesource.com/platform/ndk/+/refs/heads/main/build/cmake/android-legacy.toolchain.cmake#490 |
| [NDK new cmake toolchain]: https://android.googlesource.com/platform/ndk/+/refs/heads/main/build/cmake/flags.cmake#56 |
| [Soong]: https://cs.android.com/android/platform/superproject/main/+/main:build/soong/cc/sanitize.go?q=sanitize.go&ss=android%2Fplatform%2Fsuperproject%2Fmain |
| [decideTaggingLevel]: https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/com/android/internal/os/Zygote.java?q=symbol:decideTaggingLevel |
| [picked up by libc init]: https://cs.android.com/android/platform/superproject/main/+/main:bionic/libc/bionic/libc_init_mte.cpp?q=symbol:__get_tagging_level%20f:bionic |
| [enables all three]: https://github.com/llvm/llvm-project/blob/e732d1ce86783b1d7fe30645fcb30434109505b9/clang/include/clang/Basic/Sanitizers.def#L62 |
| [passed in]: https://github.com/llvm/llvm-project/blob/ff2e619dfcd77328812a42d2ba2b11c3ff96f410/clang/lib/Driver/SanitizerArgs.cpp#L719 |
| [passed in by the clang driver]: https://github.com/llvm/llvm-project/blob/ff2e619dfcd77328812a42d2ba2b11c3ff96f410/clang/lib/Driver/ToolChains/CommonArgs.cpp#L1595 |
| [adding the Android-specific ELF note]: https://github.com/llvm/llvm-project/blob/435cb0dc5eca08cdd8d9ed0d887fa1693cc2bf33/lld/ELF/Driver.cpp#L1258 |
| [ELF standard]: https://github.com/ARM-software/abi-aa/blob/main/memtagabielf64/memtagabielf64.rst#6dynamic-section |
| [add appropriate dynamic sections]: https://github.com/llvm/llvm-project/blob/7022498ac2f236e411e8a0f9a48669e754000a4b/lld/ELF/SyntheticSections.cpp#L1473 |
| [storeTags]: https://cs.android.com/android/platform/superproject/main/+/main:external/scudo/standalone/memtag.h?q=f:scudo%20f:memtag.h%20function:storeTags |
| [SAC page on MTE]: https://source.android.com/docs/security/test/memory-safety/arm-mte |
| [NDK page on MTE]: https://developer.android.com/ndk/guides/arm-mte |
| [AArch64StackTagging]: https://github.com/llvm/llvm-project/blob/main/llvm/lib/Target/AArch64/AArch64StackTagging.cpp |
| [scudo's memtag.h]: https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/scudo/standalone/memtag.h |
| [combined.h]: https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/scudo/standalone/combined.h |
| [secondary.h]: https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/scudo/standalone/secondary.h |
| [__pthread_internal_remap_stack_with_mte]: https://cs.android.com/android/platform/superproject/main/+/main:bionic/libc/bionic/pthread_internal.cpp?q=__pthread_internal_remap_stack_with_mte |
| [stack script]: https://cs.android.com/android/platform/superproject/main/+/main:development/scripts/stack?q=stack |
| [Android make]: https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/config_sanitizers.mk |
| [store a function pointer]: https://cs.android.com/android/platform/superproject/main/+/main:bionic/libc/bionic/libc_init_dynamic.cpp;l=168?q=memtag_stack_dlopen_callback |
| [tombstone\_proto.cpp]: https://cs.android.com/android/platform/superproject/main/+/main:system/core/debuggerd/libdebuggerd/tombstone_proto.cpp?q=tombstone_proto.cpp |
| [tombstone\_proto\_to\_text.cpp]: https://cs.android.com/android/platform/superproject/main/+/main:system/core/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp |
| [SAC MTE bootloader support]: https://source.android.com/docs/security/test/memory-safety/bootloader-support |
| [system/extras/mtectrl]: https://cs.android.com/android/platform/superproject/main/+/main:system/extras/mtectrl/ |
| [API in Device Policy Manager]: https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/admin/DevicePolicyManager.java?q=symbol:setMtePolicy%20f:DevicePolicyManager.java |
| [debuggerd\_signal_handler]: https://cs.android.com/android/platform/superproject/main/+/main:system/core/debuggerd/handler/debuggerd_handler.cpp?q=f:debuggerd_handler.cpp%20symbol:debuggerd_signal_handler |
| [enable_mte_signal_handler]: https://cs.android.com/android/platform/superproject/main/+/main:bionic/libc/bionic/libc_init_mte.cpp?q=symbol:__enable_mte_signal_handler |
| [in init]: https://cs.android.com/android/platform/superproject/main/+/main:system/core/init/service.cpp?q=f:system%2Fcore%2Finit%2Fservice.cpp%20should_upgrade_mte |
| [crashes in ASYNC mode]: https://cs.android.com/android/platform/superproject/main/+/main:system/core/debuggerd/handler/debuggerd_handler.cpp;l=799?q=BIONIC_SIGNAL_ART_PROFILER |
| [libc startup]: https://cs.android.com/android/platform/superproject/main/+/main:bionic/libc/bionic/libc_init_mte.cpp?q=BIONIC_MEMTAG_UPGRADE_SECS |