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 }