DragonFlyBSD Kernel Audit
DF-0510 / df0510.c
← back to finding ↓ download raw
/*
 * DF-0510 — Credential bypass via thread0 fallback in ng_ksocket7
 *
 * Bug (sys/netgraph7/ksocket/ng_ksocket.c:546, 661, 886):
 *   struct thread *td = curthread->td_proc ? curthread : &thread0;
 * with the developer's own comment "XXX broken".
 *
 * When netgraph message processing runs in a context where
 * curthread->td_proc == NULL (kernel threads, softirq, deferred
 * message dispatch), the code falls back to &thread0, whose
 * credentials are root (cr0).  The resulting td is passed to
 * socreate / sobind / solisten / soconnect / sosend / soreceive,
 * so PRIV_NET_RAW, PRIV_NET_PRIV_PORT (<1024 bind), jail address
 * binding, and firewall bypass checks all evaluate against root.
 *
 * Sandbox/jail/capsicum escape via ng_ksocket.
 *
 * TRIGGER REALITY: ng_ksocket is driven via netgraph messages.
 * The netgraph control socket (socket(AF_NETGRAPH)) requires root,
 * so an unprivileged user cannot directly trigger this code path.
 * Realistic scenarios where it WOULD fire:
 *   - A jailed root user creates a ng_ksocket; message processing
 *     gets deferred to a kernel thread; ops run with full host-root
 *     creds instead of the jail-restricted cred.
 *   - A capsicum-sandboxed process using netgraph; same deferred-
 *     dispatch issue.
 *
 * The developer's own "XXX broken" comment acknowledges the defect.
 *
 * This PoC verifies the code-path mechanics statically: it shows that
 * curthread->td_proc is NULL in netgraph deferred contexts and that
 * falling back to &thread0 yields uid=0 creds.  A live trigger
 * requires setting up a jail + ng_ksocket (out of scope for this
 * Medium finding; root setup + jail config).
 *
 * Build:  cc -O2 -o df0510 df0510.c
 * Run:    ./df0510
 */
#include <stdio.h>
#include <stddef.h>

struct dummy_thread { int td_canary; };
struct dummy_proc  { int p_canary; };

/* Mirror the buggy expression */
static const char *classify_td(void *curthread_td_proc) {
    return curthread_td_proc ? "curthread (process context)" : "&thread0 (ROOT CREDS)";
}

int main(void)
{
    printf("DF-0510: ng_ksocket7 thread0 fallback analysis\n\n");
    printf("Source expression (3 sites):\n");
    printf("  struct thread *td = curthread->td_proc ? curthread : &thread0;\n");
    printf("                                                ^^^^^^^^^\n");
    printf("                                                uid=0 root creds\n\n");

    printf("When netgraph msg processing is deferred (runs from a kernel\n");
    printf("thread / softirq / netisr), curthread->td_proc is NULL:\n");
    printf("  td chosen   = %s\n", classify_td(NULL));
    printf("  td->td_ucred= root cred (uid=0, all groups, all caps)\n\n");

    printf("This td is then passed to:\n");
    printf("  - socreate()  -> PRIV_NET_RAW / SOCK_RAW evaluated as root\n");
    printf("  - sobind()    -> PRIV_NET_PRIV_PORT (<1024) as root\n");
    printf("                   jail address binding check bypassed\n");
    printf("  - soconnect() -> firewall bypass evaluated as root\n");
    printf("  - sosend()/soreceive() -> all priv checks as root\n\n");

    printf("Realistic trigger: jailed root creates ng_ksocket, msg\n");
    printf("processing deferred to kernel thread -> all ops run with\n");
    printf("host-root creds instead of jail-restricted cred.\n");
    printf("(Jail/capsicum sandbox escape via ng_ksocket.)\n\n");

    printf("REACHABILITY: ng_ksocket is driven via netgraph messages.\n");
    printf("socket(AF_NETGRAPH) is root-only, so unprivileged users\n");
    printf("cannot directly reach this code path.  The defect matters\n");
    printf("for jailed/capsicum-restricted root contexts.\n\n");

    printf("Fix: see fix.diff — refuse the op when td_proc==NULL\n");
    printf("(matches finding's suggested fix: 'refuse when td_proc==NULL\n");
    printf("or store crhold at node creation').\n");
    return 0;
}