Skip to content

Commit 13d41e2

Browse files
authored
Merge pull request #2473 from HackTricks-wiki/research_update_src_binary-exploitation_common-binary-protections-and-bypasses_memory-tagging-extension-mte_20260706_154514
Research Update Enhanced src/binary-exploitation/common-bina...
2 parents da449aa + 9254beb commit 13d41e2

1 file changed

Lines changed: 86 additions & 19 deletions

File tree

src/binary-exploitation/common-binary-protections-and-bypasses/memory-tagging-extension-mte.md

Lines changed: 86 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,62 +28,129 @@ Therefore, this allows up to **16 different tag values**.
2828

2929
Every **16B of physical memory** have a corresponding **memory tag**.
3030

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**.
3232

3333
ARM introduces the following instructions to manipulate these tags in the dedicated RAM memory:
3434

3535
```asm
3636
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
3838
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>
3941
...
4042
```
4143

4244
## Checking Modes
4345

4446
### Sync
4547

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

4951
### Async
5052

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

53-
### Mixed
55+
### Asymmetric / per-core upgrades
5456

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?"**
64+
65+
### Linux / Android quick checks
66+
67+
```bash
68+
grep -i mte /proc/cpuinfo
69+
readelf -n ./target | grep -E 'AARCH64_FEATURE_1_(BTI|PAC)'
70+
rg -n 'memtagMode|PR_SET_TAGGED_ADDR_CTRL|PROT_MTE' .
71+
```
72+
73+
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)
79+
80+
A minimal userspace mapping looks like:
81+
82+
```c
83+
void *p = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_MTE,
84+
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
85+
prctl(PR_SET_TAGGED_ADDR_CTRL,
86+
PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC,
87+
0, 0, 0);
88+
```
89+
90+
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).
5691
5792
## Implementation & Detection Examples
5893
5994
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.
6196
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).
6398
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**:
65100
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.
67103
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.
69105
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.
71107
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.
73109
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**.
75111
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).
77113
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.
79137
80138
### Speculative Tag Leakage (TikTag)
81139
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.
83150
84151
## References
85152
86-
- [https://www.youtube.com/watch?v=UwMt0e_dC_Q](https://www.youtube.com/watch?v=UwMt0e_dC_Q)
153+
- [Memory Tagging Extension (MTE) in AArch64 Linux](https://docs.kernel.org/arch/arm64/memory-tagging-extension.html)
87154
- [TikTag: Breaking ARM's Memory Tagging Extension with Speculative Execution](https://arxiv.org/abs/2406.08719)
88155
89156
{{#include ../../banners/hacktricks-training.md}}

0 commit comments

Comments
 (0)