219 lines
7.1 KiB
C
219 lines
7.1 KiB
C
// 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;
|
||
|
|
}
|