Memory Tagging + Linux kernel =
or Mitigating Linux kernel memory corruptions with Arm Memory Tagging
Andrey Konovalov, xairy.io
Linux Security Summit
October 1st 2021
| | | | | |
Bugs
Vulnerabilities
Bad Binder
Android vulnerabilities by cause
Queue the Hardening Enhancements, Google Online Security Blog
OOB write
OOB read
Use-after-free
Integer overflow
Other
Incorrect crypto
Uninitialized memory
Android vulnerabilities by cause
Queue the Hardening Enhancements, Google Online Security Blog
OOB write
OOB read
Use-after-free
Integer overflow
Other
Incorrect crypto
Uninitialized memory
90%
Memory corruptions!
Mitigating memory corruptions
⇒ Invest into mitigations instead
⇒ Focus on them first
Who am I?
Andrey Konovalov
Agenda
Memory tagging
Memory tagging
Concept
| | | | | |
Granule #1
#2
...
#N
Concept
Granule #1
#2
...
#N
| | | | | |
Concept
| | | | | |
char *p1
char *p2
Concept
| | | | | |
Concept
| | | | | |
char *p1
char *p2
Concept
==
*p1 = ...;
| | | | | |
All is good,
proceed
Concept
!=
| | | | | |
*(p1 + N) = ...;
Raise an
exception!
From concept to implementation
Arm Memory Tagging Extension
Arm Memory Tagging Extension (MTE)
Implementation details
Arm Top Byte Ignore (TBI)
0x42ff801234567800
MTE pointer tags
0xf2ff801234567800
MTE memory tags
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
0xA | 0x2 | 0x2 | 0x2 | 0xE | 0xB |
Memory tags
Memory granules
MTE memory tags
MTE instructions
Instruction | Action |
STG [<Xn/SP>], #<simm> | Store Allocation (memory) Tag |
LDG <Xt>, [<Xn/SP>] | Load Allocation (memory) Tag |
IRG <Xd/SP>, <Xn/SP> | Insert Random [pointer] Tag |
GMI <Xd>, <Xn/SP>, <Xm> | Tag Mask Insert |
STZG [<Xn/SP>], #<simm> | Store Allocation Tag, Zeroing |
ADDG ... | Add with Tag |
And more... | |
MTE tag checking modes
Mode | Tag check runs | Speed | Detection |
Sync | During instruction execution | Slower | Precise |
Async | Asynchronously | Faster | Imprecise |
Mixed | Sync for reads, async for writes | | |
Summary
Detailed summary
Next step
In-kernel MTE
or Hardware Tag-Based KASAN
Integration parts
Implementing part #2
KASAN Overview
KASAN vs MTE implementation
Software Tag-Based KASAN
Hardware Tag-Based KASAN
Allocator annotations
void *kmalloc(size) {
void *ptr = ...;
return ptr;
}
void *kmalloc(size) {
size = round_up(size, 16);
void *ptr = ...;
return kasan_kmalloc(ptr);
}
Preventing memory corruptions
with Hardware Tag-Based KASAN
Allocating memory
char *p = kmalloc(35);
Allocating memory
char *p = kmalloc(35); // ends up in kmalloc-64 cache
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
Allocating memory
char *p = kmalloc(35);
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
? | ? | ? | ? | ? | ? |
Memory tags
Allocating memory
char *p = kmalloc(35);
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
Allocating memory
char *p = kmalloc(35); // 0xf2ff801234567800
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
Allocating memory
char *p = kmalloc(35); // 0xf2ff801234567800
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
Allocating memory
char *p = kmalloc(35); // 0xf2ff801234567800
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
Allocating memory
char *p = kmalloc(35); // 0xf2ff801234567800
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
? | 0x2 | 0x2 | 0x2 | 0xE | ? |
Memory tags
In-bounds
char *p = kmalloc(35);
p[20] = ...;
==
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
Preventing out-of-bounds
char *p = kmalloc(35);
p[..] = ...;
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
Preventing out-of-bounds
char *p = kmalloc(35);
p[50] = ...;
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
Preventing out-of-bounds
char *p = kmalloc(35);
p[50] = ...; // out-of-bounds:
!=
Raise an
exception!
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
Preventing out-of-bounds
char *p = kmalloc(35);
p[70] = ...;
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
Preventing out-of-bounds
char *p = kmalloc(35);
p[70] = ...; // out-of-bounds:
!=
Raise an
exception!
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
(Not) preventing out-of-bounds
char *p = kmalloc(35);
p[70] = ...;
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
Could be the same,
if tags are random
(Not) preventing out-of-bounds
char *p = kmalloc(35);
p[70] = ...; // out-of-bounds missed:
==
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
Could be the same,
if tags are random
(Not) preventing out-of-bounds
char *p = kmalloc(35);
p[40] = ...;
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
(Not) preventing out-of-bounds
char *p = kmalloc(35);
p[40] = ...; // out-of-bounds missed:
==
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
Preventing use-after-free
char *p = kmalloc(35);
kfree(p);
p[8] = ...;
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
Preventing use-after-free
char *p = kmalloc(35);
kfree(p); // retag memory:
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
Preventing use-after-free
char *p = kmalloc(35); // 0xf2ff801234567800
kfree(p); // retag memory:
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
0x2
0xE
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
? | 0xE | 0xE | 0xE | 0xE | ? |
Memory tags
Preventing use-after-free
char *p = kmalloc(35);
kfree(p); // retag memory:
p[8] = ...;
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
Preventing use-after-free
char *p = kmalloc(35);
kfree(p); // retag memory:
p[8] = ...; // use-after-free:
!=
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
Raise an
exception!
Preventing use-after-realloc
char *p = kmalloc(35);
kfree(p); q = kmalloc(60);
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
Preventing use-after-realloc
char *p = kmalloc(35);
kfree(p); q = kmalloc(60); // if lands on p:
p[8] = ...;
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
Preventing use-after-realloc
char *p = kmalloc(35);
kfree(p); q = kmalloc(60); // if lands on p:
p[8] = ...; // use-after-free:
!=
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
Raise an
exception!
(Not) preventing use-after-realloc
char *p = kmalloc(35);
kfree(p); q = kmalloc(60); // if lands on p:
p[8] = ...;
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
(Not) preventing use-after-realloc
char *p = kmalloc(35);
kfree(p); q = kmalloc(60); // if lands on p:
p[8] = ...; // use-after-free missed:
==
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
[-16, 0) | [0, 16) | [16, 32) | [32, 48) | [48, 64) | [64, 80) |
Implementation details
of Hardware Tag-Based KASAN
Reserved tags
Tagged memory
More details
RAM impact
From what | How much |
Memory tags | ~3% (4 bits for each 16 bytes) |
Aligning memory objects | Negligible |
Stack traces and redzones | 0 (no stack traces in prod mode, no redzones) |
Performance impact
Mode | How much |
Sync | < 10% (Faster if combined with memory init) |
Async | Faster than sync |
Disabled via command line | 0 |
Summary
Weaknesses
of Hardware Tag-Based KASAN
Non-tagged memory
⇒ Can be targeted by an exploit
Probabilistic detection
⇒ Need to panic on mismatches
⇒ Single attempt to run the exploit
Async window
Match-all tag
⇒ Can access any memory
⇒ MTE will detect this
Intra-object-overflows
More
Summary
Outro
Summary
Promising future
Call to action
(For device manufacturers, distro vendors, and kernel developers)
Thanks!
Andrey Konovalov, xairy.io
| | | | | |
Extra
Report: mode=sync, stacktrace=on [1/4]
==================================================================
BUG: KASAN: invalid-access in kmalloc_oob_right+0x1ac/0x23c lib/test_kasan.c:144
Read at addr f2ff0000039ca880 by task kunit_try_catch/102
Pointer tag: [f2], memory tag: [fe]
CPU: 0 PID: 102 Comm: kunit_try_catch Tainted: G B 5.14.0-rc5 #389
Hardware name: linux,dummy-virt (DT)
Call trace:
...
el1h_64_sync_handler+0x50/0x80 arch/arm64/kernel/entry-common.c:318
el1h_64_sync+0x78/0x7c arch/arm64/kernel/entry.S:569
kmalloc_oob_right+0x1ac/0x23c lib/test_kasan.c:144
...
Report: mode=sync, stacktrace=on [2/4]
Allocated by task 102:
...
kasan_kmalloc include/linux/kasan.h:264
kmem_cache_alloc_trace include/linux/slab.h:489
kmalloc include/linux/slab.h:591
kmalloc_oob_right+0x60/0x23c lib/test_kasan.c:127
...
Freed by task 0:
(stack is not available)
Report: mode=sync, stacktrace=on [3/4]
The buggy address belongs to the object at ffff0000039ca800
which belongs to the cache kmalloc-128 of size 128
The buggy address is located 0 bytes to the right of
128-byte region [ffff0000039ca800, ffff0000039ca880)
The buggy address belongs to the page:
page:(____ptrval____) refcount:1 mapcount:0
mapping:0000000000000000 index:0xf6ff0000039ca600 pfn:0x439ca
flags: 0xffff00000000200(slab|node=0|zone=0|lastcpupid=0xffff|kasantag=0x0)
raw: 0ffff00000000200 0000000000000000 dead000000000122 f5ff000002401200
raw: f6ff0000039ca600 000000008010000f 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected
Report: mode=sync, stacktrace=on [4/4]
Memory state around the buggy address:
ffff0000039ca600: fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe
ffff0000039ca700: f6 f6 f6 f6 f6 f6 f6 fe fe fe fe fe fe fe fe fe
>ffff0000039ca800: f2 f2 f2 f2 f2 f2 f2 fe fe fe fe fe fe fe fe fe
^
ffff0000039ca900: fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe
ffff0000039caa00: fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe fe
==================================================================
Report: mode=async
==================================================================
BUG: KASAN: invalid-access
Asynchronous mode enabled: no access details available
CPU: 1 PID: 102 Comm: kunit_try_catch Not tainted 5.14.0-rc5 #389
Hardware name: linux,dummy-virt (DT)
Report: mode=async
Call trace:
...
kasan_report_async+0xf0/0x170 mm/kasan/report.c:378
mte_check_tfsr_el1+0x40/0x44 arch/arm64/kernel/mte.c:191
mte_check_tfsr_entry arch/arm64/include/asm/mte.h:108
enter_from_kernel_mode+0x24/0x48 arch/arm64/kernel/entry-common.c:49
enter_el1_irq_or_nmi+0x10/0x1c arch/arm64/kernel/entry-common.c:113
el1_interrupt+0x28/0xa0 arch/arm64/kernel/entry-common.c:350
el1h_64_irq_handler+0x18/0x24 arch/arm64/kernel/entry-common.c:367
el1h_64_irq+0x78/0x7c arch/arm64/kernel/entry.S:570
kmalloc_type include/linux/slab.h:348
...
==================================================================
Links
Thanks