/* Copyright (c) 2022 Google, LLC. * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #if defined(CONFIG_BOARD_NATIVE_SIM) #include #include #else #error "Platform not supported" #endif /* Fuzz testing is coverage-based, so we want to hide a failure case * (a write through a null pointer in this case) down inside a call * tree in such a way that it would be very unlikely to be found by * randomly-selected input. But the fuzzer can still find it in * linear(-ish) time by discovering each new function along the way * and then probing that new space. The 1 in 2^56 case here would * require months-to-years of work for a large datacenter, but the * fuzzer gets it in 20 seconds or so. This requires that the code for * each case be distinguishable/instrumentable though, which is why we * generate the recursive handler functions this way and disable * inlining to prevent optimization. */ int *global_null_ptr; static const uint8_t key[] = { 0x9e, 0x21, 0x0c, 0x18, 0x9d, 0xd1, 0x7d }; bool found[ARRAY_SIZE(key)]; #define LASTKEY (ARRAY_SIZE(key) - 1) #define GEN_CHECK(cur, nxt) \ void check##nxt(const uint8_t *data, size_t sz); \ void __noinline check##cur(const uint8_t *data, size_t sz) \ { \ if (cur < sz && data[cur] == key[cur]) { \ if (!found[cur]) { \ printk("#\n# Found key %d\n#\n", cur); \ found[cur] = true; \ } \ if (cur == LASTKEY) { \ *global_null_ptr = 0; /* boom! */ \ } else { \ check##nxt(data, sz); \ } \ } \ } GEN_CHECK(0, 1) GEN_CHECK(1, 2) GEN_CHECK(2, 3) GEN_CHECK(3, 4) GEN_CHECK(4, 5) GEN_CHECK(5, 6) GEN_CHECK(6, 0) /* Fuzz input received from LLVM via "interrupt" */ static const uint8_t *fuzz_buf; static size_t fuzz_sz; K_SEM_DEFINE(fuzz_sem, 0, K_SEM_MAX_LIMIT); static void fuzz_isr(const void *arg) { /* We could call check0() to execute the fuzz case here, but * pass it through to the main thread instead to get more OS * coverage. */ k_sem_give(&fuzz_sem); } int main(void) { printk("Hello World! %s\n", CONFIG_BOARD); IRQ_CONNECT(CONFIG_ARCH_POSIX_FUZZ_IRQ, 0, fuzz_isr, NULL, 0); irq_enable(CONFIG_ARCH_POSIX_FUZZ_IRQ); while (true) { k_sem_take(&fuzz_sem, K_FOREVER); /* Execute the fuzz case we got from LLVM and passed * through an interrupt to this thread. */ check0(fuzz_buf, fuzz_sz); } return 0; } /** * Entry point for fuzzing. Works by placing the data * into two known symbols, triggering an app-visible interrupt, and * then letting the simulator run for a fixed amount of time (intended to be * "long enough" to handle the event and reach a quiescent state * again) */ #if defined(CONFIG_BOARD_NATIVE_SIM) NATIVE_SIMULATOR_IF /* We expose this function to the final runner link stage*/ #endif int LLVMFuzzerTestOneInput(const uint8_t *data, size_t sz) { static bool runner_initialized; if (!runner_initialized) { nsi_init(0, NULL); runner_initialized = true; } /* Provide the fuzz data to the embedded OS as an interrupt, with * "DMA-like" data placed into native_fuzz_buf/sz */ fuzz_buf = (void *)data; fuzz_sz = sz; hw_irq_ctrl_set_irq(CONFIG_ARCH_POSIX_FUZZ_IRQ); /* Give the OS time to process whatever happened in that * interrupt and reach an idle state. */ nsi_exec_for(k_ticks_to_us_ceil64(CONFIG_ARCH_POSIX_FUZZ_TICKS)); return 0; }