โฌข DragonFlyBSD Kernel Audit
DF-0700 / ipfw3_sync_deadpath.c
โ† back to finding โ†“ download raw
/*
 * DF-0700 / DF-0701 / DF-0702 / DF-0703 โ€” dead-path demonstration.
 *
 * All four findings live in sys/net/ipfw3_basic/ip_fw3_sync.c (the ipfw3
 * state-synchronization sub-component). They are reached, in principle, via the
 * IP_FW_SYNC_* sockopt cases in ip_fw3_ctl() (sys/net/ipfw3/ip_fw3.c). This
 * program empirically demonstrates the path is DEAD on a default kernel:
 *
 *   userspace setsockopt(IPPROTO_IP, IP_FW_X, {opcode=IP_FW_SYNC_CENTRE_CONF,...})
 *     -> raw_ip.c:386  ip_fw3_sockopt(sopt)             [base-kernel STUB]
 *     -> ip_fw3_glue.c:57  if (IPFW3_LOADED) ... else return ENOPROTOOPT;
 *
 * Without `kldload ipfw3` (root), every IP_FW_X sockopt returns ENOPROTOOPT โ€”
 * the SYNC_* cases are never even reached. And EVEN WITH the module loaded, the
 * SYNC_* dispatch in ip_fw3_ctl() is gated on:
 *
 *     if (ip_fw3_ctl_sync_ptr != NULL)          // ip_fw3.c:1125
 *         error = ip_fw3_ctl_sync_ptr(sopt);
 *
 * but `ip_fw3_ctl_sync_ptr` (ip_fw3.c:133) is initialized to NULL and is NEVER
 * assigned anywhere in the entire source tree (grep-verified). So even
 * post-kldload, the SYNC_* sockopts are silent no-ops (error stays 0). The
 * buggy functions โ€” ip_fw3_ctl_sync_centre_conf/_centre_start/_centre_test/
 * _edge_socket_handler/ip_fw3_sync_send_state โ€” are unreachable from userspace.
 *
 * Run as unprivileged maxx: prints the ENOPROTOOPT (or, on a hypothetical
 * system where the ptr was wired up, the EINVAL from the fix's bounds check).
 *
 * Build: cc -o ipfw3_sync_deadpath ipfw3_sync_deadpath.c
 * Run  : ./ipfw3_sync_deadpath
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#ifndef IP_FW_X
#define IP_FW_X 49   /* sys/netinet/in.h: ipfw3 firewall sockopt */
#endif

/* ip_fw_x_header + ipfw3_ioc_sync_centre (count=9999, way over MAX_EDGES=10) */
struct ip_fw_x_header { uint16_t opcode; uint16_t pad; };

struct ipfw3_sync_edge { uint32_t addr; uint16_t port; };
struct ipfw3_ioc_sync_centre { int count; struct ipfw3_sync_edge edges[0]; };

#define IP_FW_SYNC_CENTRE_CONF 89

int main(void)
{
    int s, rc;
    /* opcode=IP_FW_SYNC_CENTRE_CONF, followed by an out-of-range count */
    struct {
        struct ip_fw_x_header h;
        struct ipfw3_ioc_sync_centre c;
        struct ipfw3_sync_edge e;   /* one dummy edge */
    } __attribute__((packed)) msg;

    s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
    if (s < 0) {
        if (errno == EPERM || errno == EACCES) {
            /* SOCK_RAW needs root on DragonFly; fall back to SOCK_DGRAM */
            s = socket(AF_INET, SOCK_DGRAM, 0);
        }
        if (s < 0) { perror("socket"); return 1; }
    }

    memset(&msg, 0, sizeof(msg));
    msg.h.opcode  = IP_FW_SYNC_CENTRE_CONF;
    msg.c.count   = 9999;   /* would be a clear BSS OOB write if reachable */

    rc = setsockopt(s, IPPROTO_IP, IP_FW_X, &msg, sizeof(msg));
    printf("setsockopt(IPPROTO_IP, IP_FW_X, {SYNC_CENTRE_CONF, count=9999}) = %d\n", rc);
    if (rc < 0)
        printf("  errno=%d (%s)\n", errno, strerror(errno));
    else
        printf("  returned SUCCESS (silent no-op: ip_fw3_ctl_sync_ptr is NULL)\n");

    printf("\nInterpretation:\n");
    if (rc < 0 && errno == ENOPROTOOPT)
        printf("  ENOPROTOOPT => ipfw3 module not loaded; the SYNC_* cases in\n"
               "  ip_fw3_ctl() are never reached. (Default kernel state.)\n");
    else if (rc < 0 && (errno == EPERM || errno == EACCES))
        printf("  EPERM/EACCES => raw-sockopt needs privilege; the underlying\n"
               "  reachability argument is unchanged (see VERDICT.md).\n");
    else if (rc == 0)
        printf("  SUCCESS-no-op => ipfw3 loaded BUT ip_fw3_ctl_sync_ptr (ip_fw3.c:133)\n"
               "  is never assigned, so the SYNC_* dispatch (ip_fw3.c:1125-1126) is\n"
               "  skipped and the buggy centre_conf() is never called.\n");

    /* The 4 bugs are CONFIRMED at source level but UNREACHABLE on this kernel.
       See VERDICT.md for the path:line trace and fix.diff for the hardening. */
    close(s);
    return 0;
}