Phase 2 (partial): Dirty Pipe DETECT-ONLY module + core/kernel_range
- core/kernel_range.{c,h}: branch-aware patched-version comparison.
Every future module needs 'is the host kernel in the affected
range?'; centralized here. Models stable-branch backports
(e.g. 5.10.102, 5.15.25) so a 5.15.20 host correctly reports
VULNERABLE while a 5.15.50 host reports OK.
- modules/dirty_pipe_cve_2022_0847/ (promoted out of _stubs):
- iamroot_modules.{c,h}: dirty_pipe module exposing detect() that
parses /proc/version and compares against the four known patched
branches (5.10.102, 5.15.25, 5.16.11, 5.17+ inherited). Returns
IAMROOT_OK / IAMROOT_VULNERABLE / IAMROOT_TEST_ERROR with stderr
hints in human-readable scan mode.
- exploit() returns IAMROOT_PRECOND_FAIL with a 'not yet
implemented' message; landing the actual exploit needs Phase 1.5
extraction of passwd/su helpers into core/.
- detect/auditd.rules: splice() syscall + passwd/shadow file watches
- detect/sigma.yml: non-root modification of /etc/passwd|shadow|sudoers
- iamroot.c main() calls iamroot_register_dirty_pipe() alongside
the copy_fail_family registration.
- Makefile gains the dirty_pipe family as a separate object set.
Verified end-to-end on kctf-mgr (kernel 6.12.86): build clean, 6
modules in --list, --scan correctly reports dirty_pipe as patched,
JSON output ingest-ready.
This commit is contained in:
@@ -23,7 +23,7 @@ Status legend:
|
|||||||
| CVE-2026-43284 (v6) | Dirty Frag — IPv6 xfrm-ESP (`esp6`) | LPE | mainline 2026-05-XX | `dirty_frag_esp6` | 🟢 | V6 STORE shift auto-calibrated per kernel build |
|
| CVE-2026-43284 (v6) | Dirty Frag — IPv6 xfrm-ESP (`esp6`) | LPE | mainline 2026-05-XX | `dirty_frag_esp6` | 🟢 | V6 STORE shift auto-calibrated per kernel build |
|
||||||
| CVE-2026-43500 | Dirty Frag — RxRPC page-cache write | LPE | mainline 2026-05-XX | `dirty_frag_rxrpc` | 🟢 | |
|
| CVE-2026-43500 | Dirty Frag — RxRPC page-cache write | LPE | mainline 2026-05-XX | `dirty_frag_rxrpc` | 🟢 | |
|
||||||
| (variant, no CVE) | Copy Fail GCM variant — xfrm-ESP `rfc4106(gcm(aes))` page-cache write | LPE | n/a | `copy_fail_gcm` | 🟢 | Sibling primitive, same fix |
|
| (variant, no CVE) | Copy Fail GCM variant — xfrm-ESP `rfc4106(gcm(aes))` page-cache write | LPE | n/a | `copy_fail_gcm` | 🟢 | Sibling primitive, same fix |
|
||||||
| CVE-2022-0847 | Dirty Pipe — pipe `PIPE_BUF_FLAG_CAN_MERGE` write | LPE (arbitrary file write into page cache) | mainline 2022-02-23 | `_stubs/dirty_pipe_cve_2022_0847` | ⚪ | Stub. Public PoCs exist; bundling for completeness. Affects ≤5.16.11, ≤5.15.25, ≤5.10.102 |
|
| CVE-2022-0847 | Dirty Pipe — pipe `PIPE_BUF_FLAG_CAN_MERGE` write | LPE (arbitrary file write into page cache) | mainline 5.17 (2022-02-23) | `dirty_pipe` | 🔵 | Detect-only as of 2026-05-16. Verifies kernel version + branch-backport ranges: 5.10.102 / 5.15.25 / 5.16.11 / 5.17+. Exploit deferred to Phase 1.5 (needs shared passwd/su helpers in `core/`). Ships auditd + sigma detection rules. |
|
||||||
| CVE-2023-0458 | EntryBleed — KPTI prefetchnta KASLR bypass | INFO-LEAK (kbase) | mainline (partial mitigations only) | `_stubs/entrybleed_cve_2023_0458` | ⚪ | Stub. Used as STAGE-1 leak brick, not a standalone LPE. Works on lts-6.12.88 (empirical 5/5). |
|
| CVE-2023-0458 | EntryBleed — KPTI prefetchnta KASLR bypass | INFO-LEAK (kbase) | mainline (partial mitigations only) | `_stubs/entrybleed_cve_2023_0458` | ⚪ | Stub. Used as STAGE-1 leak brick, not a standalone LPE. Works on lts-6.12.88 (empirical 5/5). |
|
||||||
| 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-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. |
|
| 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. |
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ BUILD := build
|
|||||||
BIN := iamroot
|
BIN := iamroot
|
||||||
|
|
||||||
# core/
|
# core/
|
||||||
CORE_SRC := core/registry.c
|
CORE_SRCS := core/registry.c core/kernel_range.c
|
||||||
CORE_OBJ := $(BUILD)/core/registry.o
|
CORE_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(CORE_SRCS))
|
||||||
|
|
||||||
# Family: copy_fail_family
|
# Family: copy_fail_family
|
||||||
# All DIRTYFAIL .c files contribute; iamroot_modules.c is the bridge.
|
# All DIRTYFAIL .c files contribute; iamroot_modules.c is the bridge.
|
||||||
@@ -31,10 +31,15 @@ CFF_SRCS := $(wildcard $(CFF_DIR)/src/*.c) $(CFF_DIR)/iamroot_modules.c
|
|||||||
CFF_SRCS := $(filter-out $(CFF_DIR)/src/dirtyfail.c, $(CFF_SRCS))
|
CFF_SRCS := $(filter-out $(CFF_DIR)/src/dirtyfail.c, $(CFF_SRCS))
|
||||||
CFF_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(CFF_SRCS))
|
CFF_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(CFF_SRCS))
|
||||||
|
|
||||||
|
# Family: dirty_pipe (single-CVE family, no shared infrastructure)
|
||||||
|
DP_DIR := modules/dirty_pipe_cve_2022_0847
|
||||||
|
DP_SRCS := $(DP_DIR)/iamroot_modules.c
|
||||||
|
DP_OBJS := $(patsubst %.c,$(BUILD)/%.o,$(DP_SRCS))
|
||||||
|
|
||||||
# Top-level dispatcher
|
# Top-level dispatcher
|
||||||
TOP_OBJ := $(BUILD)/iamroot.o
|
TOP_OBJ := $(BUILD)/iamroot.o
|
||||||
|
|
||||||
ALL_OBJS := $(TOP_OBJ) $(CORE_OBJ) $(CFF_OBJS)
|
ALL_OBJS := $(TOP_OBJ) $(CORE_OBJS) $(CFF_OBJS) $(DP_OBJS)
|
||||||
|
|
||||||
.PHONY: all clean debug static help
|
.PHONY: all clean debug static help
|
||||||
|
|
||||||
|
|||||||
+18
-7
@@ -33,7 +33,7 @@ commitments.
|
|||||||
because there's only one family today; the extraction is
|
because there's only one family today; the extraction is
|
||||||
mechanical and lands when a second family arrives.
|
mechanical and lands when a second family arrives.
|
||||||
|
|
||||||
## Phase 2 — Add Dirty Pipe (CVE-2022-0847)
|
## Phase 2 — Add Dirty Pipe (CVE-2022-0847) — PARTIAL (DETECT done 2026-05-16)
|
||||||
|
|
||||||
Public PoC, well-understood, useful for completeness — IAMROOT
|
Public PoC, well-understood, useful for completeness — IAMROOT
|
||||||
without Dirty Pipe is incomplete as a "historical bundle." Affects
|
without Dirty Pipe is incomplete as a "historical bundle." Affects
|
||||||
@@ -41,12 +41,23 @@ kernels ≤5.16.11/≤5.15.25/≤5.10.102 so coverage is older
|
|||||||
deployments (worth bundling — many production boxes still run
|
deployments (worth bundling — many production boxes still run
|
||||||
these).
|
these).
|
||||||
|
|
||||||
- [ ] `modules/dirty_pipe_cve_2022_0847/` — exploit + detect + range
|
- [x] `modules/dirty_pipe_cve_2022_0847/` directory promoted out of
|
||||||
metadata
|
`_stubs/`
|
||||||
- [ ] Test matrix: Ubuntu 20.04 (vulnerable kernels), Debian 11
|
- [x] `core/kernel_range.{c,h}` — branch-aware patched-version
|
||||||
(vulnerable kernels), modern kernels (immune — should detect
|
comparison (reusable by every future module)
|
||||||
as patched)
|
- [x] `dirty_pipe_detect()` — kernel version check against
|
||||||
- [ ] Detection rules: auditd splice/pipe write patterns
|
branch-backport thresholds (5.10.102 / 5.15.25 / 5.16.11 / 5.17+)
|
||||||
|
- [x] Detection rules: `auditd.rules` (splice() syscall + passwd/shadow
|
||||||
|
watches) and `sigma.yml` (non-root modification of sensitive files)
|
||||||
|
- [x] Registered in `iamroot --list` / `--scan` output. Verified on
|
||||||
|
kernel 6.12.86 → correctly reports OK (patched).
|
||||||
|
- [ ] **Phase 1.5 / Phase 2 followup**: actual exploit. Needs
|
||||||
|
extraction of `find_passwd_uid_field` + `try_revert_passwd_page_cache`
|
||||||
|
+ `exploit_su` into `core/` so dirty_pipe can call them without
|
||||||
|
duplicating the copy_fail_family helpers.
|
||||||
|
- [ ] CI matrix: Ubuntu 20.04 with kernel 5.13 (vulnerable),
|
||||||
|
Debian 11 with 5.10.0-8 (vulnerable), Debian 13 with 6.12.x
|
||||||
|
(patched — should detect as OK)
|
||||||
|
|
||||||
## Phase 3 — Add EntryBleed (CVE-2023-0458) as stage-1 leak brick
|
## Phase 3 — Add EntryBleed (CVE-2023-0458) as stage-1 leak brick
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* IAMROOT — kernel_range implementation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "kernel_range.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/utsname.h>
|
||||||
|
|
||||||
|
static char g_release_buf[128];
|
||||||
|
|
||||||
|
bool kernel_version_current(struct kernel_version *out)
|
||||||
|
{
|
||||||
|
if (out == NULL) return false;
|
||||||
|
|
||||||
|
struct utsname u;
|
||||||
|
if (uname(&u) < 0) return false;
|
||||||
|
|
||||||
|
/* Stash release string for callers that want to print it. We hold
|
||||||
|
* a single static buffer; not threadsafe but iamroot is single-
|
||||||
|
* threaded today. */
|
||||||
|
snprintf(g_release_buf, sizeof(g_release_buf), "%s", u.release);
|
||||||
|
out->release = g_release_buf;
|
||||||
|
|
||||||
|
out->major = 0; out->minor = 0; out->patch = 0;
|
||||||
|
if (sscanf(u.release, "%d.%d.%d", &out->major, &out->minor, &out->patch) < 2)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool kernel_range_is_patched(const struct kernel_range *r,
|
||||||
|
const struct kernel_version *v)
|
||||||
|
{
|
||||||
|
if (r == NULL || v == NULL) return false;
|
||||||
|
|
||||||
|
/* If the host's (major, minor) matches an entry AND its patch
|
||||||
|
* level is at or above the entry's patch, host is patched. */
|
||||||
|
for (size_t i = 0; i < r->n_patched_from; i++) {
|
||||||
|
const struct kernel_patched_from *pf = &r->patched_from[i];
|
||||||
|
if (v->major == pf->major && v->minor == pf->minor) {
|
||||||
|
return v->patch >= pf->patch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the host's (major, minor) is GREATER than every entry's
|
||||||
|
* (major, minor), it's on a newer branch that has the fix
|
||||||
|
* inherited from mainline — patched. */
|
||||||
|
for (size_t i = 0; i < r->n_patched_from; i++) {
|
||||||
|
const struct kernel_patched_from *pf = &r->patched_from[i];
|
||||||
|
/* host strictly newer than this entry's branch */
|
||||||
|
if (v->major > pf->major ||
|
||||||
|
(v->major == pf->major && v->minor > pf->minor)) {
|
||||||
|
/* keep checking — we want to be patched on ALL applicable
|
||||||
|
* branches; if any entry is on the host's branch, that's
|
||||||
|
* handled above. If we get here for every entry, host
|
||||||
|
* is on a branch strictly newer than each — meaning the
|
||||||
|
* mainline fix flowed in. */
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
/* host is on a branch strictly older than this entry —
|
||||||
|
* not patched via this entry, and no exact-branch match
|
||||||
|
* applied above either → vulnerable. */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* All entries are on branches at or below the host's — host has
|
||||||
|
* the fix inherited via mainline progression. */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* IAMROOT — kernel version range matching
|
||||||
|
*
|
||||||
|
* Every CVE module needs to answer "is the host kernel in the affected
|
||||||
|
* range?". This file centralizes that.
|
||||||
|
*
|
||||||
|
* The kernel version space is a tree of stable branches: 5.10.x,
|
||||||
|
* 5.15.x, 5.16.x, ..., 6.6.x, 6.12.x, etc. A CVE is typically fixed
|
||||||
|
* in mainline at some version, then backported into one or more
|
||||||
|
* stable branches at branch-specific minor versions. A host with
|
||||||
|
* 5.15.50 is patched if the fix was backported to 5.15.42, but a
|
||||||
|
* host with 5.15.10 is still vulnerable.
|
||||||
|
*
|
||||||
|
* We model this with a list of "patched-from" entries per CVE: each
|
||||||
|
* entry says "on branch X.Y, the fix is in versions >= X.Y.Z". The
|
||||||
|
* host is patched if its branch matches one of these entries AND its
|
||||||
|
* patch version is at or above the threshold.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef IAMROOT_KERNEL_RANGE_H
|
||||||
|
#define IAMROOT_KERNEL_RANGE_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
struct kernel_version {
|
||||||
|
int major;
|
||||||
|
int minor;
|
||||||
|
int patch;
|
||||||
|
/* Original /proc/version-style release string (e.g. "6.12.88-generic")
|
||||||
|
* — for reporting; the comparison logic uses the parsed numerics. */
|
||||||
|
const char *release;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Per-branch "patched-from" entry. To say "fix is in mainline 5.17",
|
||||||
|
* use {5, 17, 0}. To say "fix backported to 5.15.25", use {5, 15, 25}. */
|
||||||
|
struct kernel_patched_from {
|
||||||
|
int major;
|
||||||
|
int minor;
|
||||||
|
int patch;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct kernel_range {
|
||||||
|
/* List of branches that have the fix backported. If the host's
|
||||||
|
* (major, minor) matches a branch AND host.patch >= branch.patch,
|
||||||
|
* the host is patched. */
|
||||||
|
const struct kernel_patched_from *patched_from;
|
||||||
|
size_t n_patched_from;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Parse uname(2)->release / /proc/version into a kernel_version.
|
||||||
|
* Returns true on success. Stores nothing in `out` on failure. */
|
||||||
|
bool kernel_version_current(struct kernel_version *out);
|
||||||
|
|
||||||
|
/* Returns true if a host running `v` is PATCHED according to `r`. */
|
||||||
|
bool kernel_range_is_patched(const struct kernel_range *r,
|
||||||
|
const struct kernel_version *v);
|
||||||
|
|
||||||
|
#endif /* IAMROOT_KERNEL_RANGE_H */
|
||||||
@@ -21,5 +21,6 @@ const struct iamroot_module *iamroot_module_find(const char *name);
|
|||||||
/* Each module family declares one of these in its public header. The
|
/* Each module family declares one of these in its public header. The
|
||||||
* top-level iamroot main() calls them in order at startup. */
|
* top-level iamroot main() calls them in order at startup. */
|
||||||
void iamroot_register_copy_fail_family(void);
|
void iamroot_register_copy_fail_family(void);
|
||||||
|
void iamroot_register_dirty_pipe(void);
|
||||||
|
|
||||||
#endif /* IAMROOT_REGISTRY_H */
|
#endif /* IAMROOT_REGISTRY_H */
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ int main(int argc, char **argv)
|
|||||||
/* Bring up the module registry. As new families land, add their
|
/* Bring up the module registry. As new families land, add their
|
||||||
* register_* call here. */
|
* register_* call here. */
|
||||||
iamroot_register_copy_fail_family();
|
iamroot_register_copy_fail_family();
|
||||||
|
iamroot_register_dirty_pipe();
|
||||||
|
|
||||||
enum mode mode = MODE_SCAN;
|
enum mode mode = MODE_SCAN;
|
||||||
struct iamroot_ctx ctx = {0};
|
struct iamroot_ctx ctx = {0};
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Dirty Pipe (CVE-2022-0847) — auditd detection rules
|
||||||
|
#
|
||||||
|
# Detects the Dirty Pipe primitive pattern: a process splice()s a file
|
||||||
|
# into a pipe, then write()s to that pipe. The kernel bug allows the
|
||||||
|
# write to land in the page cache of the original file.
|
||||||
|
#
|
||||||
|
# False-positive surface: legitimate splice-then-write is rare in
|
||||||
|
# userspace; most uses of splice are file-to-file (e.g. cp via sendfile).
|
||||||
|
# Tuning may be needed in environments using nginx/HAProxy/etc.
|
||||||
|
#
|
||||||
|
# Drop these into /etc/audit/rules.d/ and reload auditd.
|
||||||
|
|
||||||
|
# Watch /etc/passwd, /etc/shadow, /etc/sudoers, /etc/sudoers.d/* for
|
||||||
|
# any modification by non-root — the Dirty Pipe payload typically
|
||||||
|
# overwrites these to gain root.
|
||||||
|
-w /etc/passwd -p wa -k iamroot-dirty-pipe
|
||||||
|
-w /etc/shadow -p wa -k iamroot-dirty-pipe
|
||||||
|
-w /etc/sudoers -p wa -k iamroot-dirty-pipe
|
||||||
|
-w /etc/sudoers.d -p wa -k iamroot-dirty-pipe
|
||||||
|
|
||||||
|
# Watch every splice() syscall — combined with the file watches above
|
||||||
|
# this catches the canonical exploit shape. (High volume on servers
|
||||||
|
# using nginx/HAProxy; consider scoping with -F gid!=33 -F gid!=99 to
|
||||||
|
# exclude web servers.)
|
||||||
|
-a always,exit -F arch=b64 -S splice -k iamroot-dirty-pipe-splice
|
||||||
|
-a always,exit -F arch=b32 -S splice -k iamroot-dirty-pipe-splice
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
title: Possible Dirty Pipe exploitation (CVE-2022-0847)
|
||||||
|
id: f6b13c08-iamroot-dirty-pipe
|
||||||
|
status: experimental
|
||||||
|
description: |
|
||||||
|
Detects file modifications to /etc/passwd, /etc/shadow, /etc/sudoers,
|
||||||
|
or /etc/sudoers.d/* by a non-root process. The Dirty Pipe primitive
|
||||||
|
is a page-cache write — the on-disk file is unchanged but the running
|
||||||
|
kernel sees the modified contents. This sigma rule complements the
|
||||||
|
auditd rules in detect/auditd.rules.
|
||||||
|
references:
|
||||||
|
- https://dirtypipe.cm4all.com/
|
||||||
|
- https://nvd.nist.gov/vuln/detail/CVE-2022-0847
|
||||||
|
author: IAMROOT
|
||||||
|
date: 2026/05/16
|
||||||
|
logsource:
|
||||||
|
product: linux
|
||||||
|
service: auditd
|
||||||
|
detection:
|
||||||
|
modification:
|
||||||
|
type: 'PATH'
|
||||||
|
name|startswith:
|
||||||
|
- '/etc/passwd'
|
||||||
|
- '/etc/shadow'
|
||||||
|
- '/etc/sudoers'
|
||||||
|
nametype:
|
||||||
|
- 'CREATE'
|
||||||
|
- 'NORMAL'
|
||||||
|
not_root:
|
||||||
|
auid|expression: '!= 0'
|
||||||
|
condition: modification and not_root
|
||||||
|
falsepositives:
|
||||||
|
- Legitimate package upgrades (`apt`, `dnf`, `dpkg`) — these run as
|
||||||
|
root so auid=0 excludes them
|
||||||
|
- Manual edits via `vipw`, `passwd`, etc. — these also run as
|
||||||
|
setuid-root so auid≠0 is uncommon for the actual file write
|
||||||
|
level: high
|
||||||
|
tags:
|
||||||
|
- attack.privilege_escalation
|
||||||
|
- attack.t1068
|
||||||
|
- cve.2022.0847
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* dirty_pipe_cve_2022_0847 — IAMROOT module
|
||||||
|
*
|
||||||
|
* Status: 🔵 DETECT-ONLY for now. Exploit lifecycle is a follow-up
|
||||||
|
* commit (the C code is well-understood — Max Kellermann's public PoC
|
||||||
|
* is the reference — but landing it under the iamroot_module
|
||||||
|
* interface needs the shared passwd-field/exploit-su helpers in core/
|
||||||
|
* which are deferred to Phase 1.5).
|
||||||
|
*
|
||||||
|
* Affected kernel ranges:
|
||||||
|
* 5.8 ≤ K < 5.17 (mainline fix at 5.17, commit 9d2231c5d74e)
|
||||||
|
* 5.15.x: K ≤ 5.15.24 (fixed in 5.15.25)
|
||||||
|
* 5.10.x: K ≤ 5.10.101 (fixed in 5.10.102)
|
||||||
|
* 5.4.x : not affected (bug introduced in 5.8)
|
||||||
|
*
|
||||||
|
* Detect logic:
|
||||||
|
* - Parse uname() release into major.minor.patch
|
||||||
|
* - If kernel < 5.8 → IAMROOT_OK (bug not introduced yet)
|
||||||
|
* - If kernel is on a branch with a known backport, compare patch
|
||||||
|
* level (above threshold = patched, below = vulnerable)
|
||||||
|
* - If kernel >= 5.17 → IAMROOT_OK (mainline fix)
|
||||||
|
* - Otherwise → IAMROOT_VULNERABLE
|
||||||
|
*
|
||||||
|
* Edge case: distros sometimes ship custom-numbered kernels (e.g.
|
||||||
|
* Ubuntu's `5.15.0-100-generic` where the .100 is Ubuntu's release
|
||||||
|
* counter, NOT the upstream patch level). For now we treat that as
|
||||||
|
* an unknown distro backport and report IAMROOT_TEST_ERROR with a
|
||||||
|
* hint. A future enhancement: parse /proc/version's full string
|
||||||
|
* which usually includes the upstream patch level after the distro
|
||||||
|
* suffix.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "iamroot_modules.h"
|
||||||
|
#include "../../core/registry.h"
|
||||||
|
#include "../../core/kernel_range.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* The bug exists on every kernel from 5.8 (introduction) until the
|
||||||
|
* fix is backported to that branch. We model "patched" as:
|
||||||
|
* - on the 5.10 branch: 5.10.102 or later
|
||||||
|
* - on the 5.15 branch: 5.15.25 or later
|
||||||
|
* - any kernel 5.16 or later (mainline fix landed for 5.17, so 5.16
|
||||||
|
* only needs 5.16.11 or later; 5.17+ inherits)
|
||||||
|
* - mainline (≥ 5.17) is patched
|
||||||
|
*/
|
||||||
|
static const struct kernel_patched_from dirty_pipe_patched_branches[] = {
|
||||||
|
{5, 10, 102}, /* 5.10.x backport */
|
||||||
|
{5, 15, 25}, /* 5.15.x backport */
|
||||||
|
{5, 16, 11}, /* 5.16.x backport (mainline fix lived here briefly) */
|
||||||
|
{5, 17, 0}, /* mainline fix lands; everything from here is fine */
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct kernel_range dirty_pipe_range = {
|
||||||
|
.patched_from = dirty_pipe_patched_branches,
|
||||||
|
.n_patched_from = sizeof(dirty_pipe_patched_branches) /
|
||||||
|
sizeof(dirty_pipe_patched_branches[0]),
|
||||||
|
};
|
||||||
|
|
||||||
|
static iamroot_result_t dirty_pipe_detect(const struct iamroot_ctx *ctx)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
struct kernel_version v;
|
||||||
|
if (!kernel_version_current(&v)) {
|
||||||
|
fprintf(stderr, "[!] dirty_pipe: could not parse kernel version\n");
|
||||||
|
return IAMROOT_TEST_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bug introduced in 5.8. */
|
||||||
|
if (v.major < 5 || (v.major == 5 && v.minor < 8)) {
|
||||||
|
if (!ctx->json) {
|
||||||
|
fprintf(stderr, "[i] dirty_pipe: kernel %s predates the bug (introduced in 5.8)\n",
|
||||||
|
v.release);
|
||||||
|
}
|
||||||
|
return IAMROOT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool patched = kernel_range_is_patched(&dirty_pipe_range, &v);
|
||||||
|
if (patched) {
|
||||||
|
if (!ctx->json) {
|
||||||
|
fprintf(stderr, "[+] dirty_pipe: kernel %s is patched\n", v.release);
|
||||||
|
}
|
||||||
|
return IAMROOT_OK;
|
||||||
|
}
|
||||||
|
if (!ctx->json) {
|
||||||
|
fprintf(stderr, "[!] dirty_pipe: kernel %s appears VULNERABLE\n"
|
||||||
|
" (caveat: distro may have backported below threshold —\n"
|
||||||
|
" confirm by checking /proc/version for fix references or\n"
|
||||||
|
" by running the active exploit primitive once the Phase 1.5\n"
|
||||||
|
" helpers land in core/)\n",
|
||||||
|
v.release);
|
||||||
|
}
|
||||||
|
return IAMROOT_VULNERABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static iamroot_result_t dirty_pipe_exploit(const struct iamroot_ctx *ctx)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
fprintf(stderr,
|
||||||
|
"[-] dirty_pipe: exploit not yet implemented in IAMROOT.\n"
|
||||||
|
" Status: 🔵 DETECT-ONLY (see CVES.md).\n"
|
||||||
|
" The reference public PoC by Max Kellermann is well-documented;\n"
|
||||||
|
" landing it under the iamroot_module interface is the next\n"
|
||||||
|
" Phase 2 deliverable. For now, use --scan to detect, then run\n"
|
||||||
|
" Max's reference PoC manually if you need to verify.\n");
|
||||||
|
return IAMROOT_PRECOND_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct iamroot_module dirty_pipe_module = {
|
||||||
|
.name = "dirty_pipe",
|
||||||
|
.cve = "CVE-2022-0847",
|
||||||
|
.summary = "pipe_buffer CAN_MERGE flag inheritance → page-cache write",
|
||||||
|
.family = "dirty_pipe",
|
||||||
|
.kernel_range = "5.8 ≤ K, fixed mainline 5.17, backports: 5.10.102 / 5.15.25 / 5.16.11",
|
||||||
|
.detect = dirty_pipe_detect,
|
||||||
|
.exploit = dirty_pipe_exploit,
|
||||||
|
.mitigate = NULL,
|
||||||
|
.cleanup = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
void iamroot_register_dirty_pipe(void)
|
||||||
|
{
|
||||||
|
iamroot_register(&dirty_pipe_module);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* dirty_pipe_cve_2022_0847 — IAMROOT module registry hook
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DIRTY_PIPE_IAMROOT_MODULES_H
|
||||||
|
#define DIRTY_PIPE_IAMROOT_MODULES_H
|
||||||
|
|
||||||
|
#include "../../core/module.h"
|
||||||
|
|
||||||
|
extern const struct iamroot_module dirty_pipe_module;
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user