Files
MicrOBU/obu-firmware/test/host/fuzz_gn_unwrap.c
T

219 lines
7.1 KiB
C
Raw Normal View History

// Mutation fuzzer for gn_unwrap_its, the one function in this firmware that parses bytes from the
// air. See README.md.
//
// Seeds are every recorded frame in the pcaps given, plus frames built by the firmware's own TX
// code. Each iteration takes a seed, applies 1-4 random edits - bit flips, random bytes, boundary
// bytes such as COER length markers, 16-bit length fields, truncation, insertion, deletion,
// appended bytes - mostly within the first 128 bytes where the headers are, and runs gn_unwrap_its
// on the result placed against the guard page. A read past the end crashes and prints the input;
// an accepted frame whose payload is not inside the input is reported the same way. MinGW has
// neither libFuzzer nor AddressSanitizer, hence this rather than coverage guidance. The same seed
// gives the same run.
//
// Usage: fuzz_gn_unwrap iterations seed [capture.pcap ...]
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "dot11p.h"
#include "geonet.h"
#include "gn_unwrap.h"
#include "test_util.h"
#define MAX_FRAME 2048 // within the guard page's one page, and above any 802.11 frame
static uint8_t **s_seeds;
static int *s_seed_len;
static int s_nseeds;
static int s_seed_cap;
static void add_seed(const uint8_t *f, int len)
{
if (len <= 0 || len > MAX_FRAME) {
return;
}
if (s_nseeds == s_seed_cap) {
s_seed_cap = s_seed_cap ? 2 * s_seed_cap : 1024;
s_seeds = realloc(s_seeds, (size_t)s_seed_cap * sizeof *s_seeds);
s_seed_len = realloc(s_seed_len, (size_t)s_seed_cap * sizeof *s_seed_len);
if (!s_seeds || !s_seed_len) {
fprintf(stderr, "out of memory\n");
exit(2);
}
}
s_seeds[s_nseeds] = malloc((size_t)len);
if (!s_seeds[s_nseeds]) {
fprintf(stderr, "out of memory\n");
exit(2);
}
memcpy(s_seeds[s_nseeds], f, (size_t)len);
s_seed_len[s_nseeds++] = len;
}
static void on_frame(const uint8_t *f, int len, int index, void *ctx)
{
(void)index;
(void)ctx;
add_seed(f, len);
}
static void add_own_seeds(void)
{
static const uint8_t cam[] = {0x02, 0x02, 0x00, 0x0f, 0x42, 0x3f, 0x37, 0x00, 0x40, 0x2a, 0xb2};
gn_lpv_t lpv;
memset(&lpv, 0, sizeof lpv);
lpv.mac[0] = 0x02;
lpv.station_type = 2;
uint8_t gn[256];
uint8_t f[512];
const int gn_len = geonet_wrap_shb(cam, (int)sizeof cam, &lpv, 2001, gn, sizeof gn);
for (int qos = 0; qos <= 1; qos++) {
add_seed(f, dot11p_build_frame(gn, gn_len, lpv.mac, f, sizeof f, qos));
}
}
static uint64_t s_rng;
static uint64_t rnd(void)
{
s_rng ^= s_rng << 13;
s_rng ^= s_rng >> 7;
s_rng ^= s_rng << 17;
return s_rng;
}
static int below(int n)
{
return n <= 0 ? 0 : (int)(rnd() % (uint64_t)n);
}
// Three times in four inside the headers, otherwise anywhere.
static int pick_pos(int len)
{
return below(4) ? below(len < 128 ? len : 128) : below(len);
}
static const uint8_t k_bytes[] = {0x00, 0x01, 0x02, 0x03, 0x05, 0x10, 0x12, 0x20,
0x40, 0x50, 0x7F, 0x80, 0x81, 0x82, 0x83, 0xFF};
static const uint16_t k_words[] = {0x0000, 0x0001, 0x0003, 0x0004, 0x0005, 0x007F,
0x0080, 0x00FF, 0x0100, 0x7FFF, 0x8000, 0xFFFF};
static void mutate(uint8_t *b, int *len)
{
const int edits = 1 + below(4);
for (int e = 0; e < edits; e++) {
const int n = *len;
switch (below(8)) {
case 0: // flip a bit
if (n) {
b[pick_pos(n)] ^= (uint8_t)(1u << below(8));
}
break;
case 1: // random byte
if (n) {
b[pick_pos(n)] = (uint8_t)rnd();
}
break;
case 2: // boundary byte
if (n) {
b[pick_pos(n)] = k_bytes[below((int)sizeof k_bytes)];
}
break;
case 3: // truncate
*len = below(n + 1);
break;
case 4: { // append
const int add = 1 + below(16);
if (n + add <= MAX_FRAME) {
for (int i = 0; i < add; i++) {
b[n + i] = (uint8_t)rnd();
}
*len = n + add;
}
break;
}
case 5: // insert a byte
if (n < MAX_FRAME) {
const int p = below(n + 1);
memmove(b + p + 1, b + p, (size_t)(n - p));
b[p] = (uint8_t)rnd();
*len = n + 1;
}
break;
case 6: // delete a byte
if (n) {
const int p = below(n);
memmove(b + p, b + p + 1, (size_t)(n - p - 1));
*len = n - 1;
}
break;
default: // boundary 16-bit big-endian value, e.g. a length field
if (n >= 2) {
const int p = pick_pos(n - 1);
const uint16_t v = k_words[below((int)(sizeof k_words / sizeof k_words[0]))];
b[p] = (uint8_t)(v >> 8);
b[p + 1] = (uint8_t)v;
}
break;
}
}
}
int main(int argc, char **argv)
{
if (argc < 3) {
fprintf(stderr, "usage: fuzz_gn_unwrap iterations seed [capture.pcap ...]\n");
return 2;
}
const unsigned long long iterations = strtoull(argv[1], NULL, 10);
s_rng = (strtoull(argv[2], NULL, 10) * 0x9E3779B97F4A7C15ull) | 1;
tu_install_crash_handler();
tu_guard_init();
for (int i = 3; i < argc; i++) {
if (tu_pcap_foreach(argv[i], on_frame, NULL) < 0) {
fprintf(stderr, "cannot read %s as a pcap\n", argv[i]);
return 2;
}
}
add_own_seeds();
static uint8_t buf[MAX_FRAME];
int len = 0;
tu_set_crash_input(buf, &len);
unsigned long long accepted = 0, signed_frames = 0, truncated = 0;
for (unsigned long long it = 0; it < iterations; it++) {
const int s = below(s_nseeds);
len = s_seed_len[s];
memcpy(buf, s_seeds[s], (size_t)len);
mutate(buf, &len);
tu_set_context("fuzz iteration %llu (seed %s), mutated from seed frame %d", it, argv[2], s);
const uint8_t *g = tu_guarded(buf, len);
gn_rx_t rx;
if (gn_unwrap_its(g, len, &rx)) {
accepted++;
signed_frames += rx.signed_unverified;
truncated += rx.truncated;
const uintptr_t lo = (uintptr_t)g;
const uintptr_t p = (uintptr_t)rx.payload;
if (p < lo || rx.payload_len <= 0 || p + (uintptr_t)rx.payload_len > lo + (uintptr_t)len) {
fprintf(stderr, "FAIL during %s: accepted payload lies outside the input\ninput (%d bytes): ",
tu_context(), len);
for (int i = 0; i < len; i++) {
fprintf(stderr, "%02x", buf[i]);
}
fputc('\n', stderr);
return 1;
}
}
}
printf("fuzz_gn_unwrap: %llu iterations from %d seed frames, %llu accepted (%llu signed, "
"%llu truncated), no crash\n",
iterations, s_nseeds, accepted, signed_frames, truncated);
return 0;
}