Starting in Android R, heap allocations have an implementation defined tag set in the top byte of the pointer on devices with kernel support for ARM Top-byte Ignore (TBI). Any application that modifies this tag is terminated when the tag is checked during deallocation. This is necessary for future hardware with ARM Memory Tagging Extension (MTE) support.
ARM's Top-byte Ignore feature is available in all Armv8 AArch64 hardware. This feature means that the hardware ignores the top byte of a pointer when accessing memory.
TBI requires a compatible kernel that correctly handles tagged pointers passed from userspace. Android Common Kernels from 4.14 and higher feature the required TBI patches. Starting with the Pixel 2, Pixel devices feature custom backports of the required TBI patches for their 4.4/4.9 kernels.
Devices with the TBI support in the kernel are dynamically detected at process start time and an implementation-dependent tag is inserted into the top byte of the pointer for all heap allocations. After this, a check is run to ensure the tag hasn't been truncated when deallocating the memory.
Memory Tagging Extension readiness
ARM's Memory Tagging Extension (MTE) helps address memory safety issues. MTE works by tagging the 56th-59th address bits of each memory allocation on the stack, heap, and globals. The hardware and instruction set automatically checks that the correct tag is used upon every memory access.
Android apps that incorrectly store information in the top byte of the pointer are guaranteed to break on an MTE-enabled device. Tagged pointers make it easier to detect and reject incorrect uses of the top byte of the pointer before MTE devices are available.
If your app crashed and you were prompted with this link that means something in your app modified the top byte of a pointer. The top byte of the pointer can't be modified and your code needs to be changed to fix this issue.
Examples of the top byte pointer being incorrectly modified.
- Pointers to a particular type have application specific metadata stored in the top 16 address bits.
- A pointer cast to double and then back, losing the lower address bits.
- Code computing the difference between the addresses of local variables from different stack frames as a way to measure recursion depth.