af01d112a5
build / build (clang / debug) (push) Waiting to run
build / build (clang / default) (push) Waiting to run
build / build (gcc / debug) (push) Waiting to run
build / build (gcc / default) (push) Waiting to run
build / sanitizers (ASan + UBSan) (push) Waiting to run
build / clang-tidy (push) Waiting to run
build / drift-check (CISA KEV + Debian tracker) (push) Waiting to run
build / static-build (push) Waiting to run
Attempted the most tractable kernel primitive to gauge the frontier. Kernel
confirmed vulnerable (focal GA 5.4.0-26 and a provisioned mainline 5.8.0, both
pre the 5.4.0-77/5.8.0-53 fix). But Andy Nguyen's well-regarded single-file
public exploit consistently fails at STAGE 1 ("could not corrupt any primary
message") on both — it is tuned for Ubuntu's exact 5.8.0-48-generic slab config
(freelist randomisation / memcg kmem accounting / SLUB merge), which mainline
kernels don't match, and Ubuntu's EOL 5.8.0-48 HWE debs aren't readily
sourceable. Recorded in docs/EXPLOITED.md: kernel primitives are
config-and-version-specific exploit-dev — even a reference exploit needs its
exact target kernel image + per-target tuning + a full port, a per-primitive
multi-session effort. No code change; documentation of the empirical result.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118iUgHY44hdRtANgyCmu7y
279 lines
21 KiB
Markdown
279 lines
21 KiB
Markdown
# Exploit verification ledger
|
||
|
||
**What this is:** results of actually *running each exploit* against a genuinely
|
||
vulnerable VM and confirming `uid=0` **out of band** (an independent root-owned
|
||
write / `/etc/shadow` read / setuid-bash sentinel — never the module's own
|
||
self-report). This is distinct from `docs/VERIFICATIONS.jsonl`'s historical
|
||
records, which only checked that `detect()` returns the right verdict.
|
||
|
||
Harness: rootless qemu/KVM over frozen point-release cloud images (unpatched →
|
||
vulnerable by default), driver in the session scratch dir. Root witnessed via
|
||
`witness.sh` (shell-probe + setuid-bash finisher + module sentinels + `/etc/passwd`
|
||
tamper check).
|
||
|
||
> **Headline finding:** the corpus was only ever *detect*-verified, never
|
||
> *exploit*-verified. Running the exploits shows a mix of genuinely-working,
|
||
> honestly-failing, and **falsely-succeeding** modules. Three modules reported
|
||
> `EXPLOIT_OK` while obtaining **no root at all** (`pwnkit`, `ptrace_traceme`,
|
||
> `dirty_pipe`) — a false positive from the dispatcher's "execve transferred →
|
||
> clean child exit = OK" path (the exploit `execlp`'s a helper that then fails).
|
||
> `dirty_pipe` additionally *corrupted the running system* (its unprivileged
|
||
> `drop_caches` revert left /etc/passwd poisoned). All three are fixed below and
|
||
> now either land real root or fail honestly.
|
||
|
||
## Confirmed landing root (uid=0 witnessed out of band)
|
||
|
||
| module | CVE | target | notes |
|
||
|---|---|---|---|
|
||
| `refluxfs` | CVE-2026-64600 | Rocky 9.8 / 5.14.0-687.el9 | full chain, `/etc/passwd` → root (earlier) |
|
||
| `overlayfs` | CVE-2021-3493 | Ubuntu 20.04.0 / 5.4.0-26 | userns + xattr copy-up; **direct uid=0 witness** (cap'd payload drops a root-owned proof) |
|
||
| `overlayfs_setuid` | CVE-2023-0386 | Ubuntu 22.04.0 / 5.15.0-25 | **after a full rewrite** — see below |
|
||
| `pwnkit` | CVE-2021-4034 | Ubuntu 20.04.0 / polkit 0.105-26ubuntu1 | **after a fix** — see below |
|
||
| `sudo_runas_neg1` | CVE-2019-14287 | Ubuntu 18.04.2 / sudo 1.8.21p2 + sudoers `(ALL,!root)` | `sudo -u#-1` → uid 0 |
|
||
| `sudoedit_editor` | CVE-2023-22809 | Ubuntu 22.04.0 / sudo 1.9.9 + sudoers `sudoedit` grant | **after 2 fixes** — `chdir("/")` + helper basename match; `su skel` → uid 0 |
|
||
| `sudo_host` | CVE-2025-32462 | Ubuntu 22.04.0 / sudo 1.9.9 + host-restricted sudoers rule | works as shipped; `sudo -h <host>` → uid 0 (needs a host-scoped rule + resolvable host) |
|
||
| `ptrace_traceme` | CVE-2019-13272 | Ubuntu 18.04.0 / 4.15.0-50 + pkexec + active-session polkit | **after a full rewrite** — `skeletonkey --exploit ptrace_traceme` (uid 1000) → root-owned setuid bash. See below |
|
||
| `sudo_samedit` | CVE-2021-3156 | Ubuntu 18.04.0 / sudo 1.8.21p2 / libc-2.27 | **after a full rewrite** — Baron Samedit; `skeletonkey --exploit sudo_samedit` (uid 1000, non-sudoer) → root-owned setuid bash. See below |
|
||
| `dirty_pipe` | CVE-2022-0847 | mainline 5.16.0 on Ubuntu 22.04 userspace | **after fixing 3 bugs** — `skeletonkey --exploit dirty_pipe` (uid 1000) → root-owned setuid bash; /etc/passwd byte-identical after revert. See below |
|
||
| `dirty_cow` | CVE-2016-5195 | mainline 4.8.0 on Ubuntu 16.04.7 | **after fixing the false-OK** — verbatim-module standalone (uid 1000) → root-owned setuid bash; /etc/passwd byte-identical after revert. See below |
|
||
|
||
## Fixed this session
|
||
|
||
- **`pwnkit`** — reported `EXPLOIT_OK` but did **not** root (glibc "Could not
|
||
open converter … to PWNKIT"). Root cause: missing the `GCONV_PATH=.`
|
||
re-injection directory + `chdir(workdir)`. Fixed → now lands real root on a
|
||
vulnerable host. (commit `24b839e`)
|
||
- **`ptrace_traceme`** (CVE-2019-13272) — first made **honest** (it had reported a
|
||
false `EXPLOIT_OK` with a placeholder that had the mechanism *backwards* —
|
||
attaching to the parent), then **rewritten and now lands real root** (uid=0
|
||
witnessed out-of-band on Ubuntu 18.04.0 / 4.15.0-50). The correct mechanism is
|
||
the reverse of the old placeholder: a *middle* process execs setuid `pkexec`
|
||
(euid 0 for a window); its *child* spins until it sees that euid-0, calls
|
||
`PTRACE_TRACEME` (recording the parent's **root** creds as its ptracer_cred —
|
||
the bug), then execs `pkexec` itself — the traced setuid exec is **not
|
||
degraded** because ptracer_cred is root, so the child becomes real root, and a
|
||
staged `execveat()` self-re-exec injects the payload. Ported the proven Jann
|
||
Horn / bcoles PoC verbatim (only `spawn_shell()` changed, to plant a root-owned
|
||
proof + setuid bash), embedded as `ptrace_helper_src.h`, compiled on the target
|
||
at runtime with unique `-DSK_PROOF/-DSK_ROOTBASH` paths, run, and verified by
|
||
`stat()`-ing the root-owned artifacts. **Real-world precondition** (honestly
|
||
reported): pkexec must *authorize* an auto-discovered `implicit-active=yes`
|
||
helper, which needs an **active local session** (desktop) or an equivalently
|
||
permissive polkit policy; over a bare *inactive* ssh session pkexec returns
|
||
"Not authorized" and the module reports `EXPLOIT_FAIL` with that diagnosis. On
|
||
the headless VM this was isolated with a permissive `pkla` for the backlight
|
||
helper action — the kernel bug and the whole technique are confirmed; the gate
|
||
is polkit, not the exploit.
|
||
- **`overlayfs_setuid`** (CVE-2023-0386) — **rewritten and now lands real root**
|
||
(uid=0 witnessed out-of-band on Ubuntu 22.04.0 / 5.15.0-25). The shipped
|
||
module used a bogus `chown`-the-merged-view technique that never worked. The
|
||
real bug needs a **FUSE lower layer** exporting a setuid-root file; overlay
|
||
copy-up then materialises it in the real upper as a genuine setuid-root
|
||
binary. Key findings from the port (all four were required):
|
||
1. Overlay refuses a **userns-mounted** FUSE lowerdir (ENOSYS) — FUSE must
|
||
be mounted in the **init ns** via the setuid `fusermount` helper (libfuse
|
||
does this). A raw `/dev/fuse` server was tried and abandoned: its INIT
|
||
handshake needs `poll()` on the non-blocking fd, and a malformed reply
|
||
destabilised the kernel — fragile and inappropriate. libfuse is linked
|
||
conditionally (pkg-config `fuse`/`fuse3`), matching `pack2theroot`.
|
||
2. **fuse2** low-level API (`fuse_mount`/`fuse_new`/`fuse_loop_mt`, empty
|
||
args) — `fuse_main` advertises splice/copy_file_range caps that make the
|
||
kernel attempt `copy_file_range` at copy-up → ENOSYS with no fallback.
|
||
3. **`read_buf`** callback (copy-up's splice read path).
|
||
4. **`ioctl`** callback — copy-up issues `FS_IOC_GETFLAGS` on the lower; a
|
||
server without an ioctl handler returns ENOSYS and copy-up fails. This
|
||
was the last missing piece.
|
||
Debugging was isolated by driving the exploit orchestration against the public
|
||
PoC's `./fuse`, then swapping servers, then comparing `fops`.
|
||
- **`sudo_samedit`** (CVE-2021-3156, "Baron Samedit") — the corpus's hardest
|
||
userspace target, **rewritten and now lands real root** (uid=0 witnessed
|
||
out-of-band on Ubuntu 18.04.0 / sudo 1.8.21p2 / libc-2.27, as an unprivileged
|
||
non-sudoer). The shipped module drove a structural trigger with no offsets and
|
||
honestly reported `EXPLOIT_FAIL`. Ported blasty's technique: the `sudoedit -s`
|
||
unescape overflow overwrites a glibc NSS `service_user`, so the lookup dlopen's
|
||
an attacker-planted `libnss_X/'P0P_SH3LLZ_ .so.2'` from CWD; its constructor
|
||
runs while sudo is still root. The module compiles the NSS payload on the target
|
||
(unique `-DSK_PROOF/-DSK_ROOTBASH`), lays out the `libnss_X/` dir, execs sudoedit
|
||
with the crafted argv/env (per-libc grooming lengths: Ubuntu 56/54/63/212,
|
||
Debian 64/49/60/214), and verifies root by `stat()`-ing the artifacts. Primary
|
||
lengths landed first try; a `null_stomp_len` sweep (±8, the axis blasty's
|
||
brute.sh perturbs) is the fallback for libc drift. Needs cc on the target.
|
||
- **`dirty_pipe`** (CVE-2022-0847) — **three bugs fixed; now lands real root**
|
||
(uid=0 witnessed out-of-band on a genuinely pre-fix **mainline 5.16.0** kernel —
|
||
provisioned by installing the kernel.ubuntu.com 5.16.0 debs on the jammy image,
|
||
since every cached cloud image was either pre-5.8 or backport-patched). The
|
||
shipped exploit (1) flipped the *caller's* UID to `0000` and ran `su self`,
|
||
which still demands the caller's password — it never rooted anything; (2)
|
||
`execlp`'d su, so the dispatcher's exec-transfer path reported a **false
|
||
`EXPLOIT_OK`** even on the auth failure; and (3) reverted with `drop_caches`,
|
||
which needs root — so as an unprivileged caller it **left the running system's
|
||
/etc/passwd page cache corrupted** (this actually broke sshd's user resolution
|
||
in testing). Rewrote it to the reliable technique: overwrite **root's** password
|
||
field with a known crypt hash, authenticate as root over a **pty** with the
|
||
matching password (su reads the password from the controlling tty, not stdin),
|
||
plant a root-owned proof + setuid bash, and **revert the page cache via the
|
||
Dirty Pipe primitive itself** (write the saved original bytes back — no root, no
|
||
drop_caches). Verified `/etc/passwd` is byte-identical afterward. Root judged
|
||
only by the out-of-band artifact.
|
||
- **`dirty_cow`** (CVE-2016-5195) — **same three bugs as `dirty_pipe`, fixed the
|
||
same way** (found by the false-`EXPLOIT_OK` audit below). It raced the
|
||
*caller's* UID field to `0000` then ran `su self` (needs the caller's password
|
||
→ never rooted anything), `execlp`'d su so the exec-transfer path reported a
|
||
**false `EXPLOIT_OK`**, and reverted with `drop_caches` (needs root → corrupts
|
||
the running /etc/passwd). Rewrote to: race **root's** password field to a known
|
||
`$6$` hash → authenticate as root over a pty → plant a root-owned proof + setuid
|
||
bash → revert by racing the original bytes back through the Dirty COW primitive.
|
||
Also fixed a latent buffer overflow (the success-check `readback[16]` was too
|
||
small for a >16-byte payload), and made the `su`-over-pty step **poll for the
|
||
prompt with a hard 20s cap** — a fixed-delay write raced su's prompt setup and
|
||
**hung on xenial**, which (without the cap) would have blocked the revert and
|
||
left /etc/passwd poisoned. The same robust `su` helper was back-ported to
|
||
`dirty_pipe`. **Verified end-to-end on a genuinely Dirty-COW-vulnerable
|
||
mainline 4.8.0 kernel** (provisioned by installing the kernel.ubuntu.com 4.8.0
|
||
deb on a 16.04 image + a virtio-rng for entropy): a standalone built verbatim
|
||
from the module's primitive + escalation + robust su raced root's passwd field,
|
||
authenticated as root, planted a root-owned setuid bash, and left /etc/passwd
|
||
byte-identical. (The full `skeletonkey` binary won't compile on xenial's 4.4-era
|
||
uapi headers — several unrelated `nft_*` modules use newer kernel constants — so
|
||
the verbatim standalone stands in for `--exploit dirty_cow` on that box.)
|
||
- **`cgroup_release_agent`** — two real bugs fixed (commit `8c45b2b`): it read
|
||
`getuid()` **after** `unshare(CLONE_NEWUSER)` (→ `65534`, so `uid_map` write
|
||
was `"0 65534 1"` → EPERM), and it omitted `CLONE_NEWCGROUP` (→ cgroup-v1
|
||
mount EPERM). Now the userns+cgroupns+mount setup is correct. It still can't
|
||
root a **bare** unprivileged user on a stock systemd host: every v1 controller
|
||
is pre-mounted (its `release_agent` is init-owned → EACCES from the userns)
|
||
and a fresh named hierarchy is refused. Reachable in a **container** context
|
||
(CAP_SYS_ADMIN / an ownable cgroup) — matches its "host root from rootless
|
||
container" framing. The `getuid()`-after-`unshare` bug is a pattern to grep
|
||
for across the other userns modules.
|
||
|
||
## False-`EXPLOIT_OK` audit (every module that transfers the process via `exec*`)
|
||
|
||
The dispatcher's `run_callback_isolated` forks the exploit and, if it `execve`s
|
||
(FD_CLOEXEC closes the result pipe → parent reads EOF, no crash signal), reports
|
||
`EXPLOIT_OK` **regardless of whether the exec'd program actually rooted anything**.
|
||
So any exploit whose main path exec's a *not-guaranteed-root* target lies. Audited
|
||
every `exec*`-calling module:
|
||
|
||
| module | verdict | why |
|
||
|---|---|---|
|
||
| `dirty_cow` | ❌ **false-OK → fixed + verified** | raced own UID + `su self`; `execlp(su)` transfer = OK. Fixed + verified end-to-end on mainline 4.8.0 (see above). |
|
||
| `pwnkit` | ✅ fixed earlier | now re-injects gconv + verifies |
|
||
| `ptrace_traceme` | ✅ fixed earlier | rewritten; verifies OOB artifact |
|
||
| `dirty_pipe` | ✅ fixed earlier | rewritten; verifies OOB artifact |
|
||
| `sudo_host` | ✅ safe | runs `sudo -n -h <host> id -u` witness (uid 0) *before* the exec |
|
||
| `sudo_chwoot` | ✅ safe | forks sudo in a child, then `stat`s the setuid bash root-owned |
|
||
| `cgroup_release_agent` | ✅ safe | polls for the root-owned setuid shell before exec |
|
||
| `fuse_legacy` | ✅ honest | gates the exec on real `setuid(0)==0 && getuid()==0`; else `EXPLOIT_FAIL` |
|
||
| `overlayfs` | ⚠️ proxy (low risk) | confirms the `security.capability` xattr persisted via `getxattr` before exec'ing the cap'd payload — strong proxy, works, but not a direct root witness |
|
||
| `sudoedit_editor` | ⚠️ works, reporting unverified | plants a passwordless `skel:0:0` entry + `su skel` (confirmed to root), but returns `EXPLOIT_OK` unconditionally — would false-OK if su failed |
|
||
| `dirtydecrypt`, `fragnesia`, `copy_fail_family` (`exploit_su.c`) | ✅ proxy-verified | exec the hijacked setuid target only after **verifying the shellcode/payload actually landed in the page cache** (`verify_plant` / `rc==1` / `WEXITSTATUS==0`) and reverting otherwise — a real effect-check, not a blind exec-transfer. 2026-target-gated. |
|
||
| `ptrace_pidfd` | ✅ n/a | the `execve` is the *victim* being raced (fd-steal), not an escalation |
|
||
| `mutagen_astronomy` | ✅ n/a | env-gated scaffold; SIGSEGVs by design |
|
||
|
||
Net: the exec-transfer trap produced **four** genuine false-OKs (`pwnkit`,
|
||
`ptrace_traceme`, `dirty_pipe`, `dirty_cow`) — all now fixed and verified. The
|
||
audit was then **broadened to every `EXPLOIT_OK` return site** (not just
|
||
exec-transfer): the rest are backed by a genuine out-of-band check — a
|
||
root-owned artifact `stat` (`sudo_chwoot`, `overlayfs`), a `getxattr`
|
||
bug-signature, a `/etc/passwd` grep of the injected entry (`sudoedit_editor`,
|
||
`refluxfs`), a real `setuid(0)==0` gate (`fuse_legacy`), or a page-cache
|
||
`verify_plant` before the hijack exec (`copy_fail_family`, `dirtydecrypt`,
|
||
`fragnesia`). **No further false-OKs remain.** `overlayfs` was additionally
|
||
upgraded from its `getxattr` proxy to a **direct uid=0 witness** (the cap'd
|
||
payload now drops a root-owned proof, re-verified on focal 5.4.0-26).
|
||
|
||
## Needs a faithful PoC port (genuinely vulnerable target, exploit doesn't land)
|
||
|
||
| module | CVE | target tested | what's wrong |
|
||
|---|---|---|---|
|
||
| `overlayfs_setuid` | CVE-2023-0386 | Ubuntu 22.04.0 / 5.15.0-25.25 | **Kernel confirmed vulnerable empirically** — the upstream PoC (xkaneiki, libfuse) pops root here (`uid=0(root)`, root-owned witness). The working technique: mount a FUSE fs exporting `/file` (st_uid=0, mode 04777) in the **init ns** via the setuid `fusermount3` helper, then overlay-in-userns with that FUSE lowerdir + copy-up. My module's non-FUSE `chown` variant yields `upper/file` uid=1000 (no escalation); mounting FUSE **inside** the userns → overlay `ENOSYS`. Attempted a self-contained **raw `/dev/fuse`** port: got the `fusermount` fd-passing handshake (`SCM_RIGHTS`) + mount working, but the server hits `EINVAL` on `read()` after `FUSE_INIT` (non-blocking fd → needs `poll()`), and even with poll/buffer fixes the raw server serving was flaky and repeatedly **wedged/rebooted the VM** — i.e. the raw protocol reimplementation is fragile and can destabilise the target, which is *worse* for the corpus than a lib dependency. **Conclusion: use libfuse** (proven, robust; matches the `pack2theroot` conditional-lib precedent). Port is scoped and ready; needs a clean session to implement + verify. |
|
||
| *(none left in this table — `sudo_samedit` was the last, now working; see "Fixed this session")* | | | |
|
||
|
||
## Inconclusive (detect version-blind vs vendor backport)
|
||
|
||
*(none outstanding — `dirty_pipe` was here; now verified on a genuinely
|
||
pre-fix mainline 5.16.0 kernel, see "Fixed this session".)*
|
||
|
||
## Kernel primitives — offset path fixed; `nf_tables` gap scoped (this session)
|
||
|
||
**Resolver bug fixed (`core/offsets.c`, commit `cd9bea6`).** The documented
|
||
env-var offset override (`SKELETONKEY_MODPROBE_PATH` etc.) was **silently wiped on
|
||
every default host**: `parse_symfile` reads `/proc/kallsyms`, which returns
|
||
all-zero addresses under `kptr_restrict`, and then *unconditionally* zeroed
|
||
`modprobe_path`/`init_task` — clobbering the values `apply_env` had just set. Net
|
||
effect: every `--full-chain` primitive reported "offsets couldn't be resolved"
|
||
even with correct offsets supplied. Now the all-zero path only clears fields it
|
||
tagged `OFFSETS_FROM_KALLSYMS` itself. **This was the blocker for the entire
|
||
primitive full-chain path.** Verified fixed on Ubuntu 22.04.0 / 5.15.0-25:
|
||
`--full-chain` now prints `modprobe_path=0x… (env)`, the finisher engages, and the
|
||
arb-write fires.
|
||
|
||
**`nf_tables` (CVE-2024-1086) — kernel CONFIRMED vulnerable; module gap scoped.**
|
||
Followed the full methodology (test → confirm kernel → pull PoC → diff):
|
||
- **Kernel is genuinely vulnerable.** Built Notselwyn's public universal PoC
|
||
(`github.com/Notselwyn/CVE-2024-1086`, musl-static) on jammy 5.15.0-25 (below the
|
||
patched branch 5.15.149) and ran it: it drove the exploit and hit the deliberate
|
||
post-exploitation `kernel BUG at mm/slub.c:379` / `Kernel panic` — i.e. the
|
||
cross-cache slab corruption fired. Kernel confirmed exploitable.
|
||
- **The difference.** The module (its own header is honest about this) is a
|
||
**trigger + groom scaffold**: it builds the `NFT_GOTO+NFT_DROP` verdict combo
|
||
that `nft_verdict_init()` fails to reject, fires the double-free, and runs the
|
||
`msg_msg` cg-96 groom — all real. But its arb-write is "FALLBACK-DEPTH": the
|
||
exact `pipapo_elem` layout + value-pointer offset needed to redirect the write
|
||
at `modprobe_path` is a documented TODO, so the write doesn't land → honest
|
||
`EXPLOIT_FAIL`. Notselwyn's working exploit uses a *different, heavier* technique
|
||
entirely — **universal cross-cache → dirty-pagetable** (arbitrary physical R/W,
|
||
no per-kernel offsets), ~2000 LOC across multiple files with static
|
||
`libnftnl`/`libmnl`.
|
||
- **Scope of the remaining fix.** Making `nf_tables --full-chain` land root means
|
||
either (a) completing the module's own per-kernel `pipapo_elem` arb-write layout,
|
||
or (b) porting Notselwyn's universal technique. Both are substantial dedicated
|
||
exploit-dev — this is the hardest module in the corpus, not a spot-the-bug fix.
|
||
The offset resolver (above) is the piece that was actually broken and is now
|
||
fixed + pushed.
|
||
|
||
- **Other 🟡 kernel primitives** (`nft_set_uaf`, `nft_payload`, `nft_fwd_dup`,
|
||
`netfilter_xtcompat`, `af_packet`, `af_packet2`, `af_unix_gc`, `cls_route4`,
|
||
`fuse_legacy`, `stackrot`, `sequoia`, `nft_pipapo`, `vsock_uaf`, `pintheft`):
|
||
same shape — real trigger/groom scaffolds returning `EXPLOIT_FAIL` by design.
|
||
The resolver fix unblocks feeding them offsets; each still needs its arb-write
|
||
primitive completed against a matching vulnerable kernel.
|
||
|
||
**`netfilter_xtcompat` (CVE-2021-22555) — empirical note on why the primitives are
|
||
hard.** Attempted the corpus's *most tractable* primitive first: it has a clean,
|
||
well-regarded single-file public exploit (Andy Nguyen / Google, the `IPT_SO_SET_
|
||
REPLACE` heap-OOB → `msg_msg` cross-cache → cred overwrite). Kernel confirmed
|
||
vulnerable (Ubuntu 20.04 GA 5.4.0-26, and a provisioned mainline 5.8.0 — both pre
|
||
the 5.4.0-77 / 5.8.0-53 fix). **But the reference exploit consistently fails at
|
||
STAGE 1 ("could not corrupt any primary message") on both**, because it is tuned
|
||
for *Ubuntu's exact `5.8.0-48-generic` config* (the tested target). The slab
|
||
behaviour that governs whether the OOB write lands next to a sprayed `msg_msg`
|
||
(freelist randomisation, memcg kmem accounting, SLUB merge) differs between
|
||
mainline and Ubuntu-patched kernels, and Ubuntu's EOL `5.8.0-48` HWE debs are no
|
||
longer readily sourceable. Takeaway: kernel primitives are **config-and-version-
|
||
specific exploit-dev** — even a "drop-in" reference exploit needs its exact target
|
||
kernel image plus per-target slab tuning, and then a full port (~760 LOC here,
|
||
~2000 for `nf_tables`/Notselwyn). This is a per-primitive, multi-session effort;
|
||
it is NOT the "spot the bug and fix it" tier the userspace modules were.
|
||
- **Structural userspace** (`sudoedit_editor`, `sudo_chwoot`, `sudo_host`):
|
||
need specific sudo versions + sudoers config; likely tractable.
|
||
- **2026 CVEs** (`copy_fail` ×5, `dirtydecrypt`, `fragnesia`, `cifswitch`,
|
||
`nft_catchall`, `ptrace_pidfd`): need vulnerable 2026 kernels; the reconstructed
|
||
race triggers (`bad_epoll`, `ghostlock`, `nft_catchall`) are deliberately
|
||
under-driven and won't pop root by design.
|
||
- **Environment-blocked**: `vmwgfx` (VMware guest only), `dirty_cow` (needs ≤4.4),
|
||
`mutagen_astronomy` (CentOS 6 / Debian 7).
|
||
- **D-Bus/desktop** (`pack2theroot`, `udisks_libblockdev`): need the polkit/D-Bus
|
||
stack + a provisioner rule.
|
||
|
||
## Method notes for continuation
|
||
|
||
- Frozen images: `cloud-images-archive.ubuntu.com/releases/<name>/release-<date>/`
|
||
are unpatched and vulnerable-by-default for CVEs disclosed after that date — far
|
||
easier than downgrading packages on current images.
|
||
- gcc must be present *in* the VM (several exploits compile payloads at runtime);
|
||
on EOL LTS, point apt at the archive main pocket.
|
||
- **Always verify root out of band.** The module self-report is not trustworthy
|
||
(two flagships lied). `witness.sh` is the reference check.
|