Arm v9 introduces Arm Memory Tagging Extension (MTE), a hardware implementation of tagged memory.
At a high level, MTE tags each memory allocation/deallocation with additional metadata. It assigns a tag to a memory location, which then can be associated with pointers that reference that memory location. At runtime the CPU checks that the pointer and the metadata tags match on each load and store.
In Android 12 the kernel and userspace heap memory allocator can augment each allocation with metadata. This helps detect use-after-free and buffer-overflow bugs, which are the most common source of memory safety bugs in our codebases.
MTE operating modes
MTE has three operating modes:
- Synchronous mode (SYNC)
- Asynchronous mode (ASYNC)
- Asymmetric mode (ASYMM)
Synchronous mode (SYNC)
This mode is optimized for correctness of bug detection over performance and
can be used as a precise bug detection tool, when higher performance overhead is
acceptable. When enabled, MTE SYNC acts as a security mitigation.
On a tag mismatch, the processor aborts execution immediately and
terminates the process with SIGSEGV
(code
SEGV_MTESERR
) and full information about the memory access and the
faulting address.
We recommend using this mode during testing as an alternative to HWASan/KASAN or in production when the target process represents a vulnerable attack surface. In addition, when ASYNC mode has indicated the presence of a bug, an accurate bug report can be obtained by using the runtime APIs to switch execution to SYNC mode.
When running in SYNC mode, the Android allocator records stack traces for all allocations and deallocations and uses them to provide better error reports that include an explanation of a memory error, such as use-after-free, or buffer-overflow, and the stack traces of the relevant memory events. Such reports provide more contextual information and make bugs easier to trace and fix.
Asynchronous mode (ASYNC)
This mode is optimized for performance over accuracy of bug reports and can
be used as low-overhead detection for memory safety bugs.
On a tag mismatch, the processor continues execution until the nearest
kernel entry (for example, a syscall or timer interrupt), where it terminates
the process with SIGSEGV
(code SEGV_MTEAERR
) without
recording the faulting address or memory access.
We recommend using this mode in production on well tested codebases where
the density of memory safety bugs is known to be low, which is achieved by using
the SYNC mode during testing.
Asymmetric mode (ASYMM)
An additional feature in Arm v8.7-A, Asymmetric MTE mode provides synchronous checking on memory reads, and asynchronous checking of memory writes, with performance similar to that of the ASYNC mode. In most situations, this mode is an improvement over the ASYNC mode, and we recommend using it instead of ASYNC whenever it is available.
For this reason, none of the APIs described below mention the Asymmetric mode. Instead, the OS can be configured to always use Asymmetric mode when Asynchronous is requested. Please refer to the "Configuring the CPU-specific preferred MTE level" section for more information.
MTE in the userspace
The following sections describe how MTE can be enabled for system processes and apps. MTE is disabled by default, unless one of the options below is set for a particular process (see what components MTE is enabled for below).
Enable MTE using the build system
As a process-wide property, MTE is controlled by the build time setting of the main executable. The following options allow changing this setting for individual executables, or for entire subdirectories in the source tree. The setting is ignored on libraries, or any target that is neither executable nor a test.
1. Enabling MTE in Android.bp
(example),
for a particular project:
MTE Mode | Setting |
---|---|
Asynchronous MTE | sanitize: { memtag_heap: true, } |
Synchronous MTE | sanitize: { memtag_heap: true, diag: { memtag_heap: true, }, } |
or in Android.mk:
MTE Mode | Setting |
---|---|
Asynchronous MTE |
LOCAL_SANITIZE := memtag_heap |
Synchronous MTE |
LOCAL_SANITIZE := memtag_heap LOCAL_SANITIZE_DIAG := memtag_heap |
2. Enabling MTE on a subdirectory in the source tree using a product variable:
MTE mode | Include list | Exclude list |
---|---|---|
async | PRODUCT_MEMTAG_HEAP_ASYNC_INCLUDE_PATHS
MEMTAG_HEAP_ASYNC_INCLUDE_PATHS |
PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS
MEMTAG_HEAP_EXCLUDE_PATHS |
sync | PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS
MEMTAG_HEAP_SYNC_INCLUDE_PATHS |
or
MTE Mode | Setting |
---|---|
Asynchronous MTE | MEMTAG_HEAP_ASYNC_INCLUDE_PATHS |
Synchronous MTE | MEMTAG_HEAP_SYNC_INCLUDE_PATHS |
or by specifying the exclude path of an executable:
MTE Mode | Setting |
---|---|
Asynchronous MTE | PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS
MEMTAG_HEAP_EXCLUDE_PATHS |
Synchronous MTE |
Example, (similar usage to PRODUCT_CFI_INCLUDE_PATHS
)
PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS=vendor/$(vendor) PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS=vendor/$(vendor)/projectA \ vendor/$(vendor)/projectB
Enable MTE using system properties
The above build settings can be overridden at runtime by setting the following system property:
arm64.memtag.process.<basename> = (off|sync|async)
Where basename
stands for the base name of the executable.
For example, to set /system/bin/ping
or /data/local/tmp/ping
to use asynchronous MTE, use adb shell setprop arm64.memtag.process.ping async
.
Enable MTE using an environment variable
One more way to override the build setting is by defining the environment
variable: MEMTAG_OPTIONS=(off|sync|async)
If both the environment variable and the system property are defined, the
variable takes precedence.
Enable MTE for apps
If not specified MTE is disabled by default but
apps that want to use MTE can do so by setting android:memtagMode
under the <application>
or
<process>
tag in the
AndroidManifest.xml
.
android:memtagMode=(off|default|sync|async)
When set on the <application>
tag, the
attribute affects all processes used by the app, and can be overridden
for individual processes by setting the
<process>
tag.
For experimentation, compatibility
changes can be used to set the default value of
memtagMode
attribute for an app that does
not specify any value in the manifest (or specifies
default
).
These can be found under System > Advanced > Developer options
> App Compatibility Changes
in the global setting menu. Setting
NATIVE_MEMTAG_ASYNC
or NATIVE_MEMTAG_SYNC
enables MTE
for a particular app.
Alternatively, this can be set using the am
command as follows:
$ adb shell am compat enable NATIVE_MEMTAG_[A]SYNC my.app.name
Build an MTE system image
We strongly recommend enabling MTE on all native binaries during development and bring-up. This helps detect memory safety bugs early and provides realistic user coverage, if enabled in testing builds.
We strongly recommend enabling MTE in Synchronous mode on all native binaries during development
SANITIZE_TARGET=memtag_heap SANITIZE_TARGET_DIAG=memtag_heap m
As with any variable in the build system, SANITIZE_TARGET
can be
used as an environment variable or a make
setting (for example, in
a product.mk
file).
Please note that this enables MTE for all native processes, but not for
apps (which are forked from zygote64
) for which MTE can be
enabled following the instructions above.
Configure the CPU-specific preferred MTE level
On some CPUs the performance of MTE in ASYMM or even SYNC modes may be similar to
that of ASYNC. This makes it worthwhile to enable
stricter checks on those CPUs when a less strict checking mode is requested, in
order to gain the error detection benefits of the stricter checks without the
performance downsides.
By default, processes configured to run in ASYNC mode will run in ASYNC
mode on all CPUs. To configure the kernel to run these processes in SYNC mode on
specific CPUs, the value sync must be written to the
sysfs
entry
/sys/devices/system/cpu/cpu<N>/mte_tcf_preferred
at boot
time. This can be done with an init script. For example, to configure CPUs 0-1
to run ASYNC mode processes in SYNC mode, and CPUs 2-3 to use run in ASYMM mode,
the following may be added to the init clause of a vendor init script:
write /sys/devices/system/cpu/cpu0/mte_tcf_preferred sync write /sys/devices/system/cpu/cpu1/mte_tcf_preferred sync write /sys/devices/system/cpu/cpu2/mte_tcf_preferred asymm write /sys/devices/system/cpu/cpu3/mte_tcf_preferred asymm
Tombstones from ASYNC mode processes running in SYNC mode will contain a precise stack trace of the location of the memory error. However, they will not include an allocation or deallocation stack trace. These stack traces are only available if the process is configured to run in SYNC mode.
int mallopt(M_THREAD_DISABLE_MEM_INIT, level)
where level
is 0 or 1.
Disables memory initialization in malloc, and avoids changing memory tags
unless necessary for correctness.
int mallopt(M_MEMTAG_TUNING, level)
where level
is:
M_MEMTAG_TUNING_BUFFER_OVERFLOW
M_MEMTAG_TUNING_UAF
Selects tag allocation strategy.
- Default setting is
M_MEMTAG_TUNING_BUFFER_OVERFLOW
. M_MEMTAG_TUNING_BUFFER_OVERFLOW
- enables deterministic detection of linear buffer overflow and underflow bugs by assigning distinct tag values to adjacent allocations. This mode has a slightly reduced chance to detect use-after-free bugs because only half of the possible tag values are available for each memory location. Please keep in mind that MTE can not detect overflow within the same tag granule (16-byte aligned chunk), and can miss small overflows even in this mode. Such overflow can not be the cause of memory corruption, because the memory within one granule is never used for multiple allocations.M_MEMTAG_TUNING_UAF
- enables independently randomized tags for uniform ~93% probability of detecting both spatial (buffer overflow) and temporal (use after free) bugs.
In addition to the APIs described above, experienced users might want to be aware of the following:
- Setting the
PSTATE.TCO
hardware register can temporarily suppress tag checking (example). For example, when copying a range of memory with unknown tag contents, or addressing a performance bottleneck in a hot loop. - When using
M_HEAP_TAGGING_LEVEL_SYNC
, the system crash handler provides extra information such as allocation and deallocation stack traces. This functionality requires access to the tag bits and is enabled by passing theSA_EXPOSE_TAGBITS
flag when setting the signal handler. Any program that sets its own signal handler and delegates unknown crashes to the system one is recommended to do the same.
MTE in the kernel
To enable MTE-accelerated KASAN for the kernel, configure the kernel with
CONFIG_KASAN=y
, CONFIG_KASAN_HW_TAGS=y
. These configs
are enabled by default on GKI kernels, starting with Android
12-5.10
.
This can be controlled at boot time using the following command line arguments:
kasan=[on|off]
- enable or disable KASAN (default:on
)kasan.mode=[sync|async]
- choose between synchronous and asynchronous mode (default:sync
)kasan.stacktrace=[on|off]
- whether to collect stack traces (default:on
)- stack trace collection also requires
stack_depot_disable=off
.
- stack trace collection also requires
kasan.fault=[report|panic]
- whether to only print the report, or also panic the kernel (default:report
). Regardless of this option, tag checking gets disabled after the first reported error.
Recommended usage
We strongly recommend using SYNC mode during bring-up, development and testing. This option should be enabled globally for all processes using the environment variable or with the build system. In this mode, bugs are detected early in the development process, the codebase is stabilized quicker and the cost of detecting bugs later in production is avoided.
We strongly recommend using ASYNC mode in production. This provides a low overhead tool for detecting the presence of memory safety bugs in a process as well as further defense-in-depth. Once a bug is detected, the developer can leverage the runtime APIs to switch to SYNC mode and get an accurate stack trace from a sampled set of users.
We strongly recommend configuring the CPU-specific preferred MTE level for the SoC. Asymm mode typically has the same performance characteristics as ASYNC, and is almost always preferable to it. Small in-order cores often show similar performance in all three modes, and can be configured to prefer SYNC.
Developers should check the presence of crashes by checking
/data/tombstones
,
logcat
or by monitoring the vendor DropboxManager
pipeline for end user bugs. For more info on debugging Android native code, see
the information here.
MTE enabled platform components
In Android 12, a number of security critical system components use MTE ASYNC to detect end user crashes and to act as an additional layer of defense-in-depth. These components are:
- Networking daemons and utilities (with the exception of
netd
) - Bluetooth, SecureElement, NFC HALs, and system apps
statsd
daemonsystem_server
zygote64
(to allow apps to opt-in to using MTE)
These targets were selected based on the following criteria:
- A privileged process (defined as a process that has access to something that the unprivileged_app SELinux domain does not)
- Processes untrustworthy input (Rule of two)
- Acceptable performance slowdown (slowdown does not create user visible latency)
We encourage vendors to enable MTE in production for more components,
following the criteria mentioned above. During development we recommend testing
these components using SYNC mode, to detect easily fixed bugs, and assess the
ASYNC impact on their performance.
In the future, Android plans to expand the list of system components MTE is
enabled on, guided by the performance characteristics of upcoming hardware designs.