diff --git a/CVES.md b/CVES.md index f456ff9..b5eb5c4 100644 --- a/CVES.md +++ b/CVES.md @@ -28,6 +28,20 @@ Status legend: | CVE-2026-31402 | NFS replay-cache heap overflow | LPE (NFS server) | mainline 2026-04-03 | — | ⚪ | Candidate. Different audience (NFS servers) — TBD whether in-scope. | | CVE-TBD | Fragnesia (ESP shared-frag in-place encrypt) | LPE (page-cache write) | mainline TBD | `_stubs/fragnesia_TBD` | ⚪ | Stub. Per `findings/audit_leak_write_modprobe_backups_2026-05-16.md`, requires CAP_NET_ADMIN in userns netns — may or may not be in-scope depending on target environment. | +## Operations supported per module + +Symbols: ✓ = supported, — = not applicable / no automated path. + +| Module | --scan (detect) | --exploit | --mitigate | --cleanup | --detect-rules | +|---|---|---|---|---|---| +| copy_fail | ✓ | ✓ | ✓ (blacklist algif_aead + AA sysctl) | ✓ (revert mit or evict page cache) | ✓ (auditd + sigma) | +| copy_fail_gcm | ✓ | ✓ | ✓ (same family-wide) | ✓ | ✓ | +| dirty_frag_esp | ✓ | ✓ | ✓ (same family-wide) | ✓ | ✓ | +| dirty_frag_esp6 | ✓ | ✓ | ✓ (same family-wide) | ✓ | ✓ | +| dirty_frag_rxrpc | ✓ | ✓ | ✓ (same family-wide) | ✓ | ✓ | +| dirty_pipe | ✓ | ✓ | — (only fix is upgrade kernel) | ✓ (evict page cache) | ✓ (auditd + sigma) | +| entrybleed | ✓ | ✓ (leak kbase) | — (no canonical patch) | — | ✓ (sigma informational) | + ## Pipeline for additions 1. Bug must be **patched in upstream mainline** (we don't bundle diff --git a/ROADMAP.md b/ROADMAP.md index da3d30a..1aeaef2 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -115,12 +115,23 @@ primitive** that other modules can chain. Bundled because: embedded C string. Self-contained binary, no data-dir install needed. - [ ] Sample SOC playbook in `docs/DETECTION_PLAYBOOK.md` — followup -## Phase 6 — Mitigation mode +## Phase 6 — Mitigation mode (PARTIAL — copy_fail_family bridged 2026-05-16) -- [ ] `iamroot --mitigate` walks the host's vulnerabilities, applies - temporary sysctl / module-blacklist / LSM workarounds -- [ ] Per-CVE rollback procedure if the mitigation breaks something -- [ ] Idempotent: running twice is safe +- [x] copy_fail_family: `iamroot --mitigate copy_fail` (or any family + member) blacklists algif_aead + esp4 + esp6 + rxrpc, sets + `kernel.apparmor_restrict_unprivileged_userns=1`, drops page + cache. Bridged from existing DIRTYFAIL `mitigate_apply()`. +- [x] copy_fail_family: `iamroot --cleanup ` routes by visible + state: if `/etc/modprobe.d/dirtyfail-mitigations.conf` exists → + `mitigate_revert()`; else evict /etc/passwd page cache. Heuristic + sufficient for common usage patterns. +- [x] dirty_pipe: `iamroot --cleanup dirty_pipe` evicts /etc/passwd + (already landed in Phase 2 complete). +- [ ] dirty_pipe `--mitigate`: only real fix is "upgrade your kernel"; + no automated mitigation possible. Document and skip. +- [ ] entrybleed `--mitigate`: same — no canonical patch; document. +- [ ] Idempotent re-run safety: copy_fail_family's apply is already + idempotent (overwrites conf files). Re-verify per module. ## Phase 7+ — More modules diff --git a/modules/copy_fail_family/iamroot_modules.c b/modules/copy_fail_family/iamroot_modules.c index 9abf9ab..3313ae7 100644 --- a/modules/copy_fail_family/iamroot_modules.c +++ b/modules/copy_fail_family/iamroot_modules.c @@ -24,6 +24,9 @@ #include "src/dirtyfrag_esp.h" #include "src/dirtyfrag_esp6.h" #include "src/dirtyfrag_rxrpc.h" +#include "src/mitigate.h" + +#include static void apply_ctx(const struct iamroot_ctx *ctx) { @@ -34,6 +37,47 @@ static void apply_ctx(const struct iamroot_ctx *ctx) * it's a debug knob; default stays off. */ } +/* ----- Family-wide --mitigate / --cleanup ----- + * + * The family-wide mitigation (blacklist algif_aead + esp4 + esp6 + rxrpc, + * set apparmor_restrict_unprivileged_userns=1, drop_caches) is the same + * for every member of this family. All 5 modules' .mitigate fields + * therefore point at the same wrapper. + * + * For .cleanup we route based on visible state: + * - If the mitigation conf file is present, the user most recently + * ran --mitigate → revert that. + * - Otherwise the user ran --exploit → evict /etc/passwd from page + * cache. + * This is a heuristic, not a state machine. Sufficient for the common + * usage patterns. */ + +#define CFF_MITIGATE_CONF "/etc/modprobe.d/dirtyfail-mitigations.conf" + +static iamroot_result_t copy_fail_family_mitigate(const struct iamroot_ctx *ctx) +{ + apply_ctx(ctx); + return (iamroot_result_t)mitigate_apply(); +} + +static iamroot_result_t copy_fail_family_cleanup(const struct iamroot_ctx *ctx) +{ + apply_ctx(ctx); + struct stat st; + if (stat(CFF_MITIGATE_CONF, &st) == 0) { + if (!ctx->json) { + fprintf(stderr, "[*] copy_fail_family: detected mitigation conf " + "(%s); reverting mitigation\n", CFF_MITIGATE_CONF); + } + return (iamroot_result_t)mitigate_revert(); + } + if (!ctx->json) { + fprintf(stderr, "[*] copy_fail_family: no mitigation conf; " + "evicting /etc/passwd from page cache\n"); + } + return try_revert_passwd_page_cache() ? IAMROOT_OK : IAMROOT_TEST_ERROR; +} + /* ----- copy_fail (CVE-2026-31431) ----- */ static iamroot_result_t copy_fail_detect_wrap(const struct iamroot_ctx *ctx) @@ -91,8 +135,8 @@ const struct iamroot_module copy_fail_module = { .kernel_range = "≤ 6.12.84, fixed mainline 2026-04-22", .detect = copy_fail_detect_wrap, .exploit = copy_fail_exploit_wrap, - .mitigate = NULL, - .cleanup = NULL, + .mitigate = copy_fail_family_mitigate, + .cleanup = copy_fail_family_cleanup, .detect_auditd = copy_fail_family_auditd, .detect_sigma = copy_fail_family_sigma, .detect_yara = NULL, @@ -121,8 +165,8 @@ const struct iamroot_module copy_fail_gcm_module = { .kernel_range = "same as copy_fail; rfc4106(gcm(aes)) not in modprobe blacklist", .detect = copy_fail_gcm_detect_wrap, .exploit = copy_fail_gcm_exploit_wrap, - .mitigate = NULL, - .cleanup = NULL, + .mitigate = copy_fail_family_mitigate, + .cleanup = copy_fail_family_cleanup, .detect_auditd = copy_fail_family_auditd, .detect_sigma = copy_fail_family_sigma, .detect_yara = NULL, @@ -151,8 +195,8 @@ const struct iamroot_module dirty_frag_esp_module = { .kernel_range = "same family as copy_fail; xfrm-ESP path", .detect = dirty_frag_esp_detect_wrap, .exploit = dirty_frag_esp_exploit_wrap, - .mitigate = NULL, - .cleanup = NULL, + .mitigate = copy_fail_family_mitigate, + .cleanup = copy_fail_family_cleanup, .detect_auditd = copy_fail_family_auditd, .detect_sigma = copy_fail_family_sigma, .detect_yara = NULL, @@ -181,8 +225,8 @@ const struct iamroot_module dirty_frag_esp6_module = { .kernel_range = "same family as copy_fail; xfrm-ESP6 path; V6 STORE shift auto-calibrated", .detect = dirty_frag_esp6_detect_wrap, .exploit = dirty_frag_esp6_exploit_wrap, - .mitigate = NULL, - .cleanup = NULL, + .mitigate = copy_fail_family_mitigate, + .cleanup = copy_fail_family_cleanup, .detect_auditd = copy_fail_family_auditd, .detect_sigma = copy_fail_family_sigma, .detect_yara = NULL, @@ -211,8 +255,8 @@ const struct iamroot_module dirty_frag_rxrpc_module = { .kernel_range = "kernels exposing AF_RXRPC + rxkad with fcrypt fallback", .detect = dirty_frag_rxrpc_detect_wrap, .exploit = dirty_frag_rxrpc_exploit_wrap, - .mitigate = NULL, - .cleanup = NULL, + .mitigate = copy_fail_family_mitigate, + .cleanup = copy_fail_family_cleanup, .detect_auditd = copy_fail_family_auditd, .detect_sigma = copy_fail_family_sigma, .detect_yara = NULL,