pack2theroot (CVE-2026-41651) + --auto accuracy work

Adds the third ported module — Pack2TheRoot, a userspace PackageKit
D-Bus TOCTOU LPE — and spends real effort hardening --auto so its
detect step gives an accurate, robust verdict before deploying.

pack2theroot (CVE-2026-41651):
- Ported from the public Vozec PoC
  (github.com/Vozec/CVE-2026-41651). Original disclosure by the
  Deutsche Telekom security team.
- Two back-to-back InstallFiles D-Bus calls (SIMULATE then NONE)
  overwrite the cached transaction flags between polkit auth and
  dispatch. GLib priority ordering makes the overwrite deterministic,
  not a timing race; postinst of the malicious .deb drops a SUID bash
  in /tmp.
- detect() reads PackageKit's VersionMajor/Minor/Micro directly over
  D-Bus and compares against the pinned fix release 1.3.5 (commit
  76cfb675). This is a high-confidence verdict, not precondition-only.
- Debian-family only (PoC builds its own .deb in pure C; ar/ustar/
  gzip-stored inline). Cleanup removes /tmp .debs + best-effort
  unlinks /tmp/.suid_bash + sudo -n dpkg -r the staging packages.
- Adds an optional GLib/GIO build dependency. The top-level Makefile
  autodetects via `pkg-config gio-2.0`; when absent the module
  compiles as a stub returning PRECOND_FAIL.
- Embedded auditd + sigma rules cover the file-side footprint
  (/tmp/.suid_bash, /tmp/.pk-*.deb, non-root dpkg/apt execve).

--auto accuracy improvements:
- Auto-enables --active before the scan. Per-module sentinel probes
  (page-cache /tmp files, fork-isolated namespace mounts) turn
  version-only checks into definitive verdicts, so silent distro
  backports don't fool the scan and --auto won't pick blind on
  TEST_ERROR.
- Per-module verdict printing — every module's result is shown
  (VULNERABLE / patched / precondition / indeterminate), not just
  VULNERABLE rows. Operator sees the full picture.
- Scan-end summary line: "N vulnerable, M patched/n.a., K
  precondition-fail, L indeterminate" with a separate callout when
  modules crashed.
- Distro fingerprint added to the auto banner (ID + VERSION_ID from
  /etc/os-release alongside kernel/arch).
- Fork-isolated detect() — each detector runs in a child process so
  a SIGILL/SIGSEGV in one module's probe is contained and the scan
  continues. Surfaced live while testing: entrybleed's prefetchnta
  KASLR sweep SIGILLs on emulated CPUs (linuxkit on darwin); without
  isolation the whole --auto died at module 7 of 31. With isolation
  the scan reports "detect() crashed (signal 4) — continuing" and
  finishes cleanly.

module_safety_rank additions:
- pack2theroot: 95 (userspace D-Bus TOCTOU; dpkg + /tmp SUID footprint
  — clean but heavier than pwnkit's gconv-modules-only path).
- dirtydecrypt / fragnesia: 86 (page-cache writes; one step below the
  verified copy_fail/dirty_frag family at 88 to prefer verified
  modules when both apply).

Docs:
- README badge / tagline / tier table /  block / example output /
  v0.5.0 status — all updated to "28 verified + 3 ported".
- CVES.md counts line, the ported-modules note (now calling out
  pack2theroot's high-confidence detect vs. precondition-only for
  the page-cache pair), inventory row, operations table row.
- ROADMAP Phase 7+: pack2theroot moved out of carry-overs into the
  "landed (ported, pending VM verification)" group; added a new
  "--auto accuracy work" subsection documenting the dispatcher
  hardening landed in this commit.
- docs/index.html: scanning-count example bumped to 31, status line
  updated to mention 3 ported modules.

Build verification: full `make clean && make` in `docker gcc:latest`
with libglib2.0-dev installed: links into a 31-module skeletonkey
ELF (413KB), `--list` shows all modules including pack2theroot,
`--detect-rules --format=auditd` emits the new pack2theroot section,
`--auto --i-know --no-shell` exercises the new banner + active
probes + verdict table + fork isolation + scan summary end-to-end.
Only build warning is the pre-existing
`-Wunterminated-string-initialization` in dirty_pipe (not introduced
here).
This commit is contained in:
2026-05-22 22:42:07 -04:00
parent ac557b67d0
commit 9a4cc91619
13 changed files with 1152 additions and 52 deletions
+23 -11
View File
@@ -23,18 +23,28 @@ Status legend:
- 🔴 **DEPRECATED** — fully patched everywhere relevant; kept for
historical reference only
**Counts:** 30 modules total — 28 verified (🟢 14 · 🟡 14) plus 2
ported-but-unverified (`dirtydecrypt`, `fragnesia` — see note below).
🔵 0 · ⚪ 0 planned-with-stub · 🔴 0. (One ⚪ row below — CVE-2026-31402
— is a *candidate* with no module, not counted as a module.)
**Counts:** 31 modules total — 28 verified (🟢 14 · 🟡 14) plus 3
ported-but-unverified (`dirtydecrypt`, `fragnesia`, `pack2theroot`
see note below). 🔵 0 · ⚪ 0 planned-with-stub · 🔴 0. (One ⚪ row
below — CVE-2026-31402 — is a *candidate* with no module, not counted
as a module.)
> **Note on `dirtydecrypt` / `fragnesia`:** these two are ported from
> public V12 PoCs and are **not yet VM-verified** end-to-end. They are
> listed 🟡 in the table below but are **not** part of the 28-module
> verified corpus — they differ from the other 🟡 modules in two ways:
> they are self-contained page-cache writes (no `--full-chain`
> finisher), and their `detect()` is precondition-only because the CVE
> fix commits are not yet pinned. `--auto` will not fire them blind.
> **Note on `dirtydecrypt` / `fragnesia` / `pack2theroot`:** all three
> are ported from public PoCs and are **not yet VM-verified** end-to-end.
> They are listed 🟡 in the table below but are **not** part of the
> 28-module verified corpus.
>
> `pack2theroot`'s `detect()` reads PackageKit's version directly from
> the daemon over D-Bus and compares against the **pinned fix release
> (1.3.5, commit `76cfb675`)** — so its verdict is high-confidence,
> grounded in upstream's own version metadata.
>
> `dirtydecrypt` and `fragnesia` are precondition-only — their CVE fix
> commits are not yet pinned in the modules, so `detect()` returns
> `PRECOND_FAIL` / `TEST_ERROR` unless `--active` empirically fires the
> primitive against a `/tmp` sentinel. `--auto` auto-enables active
> probes (forked per module so a probe crash cannot tear down the
> scan), which lets all three become candidates on a vulnerable host.
> See each module's `MODULE.md`.
Every module ships a `NOTICE.md` crediting the original CVE
@@ -77,6 +87,7 @@ root on a host can upstream their kernel's offsets via PR.
| CVE-2023-2008 | vmwgfx DRM buffer-object size-validation OOB | LPE (kernel R/W via kmalloc-512 OOB) | mainline 6.3-rc6 (Apr 2023) | `vmwgfx` | 🟡 | vmwgfx DRM `bo` size-validation gap → OOB write in kmalloc-512. Affects 4.0 ≤ K < 6.3-rc6 on hosts with the `vmwgfx` module loaded (VMware guests). Primitive-only — fires the OOB + slab witness; no cred chain. Branch backports: 6.2.10 / 6.1.23. Ships auditd rule. |
| CVE-2026-31635 | DirtyDecrypt / DirtyCBC — rxgk missing-COW in-place decrypt | LPE (page-cache write into a setuid binary) | duplicate of an already-patched mainline flaw (fix commit not yet pinned) | `dirtydecrypt` | 🟡 | **Ported from the public V12 PoC, not yet VM-verified.** Sibling of Copy Fail / Dirty Frag in the rxgk (AFS rxrpc encryption) subsystem. `fire()` sliding-window page-cache write, ~256 fires/byte; rewrites the first 120 bytes of `/usr/bin/su` with a setuid-shell ELF. `--active` probe fires the primitive at a `/tmp` sentinel. detect() is precondition-only — see MODULE.md. x86_64. |
| CVE-2026-46300 | Fragnesia — XFRM ESP-in-TCP `skb_try_coalesce` SHARED_FRAG loss | LPE (page-cache write into a setuid binary) | distro patches 2026-05-13; mainline fix followed (commit not yet pinned) | `fragnesia` | 🟡 | **Ported from the public V12 PoC, not yet VM-verified.** Latent bug exposed by the Dirty Frag fix (`f4c50a4034e6`). AF_ALG GCM keystream table + userns/netns + XFRM ESP-in-TCP splice trigger pair; rewrites the first 192 bytes of `/usr/bin/su`. Needs `CONFIG_INET_ESPINTCP` + unprivileged userns (the in-scope question the old `_stubs/fragnesia_TBD` raised — resolved: ships, reports PRECOND_FAIL when the userns gate is closed). PoC's ANSI TUI dropped in the port. x86_64. |
| CVE-2026-41651 | Pack2TheRoot — PackageKit `InstallFiles` TOCTOU | LPE (userspace D-Bus daemon → `.deb` postinst as root) | PackageKit 1.3.5 (commit `76cfb675`, 2026-04-22) | `pack2theroot` | 🟡 | **Ported from the public Vozec PoC, not yet VM-verified.** Two back-to-back `InstallFiles` D-Bus calls — first `SIMULATE` (polkit bypass + queues a GLib idle), then immediately `NONE` + malicious `.deb` (overwrites the cached flags before the idle fires). GLib priority ordering makes the overwrite deterministic, not a race. Disclosure by **Deutsche Telekom security**. Affects PackageKit 1.0.2 → 1.3.4 — default-enabled on Ubuntu Desktop, Debian, Fedora, Rocky/RHEL via Cockpit. `detect()` reads `VersionMajor/Minor/Micro` over D-Bus → high-confidence verdict (vs. precondition-only for dirtydecrypt/fragnesia). Debian-family only (PoC's built-in `.deb` builder). Needs `libglib2.0-dev` at build time; Makefile autodetects via `pkg-config gio-2.0` and falls through to a stub when absent. |
## Operations supported per module
@@ -114,6 +125,7 @@ Symbols: ✓ = supported, — = not applicable / no automated path.
| vmwgfx | ✓ | ✓ (primitive) | — (upgrade kernel) | ✓ (log unlink) | ✓ (auditd) |
| dirtydecrypt | ✓ (+ `--active`) | ✓ (ported) | — (upgrade kernel) | ✓ (evict page cache) | ✓ (auditd + sigma) |
| fragnesia | ✓ (+ `--active`) | ✓ (ported) | — (upgrade kernel) | ✓ (evict page cache) | ✓ (auditd + sigma) |
| pack2theroot | ✓ (PK version via D-Bus) | ✓ (ported) | — (upgrade PackageKit ≥ 1.3.5) | ✓ (rm /tmp + `dpkg -r`) | ✓ (auditd + sigma) |
## Pipeline for additions