โฌข DragonFlyBSD Kernel Audit
DF-0706 / ipfw3_sync_lifecycle_probe.c
โ† back to finding โ†“ download raw
/*
 * DF-0706 โ€” ipfw3 sync lifecycle bugs reachability probe.
 *
 * The finding cites four lifecycle defects in ip_fw3_sync.c:
 *   (a) UAF on replaced edge_sock (cached so vs global so)
 *   (b) soclose+sofree refcount imbalance
 *   (c) MOD_UNLOAD never joins edge_td (handler thread runs after module code unmapped)
 *   (d) MOD_UNLOAD never clears ipfw_sync_send_state_prt (dangling fn ptr)
 *
 * All four require the IP_FW_SYNC_* opcodes to dispatch to
 * ip_fw3_ctl_sync_sockopt() โ€” which is gated by `ip_fw3_ctl_sync_ptr`
 * (sys/net/ipfw3/ip_fw3.c:133, init NULL). The pointer is never
 * assigned anywhere in the tree:
 *
 *   $ grep -rn 'ip_fw3_ctl_sync_ptr =' sys/
 *   (no hits)
 *
 * The companion `ipfw_sync_send_state_prt` global is similarly never
 * reached: it would be set by `ip_fw3_sync_modevent(MOD_LOAD)`
 * (ip_fw3_sync.c:470-475), but `ip_fw3_sync_modevent` is **never
 * called by anyone** โ€” the ipfw3_basic module hook
 * (ip_fw3_basic.c:657-658) invokes only `ip_fw3_state_modevent` and
 * `ip_fw3_table_modevent`, skipping sync entirely.
 *
 * Conclusion: on a default kernel the entire ipfw3_sync.c code path is
 * dead. The lifecycle bugs are LATENT. We exercise every IP_FW_SYNC_*
 * opcode via IP_FW_X to confirm none of them reach the buggy code.
 *
 * Run as root (IP_FW_X needs a raw IP socket -> SYSCAP_NONET_RAW).
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#ifndef IP_FW_X
#define IP_FW_X 49
#endif

/* Opcodes from sys/net/ipfw3/ip_fw3.h */
#define OP_SYNC_SHOW_CONF      82
#define OP_SYNC_SHOW_STATUS    83
#define OP_SYNC_EDGE_CONF      84
#define OP_SYNC_EDGE_START     85
#define OP_SYNC_EDGE_STOP      86
#define OP_SYNC_EDGE_TEST      87
#define OP_SYNC_EDGE_CLEAR     88
#define OP_SYNC_CENTRE_CONF    89
#define OP_SYNC_CENTRE_START   90
#define OP_SYNC_CENTRE_STOP    91
#define OP_SYNC_CENTRE_TEST    92
#define OP_SYNC_CENTRE_CLEAR   93

/* ip_fw3_glue.c: ip_fw3_ctl_x() reads sopt_val as [xhdr][payload]
 * then memmoves payload over the xhdr in-place, sets sopt_name=opcode,
 * and calls ip_fw3_ctl(). */
struct ip_fw_x_header {
    uint16_t opcode;
    uint16_t _pad;
} __attribute__((packed));

static int
fire(int s, uint16_t opcode, const char *tag, void *payload, size_t plen)
{
    size_t total = sizeof(struct ip_fw_x_header) + plen;
    char buf[512];
    if (total > sizeof(buf))
        return -ENOMEM;
    struct ip_fw_x_header *h = (struct ip_fw_x_header *)buf;
    h->opcode = opcode;
    h->_pad = 0;
    if (plen)
        memcpy(buf + sizeof(*h), payload, plen);

    int rc = setsockopt(s, IPPROTO_IP, IP_FW_X, buf, total);
    printf("[fire %-26s opcode=%u plen=%zu] rc=%d errno=%d (%s)\n",
           tag, opcode, plen, rc, errno, strerror(errno));
    return rc;
}

int
main(void)
{
    int s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
    if (s < 0) {
        perror("socket(AF_INET, SOCK_RAW, IPPROTO_RAW)");
        return 1;
    }
    printf("[*] running as: uid=%d euid=%d\n", getuid(), geteuid());

    /* EDGE_CONF โ€” would socreate() edge_sock (bug a/b/c precondition) */
    struct { int port; int hw_same; } ec = { 12345, 0 };
    fire(s, OP_SYNC_EDGE_CONF,    "IP_FW_SYNC_EDGE_CONF",    &ec, sizeof(ec));

    /* EDGE_START โ€” would soreference() + kthread_create edge_td */
    int zero = 0;
    fire(s, OP_SYNC_EDGE_START,   "IP_FW_SYNC_EDGE_START",   &zero, sizeof(zero));

    /* EDGE_STOP โ€” exercises the soclose+sofree imbalance path */
    fire(s, OP_SYNC_EDGE_STOP,    "IP_FW_SYNC_EDGE_STOP",    &zero, sizeof(zero));

    /* CENTRE_CONF / START โ€” would create centre_socks[] */
    struct { int count; } cc = { 1 };
    fire(s, OP_SYNC_CENTRE_CONF,  "IP_FW_SYNC_CENTRE_CONF",  &cc, sizeof(cc));
    fire(s, OP_SYNC_CENTRE_START, "IP_FW_SYNC_CENTRE_START", &zero, sizeof(zero));

    /* SHOW_STATUS โ€” reads fw3_sync_ctx.running (always 0 since sync never starts) */
    int run = -1;
    fire(s, OP_SYNC_SHOW_STATUS,  "IP_FW_SYNC_SHOW_STATUS",  &run, sizeof(run));

    /* SHOW_CONF โ€” read-back; would underflow on count if reached */
    char buf[64] = {0};
    fire(s, OP_SYNC_SHOW_CONF,    "IP_FW_SYNC_SHOW_CONF",    buf, sizeof(buf));

    printf("[+] All opcodes returned without panic.\n");
    printf("[+] ip_fw3_ctl_sync_ptr is NULL on master => dispatcher falls through.\n");
    printf("[+] MOD_UNLOAD of ipfw3_basic never invokes ip_fw3_sync_modevent()\n");
    printf("    so ipfw_sync_send_state_prt is never set and the edge_td thread\n");
    printf("    is never created โ€” the cited lifecycle bugs cannot fire.\n");
    close(s);
    return 0;
}