You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/binary-exploitation/common-binary-protections-and-bypasses/memory-tagging-extension-mte.md
+86-19Lines changed: 86 additions & 19 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -28,62 +28,129 @@ Therefore, this allows up to **16 different tag values**.
28
28
29
29
Every **16B of physical memory** have a corresponding **memory tag**.
30
30
31
-
The memory tags are stored in a **dedicated RAM region** (not accessible for normal usage). Having 4bits tags for every 16B memory tags up to 3% of RAM.
31
+
The memory tags are stored in a **dedicated RAM region** (not accessible for normal usage). Having 4-bit tags for every 16B of memory costs roughly **3% of RAM**.
32
32
33
33
ARM introduces the following instructions to manipulate these tags in the dedicated RAM memory:
34
34
35
35
```asm
36
36
STG [<Xn/SP>], #<simm> Store Allocation (memory) Tag
37
-
LDG <Xt>, [<Xn/SP>] Load Allocatoin (memory) Tag
37
+
LDG <Xt>, [<Xn/SP>] Load Allocation (memory) Tag
38
38
IRG <Xd/SP>, <Xn/SP> Insert Random [pointer] Tag
39
+
ADDG <Xd/SP>, <Xn/SP>, #<uimm>, #<uimm>
40
+
SUBG <Xd/SP>, <Xn/SP>, #<uimm>, #<uimm>
39
41
...
40
42
```
41
43
42
44
## Checking Modes
43
45
44
46
### Sync
45
47
46
-
The CPU check the tags **during the instruction executing**, if there is a mismatch, it raises an exception (SIGSEGV with `SEGV_MTESERR`) and you immediately know the exact instruction and address.\
47
-
This is the slowest and most secure because the offending load/store is blocked.
48
+
The CPU checks the tags **during instruction execution**. If there is a mismatch, it raises an exception (`SIGSEGV` with `SEGV_MTESERR`) and you immediately know the exact instruction and address.\
49
+
This is the slowest and most secure mode because the offending load/store is blocked before its effects become architecturally visible.
48
50
49
51
### Async
50
52
51
-
The CPU check the tags **asynchronously**, and when a mismatch is found it sets an exception bit in one of the system registers. It's **faster** than the previous one but it's **unable to point out** the exact instruction that cause the mismatch and it doesn't raise the exception immediately (`SIGSEGV` with `SEGV_MTEAERR`), giving some time to the attacker to complete his attack.
53
+
The CPU checks the tags **asynchronously**. When a mismatch is found it sets state in a fault status register and the process is usually killed on the **next kernel entry** (`SIGSEGV` with `SEGV_MTEAERR`). It's **faster** than SYNC but it's **unable to point out** the exact instruction that caused the mismatch, and some attacker-visible effects may already have happened before the signal is delivered.
52
54
53
-
### Mixed
55
+
### Asymmetric / per-core upgrades
54
56
55
-
Per-core preferences (for example writing `sync`, `async` or `asymm` to `/sys/devices/system/cpu/cpu*/mte_tcf_preferred`) let kernels silently upgrade or downgrade per-process requests, so production builds usually request ASYNC while privileged cores force SYNC when workload allows it.
57
+
Recent Arm/Android deployments also use **ASYMM** (synchronous reads, asynchronous writes) and **per-core preferred modes**. For example, writing `sync`, `async` or `asymm` to `/sys/devices/system/cpu/cpu*/mte_tcf_preferred` can silently upgrade a process that only requested ASYNC when it lands on a stricter core.
58
+
59
+
This matters during exploitation because the **manifest / `prctl()` request is not always the effective mode** you end up attacking.
60
+
61
+
## Quick Recon / Enable Checks
62
+
63
+
From an exploit-dev perspective the first question is usually not "does the CPU support MTE?" but **"is this specific process actually running with tagged mappings and enforced checks?"**
In code or during reversing, look for these indicators:
74
+
75
+
-**`HWCAP2_MTE`** advertised to userspace
76
+
-**`PROT_MTE`** used in `mmap()` / `mprotect()`
77
+
-**`prctl(PR_SET_TAGGED_ADDR_CTRL, ...)`** with `PR_MTE_TCF_SYNC` / `PR_MTE_TCF_ASYNC`
78
+
- Android manifests requesting **`android:memtagMode="sync|async"`** (and note that `readelf` is useful for **BTI/PAC**, but not as a generic detector for whether MTE is enabled for a process)
On Android, stack tagging is not automatic for arbitrary native code: if a lab or target was compiled with **`-fsanitize=memtag`** and a **`-fsanitize-memtag-mode=sync`** link mode, stack bugs that would normally stay invisible to heap-only MTE may now fault reliably. For a concrete exploitation scenario where MTE can break the overflow stage before shellcode/ROP, check [the ARM64 stack shellcode notes](../stack-overflow/stack-shellcode/stack-shellcode-arm64.md).
56
91
57
92
## Implementation & Detection Examples
58
93
59
94
Called Hardware Tag-Based KASAN, MTE-based KASAN or in-kernel MTE.\
60
-
The kernel allocators (like `kmalloc`) will **call this module** which will prepare the tag to use (randomly) attach it to the kernel space allocated and to the returned pointer.
95
+
The kernel allocators (like `kmalloc`) will **call this module** which will prepare the tag to use (randomly) and attach it to the allocated kernel memory and to the returned pointer.
61
96
62
-
Note that it'll **only mark enough memory granules** (16B each) for the requested size. So if the requested size was 35 and a slab of 60B was given, it'll mark the first 16\*3 = 48B with this tag and the **rest** will be **marked** with a so-called**invalid tag (0xE)**.
97
+
Note that it'll **only mark enough memory granules** (16B each) for the requested size. So if the requested size was 35 and a slab of 60B was given, it'll mark the first `16*3 = 48B` with this tag and the **rest** will usually be **marked** with a special **invalid tag** (commonly `0xE` in current Linux KASAN/MTE discussions).
63
98
64
-
The tag **0xF** is the **match all pointer**. A memory with this pointer allows**any tag to be used** to access its memory (no mismatches). This could prevent MTE from detecting an attack if that tag is being used in the attacked memory.
99
+
A very important nuance is that **kernel and userspace do not expose exactly the same behavior**:
65
100
66
-
Therefore there are only **14 values** that can be used to generate tags as 0xE and 0xF are reserved, giving a probability of **reusing tags** to 1/17 -> around **7%**.
101
+
- In the **Linux kernel**, the current implementation has important special cases around **tag `0xF`** (`TCMA1` / "match-all" style behavior in several paths), so forged pointers carrying `0xF` are especially interesting during kernel exploitation.
102
+
- In **normal userspace Linux MTE**, there is **no general-purpose match-all logical tag** you can rely on from an unprivileged process.
67
103
68
-
If the kernel accesses the **invalid tag granule**, the **mismatch** will be **detected**. If it accesses another memory location and the **memory has a different tag** (or the invalid tag) the mismatch will also be detected. If the attacker is lucky and the memory is using the same tag, it won't be detected. Chances are around 7%.
104
+
Therefore, if the implementation reserves `0xE` and `0xF`, there are effectively **14 practical tag values**, so a blind tag re-use or reallocation collision is around **`1/14` (~7.1%)** per try instead of being a deterministic bypass.
69
105
70
-
Another bug occurs in the **last granule** of the allocated memory. If the application requested 35B, it was given the granule from 32 to 48. Therefore, the **bytes from 36 to 47 are using the same tag** but they weren't requested. If the attacker accesses **these extra bytes, this isn't detected**.
106
+
If the kernel accesses the **invalid-tag granule**, the **mismatch** will be **detected**. If it accesses another memory location and the **memory has a different tag** (or the invalid tag) the mismatch will also be detected. If the attacker is lucky and the memory is using the same tag, it won't be detected.
71
107
72
-
When **`kfree()`**is executed, the memory is retagged with the invalid memory tag, so in a **use-after-free**, when the memory is accessed again, the **mismatch is detected**.
108
+
Another blind spot occurs in the **last granule** of the allocated memory. If the application requested 35B, it was given the granule from 32 to 48. Therefore, the **bytes from 36 to 47 are using the same tag** but they weren't requested. If the attacker accesses **these extra bytes, this isn't detected**. This is one of the most important practical MTE limitations: **intra-granule overflows are invisible** unless another software mechanism adds extra checking.
73
109
74
-
However, in a use-after-free, if the same **chunk is reallocated again with the SAME tag** as previously, an attacker will be able to use this access and this won't be detected (around 7% chance).
110
+
When **`kfree()`** is executed, the memory is usually retagged, so in a **use-after-free**, when the memory is accessed again, the **mismatch is detected**.
75
111
76
-
Moreover, only **`slab` and `page_alloc`** uses tagged memory but in the future this will also be used in `vmalloc`, `stack` and `globals` (at the moment of the video these can still be abused).
112
+
However, in a use-after-free, if the same **chunk is reallocated again with the same tag** as previously, an attacker will be able to keep using the stale pointer and this won't be detected (probabilistic bypass).
77
113
78
-
When a **mismatch is detected** the kernel will **panic** to prevent further exploitation and retries of the exploit (MTE doesn't have false positives).
114
+
Moreover, only **`slab` and `page_alloc`** are broadly covered in current Linux kernel deployments; attackers should still pay attention to **untagged paths** such as `vmalloc`, stacks, globals, DMA-backed buffers, or mixed subsystems depending on kernel version and configuration.
115
+
116
+
When a **mismatch is detected** the kernel may **panic** or at least kill the current task, depending on the exact mode/policy, to prevent further exploitation and retries.
117
+
118
+
## Exploit-Relevant Debugging Primitives
119
+
120
+
Linux exposes several interfaces that are very useful both for defenders and for attackers building local PoCs:
121
+
122
+
- **`PTRACE_PEEKMTETAGS` / `PTRACE_POKEMTETAGS`** let a tracer read or write allocation tags in a tracee.
123
+
- A thread can temporarily disable checking for its own accesses with **`PSTATE.TCO`**. This is not a pre-exploitation bypass by itself, but it matters once you already have controlled execution in the compromised thread.
124
+
- In Android app triage, `debuggerd <PID> | head -30 | grep tagged_addr` is a quick way to see which MTE fault modes a process requested.
125
+
126
+
These are not "remote bypasses", but they are extremely practical when building **local privilege-escalation exploits, debugger-assisted PoCs, or MTE-aware fuzzing harnesses**.
127
+
128
+
## Practical Bypass Notes
129
+
130
+
Project Zero's MTE testing/use-case analysis is still a good mental model:
131
+
132
+
- **Known-tag bypasses**: if you can **leak the tag**, the probabilistic part of MTE mostly disappears.
133
+
- **Unknown-tag bypasses**: even without leaking the tag, **implementation limits** can leave exploit paths where an invalid access still gives enough post-corruption leverage.
134
+
- **ASYNC / ASYMM are softer targets** than SYNC because the exploit may only need to finish before the next kernel entry, signal delivery, or scheduler event.
135
+
136
+
This is especially relevant for browser, IPC and kernel exploits where the attacker can try to keep the whole corruption chain inside one "quiet" execution window.
79
137
80
138
### Speculative Tag Leakage (TikTag)
81
139
82
-
*TikTag* (2024) demonstrated two speculative execution gadgets (TIKTAG-v1/v2) able to leak the 4-bit allocation tag of any address in <4 seconds with >95% success. By speculatively touching attacker-chosen cache lines and observing prefetch-induced timing, an attacker can derandomize the tag assigned to Chrome processes, Android system services, or the Linux kernel and then craft pointers carrying the leaked value. Once the tag space is brute-forced away, the probabilistic granule reuse assumptions (`≈7%` false-negative rate) collapse and classic heap exploits (UAF, OOB) regain near-100% reliability even when MTE is enabled. The paper also ships proof-of-concept exploits that pivot from leaked tags to retagging fake slabs, illustrating that speculative side channels remain a viable bypass path for hardware tagging schemes.
140
+
*TikTag* (2024) demonstrated two speculative execution gadgets (**TIKTAG-v1/v2**) able to leak the 4-bit allocation tag of arbitrary addresses with **>95% success** in **less than 4 seconds**. The key idea is to speculatively trigger a tag-checked access, use a cache side channel to learn whether the access matched, and iterate over candidate tags until the correct one is recovered.
141
+
142
+
That result is important because it upgrades MTE from a **probabilistic mitigation** to something an attacker may **systematically derandomize**:
143
+
144
+
1. Leak the tag of the vulnerable object.
145
+
2. Leak the tag of the target object.
146
+
3. Reallocate / groom until they match.
147
+
4. Trigger the UAF/OOB with the now-correct tag.
148
+
149
+
The paper demonstrates this against **Google Chrome** and the **Linux kernel**. Also, research such as **StickyTags** independently observed that speculative probing of tag-check success/failure is a real concern, reinforcing that **tag confidentiality** is one of the main assumptions behind MTE's offensive value.
0 commit comments