blob: 3034cc73e9f4b86e735f277200794f44672f5dcb [file] [log] [blame] [view]
# 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