Your phone can detect memory corruption in production. Not with the full instrumentation you run in CI, and not on every allocation. But the hardware in your pocket has been shipping with the necessary primitives for a few years now, and a growing number of production apps are quietly turning them on.

The short version: AddressSanitizer is too expensive for production. ARM Memory Tagging Extension is not. And if you cannot rely on MTE yet, GWP-ASan gives you probabilistic coverage with almost no overhead. Together, they answer a question that used to have a depressing answer.

Memory Corruption Is the Bug You Ship

Use-after-free, buffer overflow, and heap corruption are the bugs that pass every test suite. They live in native code, they reproduce poorly, and they often crash somewhere far from the actual mistake. By the time a user reports a crash, the original corruption has already happened, and your only evidence is a mangled stack trace or a SIGSEGV at an address that looks random.

Traditional tools catch these early. Valgrind is accurate but slow. ASan is faster but still adds roughly 2-3x memory overhead and 2x CPU overhead. That is fine for fuzzing or unit tests. It is not fine for a battery-powered device running Instagram.

Production has historically been a blind spot. You either reproduced the bug locally or you guessed.

How ARM MTE Tags Memory in Hardware

ARM Memory Tagging Extension, introduced in ARMv8.5-A, gives the CPU a cheap way to check pointer validity on every memory access. It works by assigning a 4-bit tag to every 16-byte granule of memory and storing a matching tag in the top byte of the pointer.

On every load or store, the CPU compares the pointer tag against the memory tag. If they differ, the hardware raises a fault. This is not a software check. It happens in the memory subsystem, and the overhead is typically under 5%.

The top byte of a 64-bit pointer was already ignored by the hardware. MTE repurposes those bits. A pointer like 0xb7f0_0000_1234_5678 carries tag 0xb7. The memory at that address must carry the same tag, or the access faults.

MTE is available on devices with ARMv8.5-A or later. That includes the Google Pixel 8 and later, the iPhone 15 Pro and later, and a growing share of mid-range Android devices. It is not universal, but it is no longer exotic.

The kernel exposes MTE through prctl flags. An application can request synchronous or asynchronous checking. Synchronous mode faults immediately on tag mismatch. Asynchronous mode queues the fault and delivers it later, which is cheaper but slightly delays detection.

GWP-ASan: When You Cannot Tag Everything

MTE is great, but it requires compatible hardware and tagged allocations throughout your codebase. If you ship on older devices, or if you only want targeted protection, GWP-ASan is the pragmatic alternative.

GWP-ASan stands for Guarded Write Protection AddressSanitizer. It is a sampling allocator that places a small fraction of heap allocations into guarded pages surrounded by poisoned redzones. If a use-after-free or buffer overflow touches those redzones, the hardware MMU triggers a fault immediately.

The key insight is probability. GWP-ASan might guard 1 in 10,000 allocations. That sounds useless until you realize that a buggy app will corrupt memory thousands of times before it crashes. Given enough user sessions, even a 0.01% sampling rate catches real bugs in production.

Google has run GWP-ASan in Chrome and Android system services for years. It has found hundreds of use-after-free bugs that escaped both testing and fuzzing. The CPU overhead is negligible because only a tiny fraction of allocations go through the guarded path. Memory overhead is bounded because the guarded pool is small and allocations are eventually recycled.

The Trade-Offs Nobody Wants to Talk About

MTE and GWP-ASan are not free. They are just cheap enough to be worth it.

MTE requires the top byte of every pointer, which means your codebase must be compiled with -fsanitize=memtag or -march=armv8.5-a+memtag. If you have code that masks pointers, hashes them, or passes them through JNI without proper tagging, you will get false positives. The Android NDK and modern libc implementations handle this correctly, but custom allocators or pointer packing schemes will break.

MTE also only catches bugs within the heap and stack regions you explicitly tag. A wild pointer that jumps to an untagged mmap region will not be caught. This is better than nothing, but it is not total coverage.

GWP-ASan has the opposite problem. It catches only the allocations that happen to land in the guarded pool. A bug that corrupts a non-guarded allocation will go unnoticed. You also pay a small latency cost on allocation and deallocation for the sampled set, and you need to handle the crashes gracefully in your telemetry pipeline.

Neither tool replaces ASan in your CI pipeline. They complement it. ASan gives you deterministic, high-coverage detection during testing. MTE and GWP-ASan give you a safety net in the field.

Enabling MTE on a Modern Android Device

If you have a Pixel 8 or newer, you can test MTE today. The Android NDK supports it with a single compiler flag.

First, check whether your device supports MTE:

#include <sys/prctl.h>
#include <linux/prctl.h>
#include <sys/auxv.h>
#include <asm/hwcap.h>

bool mte_supported() {
    unsigned long hwcap2 = getauxval(AT_HWCAP2);
    return (hwcap2 & HWCAP2_MTE) != 0;
}

Then build your native library with MTE enabled in your CMakeLists.txt:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=memtag")

If you want synchronous faulting, which is what you typically want in production, request it at startup:

#include <sys/prctl.h>

void enable_mte() {
    if (mte_supported()) {
        prctl(PR_SET_TAGGED_ADDR_CTRL,
              PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC | (0xfffe << PR_MTE_TAG_SHIFT),
              0, 0, 0);
    }
}

The PR_MTE_TCF_SYNC flag tells the kernel to fault immediately on tag mismatch. The tag mask 0xfffe excludes tag zero, which some system code still uses.

Setting Up GWP-ASan for Production

GWP-ASan is easier to enable because it does not require special hardware. On Android, you can link against the GWP-ASan allocator wrapper or enable it through the system allocator on newer API levels.

For a minimal integration, wrap your allocator entry points:

extern "C" void* malloc(size_t size) {
    if (__gwp_asan_sample()) {
        return __gwp_asan_guarded_malloc(size);
    }
    return __libc_malloc(size);
}

In practice, you will use the Android GWP-ASan runtime or the LLVM compiler-rt implementation. The key configuration knob is the sample rate. Start conservative:

// One guarded allocation per 10,000
__gwp_asan_set_sample_rate(10000);

Collect the resulting crashes through your existing telemetry. A GWP-ASan crash looks like a standard SIGSEGV, but the faulting address will land in a guarded page. Your crash reporter can detect this by checking whether the faulting address falls within the GWP-ASan pool.

What You Should Actually Do

If you ship native code on mobile, you should already be running ASan in CI and fuzzing. The question is what happens after you ship.

If your minimum supported device includes ARMv8.5-A chips, enable MTE in synchronous mode. The overhead is low enough that users will not notice, and the crashes you get will have accurate tag mismatch information instead of random heap corruption.

If you support older devices, enable GWP-ASan with a conservative sample rate. It will not catch every bug, but it will catch bugs that nothing else catches. Over millions of sessions, that is not theoretical. It is how Chrome found a use-after-free in its image decoder last year.

The hardware in your phone is already capable of catching the memory safety bugs you are most afraid of. The only question is whether you have turned it on.