Skip to content

Commit e509137

Browse files
committed
kernelCTF: stabilize exp449 verifier and repro races
1 parent e3a45b7 commit e509137

4 files changed

Lines changed: 529 additions & 73 deletions

File tree

pocs/linux/kernelctf/CVE-2026-23231_cos/docs/exploit.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ Outside the kernelCTF separate-leak repro path, the exploit leaks the kernel tex
2828

2929
- **Fallback scan**: On non-Intel hosts, scans mapped ranges by treating averaged timings above the calibrated threshold as mapped kernel addresses. The threshold is capped at 190 cycles by default and 130 cycles on Xeon hosts, then adjusted down to the measured unmapped baseline plus a 24-cycle margin when the host timing is lower.
3030

31-
- **Vulnerability verification**: The verifier invokes the exploit with `--vuln-trigger` on a KASAN `nokaslr` kernel. In that mode the exploit skips the LPE payload, KASLR leak, physmap spray, and candidate schedule. It saturates IPv6 hooks, uses four IPv4 LOCAL_OUT flood threads, and keeps the same INET rollback race running until the verifier's outer timeout with a msg_msg poison reclaim instead of a ROP blob. Vulnerable kernels can report the packet-path `nft_do_chain()` UAF or panic on the poison blob pointer; patched kernels should time out without a KASAN report.
31+
- **Vulnerability verification**: The verifier invokes the exploit with `--vuln-trigger` on a KASAN `nokaslr` kernel. In that mode the exploit skips the LPE payload, KASLR leak, physmap spray, and candidate schedule. It first checks `/proc/kallsyms` for KASAN report symbols so non-KASAN release kernels exit immediately as a negative verifier result. On KASAN builds it runs nine fresh child attempts; each child creates a new user+net namespace, saturates IPv6 hooks, and races the same INET rollback path for 17 seconds with eight IPv4 LOCAL_OUT flood threads, three INET `NEWCHAIN` producers, two chain dump threads, and a msg_msg poison reclaim pool instead of a ROP blob. Vulnerable kernels can report either the packet-path `nft_do_chain()` UAF or the chain-dump `nf_tables_dump_chains()` UAF; patched kernels should exit without a KASAN report.
3232

33-
- **Candidate arrays**: The repro path uses the verified primary `kbase`. When pagemap is unavailable, it tries a priority list of observed-good freed `__init` aliases, fills the rest from prefetch timing, and runs 18 candidates for 20 seconds each with a fresh user/net namespace per candidate. The highest-priority aliases are retried once before the direct-map fallback.
33+
- **Candidate arrays**: The repro path uses the verified primary `kbase`. When pagemap is unavailable, it tries prefetch-ranked freed `__init` aliases first, appends observed-good aliases as backups, and runs up to 26 candidates for 20 seconds each with a fresh user/net namespace per candidate. The first few aliases are retried once before the direct-map fallback.
3434

3535
**Objects**: None (pure side-channel, no kernel objects involved).
3636

@@ -45,7 +45,7 @@ A 2.5 GB region is allocated via `mmap(MAP_SHARED | MAP_ANONYMOUS | MAP_POPULATE
4545

4646
The direct-map base (`phbase`) provides a 1:1 virtual mapping of physical RAM, so these user pages are accessible from the kernel at predictable KVAs (`phbase + pfn * 0x1000` when pagemap is readable). When pagemap is blocked in repro, the exploit first uses the freed `__init_begin..__init_end` linear aliases as stable kernel mappings for sprayed pages.
4747

48-
If `/proc/self/pagemap` is readable, the exact physical frame number is read to compute the precise KVA. Otherwise, the exploit tries prioritized `__init` page aliases first, fills the remaining slots with prefetch-ranked aliases, gives each candidate a short fresh-child race window, and keeps the parent polling for late `core_pattern` hits. If that fails, it falls back to direct-map offsets in the `[0.5, 2.5] GB` range from `phbase` (avoiding the QEMU MMIO hole at 3-4 GB physical).
48+
If `/proc/self/pagemap` is readable, the exact physical frame number is read to compute the precise KVA. Otherwise, the exploit tries prefetch-ranked `__init` page aliases first, appends observed-good aliases if they were not already selected, gives each candidate a short fresh-child race window, and keeps the parent polling for late `core_pattern` hits. If that fails, it falls back to direct-map offsets in the `[0.5, 2.5] GB` range from `phbase` (avoiding the QEMU MMIO hole at 3-4 GB physical).
4949

5050
**Objects**: Physical pages via physmap (direct map).
5151
**Cache**: N/A (physmap pages, not slab objects).
@@ -74,6 +74,13 @@ An INET table (`ti`) is also created for the race loop.
7474

7575
Three threads run concurrently on 2 CPUs:
7676

77+
In `--vuln-trigger` verifier mode, the race is repeated across nine fresh child
78+
namespaces with 17-second windows. Each verifier attempt also runs two
79+
`NFT_MSG_GETCHAIN` dump threads over the `NFPROTO_INET` chain list and two extra
80+
INET `NEWCHAIN` producers with separate chain-name ranges. This only increases
81+
KASAN reachability for vuln-verify; the full LPE path still uses the
82+
packet-path race plus `msg_msg` reclaim described below.
83+
7784
#### Thread 1: UDP Flood (CPU 1)
7885

7986
Sends UDP packets to `127.0.0.1:12345` via `sendmmsg()` in batches of 128. Each packet traverses `NF_INET_LOCAL_OUT`, calling `nft_do_chain_inet()` -> `nft_do_chain()` on any registered IPv4 hook. Yields every 8th batch (`sched_yield()`) to report RCU quiescent states, ensuring `synchronize_rcu()` on CPU 0 completes quickly on a PREEMPT_NONE kernel.
@@ -82,7 +89,7 @@ Sends UDP packets to `127.0.0.1:12345` via `sendmmsg()` in batches of 128. Each
8289

8390
#### Thread 2: INET NEWCHAIN Loop (CPU 0)
8491

85-
Sends nfnetlink batch messages creating `NFPROTO_INET` base chains. Each attempt:
92+
Sends nfnetlink batch messages creating four independent `NFPROTO_INET` base chains per batch. Each chain attempt:
8693

8794
1. Chain is allocated (`nft_base_chain`, kmalloc-256) and published to `table->chains` via `list_add_tail_rcu()`.
8895
2. IPv4 hook registers successfully -- packets can now reach `nft_do_chain()` on this chain.
@@ -99,7 +106,7 @@ Sends nfnetlink batch messages creating `NFPROTO_INET` base chains. Each attempt
99106

100107
Runs on the **same CPU** as the race thread. When the race thread blocks in `synchronize_rcu()`, the scheduler switches to this thread. It performs:
101108

102-
1. Burst of 8 `msgsnd()` calls across 512 queues: allocates `msg_msg` objects from CPU 0's `kmalloc-cg-256` per-CPU SLUB freelist. The **first** `msgsnd` after the chain free should pick up the just-freed `nft_base_chain` slot (SLUB LIFO order), while the remaining burst covers scheduler jitter before RCU quiescence.
109+
1. Burst of 8 `msgsnd()` calls across 1024 queues: allocates `msg_msg` objects from CPU 0's `kmalloc-cg-256` per-CPU SLUB freelist. The **first** `msgsnd` after the chain free should pick up the just-freed `nft_base_chain` slot (SLUB LIFO order), while the remaining burst covers scheduler jitter before RCU quiescence.
103110
2. `sched_yield()`: forces context switch -> `rcu_note_context_switch()` -> quiescent state for CPU 0, unblocking the pending `synchronize_rcu()`.
104111

105112
**Reclaim object**: `struct msg_msg` (kmalloc-cg-256, 256-byte allocation: 48-byte msg_msg header + 208-byte mtext).
Binary file not shown.

0 commit comments

Comments
 (0)