etc.git


etc

~ scripts and random things
git clone https://github.com/djnnvx/etc.git
log | files | refs | archive

net_send.c (4512B)

      1 /*
      2  * net_send.c — TCP bridge for QEMU-backed AFL++ fuzzing.
      3  *
      4  * Reads the AFL++ test-case file (argv[1]), opens a TCP connection to
      5  * host:port (argv[2]:argv[3]), sends the raw bytes to usbipd running inside
      6  * the QEMU VM, then exits.
      7  *
      8  * Exit codes:
      9  *   0  — sent OK (or server closed connection normally)
     10  *   1  — ECONNRESET / SIGPIPE (daemon crashed → AFL++ marks as crash)
     11  *
     12  * Retries the connection 3× with 100 ms backoff to handle the brief window
     13  * when usbipd is being restarted by the watchdog in /init.
     14  *
     15  * Usage:
     16  *   afl-fuzz -S worker -i corpus -o output -t 2000 -- \
     17  *     ./net_send @@ 127.0.0.1 13240
     18  *
     19  * Compile:
     20  *   gcc -O2 -o net_send net_send.c
     21  */
     22 
     23 #include <stdio.h>
     24 #include <stdlib.h>
     25 #include <string.h>
     26 #include <stdint.h>
     27 #include <errno.h>
     28 #include <fcntl.h>
     29 #include <unistd.h>
     30 #include <signal.h>
     31 #include <time.h>
     32 #include <sys/socket.h>
     33 #include <sys/stat.h>
     34 #include <sys/time.h>
     35 #include <netinet/in.h>
     36 #include <netinet/tcp.h>
     37 #include <arpa/inet.h>
     38 
     39 #define MAX_INPUT   (1 << 16)  /* 64 KB — matches FUZZ_BUF_MAX */
     40 #define MAX_RETRIES 3
     41 #define RETRY_MS    100
     42 
     43 static void sleep_ms(int ms)
     44 {
     45     struct timespec ts = { .tv_sec = ms / 1000, .tv_nsec = (ms % 1000) * 1000000L };
     46     nanosleep(&ts, NULL);
     47 }
     48 
     49 static uint8_t ibuf[MAX_INPUT];
     50 
     51 int main(int argc, char *argv[])
     52 {
     53     if (argc < 4) {
     54         fprintf(stderr, "usage: %s <input_file> <host> <port>\n", argv[0]);
     55         return 1;
     56     }
     57 
     58     const char *path = argv[1];
     59     const char *host = argv[2];
     60     int         port = atoi(argv[3]);
     61 
     62     /* ── read input file ──────────────────────────────────────────── */
     63     int fd = open(path, O_RDONLY);
     64     if (fd < 0) {
     65         perror("open");
     66         return 1;
     67     }
     68     ssize_t len = read(fd, ibuf, sizeof(ibuf));
     69     close(fd);
     70     if (len <= 0)
     71         return 0;   /* empty input — nothing to send */
     72 
     73     /* ── ignore SIGPIPE (treat as connection error, not fatal signal) */
     74     signal(SIGPIPE, SIG_IGN);
     75 
     76     /* ── connect with retries ─────────────────────────────────────── */
     77     struct sockaddr_in addr;
     78     memset(&addr, 0, sizeof(addr));
     79     addr.sin_family = AF_INET;
     80     addr.sin_port   = htons((uint16_t)port);
     81     if (inet_pton(AF_INET, host, &addr.sin_addr) != 1) {
     82         fprintf(stderr, "bad host: %s\n", host);
     83         return 1;
     84     }
     85 
     86     int sock = -1;
     87     for (int i = 0; i < MAX_RETRIES; i++) {
     88         sock = socket(AF_INET, SOCK_STREAM, 0);
     89         if (sock < 0) {
     90             perror("socket");
     91             return 1;
     92         }
     93 
     94         /* TCP_NODELAY: no nagle buffering — send immediately */
     95         int one = 1;
     96         setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
     97 
     98         /* recv timeout: 2 s — prevents blocking forever when usbipd
     99          * reads the fuzz bytes but never replies (which is normal behaviour
    100          * for most malformed packets).  Without this every calibration seed
    101          * times out and AFL++ aborts with "All test cases time out". */
    102         struct timeval tv = { .tv_sec = 2, .tv_usec = 0 };       /* 2 s */
    103         setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
    104 
    105         if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == 0)
    106             break;
    107 
    108         close(sock);
    109         sock = -1;
    110 
    111         if (errno == ECONNREFUSED) {
    112             /* usbipd is restarting; wait and retry */
    113             sleep_ms(RETRY_MS);
    114             continue;
    115         }
    116         /* Any other error is fatal */
    117         perror("connect");
    118         return 1;
    119     }
    120 
    121     if (sock < 0) {
    122         /* Could not connect after retries — treat as timeout, not crash */
    123         return 0;
    124     }
    125 
    126     /* ── send the fuzz payload ────────────────────────────────────── */
    127     ssize_t sent = send(sock, ibuf, (size_t)len, MSG_NOSIGNAL);
    128 
    129     if (sent < 0) {
    130         close(sock);
    131         if (errno == ECONNRESET || errno == EPIPE)
    132             return 1;   /* daemon crashed */
    133         return 0;
    134     }
    135 
    136     /*
    137      * Wait briefly for a response (or connection close).
    138      * If we get ECONNRESET the daemon crashed → return 1.
    139      */
    140     uint8_t rbuf[256];
    141     ssize_t r = recv(sock, rbuf, sizeof(rbuf), 0);
    142     close(sock);
    143 
    144     if (r < 0 && (errno == ECONNRESET || errno == EPIPE))
    145         return 1;
    146 
    147     return 0;
    148 }