Skip to content

Commit 99e026f

Browse files
committed
docs: recast "Arbitrary read" as "Exploit primitives"
Reframe post-primitive KASLR bypass around two co-equal targets — the image slide and the direct-map (page_offset) base — instead of slide-first, and give data-only its own paths. Five techniques: object-pointer leak (dual-target), fixed-structure read (IDT gates), in-kernel symbol resolution, corruption without a leak, and probing candidate bases. Expand references (SCTPhantom, DirtyPipe, Copy Fail, Beyond Control, PageJack, modprobe_path scan) and propagate the rename through the ToC, exploitation.md, and the technique-map diagram.
1 parent 6f49a93 commit 99e026f

3 files changed

Lines changed: 156 additions & 14 deletions

File tree

docs/bypass-techniques.md

Lines changed: 152 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ microarchitectural or software side-channels, leaking addresses through
1313
syscalls and kernel interfaces, exploiting ioctl handlers that copy
1414
uninitialized kernel memory to userspace, brute-forcing memory layout
1515
constraints, taking advantage of weak randomization entropy, leveraging
16-
patched kernel info leak bugs, and using arbitrary read primitives.
16+
patched kernel info leak bugs, and leveraging exploit primitives.
1717

1818
Grouped by what a leak needs — from reading an interface, through measuring or
1919
inferring, to exploiting a bug:
2020

21-
![KASLR bypass technique map: eight technique categories in three bands by requirement — read an interface (filesystem leaks, syscall and interface leaks, ioctl leaks), measure or infer (side-channels, brute force, weak entropy), and exploit a bug (patched kernel bugs, arbitrary read)](diagrams/bypass-technique-map.svg)
21+
![KASLR bypass technique map: eight technique categories in three bands by requirement — read an interface (filesystem leaks, syscall and interface leaks, ioctl leaks), measure or infer (side-channels, brute force, weak entropy), and exploit a bug (patched kernel bugs, exploit primitives)](diagrams/bypass-technique-map.svg)
2222

2323
## Table of Contents
2424

@@ -33,7 +33,7 @@ inferring, to exploiting a bug:
3333
- [Brute force](#brute-force)
3434
- [Weak entropy](#weak-entropy)
3535
- [Patched kernel bugs](#patched-kernel-bugs)
36-
- [Arbitrary read](#arbitrary-read)
36+
- [Exploit primitives](#exploit-primitives)
3737

3838
## Filesystem leaks
3939

@@ -549,13 +549,69 @@ memfd hugetlb non-zeroed folio leak. `memfd_alloc_folio()` in `mm/memfd.c` alloc
549549

550550
* [mm/memfd: fix information leak in hugetlb folios](https://github.com/torvalds/linux/commit/de8798965fd0d9a6c47fc2ac57767ec32de12b49) (2025)
551551

552-
## Arbitrary read
553-
554-
Kernel vulnerabilities which provide arbitrary read (or write) primitives can
555-
be leveraged to leak kernel pointers and defeat KASLR, even when direct info
556-
leak vectors are unavailable.
557-
558-
Leaking kernel addresses using `msg_msg` struct for arbitrary read (for `KMALLOC_CGROUP` objects):
552+
## Exploit primitives
553+
554+
Once a vulnerability yields an in-kernel primitive — most often an arbitrary
555+
read, but a constrained or relative read, an out-of-bounds read, a use-after-free
556+
read, or a write repurposed into a read all qualify — KASLR is bypassed as a step
557+
of the exploit rather than through an unprivileged interface leak. Which technique
558+
applies follows from the strength of the primitive, from a narrow over-read at one
559+
end to full arbitrary read/write or code execution at the other.
560+
561+
Exploitation makes two co-equal demands on KASLR, set by the strategy rather than
562+
by any single canonical slide:
563+
564+
* **Control-flow hijack and fixed-symbol overwrite** need the **image slide**
565+
(`.text`/`.data` base) — gadget addresses, or the offset to a global such as
566+
`modprobe_path` or `core_pattern`.
567+
* **Data-only corruption** — flipping `cred`, page tables, or a `pipe_buffer`,
568+
which sidesteps CFI/CET/SMEP — needs the **direct-map (`page_offset`) base**,
569+
the physical↔virtual pivot that makes an arbitrary heap object addressable.
570+
571+
On x86-64 the two bases are decoupled (`RANDOMIZE_MEMORY`), so which one an exploit
572+
needs is set by its strategy. Recovering the image slide is a subtraction —
573+
`slide = leaked_pointer − link_time(symbol)`, checked against the image alignment
574+
(`CONFIG_PHYSICAL_ALIGN`, 2 MiB on x86-64) — while recovering the direct-map base
575+
is an alignment: a leaked heap pointer lies in the linear map, so `page_offset`
576+
follows from rounding it down to the 1 GiB grid. A third strategy needs neither:
577+
corruption that reaches its target relative to an object already held — up to
578+
DirtyPipe-style page-cache overwrites — bypasses KASLR by not requiring any kernel
579+
address.
580+
581+
### Leaking a pointer from a reachable object
582+
583+
A relative, out-of-bounds, or use-after-free read that reaches an adjacent or
584+
reclaimed slab object leaks a pointer; which pointer it targets decides which base
585+
falls out.
586+
587+
**To the image slide**, many heap objects embed pointers with known link-time
588+
values:
589+
590+
* **Operations tables**`file_operations`, `tty_operations`, `proto_ops`,
591+
`seq_operations`, `pipe_buffer->ops` (`anon_pipe_buf_ops`), `sk->sk_prot` and
592+
similar `*_ops` fields point into `.rodata`/`.data`/`.text`. Reclaiming a freed
593+
object with one that exposes such a field — the freed `seq_operations` of an
594+
`open("/proc/…")` file landing in a use-after-free slot is the archetype —
595+
yields a `.text`/`.data` pointer directly.
596+
* **Deferred-work callbacks**`timer_list.function`, `work_struct.func`.
597+
* **Pointer-chasing** — leak any object pointer (a `task_struct`, a namespace),
598+
then traverse to one whose target is a fixed `.data`/`.bss` symbol (`init_task`,
599+
`init_cred`, `init_net`, `init_pid_ns`, `init_mm`, `init_ipc_ns`); the
600+
traversal, not the first read, lands the known symbol.
601+
602+
**To the direct-map base**, the object's own storage is the target: any slab
603+
object's address is a linear-map pointer, so leaking a heap pointer — a list
604+
neighbour, a self-reference, a back-pointer — bounds `page_offset` regardless of
605+
which object it belongs to. This is the data-only pivot: the recovered base makes a
606+
sprayed or target object (`cred`, page-table pages, a `pipe_buffer`) addressable,
607+
then feeds a data-only read/write primitive such as `pipe_buffer.page` AARW.
608+
609+
The `msg_msg` structure is the usual delivery vehicle for either: corrupting its
610+
`m_ts` length or `next` pointer turns a one-shot overflow into a controlled
611+
over-read into an adjacent object. A write primitive discloses nothing by itself,
612+
but the same bug's write is frequently what builds the read.
613+
614+
Leaking a kernel pointer from a reachable object (`msg_msg` and related objects):
559615

560616
* [Four Bytes of Power: Exploiting CVE-2021-26708 in the Linux kernel | Alexander Popov](https://a13xp0p0v.github.io/2021/02/09/CVE-2021-26708.html)
561617
* [CVE-2021-22555: Turning \x00\x00 into 10000$ | security-research](https://google.github.io/security-research/pocs/linux/cve-2021-22555/writeup.html)
@@ -564,6 +620,47 @@ Leaking kernel addresses using `msg_msg` struct for arbitrary read (for `KMALLOC
564620
* [Will's Root: corCTF 2021 Fire of Salvation Writeup: Utilizing msg_msg Objects for Arbitrary Read and Arbitrary Write in the Linux Kernel](https://www.willsroot.io/2021/08/corctf-2021-fire-of-salvation-writeup.html)
565621
* [[corCTF 2021] Wall Of Perdition: Utilizing msg_msg Objects For Arbitrary Read And Arbitrary Write In The Linux Kernel](https://syst3mfailure.io/wall-of-perdition)
566622
* [[CVE-2021-42008] Exploiting A 16-Year-Old Vulnerability In The Linux 6pack Driver](https://syst3mfailure.io/sixpack-slab-out-of-bounds)
623+
* [CVE-2022-0185: Linux kernel slab out-of-bounds write: exploit and writeup](https://www.openwall.com/lists/oss-security/2022/01/25/14) — partial-overwrite of `msg_msg.m_ts` extends the read to leak `init_ipc_ns` via a sprayed `shm_file_data`
624+
* [\[CVE-2022-1786\] A Journey To The Dawn | kylebot's Blog](https://blog.kylebot.net/2022/10/16/CVE-2022-1786/)`timerfd_ctx` list pointers and armed-timer `.text` callbacks
625+
* [HTB UNI CTF 2021: Steam Driver Kernel Pwnable](https://www.hackthebox.com/blog/uni-ctf-writeup-steam-driver)`msg_msg` user-copy KASLR rebase
626+
* [Escaping the Google kCTF Container with a Data-Only Exploit](https://h0mbre.github.io/kCTF_Data_Only_Exploit/) (h0mbre) — `init_task`/`cred` traversal, data-only
627+
* [pipe_buffer exploitation experiments](https://a13xp0p0v.github.io/2026/04/20/pipe-buffer-experiments.html) (Alexander Popov, 2026) — data-only `pipe_buffer.page` AARW; the direct-map pivot a recovered base feeds
628+
629+
### Reading a fixed, KASLR-invariant structure
630+
631+
A true arbitrary read — one that dereferences an attacker-chosen absolute address
632+
— can target a structure mapped at a fixed, KASLR-independent address whose
633+
contents are slid kernel-text pointers, sidestepping the object grooming above.
634+
The interrupt descriptor table is the canonical target: on x86-64 a read-only IDT
635+
alias is mapped at the constant address `0xfffffe0000000000` (`CPU_ENTRY_AREA_RO_IDT`,
636+
equal to `CPU_ENTRY_AREA_BASE` and unmoved by KASLR), while each gate still holds
637+
the runtime address of its handler.
638+
639+
A 16-byte gate splits the 64-bit handler across `offset_low` (bits 0–15),
640+
`offset_middle` (16–31) and `offset_high` (32–63). Because the image slide is
641+
2 MiB-aligned, the low 21 bits of every handler are invariant and the top half is
642+
the canonical `0xffffffff…` window, so the only unknown bits — the slide entropy —
643+
sit in `offset_middle`; a single 32-bit read across that field reconstructs the
644+
handler, and subtracting the link-time symbol (gate 0 → `asm_exc_divide_error`)
645+
yields the slide. This is the memory-disclosure successor to the unprivileged
646+
`SIDT`/`SGDT` instruction leak (see [Side-channels](#side-channels)), which read
647+
the IDT/GDT *base*: the fixed read-only alias and UMIP defeat the instruction, but
648+
neither touches the gate *contents*, so an arbitrary read recovers the slide even
649+
where `SIDT` is blocked.
650+
651+
* [SIMPLE IS BETTER: Kernel Information Leak with Unprivileged Instructions (SIDT, SGDT) on x86](http://hypervsir.blogspot.com/2014/10/kernel-information-leak-with.html)
652+
* [make cpu-entry-area great again — kqx](https://kqx.io/post/sp0/) — fixed IDT mapping and `cea_offset` randomization
653+
* [SCTPhantom (CVE-2026-64564)](https://matrix.tencent.com/en/2026/08/06/sctphantom-CVE-2026-64564) — IDT gate 0 read from a repeatable 4-byte kernel read, bypassing UMIP
654+
655+
### Resolving symbols from an in-kernel vantage
656+
657+
The strongest primitives — arbitrary read/write from kernel context, or kernel
658+
code execution — make recovery direct rather than inferential: an attacker inside
659+
the kernel can read the compiled `kallsyms` tables, read a `.text` pointer from a
660+
CPU register (`rdmsr MSR_LSTAR` returns `entry_SYSCALL_64`), walk page tables from
661+
`CR3`, or read descriptor-table and segment state from ring 0. The task shifts from
662+
defeating KASLR to resolving symbol addresses under restrictions such as
663+
`kptr_restrict`.
567664

568665
Leaking kernel addresses using privileged arbitrary read (or write) in kernel space:
569666

@@ -572,3 +669,48 @@ Leaking kernel addresses using privileged arbitrary read (or write) in kernel sp
572669
* https://www.openwall.com/lists/oss-security/2018/08/09/6
573670
* https://xairy.io/articles/cve-2017-18344
574671
* [xairy/kernel-exploits/CVE-2017-18344](https://github.com/xairy/kernel-exploits/tree/master/CVE-2017-18344)
672+
673+
### Corruption without a leak
674+
675+
KASLR need not be recovered at all when the corruption reaches its target without a
676+
kernel address — the leak-free end of data-only exploitation:
677+
678+
* **Partial pointer overwrite** — because the slide is 2 MiB-aligned, the low
679+
bits of every already-slid kernel pointer are KASLR-invariant, so overwriting
680+
only the low byte(s) of an existing pointer retargets it within its aligned
681+
region (a nearby object or gadget) with no base knowledge.
682+
* **Relative writes** — overwriting a field at a known offset inside an object
683+
already held: a use-after-free-reclaimed `cred`, a neighbouring slab object.
684+
* **Content-only overwrites** — DirtyPipe (CVE-2022-0847) rewrites read-only
685+
page-cache contents through an uninitialized `pipe_buffer.flags`, corrupting a
686+
file (a setuid binary, `/etc/passwd`) with no kernel pointer whatsoever;
687+
CVE-2026-31431 ("Copy Fail") reaches the page cache the same way through
688+
`algif_aead` / `AF_ALG` and `splice()`.
689+
* **Page-level use-after-free** — reclaiming a freed *physical page* as a
690+
different object type (PageJack / Page-UAF) corrupts the overlapping structure
691+
data-only.
692+
693+
Writeups and targets:
694+
695+
* [The Dirty Pipe Vulnerability (CVE-2022-0847)](https://dirtypipe.cm4all.com/) (Max Kellermann, 2022) — content-only page-cache overwrite, no KASLR required
696+
* [CVE-2026-31431 "Copy Fail"](https://www.sysdig.com/blog/cve-2026-31431-copy-fail-linux-kernel-flaw-lets-local-users-gain-root-in-seconds) (2026) — `algif_aead`/`AF_ALG` + `splice()` 4-byte page-cache write corrupts a setuid binary for root, no KASLR required (fix reverts commit `72548b093ee3`)
697+
* [Beyond Control: Exploring Novel File System Objects for Data-Only Attacks on Linux Systems](https://arxiv.org/abs/2401.17618) (2024) — systematic study of data-only file-subsystem targets, many exploitable without a KASLR bypass
698+
* [Page-UAF / PageJack (Black Hat USA 2024)](https://github.com/Lotuhu/Page-UAF) — page-level UAF PoCs (CVE-2021-22555, CVE-2022-0185, CVE-2022-0995, CVE-2023-5345); almost none require bypassing KASLR
699+
* [Exploit Methods/Function pointer overwrite - Linux Kernel Security Subsystem](https://kernsec.org/wiki/index.php/Exploit_Methods/Function_pointer_overwrite) — where `.text`/fops/descriptor-table pointers live
700+
701+
### Probing candidate bases
702+
703+
With no leaked pointer to read directly, the base can still be found by *probing*
704+
the 2 MiB-aligned candidates: a survivable arbitrary read can scan them for the
705+
kernel's mapped contents, or — where nothing can be read back — an observable
706+
non-fatal side effect can distinguish a mapped from an unmapped address. A wrong
707+
guess that panics is single-shot, so a purely fatal primitive cannot be
708+
brute-forced this way, unlike the userland [Brute force](#brute-force) case.
709+
710+
* [Overwriting the modprobe_path](https://ian.nl/blog/overwrite-modprobe-path) (ian.nl) — recovers the slide by scanning 2 MiB-aligned bases with an arbitrary read, then overwrites `modprobe_path`
711+
712+
## See also
713+
714+
* [xairy/linux-kernel-exploitation](https://github.com/xairy/linux-kernel-exploitation) — curated Linux kernel exploitation reference collection
715+
* [Kernel Address Space Layout Randomization (KASLR)](https://breaking-bits.gitbook.io/breaking-bits/exploit-development/linux-kernel-exploit-development/kernel-address-space-layout-randomization-kalsr) — exploit-development walkthrough
716+
* [Exploiting a Linux Kernel Infoleak to bypass Linux kASLR](https://marcograss.github.io/security/linux/2016/01/24/exploiting-infoleak-linux-kaslr-bypass.html) (Marco Grassi, 2016)

docs/diagrams/bypass-technique-map.svg

Lines changed: 3 additions & 3 deletions
Loading

docs/exploitation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ pointer usable:
244244
```
245245

246246
See also [Side-channels](bypass-techniques.md#side-channels) (SLUBStick,
247-
cross-cache) and [Arbitrary read](bypass-techniques.md#arbitrary-read).
247+
cross-cache) and [Exploit primitives](bypass-techniques.md#exploit-primitives).
248248

249249
`kasld` supplies *layout*: the slid image (any global symbol) plus the direct-map
250250
and vmemmap bases (physical↔virtual, `struct page`). It does **not** locate a

0 commit comments

Comments
 (0)