Arm Memory Tagging Extension

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 applications. 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).

Enabling 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)

  RODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS=vendor/$(vendor)
  PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS=vendor/$(vendor)/projectA \
                                    vendor/$(vendor)/projectB

Enabling 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.

Enabling 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.

Enabling MTE for applications

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 application, 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 application 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 application.
Alternatively, this can be set using the am command as follows:

$ adb shell am compat enable NATIVE_MEMTAG_[A]SYNC my.app.name

Building 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 applications (which are forked from zygote64) for which MTE can be enabled following the instructions above.

Configuring 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 thePSTATE.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 the SA_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.
  • 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.

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 applications
  • statsd daemon
  • system_server
  • zygote64 (to allow applications 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.