1 /* Copyright (c) 2022 Google, LLC.
2 * SPDX-License-Identifier: Apache-2.0
3 */
4 #include <zephyr/kernel.h>
5 #include <string.h>
6 #include <zephyr/irq.h>
7 #include <zephyr/toolchain.h>
8 #include <irq_ctrl.h>
9 #if defined(CONFIG_BOARD_NATIVE_SIM)
10 #include <nsi_cpu_if.h>
11 #include <nsi_main_semipublic.h>
12 #else
13 #error "Platform not supported"
14 #endif
15
16 /* Fuzz testing is coverage-based, so we want to hide a failure case
17 * (a write through a null pointer in this case) down inside a call
18 * tree in such a way that it would be very unlikely to be found by
19 * randomly-selected input. But the fuzzer can still find it in
20 * linear(-ish) time by discovering each new function along the way
21 * and then probing that new space. The 1 in 2^56 case here would
22 * require months-to-years of work for a large datacenter, but the
23 * fuzzer gets it in 20 seconds or so. This requires that the code for
24 * each case be distinguishable/instrumentable though, which is why we
25 * generate the recursive handler functions this way and disable
26 * inlining to prevent optimization.
27 */
28 int *global_null_ptr;
29 static const uint8_t key[] = { 0x9e, 0x21, 0x0c, 0x18, 0x9d, 0xd1, 0x7d };
30 bool found[ARRAY_SIZE(key)];
31
32 #define LASTKEY (ARRAY_SIZE(key) - 1)
33
34 #define GEN_CHECK(cur, nxt) \
35 void check##nxt(const uint8_t *data, size_t sz); \
36 void __noinline check##cur(const uint8_t *data, size_t sz) \
37 { \
38 if (cur < sz && data[cur] == key[cur]) { \
39 if (!found[cur]) { \
40 printk("#\n# Found key %d\n#\n", cur); \
41 found[cur] = true; \
42 } \
43 if (cur == LASTKEY) { \
44 *global_null_ptr = 0; /* boom! */ \
45 } else { \
46 check##nxt(data, sz); \
47 } \
48 } \
49 }
50
51 GEN_CHECK(0, 1)
52 GEN_CHECK(1, 2)
53 GEN_CHECK(2, 3)
54 GEN_CHECK(3, 4)
55 GEN_CHECK(4, 5)
56 GEN_CHECK(5, 6)
57 GEN_CHECK(6, 0)
58
59 /* Fuzz input received from LLVM via "interrupt" */
60 static const uint8_t *fuzz_buf;
61 static size_t fuzz_sz;
62
63 K_SEM_DEFINE(fuzz_sem, 0, K_SEM_MAX_LIMIT);
64
fuzz_isr(const void * arg)65 static void fuzz_isr(const void *arg)
66 {
67 /* We could call check0() to execute the fuzz case here, but
68 * pass it through to the main thread instead to get more OS
69 * coverage.
70 */
71 k_sem_give(&fuzz_sem);
72 }
73
main(void)74 int main(void)
75 {
76 printk("Hello World! %s\n", CONFIG_BOARD);
77
78 IRQ_CONNECT(CONFIG_ARCH_POSIX_FUZZ_IRQ, 0, fuzz_isr, NULL, 0);
79 irq_enable(CONFIG_ARCH_POSIX_FUZZ_IRQ);
80
81 while (true) {
82 k_sem_take(&fuzz_sem, K_FOREVER);
83
84 /* Execute the fuzz case we got from LLVM and passed
85 * through an interrupt to this thread.
86 */
87 check0(fuzz_buf, fuzz_sz);
88 }
89 return 0;
90 }
91
92 /**
93 * Entry point for fuzzing. Works by placing the data
94 * into two known symbols, triggering an app-visible interrupt, and
95 * then letting the simulator run for a fixed amount of time (intended to be
96 * "long enough" to handle the event and reach a quiescent state
97 * again)
98 */
99 #if defined(CONFIG_BOARD_NATIVE_SIM)
100 NATIVE_SIMULATOR_IF /* We expose this function to the final runner link stage*/
101 #endif
LLVMFuzzerTestOneInput(const uint8_t * data,size_t sz)102 int LLVMFuzzerTestOneInput(const uint8_t *data, size_t sz)
103 {
104 static bool runner_initialized;
105
106 if (!runner_initialized) {
107 nsi_init(0, NULL);
108 runner_initialized = true;
109 }
110
111 /* Provide the fuzz data to the embedded OS as an interrupt, with
112 * "DMA-like" data placed into native_fuzz_buf/sz
113 */
114 fuzz_buf = (void *)data;
115 fuzz_sz = sz;
116
117 hw_irq_ctrl_set_irq(CONFIG_ARCH_POSIX_FUZZ_IRQ);
118
119 /* Give the OS time to process whatever happened in that
120 * interrupt and reach an idle state.
121 */
122 nsi_exec_for(k_ticks_to_us_ceil64(CONFIG_ARCH_POSIX_FUZZ_TICKS));
123
124 return 0;
125 }
126