How Apple MIE Makes UAF Exploitation Harder?
How Apple MIE Makes UAF Exploitation Harder?
Memory corruption bugs are a recurring starting point in sophisticated attacks targeting mobile devices. Attackers use native-code bugs such as buffer overflows and use-after-free to gain code execution, then chain that capability into a sandbox escape or privilege escalation. To counter this pattern, vendors have introduced mitigations such as ASLR, PAC, and CFI to raise the exploit cost. Traditional mitigations, however, still have limits.
Arm introduced MTE, the Memory Tagging Extension, to narrow that gap. Android uses MTE both to find bugs during development and as an exploit mitigation on deployed devices. Apple concluded that baseline MTE alone was insufficient as a real-time mitigation. It combined synchronous EMTE, type-aware allocators, and tag-confidentiality protections into Memory Integrity Enforcement, or MIE. That broader design is why MIE, introduced on A19-based iPhones in 2025, is more than an “Apple version of MTE.” AOSP MTE documentation, Apple MIE design article
The two systems begin with the same idea, but their names cover different scopes. MTE is an Arm hardware feature; MIE is Apple's hardware-and-OS defense. This article starts with the tag check, moves through Android's deployment choices and Apple's additional protections, and finishes with a use-after-free experiment run on an M5 Pro.
Same Address, Different Object
An access can go wrong in two basic ways: it can cross an allocation's boundary, or happen after the allocation's lifetime has ended.
| Category |
Invalid access |
Example |
| Spatial |
Outside an object's permitted range |
Writing past a buffer into another allocation |
| Temporal |
Outside an object's lifetime |
Reading through an old pointer after free() |
Page permissions protect entire pages. When several small objects share a writable page, page permissions alone cannot distinguish access to object A from an accidental access to object B. Tagging introduces a smaller unit of checking.
Its question is “Does this pointer's tag match this memory's tag?” That differs from “Is this exactly the intended byte of a live object?” This distinction explains both the mechanism's usefulness and its limits.
How MTE Tags Memory
Conventional AArch64 MTE associates a 4-bit allocation tag with each 16-byte granule. The pointer's logical tag occupies bits 59–56. For memory configured for checking, the CPU compares these values. TBI, Top Byte Ignore, excludes the upper byte from address translation, and MTE uses that space for the tag checked on memory access. Linux arm64 MTE documentation
Pointer A
logical tag = 3
|
+-----------+-----------+
| |
access A cross into B
| |
v v
+----------------------+ +----------------------+
| Allocation A | | Allocation B |
| allocation tag = 3 | | allocation tag = 7 |
+----------+-----------+ +----------+-----------+
| 3 == 3 | 3 == 7
v v
Tag check passes Tag check fails
This diagram is a simplified example of the tag comparison performed when a pointer crosses into another allocation.
Android's Scudo allocator updates tags during allocation and deallocation. Heap tagging lives in the allocator; stack tagging requires LLVM instrumentation to manage stack-object lifetimes. bionic, the dynamic loader, Zygote, and debuggerd participate in activation and reporting. bionic MTE implementation
The same idea applies to use-after-free:
Pointer (tag 3) Memory
| |
| -------- Valid access ---------> | allocation tag = 3
| <----------- Allowed ----------- | (3 == 3)
| |
| free and retag to 7
| |
| ---- Access through stale ----X | (3 != 7)
| Tag check fails |
When a new object reuses the same virtual address, the allocator gives the memory a new tag. The old tag remains in the stale pointer, so the next access produces a mismatch.
What Four Bits Mean
Four bits can represent at most 16 values. Under a simplified model of independent, uniform selection over all 16 values, the match probability is 1/16, and the mismatch probability is 15/16 = 93.75%. The actual detection probability depends on how the allocator selects and reuses tags. AOSP documents several assignment strategies. AOSP MTE documentation
The 3.125% obtained from 4 bits / 16 bytes is the ratio for densely packed tag metadata. Measuring memory use and CPU cost also includes alignment, allocator metadata, diagnostics, and page management.
When Android MTE Stops an Invalid Access
Once tags differ, the next question is whether that access stops immediately or execution continues until a later fault report. This detail is easy to miss when reading about Android MTE.
| Mode |
Invalid read |
Invalid write |
Interpretation |
| SYNC |
Synchronous fault at access |
Synchronous fault at access |
Precise location; invalid access blocked |
| ASYNC |
Execution can continue after recording a fault |
Execution can continue after recording a fault |
Later termination does not imply immediate blocking |
| ASYMM |
SYNC behavior |
ASYNC behavior |
Different policies for reads and writes |
These semantics are documented in the Linux arm64 MTE documentation.
Android reports SYNC faults as SIGSEGV / SEGV_MTESERR and ASYNC faults as SIGSEGV / SEGV_MTEAERR. An app manifest can request sync, async, off, or default; on supported hardware, the OS selects ASYMM as a CPU checking mode. AOSP MTE documentation
SYNC raises a fault at the invalid access, while ASYNC records the error and reports it later. Security behavior therefore depends on both the crash and the time of checking.
Requested mode can also differ from the effective CPU mode. OEM configuration can upgrade ASYNC requests to SYNC or ASYMM on particular cores. AOSP's deployment guide distinguishes explicit sync, with allocation-history costs, from requesting async and selecting stricter CPU checking. AOSP MTE configuration
Tagging policy varies across Android distributions. GrapheneOS, for example, provides hardware tagging in its own allocator and in kernel allocators. GrapheneOS features
Enabling Heap Tagging for an Android App
In a development app, android:memtagMode requests heap tagging through AndroidManifest.xml. This example selects SYNC. The Android section describes documented configuration; the hardware execution results in this article come from the Mac experiment below.
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:memtagMode="sync" />
</manifest>
The NDK guide lists stack tagging from Android 14 QPR3. Stack checking uses a separately instrumented debug build on an MTE-capable device. Custom allocators must implement tag handling during allocation and deallocation. Android NDK MTE guide
SYNC reports contain the fault address and backtrace, with allocation and deallocation history when available. ASYNC reports record the later SEGV_MTEAERR event. AOSP MTE reports
MIE Protects More Than the Tag Check
Apple MIE combines secure allocators, synchronous EMTE, and Tag Confidentiality Enforcement. Current platform documentation lists A19 and M5 processors or later. The feature commonly discussed as “iOS MIE” also has Mac support. Apple Platform Security
[Allocation request + type]
|
v
[Type-aware allocator] <........ [Protect tags and allocator state]
| :
v :
[Placement policy + memory tagging] :
| :
v :
[Synchronous CPU EMTE checking] <................
|
v
[OS fault handling on mismatch]
MIE combines the CPU tag comparison with type-aware placement and protection of allocator state. Each memory type follows its corresponding allocator path.
Type-Aware Allocation
Apple identifies kernel kalloc_type, userspace xzone malloc, and WebKit libpas as components. Type-based placement complements tagging of smaller allocations. At the 2025 iPhone launch, Apple announced coverage for the kernel and more than 70 userspace processes. Apple MIE design article
Type-aware allocation uses type descriptors to choose allocation regions. The compiler can rewrite malloc, calloc, realloc, and C++ new to typed equivalents. Custom wrappers that lose type information require additional integration. Apple typed-allocation guide
Type-aware allocation limits the kinds of objects placed in the same memory region. Existing code continues to manage object lifetimes and bounds. Apple's separate kalloc_type analysis provides the background and threat model.
EMTE and Tag Confidentiality
Apple describes EMTE as adding constraints to accesses reaching untagged memory. Apple Platform Security MIE uses SPTM to protect kernel allocator backing and tag storage; its confidentiality policies also address timing and speculative leakage. Apple MIE design article
Tag selection and storage directly affect how difficult it is for an attacker to guess a valid tag. TikTag analyzed speculative-execution paths that expose tags in particular MTE implementations. MIE's Tag Confidentiality Enforcement extends protection to these tag-leakage paths. TikTag paper
Deployment and Coverage: Android MTE vs. Apple MIE
| Criterion |
Android's use of MTE |
Apple MIE |
| Comparison unit |
OS, allocator, and app deployment of an Arm feature |
Apple's integrated mitigation design |
| Fault handling |
Depends on requested mode, CPU, and OEM policy |
Platform design centers on synchronous EMTE; check app soft mode separately |
| Activation |
Verify device, OS, and process configuration |
Separate hardware support from third-party app opt-in |
| Placement |
Inspect the allocator and distribution |
Type-aware placement is part of the design |
| Process evidence |
Configuration and tombstone |
Signed app configuration and tag-fault report |
| Performance |
Measure the same workload |
Measure the same workload |
MIE covers allocator policy and tag confidentiality in addition to the tag check. Android also supports SYNC, so comparing the platforms requires their deployed modes and protection scope.
A performance comparison requires a separate measurement with the same application and workload. The example below focuses on tag-checking behavior.
Reproducing the Check on an M5 Pro
The experiment allocates 64 bytes and reads them before and after deallocation. It compares a regular binary with one signed using heap-tagging entitlements to see how each handles the use-after-free.
Test Environment
| Item |
Observed value |
| Chip |
Apple M5 Pro |
| OS |
macOS 27.0.1, build 26A434 |
| Architecture |
Native arm64 |
| Xcode |
27.0, build 27A266a |
| Compiler |
Apple clang 21.0.0 |
hw.optional.arm.FEAT_MTE |
1 |
| Date |
2026-10-05, Asia/Seoul |
These values were read directly from the M5 Pro used for the experiment.
Check your own environment with:
sw_vers
uname -m
sysctl -n machdep.cpu.brand_string
sysctl -n hw.optional.arm.FEAT_MTE
xcodebuild -version
FEAT_MTE=1 means that the hardware supports MTE.
Test Program and Entitlements
Save the following as mie_demo.c. It accepts either valid or uaf and prints the pointer's logical tag before deallocation. The uaf path frees the memory and reads it again through the old pointer. volatile ensures that the read takes place.
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv) {
if (argc != 2 || (strcmp(argv[1], "valid") && strcmp(argv[1], "uaf"))) {
fprintf(stderr, "usage: %s valid|uaf\n", argv[0]);
return 2;
}
setvbuf(stdout, NULL, _IONBF, 0);
unsigned char *p = malloc(64);
if (!p) return 1;
memset(p, 0x41, 64);
printf("allocation=%p logical_tag=0x%lx\n", (void *)p,
(unsigned long)(((uintptr_t)p >> 56) & 0xf));
if (!strcmp(argv[1], "valid")) {
printf("valid read: 0x%02x\n", (unsigned)*p);
free(p);
return 0;
}
volatile unsigned char *stale = p;
free(p);
puts("about to read freed memory (intentional undefined behavior)");
unsigned char value = *stale;
printf("read completed: 0x%02x\n", (unsigned)value);
return 0;
}
Save the following complete plist as checked.entitlements:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.hardened-process</key>
<true/>
<key>com.apple.security.hardened-process.enhanced-security-version-string</key>
<string>1</string>
<key>com.apple.security.hardened-process.checked-allocations</key>
<true/>
<key>com.apple.security.hardened-process.checked-allocations.enable-pure-data</key>
<true/>
</dict>
</plist>
checked-allocations enables memory tagging, and enhanced-security-version-string is set to the string 1. checked-allocations reference, protection-version reference
enable-pure-data adds the 64-byte buffer to the tagged allocations. Pure-data entitlement reference
The example omits soft-mode so that a tag fault terminates the process. Soft-mode reference
Build and Run
Run the following in the directory containing both files. mie-baseline is the regular build, while mie-checked is ad-hoc signed with the memory-tagging entitlements.
xcrun clang -arch arm64 -O0 -g -Wall -Wextra \
mie_demo.c -o mie-baseline
cp mie-baseline mie-checked
codesign --force --sign - \
--entitlements checked.entitlements mie-checked
codesign --verify --strict mie-checked
codesign -d --entitlements - mie-checked
./mie-baseline valid
./mie-baseline uaf
./mie-checked valid
./mie-checked uaf
The final ./mie-checked uaf command terminates with a tag fault. The resulting mie-checked-*.ips report under ~/Library/Logs/DiagnosticReports/ contains EXC_ARM_MTE_TAGCHECK_FAIL and MTE_FAIL.
Observed Result
| Executable |
Path |
Observed logical tag |
Outcome |
| baseline |
valid |
0x0 |
Valid read completed |
| baseline |
uaf |
0x0 |
Invalid read completed |
| checked |
valid |
0x5 |
Valid read completed |
| checked |
uaf |
0xa |
Terminated with SIGKILL and a tag fault |
The checked UAF followed this path:
mie-checked Tagged allocator EMTE check macOS
| | | |
| ---- malloc(64) --> | | |
| <-- pointer 0xa --- | | |
| ---- free(p) -----> | | |
| |--+ | |
| | | retag memory | |
| |<-+ | |
| -------- read through stale pointer ---> X |
| | | -- tag fault --> |
X <------------------- SIGKILL / MTE_FAIL ------------------- |
Relevant fields extracted from that process's macOS .ips report were:
{
"exception": {
"type": "EXC_BAD_ACCESS",
"signal": "SIGKILL",
"subtype": "EXC_ARM_MTE_TAGCHECK_FAIL at 0x0a00000105355660"
},
"termination": {
"namespace": "MTE_FAIL",
"code": 262
}
}
The macOS .ips report records SIGKILL as the signal and MTE_FAIL as the termination namespace. The shell displayed exit status 137.
In the pointer output, 0xa is the value in bits 59–56. Addresses and tags can change between runs.
The baseline completed the read from freed memory, while the same read in the checked binary stopped with a tag fault. The two runs show the difference made by enabling checked allocations for the executable.
Enabling MIE in an App
In Xcode, enable Signing & Capabilities → Enhanced Security → Enable Hardware Memory Tagging. To make tag faults terminate the process, deselect Enable Soft Mode for Memory Tagging. Apple's guide lists M5-or-later Macs and supported A19-or-later devices. Apple Enhanced Security guide
Coverage and Limits of Memory Tagging
The experiment caught a UAF. Memory tagging checks tags assigned to granules and allocations. Existing code continues to check byte-level boundaries and program semantics.
| Situation |
Information visible to the tag check |
| Crossing field boundaries inside one allocation |
The fields can share a tag |
| A small bounds error within one granule |
Both addresses share the granule tag |
| A collision between different objects' tags |
A small tag space permits matching values |
| Modifying the wrong field through a valid pointer |
The check sees only the address and matching tag |
| A memory-management path without tagging |
Checking applies to paths where tagging is active |
For example, two logical fields in one 64-byte allocation may share a tag. Crossing from one field into the other leaves the tag unchanged, so bounds checks and lifetime management in the code control that access.
MTE performs tag checks in the CPU. HWASan uses pointer tagging and shadow memory, with compiler-generated load/store checks. LLVM HWASan design
The M5 Pro example was built without ASan or HWASan, and the tag fault appeared in the macOS .ips report.
Understanding MTE starts with the pointer and memory tag comparison. Understanding MIE takes another step into allocator policy, coverage, and confidentiality. The small M5 Pro UAF shows one part of that design working in practice. Looking at which access stopped under which settings reveals the difference between the two mitigations more clearly than comparing feature names alone.