Files
SKELETONKEY/modules/copy_fail_family/mitigate.c
T

183 lines
6.8 KiB
C

/*
* DIRTYFAIL — mitigate.c — defensive deployment
*
* See mitigate.h for the design.
*/
#include "mitigate.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <stdlib.h>
#define MODPROBE_CONF "/etc/modprobe.d/dirtyfail-mitigations.conf"
#define SYSCTL_CONF "/etc/sysctl.d/99-dirtyfail-mitigations.conf"
/* Modules to blacklist. Each is the kernel module name + reason. */
static const struct {
const char *name;
const char *reason;
} BLACKLIST[] = {
{"algif_aead", "Copy Fail (CVE-2026-31431) — authencesn page-cache STORE primitive"},
{"esp4", "Dirty Frag (CVE-2026-43284) — xfrm-ESP IPv4 path"},
{"esp6", "Dirty Frag (CVE-2026-43284) — xfrm-ESP IPv6 path"},
{"rxrpc", "Dirty Frag (CVE-2026-43500) — RxRPC pcbc(fcrypt) path"},
{NULL, NULL},
};
static bool write_file(const char *path, const char *content)
{
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) return false;
size_t n = strlen(content);
ssize_t got = write(fd, content, n);
close(fd);
return got == (ssize_t)n;
}
static bool require_root(void)
{
if (geteuid() != 0) {
log_bad("mitigate requires root — re-run as `sudo dirtyfail --mitigate`");
return false;
}
return true;
}
static int rmmod_if_loaded(const char *name)
{
/* Try via /sbin/rmmod (system shell). Returns 0 if module wasn't
* loaded or unload succeeded; 1 if unload failed. */
char cmd[256];
snprintf(cmd, sizeof(cmd),
"if lsmod | grep -q '^%s '; then "
" rmmod %s 2>/dev/null && echo unloaded || echo \"unload failed (in use?)\"; "
"else "
" echo \"not loaded\"; "
"fi", name, name);
return system(cmd) == 0 ? 0 : 1;
}
df_result_t mitigate_apply(void)
{
log_step("DIRTYFAIL — defensive mitigation deployment");
if (!require_root()) return DF_TEST_ERROR;
log_warn("about to apply system-wide mitigations:");
log_warn(" 1. blacklist algif_aead, esp4, esp6, rxrpc via modprobe");
log_warn(" 2. unload those modules if loaded");
log_warn(" 3. set kernel.apparmor_restrict_unprivileged_userns=1 (where AA loaded)");
log_warn(" 4. drop page cache");
fputc('\n', stderr);
log_warn("SIDE EFFECTS:");
log_warn(" - blacklisting esp4/esp6 BREAKS IPsec / strongSwan / libreswan VPNs");
log_warn(" - blacklisting rxrpc BREAKS AFS distributed file system clients");
log_warn(" - blacklisting algif_aead BREAKS userspace AEAD via AF_ALG (rare)");
fputc('\n', stderr);
log_warn("undo with `dirtyfail --cleanup-mitigate` (removes config files, leaves modules unloaded)");
if (!typed_confirm("DIRTYFAIL")) {
log_bad("confirmation declined — aborting");
return DF_OK;
}
/* 1. Write modprobe blacklist */
char buf[2048];
char *p = buf;
p += snprintf(p, sizeof(buf) - (p - buf),
"# DIRTYFAIL mitigations — blacklist modules that expose the\n"
"# Copy Fail (CVE-2026-31431) and Dirty Frag (CVE-2026-43284,\n"
"# CVE-2026-43500) page-cache write primitives.\n"
"#\n"
"# Generated by `dirtyfail --mitigate`. Remove with\n"
"# `dirtyfail --cleanup-mitigate` or by deleting this file.\n"
"\n");
for (int i = 0; BLACKLIST[i].name; i++) {
p += snprintf(p, sizeof(buf) - (p - buf),
"# %s\n"
"install %s /bin/false\n",
BLACKLIST[i].reason, BLACKLIST[i].name);
}
if (!write_file(MODPROBE_CONF, buf)) {
log_bad("failed to write %s: %s", MODPROBE_CONF, strerror(errno));
return DF_TEST_ERROR;
}
log_ok("wrote %s", MODPROBE_CONF);
/* 2. Unload currently loaded modules */
log_step("unloading currently-loaded modules:");
for (int i = 0; BLACKLIST[i].name; i++) {
printf(" %s: ", BLACKLIST[i].name);
fflush(stdout);
rmmod_if_loaded(BLACKLIST[i].name);
}
/* 3. Set AppArmor sysctl (only if AA is loaded) */
int sysctl_fd = open("/proc/sys/kernel/apparmor_restrict_unprivileged_userns", O_WRONLY);
if (sysctl_fd >= 0) {
if (write(sysctl_fd, "1\n", 2) == 2)
log_ok("set apparmor_restrict_unprivileged_userns=1 (runtime)");
else
log_warn("could not set apparmor_restrict_unprivileged_userns: %s", strerror(errno));
close(sysctl_fd);
/* Persist via sysctl.d */
const char *sysctl_content =
"# DIRTYFAIL mitigations — block unprivileged userns capability acquisition.\n"
"# This prevents the xfrm-ESP / RxRPC / GCM exploit infrastructure from\n"
"# obtaining CAP_NET_ADMIN inside a fresh user namespace.\n"
"kernel.apparmor_restrict_unprivileged_userns = 1\n";
if (write_file(SYSCTL_CONF, sysctl_content))
log_ok("wrote %s (persists across reboot)", SYSCTL_CONF);
else
log_warn("could not write %s: %s", SYSCTL_CONF, strerror(errno));
} else {
log_hint("AppArmor sysctl not present (kernel without AA, or AA not loaded) — skipping");
}
/* 4. Drop page cache */
int dc = open("/proc/sys/vm/drop_caches", O_WRONLY);
if (dc >= 0) {
ssize_t n = write(dc, "3\n", 2);
close(dc);
if (n == 2) log_ok("dropped page cache");
}
fputc('\n', stdout);
log_ok("=== mitigation summary ===");
log_ok(" modprobe blacklist: %s", MODPROBE_CONF);
log_ok(" sysctl persistence: %s", SYSCTL_CONF);
log_ok(" modules unloaded: algif_aead, esp4, esp6, rxrpc (where loaded)");
fputc('\n', stdout);
log_hint("Re-verify with `dirtyfail --scan` — should now report most modes as");
log_hint("preconditions missing or mitigated.");
fputc('\n', stdout);
log_hint("Ultimate fix: install kernel update with f4c50a4034e6 backport.");
return DF_OK;
}
df_result_t mitigate_revert(void)
{
log_step("DIRTYFAIL — revert mitigations");
if (!require_root()) return DF_TEST_ERROR;
log_warn("removing %s + %s", MODPROBE_CONF, SYSCTL_CONF);
log_warn("modules will NOT be auto-loaded — operator decides if/when");
if (!typed_confirm("DIRTYFAIL")) {
log_bad("confirmation declined");
return DF_OK;
}
if (unlink(MODPROBE_CONF) == 0) log_ok("removed %s", MODPROBE_CONF);
else if (errno == ENOENT) log_hint("%s did not exist", MODPROBE_CONF);
else log_bad("unlink %s: %s", MODPROBE_CONF, strerror(errno));
if (unlink(SYSCTL_CONF) == 0) log_ok("removed %s", SYSCTL_CONF);
else if (errno == ENOENT) log_hint("%s did not exist", SYSCTL_CONF);
else log_bad("unlink %s: %s", SYSCTL_CONF, strerror(errno));
log_hint("modules can be reloaded individually with `sudo modprobe <name>`");
return DF_OK;
}