samples: Add fuzz sample

This is a simple example of fuzz test integration with Zephyr apps
that displays LLVM libfuzzer's most important feature: it's ability to
detect and explore deep and complicated call trees by exploiting
coverage information gleaned from instrumented binaries.

See README.rst and the inline comments for details

Signed-off-by: Andy Ross <andyross@google.com>
This commit is contained in:
Andy Ross 2022-08-23 14:36:41 -07:00 committed by Carles Cufí
parent 65d657685e
commit 7f5e1904cc
5 changed files with 177 additions and 0 deletions

View file

@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(fuzz)
target_sources(app PRIVATE src/main.c)

View file

@ -0,0 +1,73 @@
Fuzzing Example
###############
Overview
********
This is a simple example of fuzz test integration with Zephyr apps
that displays LLVM libfuzzer's most important feature: it's ability to
detect and explore deep and complicated call trees by exploiting
coverage information gleaned from instrumented binaries.
Building and Running
********************
Right now, the only toolchain that works with libfuzzer is a recent 64
bit clang (clang 14 was used at development time). Make sure such a
toolchain is installed in your host environment, and build with:
.. code-block:: console
$ clang --version
clang version 14.0.6
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
$ export ZEPHYR_TOOLCHAIN_VARIANT=llvm
$ west build -t run -b native_posix_64 samples/subsys/debug/fuzz
Over 10-20 seconds or so (runtimes can be quite variable) you will see
it discover and recurse deeper into the test's deliberately
constructed call tree, eventually crashing when it reaches the final
state and reporting the failure.
Example output:
.. code-block:: console
-- west build: running target run
[0/1] cd /home/andy/z/zephyr/build && .../andy/z/zephyr/build/zephyr/zephyr.exe
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 108038547
INFO: Loaded 1 modules (2112 inline 8-bit counters): 2112 [0x55cbe336ec55, 0x55cbe336f495),
INFO: Loaded 1 PC tables (2112 PCs): 2112 [0x55cbe336f498,0x55cbe3377898),
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
*** Booting Zephyr OS build zephyr-v3.1.0-3976-g806034e02865 ***
Hello World! native_posix_64
INFO: A corpus is not provided, starting from an empty corpus
#2 INITED cov: 101 ft: 102 corp: 1/1b exec/s: 0 rss: 30Mb
#
# Found key 0
#
NEW_FUNC[1/6]: 0x55cbe3339c45 in check1 /home/andy/z/zephyr/samples/subsys/debug/fuzz/src/main.c:43
NEW_FUNC[2/6]: 0x55cbe333c8d8 in char_out /home/andy/z/zephyr/lib/os/printk.c:108
...
...
...
#418965 REDUCE cov: 165 ft: 166 corp: 15/400b lim: 4052 exec/s: 38087 rss: 31Mb L: 5/256 MS: 1 EraseBytes-
#524288 pulse cov: 165 ft: 166 corp: 15/400b lim: 4096 exec/s: 40329 rss: 31Mb
#
# Found key 5
#
NEW_FUNC[1/1]: 0x55cbe3339ff7 in check6 /home/andy/z/zephyr/samples/subsys/debug/fuzz/src/main.c:48
#579131 NEW cov: 168 ft: 169 corp: 16/406b lim: 4096 exec/s: 38608 rss: 31Mb L: 6/256 MS: 1 InsertByte-
#579432 NEW cov: 170 ft: 171 corp: 17/414b lim: 4096 exec/s: 38628 rss: 31Mb L: 8/256 MS: 1 PersAutoDict- DE: "\000\000"-
#579948 REDUCE cov: 170 ft: 171 corp: 17/413b lim: 4096 exec/s: 38663 rss: 31Mb L: 7/256 MS: 1 EraseBytes-
#
# Found key 6
#
UndefinedBehaviorSanitizer:DEADLYSIGNAL
==3243305==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address 0x000000000000 (pc 0x55cbe333a09d bp 0x7f3114afadf0 sp 0x7f3114afade0 T3243308)
==3243305==The signal is caused by a WRITE memory access.
==3243305==Hint: address points to the zero page.
#0 0x55cbe333a09d in check6 /home/andy/z/zephyr/samples/subsys/debug/fuzz/src/main.c:48:1

View file

@ -0,0 +1 @@
CONFIG_ARCH_POSIX_LIBFUZZER=y

View file

@ -0,0 +1,16 @@
sample:
description: native_posix-based libfuzzer example
name: fuzz
tests:
debug.fuzz:
toolchain_allow: llvm
platform_allow: native_posix_64
harness: console
harness_config:
type: one_line
# The sample will run all the way to key 6 (to show off its
# ability to search a 1-in-2^56 haystack), but that takes 20-60s
# and is quite variable. It's enough to know that it's finding
# new code paths by the third new coverage announcement.
regex:
- "Found key 2"

View file

@ -0,0 +1,79 @@
/* Copyright (c) 2022 Google, LLC.
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/zephyr.h>
#include <string.h>
/* 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(uint8_t *data, size_t sz); \
void __attribute__((noinline)) check##cur(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" */
extern uint8_t *posix_fuzz_buf, posix_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);
}
void 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(posix_fuzz_buf, posix_fuzz_sz);
}
}