etc.git


etc

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

kcov_shim.c (10481B)

      1 /*
      2  * kcov_shim.c — KCOV-backed AFL++ coverage shim for QEMU-mode kernel fuzzing.
      3  *
      4  * WHAT THIS DOES
      5  * ───────────────
      6  * This binary runs INSIDE the QEMU VM.  AFL++ runs on the HOST.  The two
      7  * communicate over a virtio-serial channel (or a pipe if using netns mode).
      8  *
      9  * Per iteration:
     10  *   1. AFL++ writes the test case bytes over the serial link.
     11  *   2. This shim reads them, enables KCOV, sends the bytes to usbipd via TCP.
     12  *   3. usbipd processes them → kernel code executes → KCOV records PC trace.
     13  *   4. This shim reads the KCOV buffer, hashes consecutive (PC[i], PC[i+1])
     14  *      pairs as coverage "edges", and writes the edge bitmap back to AFL++ over
     15  *      the serial link.
     16  *   5. AFL++ uses the edge bitmap to guide mutation (same as in-process mode
     17  *      but the coverage comes from the kernel, not userspace).
     18  *
     19  * CHANNEL PROTOCOL (over virtio-serial)
     20  * ───────────────────────────────────────
     21  *   HOST→GUEST: [len:4 LE][payload:len]   (test case)
     22  *   GUEST→HOST: [bitmap:MAP_SIZE]          (AFL++ shared map, 64 KB)
     23  *   GUEST→HOST: [crash:1]                 (0x00 = ok, 0x01 = crash detected)
     24  *
     25  * MAP_SIZE = 65536 (matches AFL++ default __AFL_SHM_FUZZ_LEN)
     26  *
     27  * QEMU INVOCATION (add to qemu-system-x86_64 command line)
     28  * ─────────────────────────────────────────────────────────
     29  *   -chardev socket,id=kcov,path=/tmp/kcov.sock,server=on,wait=off \
     30  *   -device virtio-serial \
     31  *   -device virtserialport,chardev=kcov,name=kcov0
     32  *
     33  *   # Inside VM /init: exec kcov_shim /dev/vport0p1 127.0.0.1 3240
     34  *
     35  * HOST SIDE
     36  * ─────────
     37  *   afl-fuzz ... -- ./kcov_host_relay /tmp/kcov.sock @@
     38  *   (kcov_host_relay is a thin wrapper that reads @@ and writes to the socket)
     39  *
     40  * COMPILE (inside VM, or cross-compile)
     41  * ───────────────────────────────────────
     42  *   gcc -O2 -static -o kcov_shim kcov_shim.c
     43  *
     44  * KERNEL CONFIG REQUIREMENTS
     45  * ───────────────────────────
     46  *   CONFIG_KCOV=y
     47  *   CONFIG_KCOV_ENABLE_COMPARISONS=y
     48  *   CONFIG_DEBUG_FS=y         (debugfs must be mounted at /sys/kernel/debug)
     49  *
     50  * NOTE: KCOV covers code running in task context for the current process.
     51  * The TCP receive path and usbip parsing run in task context (kthread or
     52  * workqueue bound to the usbipd process's fd) and WILL be covered.
     53  * IRQ and softirq handlers will NOT be covered (KCOV limitation).
     54  */
     55 
     56 #include <arpa/inet.h>
     57 #include <errno.h>
     58 #include <fcntl.h>
     59 #include <netinet/in.h>
     60 #include <netinet/tcp.h>
     61 #include <signal.h>
     62 #include <stdint.h>
     63 #include <stdio.h>
     64 #include <stdlib.h>
     65 #include <string.h>
     66 #include <sys/ioctl.h>
     67 #include <sys/mman.h>
     68 #include <sys/socket.h>
     69 #include <sys/stat.h>
     70 #include <sys/time.h>
     71 #include <time.h>
     72 #include <unistd.h>
     73 
     74 /* ── KCOV kernel ABI ────────────────────────────────────────────────────────── */
     75 /* From linux/kcov.h (reproduced here to avoid kernel header dependency) */
     76 #define KCOV_INIT_TRACE         _IOR('c', 1, unsigned long)
     77 #define KCOV_ENABLE             _IO('c',  100)
     78 #define KCOV_DISABLE            _IO('c',  101)
     79 #define KCOV_TRACE_PC           0
     80 
     81 #define KCOV_ENTRIES            (64 * 1024)     /* 64K entries = 512 KB mmap */
     82 #define KCOV_PATH               "/sys/kernel/debug/kcov"
     83 
     84 /* ── AFL++ shared map ───────────────────────────────────────────────────────── */
     85 #define MAP_SIZE                (1 << 16)       /* 64 KB — AFL++ default */
     86 
     87 /* ── protocol sizes ─────────────────────────────────────────────────────────── */
     88 #define MAX_INPUT               (1 << 16)       /* 64 KB max test case */
     89 
     90 /* ── globals ─────────────────────────────────────────────────────────────────── */
     91 static uint8_t  afl_map[MAP_SIZE];              /* coverage edge bitmap */
     92 static uint8_t  ibuf[MAX_INPUT];                /* incoming test case */
     93 static uint64_t *kcov_buf;                      /* mmap of KCOV trace buffer */
     94 static int       kcov_fd = -1;
     95 
     96 /* ── KCOV helpers ────────────────────────────────────────────────────────────── */
     97 
     98 static int kcov_init(void)
     99 {
    100     kcov_fd = open(KCOV_PATH, O_RDWR);
    101     if (kcov_fd < 0) {
    102         perror("open " KCOV_PATH);
    103         return -1;
    104     }
    105 
    106     if (ioctl(kcov_fd, KCOV_INIT_TRACE, KCOV_ENTRIES) < 0) {
    107         perror("ioctl KCOV_INIT_TRACE");
    108         return -1;
    109     }
    110 
    111     kcov_buf = mmap(NULL, KCOV_ENTRIES * sizeof(uint64_t),
    112                     PROT_READ | PROT_WRITE, MAP_SHARED, kcov_fd, 0);
    113     if (kcov_buf == MAP_FAILED) {
    114         perror("mmap kcov");
    115         return -1;
    116     }
    117 
    118     return 0;
    119 }
    120 
    121 static void kcov_reset_and_enable(void)
    122 {
    123     kcov_buf[0] = 0;        /* reset count */
    124     ioctl(kcov_fd, KCOV_ENABLE, KCOV_TRACE_PC);
    125 }
    126 
    127 static void kcov_disable_and_harvest(void)
    128 {
    129     ioctl(kcov_fd, KCOV_DISABLE, 0);
    130 
    131     uint64_t count = kcov_buf[0];
    132     if (count > (uint64_t)(KCOV_ENTRIES - 1))
    133         count = KCOV_ENTRIES - 1;
    134 
    135     /* Hash consecutive PC pairs as edge coverage.
    136      * AFL++ treats (src, dst) pairs as edges in the control-flow graph.
    137      * We use a simple hash: edge = ((pc[i] >> 1) ^ pc[i+1]) % MAP_SIZE
    138      * This is the same scheme used by honggfuzz and oracle/kernel-fuzzing. */
    139     for (uint64_t i = 0; i + 1 < count; i++) {
    140         uint64_t a = kcov_buf[1 + i];
    141         uint64_t b = kcov_buf[1 + i + 1];
    142         uint32_t edge = (uint32_t)(((a >> 1) ^ b) % MAP_SIZE);
    143         if (afl_map[edge] < 255)
    144             afl_map[edge]++;
    145     }
    146 }
    147 
    148 /* ── TCP helpers ─────────────────────────────────────────────────────────────── */
    149 
    150 static int recv_all(int fd, void *buf, size_t len)
    151 {
    152     size_t done = 0;
    153     while (done < len) {
    154         ssize_t r = recv(fd, (char *)buf + done, len - done, 0);
    155         if (r <= 0) return -1;
    156         done += (size_t)r;
    157     }
    158     return 0;
    159 }
    160 
    161 static int send_all_fd(int fd, const void *buf, size_t len)
    162 {
    163     size_t done = 0;
    164     while (done < len) {
    165         ssize_t r = write(fd, (const char *)buf + done, len - done);
    166         if (r <= 0) return -1;
    167         done += (size_t)r;
    168     }
    169     return 0;
    170 }
    171 
    172 static int tcp_connect(const char *host, int port)
    173 {
    174     int sock = socket(AF_INET, SOCK_STREAM, 0);
    175     if (sock < 0) return -1;
    176 
    177     struct sockaddr_in sa;
    178     memset(&sa, 0, sizeof(sa));
    179     sa.sin_family = AF_INET;
    180     sa.sin_port   = htons((uint16_t)port);
    181     inet_pton(AF_INET, host, &sa.sin_addr);
    182 
    183     int one = 1;
    184     setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
    185     struct timeval tv = { .tv_sec = 0, .tv_usec = 200000 };  /* 200 ms */
    186     setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
    187 
    188     if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
    189         close(sock);
    190         return -1;
    191     }
    192     return sock;
    193 }
    194 
    195 /* ── main loop ───────────────────────────────────────────────────────────────── */
    196 
    197 int main(int argc, char *argv[])
    198 {
    199     if (argc < 4) {
    200         fprintf(stderr,
    201                 "usage: %s <serial-dev> <usbipd-host> <usbipd-port>\n"
    202                 "  serial-dev   — virtio-serial device (e.g. /dev/vport0p1)\n"
    203                 "  usbipd-host  — usbipd address (usually 127.0.0.1)\n"
    204                 "  usbipd-port  — usbipd port (usually 3240)\n",
    205                 argv[0]);
    206         return 1;
    207     }
    208 
    209     const char *serial_dev = argv[1];
    210     const char *usbipd_host = argv[2];
    211     int         usbipd_port = atoi(argv[3]);
    212 
    213     signal(SIGPIPE, SIG_IGN);
    214 
    215     /* Open virtio-serial channel to host */
    216     int serial = open(serial_dev, O_RDWR);
    217     if (serial < 0) {
    218         perror("open serial");
    219         return 1;
    220     }
    221 
    222     /* Init KCOV */
    223     if (kcov_init() < 0)
    224         return 1;
    225 
    226     fprintf(stderr, "[kcov_shim] ready on %s, targeting %s:%d\n",
    227             serial_dev, usbipd_host, usbipd_port);
    228 
    229     /* Main loop: one iteration per AFL++ test case */
    230     for (;;) {
    231         /* ── receive test case from host (via virtio-serial) ── */
    232         uint32_t len_le;
    233         if (recv_all(serial, &len_le, 4) < 0)
    234             break;
    235         uint32_t ilen = le32toh(len_le);
    236         if (ilen == 0 || ilen > MAX_INPUT)
    237             ilen = 0;
    238 
    239         if (ilen > 0 && recv_all(serial, ibuf, ilen) < 0)
    240             break;
    241 
    242         /* ── reset edge map for this iteration ── */
    243         memset(afl_map, 0, MAP_SIZE);
    244 
    245         uint8_t crash = 0;
    246 
    247         if (ilen > 0) {
    248             /* ── enable KCOV ── */
    249             kcov_reset_and_enable();
    250 
    251             /* ── send payload to usbipd via TCP ── */
    252             int sock = tcp_connect(usbipd_host, usbipd_port);
    253             if (sock >= 0) {
    254                 /* Send; ignore errors — crash detection is via KCOV + exit */
    255                 send(sock, ibuf, ilen, MSG_NOSIGNAL);
    256 
    257                 /* Wait briefly for response (or connection close) */
    258                 uint8_t rbuf[256];
    259                 ssize_t r = recv(sock, rbuf, sizeof(rbuf), 0);
    260                 if (r < 0 && (errno == ECONNRESET || errno == EPIPE))
    261                     crash = 1;
    262                 close(sock);
    263             }
    264 
    265             /* ── disable KCOV and harvest coverage ── */
    266             kcov_disable_and_harvest();
    267         }
    268 
    269         /* ── send bitmap + crash byte back to AFL++ ── */
    270         if (send_all_fd(serial, afl_map, MAP_SIZE) < 0)
    271             break;
    272         if (send_all_fd(serial, &crash, 1) < 0)
    273             break;
    274     }
    275 
    276     close(serial);
    277     return 0;
    278 }