test/fuzz: build with CMake, keep the OSS-Fuzz make entry points (#4219) Closes #4209.
diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index ff75a8b..126c2b1 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml
@@ -10,6 +10,9 @@ - '**.cpp' - '**.cxx' - '**.h' + - 'test/fuzz/**' + - '!test/fuzz/**.md' + - '.github/workflows/cifuzz.yml' jobs: Fuzzing: runs-on: ubuntu-latest
diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 09f912b..1717005 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml
@@ -34,15 +34,19 @@ - name: Run pre-commit uses: pre-commit/action@v3.0.1 - - name: Build Fuzzer + - name: Fuzz run: | - sudo apt install libc++-dev libc++abi-dev - clang --version - export CC=clang - export CXX=clang++ - fuzz_harness=$(ls -d test/fuzz/device/*/) - for h in $fuzz_harness - do - make -C $h get-deps - make -C $h all + python3 tools/get_deps.py + cmake -S test/fuzz -B test/fuzz/_build -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ + cmake --build test/fuzz/_build --parallel + ctest --test-dir test/fuzz/_build --output-on-failure + pids="" + for d in test/fuzz/device/*/; do + h=$(basename $d) + test/fuzz/_build/$h -max_total_time=10 test/fuzz/_build/corpus/$h > fuzz-$h.log 2>&1 & + pids="$pids $!" done + rc=0 + for p in $pids; do wait $p || rc=1; done + if [ $rc -ne 0 ]; then cat fuzz-*.log; else tail -n 3 fuzz-*.log; fi + exit $rc
diff --git a/.oss-scanner/Dockerfile b/.oss-scanner/Dockerfile index 13ccff8..1761241 100644 --- a/.oss-scanner/Dockerfile +++ b/.oss-scanner/Dockerfile
@@ -5,7 +5,7 @@ CC=clang \ CXX=clang++ RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates build-essential clang libclang-rt-14-dev lld libc++-dev libc++abi-dev git python3 ruby unzip \ + ca-certificates build-essential clang cmake libclang-rt-14-dev lld git python3 ruby unzip \ && rm -rf /var/lib/apt/lists/* # pinned like .github/workflows/pre-commit.yml RUN gem install --no-document ceedling -v 1.0.1 @@ -13,13 +13,13 @@ COPY . /src WORKDIR /src -# COVERAGE_FLAGS is cleared as OSS-Fuzz does: libFuzzer refuses to run a binary built with make.mk's default -# -fsanitize-coverage=trace-pc-guard +# same make entry points OSS-Fuzz uses; each harness then replays its seeds once RUN python3 tools/get_deps.py && \ for h in test/fuzz/device/*/; do \ name=$(basename "$h"); \ - make -C "$h" all COVERAGE_FLAGS= && \ - if [ -f "$h/${name}_seed_corpus.zip" ]; then unzip -qo "$h/${name}_seed_corpus.zip" -d "$h/corpus"; fi \ + make -C "$h" all && mkdir -p "$h/corpus" && \ + if [ -f "$h/${name}_seed_corpus.zip" ]; then unzip -qo "$h/${name}_seed_corpus.zip" -d "$h/corpus"; fi && \ + "$h/_build/$name" -runs=0 "$h/corpus" \ || exit 1; \ done
diff --git a/.oss-scanner/threat_model.md b/.oss-scanner/threat_model.md index f945128..789cf50 100644 --- a/.oss-scanner/threat_model.md +++ b/.oss-scanner/threat_model.md
@@ -28,9 +28,14 @@ those libraries stays in scope. ## How to exercise it -- libFuzzer + ASan harnesses: `test/fuzz/device/<cdc|msc|net|net_ncm>/_build/<name>`; where a seed archive exists - it is unpacked to `test/fuzz/device/<name>/corpus/`. The `cdc`, `msc` and `net` harnesses drive the device stack through a synthetic - controller; `net_ncm` tests the NCM receive validator alone. None covers host mode. +- libFuzzer + ASan harnesses: `test/fuzz/device/<cdc|msc|net|net_ncm>/_build/<name>`, seeds unpacked to + `test/fuzz/device/<name>/corpus/` (empty for `net`). Replay with `<binary> -runs=0 <corpus or file>`; rebuild offline + with `make -C test/fuzz/device/<name> all` (CMake, `test/fuzz/CMakeLists.txt`). +- Harness reach is narrow; absence of a harness crash is not evidence of safety. `cdc`, `msc` and `net` drive the device + stack through a synthetic controller that delivers bus events and SETUP packets but never completes a transfer, so + control data stages and class bulk data (MSC SCSI, RNDIS/ECM payloads) are not reached. The MSC and ECM/RNDIS + drivers are never opened either: `msc` uses endpoint 5 while its build allows five endpoints (0-4), and `net`'s + descriptor advertises only CDC. `net_ncm` tests the NCM receive validator alone. None covers host mode. - Unit tests: `cd test/unit-test && ceedling test:all` (device and host classes, hub, FIFO). ## How you rate severity
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt new file mode 100644 index 0000000..fabaaea --- /dev/null +++ b/test/fuzz/CMakeLists.txt
@@ -0,0 +1,134 @@ +cmake_minimum_required(VERSION 3.20) + +project(tinyusb_fuzz C CXX) + +cmake_path(SET TOP NORMALIZE ${CMAKE_CURRENT_LIST_DIR}/../..) +set(TINYUSB_FUZZ_TARGET "" CACHE STRING "Harness to configure: cdc, msc, net or net_ncm; empty for all") + +if (NOT CMAKE_C_COMPILER_ID MATCHES "Clang" OR NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang") + message(FATAL_ERROR "fuzz harnesses need clang: configure with CC=clang CXX=clang++") +endif () + +# OSS-Fuzz (LIB_FUZZING_ENGINE defined) owns the engine, sanitizer, coverage and optimisation flags through +# CFLAGS/CXXFLAGS; its trunk clang also gains warnings over time, so -Werror is local only. +if (DEFINED ENV{LIB_FUZZING_ENGINE}) + separate_arguments(FUZZ_ENGINE UNIX_COMMAND "$ENV{LIB_FUZZING_ENGINE}") + set(FUZZ_OPTIONS "") +else () + set(FUZZ_ENGINE -fsanitize=fuzzer,address) + set(FUZZ_OPTIONS -fsanitize=fuzzer-no-link,address -O1 -g -Werror) +endif () + +# unconditional so a reused build dir never keeps seed tests from an earlier local-mode configure +enable_testing() + +set(FUZZ_WARNINGS + -Wall + -Wextra + -Wfatal-errors + -Wdouble-promotion + -Wstrict-overflow + -Wfloat-equal + -Wundef + -Wshadow + -Wwrite-strings + -Wsign-compare + -Wmissing-format-attribute + -Wunreachable-code + -Wcast-align + -Wcast-qual + -Wnull-dereference + -Wuninitialized + -Wunused + -Wredundant-decls + -Wno-error=unreachable-code + $<$<COMPILE_LANGUAGE:C>:-Wstrict-prototypes -Werror=implicit-function-declaration> + $<$<COMPILE_LANGUAGE:CXX>:-Wno-c++11-narrowing> + ) + +# target_link_libraries rather than link options: OSS-Fuzz's AFL, honggfuzz and centipede engines are archive paths +function(fuzz_target_common TARGET) + target_link_libraries(${TARGET} PRIVATE ${FUZZ_ENGINE}) + # sections + gc drop stack paths whose controller hooks the harness lacks (e.g. dcd_edpt_iso_alloc); GNU ld keeps + # them alive through the sanitizer-coverage tables, lld does not + target_compile_options(${TARGET} PRIVATE -ffunction-sections -fdata-sections -fno-strict-aliasing ${FUZZ_WARNINGS} ${FUZZ_OPTIONS}) + target_link_options(${TARGET} PRIVATE -fuse-ld=lld -Wl,--gc-sections) + set_target_properties(${TARGET} PROPERTIES C_STANDARD 99 CXX_STANDARD 17 LINKER_LANGUAGE CXX) +endfunction() + +# Replay the seed archive once (local libFuzzer builds only; OSS-Fuzz exercises its own engines) +function(fuzz_add_seed_test NAME) + cmake_parse_arguments(ARG "" "SEED" "" ${ARGN}) + if (DEFINED ENV{LIB_FUZZING_ENGINE}) + return() + endif () + set(CORPUS ${CMAKE_CURRENT_BINARY_DIR}/corpus/${NAME}) + file(REMOVE_RECURSE ${CORPUS}) + file(MAKE_DIRECTORY ${CORPUS}) + if (ARG_SEED) + file(ARCHIVE_EXTRACT INPUT ${ARG_SEED} DESTINATION ${CORPUS}) + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${ARG_SEED}) + endif () + add_test(NAME ${NAME}_seed_replay COMMAND ${NAME} -runs=0 ${CORPUS}) + set_tests_properties(${NAME}_seed_replay PROPERTIES TIMEOUT 300) +endfunction() + +# Device stack harness driven through the synthetic controller in dcd_fuzz.cc +function(fuzz_add_device_harness NAME) + cmake_parse_arguments(ARG "" "" "SOURCES;INCLUDES" ${ARGN}) + set(DIR ${CMAKE_CURRENT_LIST_DIR}/device/${NAME}) + add_executable(${NAME} + ${TOP}/src/tusb.c + ${TOP}/src/common/tusb_fifo.c + ${TOP}/src/device/usbd.c + ${CMAKE_CURRENT_LIST_DIR}/dcd_fuzz.cc + ${CMAKE_CURRENT_LIST_DIR}/fuzz.cc + ${CMAKE_CURRENT_LIST_DIR}/usbd_fuzz.cc + ${DIR}/src/fuzz.cc + ${DIR}/src/usb_descriptors.cc + ${ARG_SOURCES} + ) + target_include_directories(${NAME} PRIVATE ${DIR}/src ${TOP}/src ${TOP}/test ${ARG_INCLUDES}) + target_compile_definitions(${NAME} PRIVATE OPT_MCU_FUZZ=1 CFG_TUSB_MCU=OPT_MCU_FUZZ _FUZZ) + fuzz_target_common(${NAME}) +endfunction() + +if (TINYUSB_FUZZ_TARGET STREQUAL "" OR TINYUSB_FUZZ_TARGET STREQUAL "cdc") + fuzz_add_device_harness(cdc SOURCES ${TOP}/src/class/cdc/cdc_device.c) + fuzz_add_seed_test(cdc SEED ${CMAKE_CURRENT_LIST_DIR}/device/cdc/cdc_seed_corpus.zip) +endif () + +if (TINYUSB_FUZZ_TARGET STREQUAL "" OR TINYUSB_FUZZ_TARGET STREQUAL "msc") + fuzz_add_device_harness(msc SOURCES + ${TOP}/src/class/msc/msc_device.c + ${CMAKE_CURRENT_LIST_DIR}/msc_fuzz.cc + ) + fuzz_add_seed_test(msc SEED ${CMAKE_CURRENT_LIST_DIR}/device/msc/msc_seed_corpus.zip) +endif () + +if (TINYUSB_FUZZ_TARGET STREQUAL "" OR TINYUSB_FUZZ_TARGET STREQUAL "net") + # lwIP headers only (rndis_reports.c needs netif/ethernet.h); fetched by tools/get_deps.py + if (NOT EXISTS ${TOP}/lib/lwip/src/include/lwip/init.h) + message(FATAL_ERROR "lib/lwip is missing: run python3 tools/get_deps.py") + endif () + fuzz_add_device_harness(net + SOURCES + ${TOP}/src/class/cdc/cdc_device.c + ${TOP}/src/class/net/ecm_rndis_device.c + ${TOP}/lib/networking/rndis_reports.c + ${CMAKE_CURRENT_LIST_DIR}/net_fuzz.cc + INCLUDES + ${TOP}/lib/lwip/src/include + ${TOP}/lib/networking + ) + fuzz_add_seed_test(net) +endif () + +# NCM receive validator alone: fuzz.c #includes ncm_device.c +if (TINYUSB_FUZZ_TARGET STREQUAL "" OR TINYUSB_FUZZ_TARGET STREQUAL "net_ncm") + set(DIR ${CMAKE_CURRENT_LIST_DIR}/device/net_ncm) + add_executable(net_ncm ${DIR}/fuzz.c) + target_include_directories(net_ncm PRIVATE ${DIR} ${TOP}/src) + fuzz_target_common(net_ncm) + fuzz_add_seed_test(net_ncm SEED ${DIR}/net_ncm_seed_corpus.zip) +endif ()
diff --git a/test/fuzz/README.md b/test/fuzz/README.md new file mode 100644 index 0000000..7e30463 --- /dev/null +++ b/test/fuzz/README.md
@@ -0,0 +1,40 @@ +# Fuzz harnesses + +libFuzzer harnesses for the device stack, built with CMake on the host: + +| Harness | Exercises | +|-----------|--------------------------------------------------------------------| +| `cdc` | device stack + CDC, through the synthetic controller `dcd_fuzz.cc` | +| `msc` | device stack + MSC | +| `net` | device stack + CDC and ECM/RNDIS | +| `net_ncm` | the NCM receive validator alone | + +The synthetic controller never completes a transfer, so only bus events and SETUP packets reach the stack; see +`.oss-scanner/threat_model.md` for the current limits. + +## Build and run + +Needs clang with the libFuzzer runtime, lld and CMake >= 3.20 (>= 3.24 for the Make entry points). `net` needs +`lib/lwip` headers: `python3 tools/get_deps.py`. + +```sh +CC=clang CXX=clang++ cmake -S test/fuzz -B test/fuzz/_build +cmake --build test/fuzz/_build --parallel +ctest --test-dir test/fuzz/_build # replays each harness's seed corpus once +mkdir -p cdc_corpus +test/fuzz/_build/cdc -max_total_time=60 cdc_corpus test/fuzz/_build/corpus/cdc +``` + +New inputs go to the first corpus directory; `_build/corpus/<harness>` is re-extracted from the seed zip on every +configure. + +CMake caches the compiler and `CFLAGS`/`CXXFLAGS` on first configure: use a fresh build directory to change them. +Without `LIB_FUZZING_ENGINE` the build adds libFuzzer + ASan, `-O1 -g` and `-Werror`. With it set (OSS-Fuzz), +the engine, sanitizers and optimisation come from `LIB_FUZZING_ENGINE` and `CFLAGS`/`CXXFLAGS` only. + +## OSS-Fuzz + +OSS-Fuzz's `projects/tinyusb/build.sh` runs `make -C test/fuzz/device/<harness> get-deps all` and copies +`_build/<harness>` and `<harness>_seed_corpus.zip`. Those Makefiles are thin wrappers around the CMake build +(`fuzz.mk`); keep that interface. `make -C test/fuzz/device/net_ncm regression` replays the NCM seed +archive (a crafted out-of-bounds NTB) once.
diff --git a/test/fuzz/device/cdc/CMakeLists.txt b/test/fuzz/device/cdc/CMakeLists.txt deleted file mode 100644 index 85094cf..0000000 --- a/test/fuzz/device/cdc/CMakeLists.txt +++ /dev/null
@@ -1,26 +0,0 @@ -cmake_minimum_required(VERSION 3.5) - -include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake) - -project(cdc) - -# Checks this example is valid for the family and initializes the project -family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}) - -add_executable(${PROJECT_NAME}) - -# Example source -target_sources(${PROJECT_NAME} PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c - ${CMAKE_CURRENT_SOURCE_DIR}/src/msc_disk.c - ${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.c - ) - -# Example include -target_include_directories(${PROJECT_NAME} PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/src - ) - -# Configure compilation flags and libraries for the example without RTOS. -# See the corresponding function in hw/bsp/FAMILY/family.cmake for details. -family_configure_device_example(${PROJECT_NAME} noos)
diff --git a/test/fuzz/device/cdc/Makefile b/test/fuzz/device/cdc/Makefile index d448907..b80d48b 100644 --- a/test/fuzz/device/cdc/Makefile +++ b/test/fuzz/device/cdc/Makefile
@@ -1,11 +1 @@ -include ../../make.mk - -INC += \ - src \ - - -# Example source -SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(wildcard src/*.c)) -SRC_CXX += $(addprefix $(EXAMPLE_PATH)/, $(wildcard src/*.cc)) - -include ../../rules.mk +include ../../fuzz.mk
diff --git a/test/fuzz/device/msc/CMakeLists.txt b/test/fuzz/device/msc/CMakeLists.txt deleted file mode 100644 index 6eb6c8c..0000000 --- a/test/fuzz/device/msc/CMakeLists.txt +++ /dev/null
@@ -1,26 +0,0 @@ -cmake_minimum_required(VERSION 3.5) - -include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake) - -project(msc) - -# Checks this example is valid for the family and initializes the project -family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}) - -add_executable(${PROJECT_NAME}) - -# Example source -target_sources(${PROJECT_NAME} PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c - ${CMAKE_CURRENT_SOURCE_DIR}/src/msc_disk.c - ${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.c - ) - -# Example include -target_include_directories(${PROJECT_NAME} PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/src - ) - -# Configure compilation flags and libraries for the example without RTOS. -# See the corresponding function in hw/bsp/FAMILY/family.cmake for details. -family_configure_device_example(${PROJECT_NAME} noos)
diff --git a/test/fuzz/device/msc/Makefile b/test/fuzz/device/msc/Makefile index d448907..b80d48b 100644 --- a/test/fuzz/device/msc/Makefile +++ b/test/fuzz/device/msc/Makefile
@@ -1,11 +1 @@ -include ../../make.mk - -INC += \ - src \ - - -# Example source -SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(wildcard src/*.c)) -SRC_CXX += $(addprefix $(EXAMPLE_PATH)/, $(wildcard src/*.cc)) - -include ../../rules.mk +include ../../fuzz.mk
diff --git a/test/fuzz/device/net/CMakeLists.txt b/test/fuzz/device/net/CMakeLists.txt deleted file mode 100644 index 84e92ad..0000000 --- a/test/fuzz/device/net/CMakeLists.txt +++ /dev/null
@@ -1,26 +0,0 @@ -cmake_minimum_required(VERSION 3.5) - -include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake) - -project(net) - -# Checks this example is valid for the family and initializes the project -family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}) - -add_executable(${PROJECT_NAME}) - -# Example source -target_sources(${PROJECT_NAME} PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c - ${CMAKE_CURRENT_SOURCE_DIR}/src/msc_disk.c - ${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.c - ) - -# Example include -target_include_directories(${PROJECT_NAME} PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/src - ) - -# Configure compilation flags and libraries for the example without RTOS. -# See the corresponding function in hw/bsp/FAMILY/family.cmake for details. -family_configure_device_example(${PROJECT_NAME} noos)
diff --git a/test/fuzz/device/net/Makefile b/test/fuzz/device/net/Makefile index 45c684e..8caeb86 100644 --- a/test/fuzz/device/net/Makefile +++ b/test/fuzz/device/net/Makefile
@@ -1,67 +1,4 @@ -include ../../make.mk +include ../../fuzz.mk -# suppress warning caused by lwip -CFLAGS += \ - -Wno-error=null-dereference \ - -Wno-error=unused-parameter \ - -Wno-error=unused-variable - -INC += \ - src \ - $(TOP)/lib/lwip/src/include \ - $(TOP)/lib/lwip/src/include/ipv4 \ - $(TOP)/lib/lwip/src/include/lwip/apps \ - $(TOP)/lib/networking - -# Example source -SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(wildcard src/*.c)) -SRC_CXX += $(addprefix $(EXAMPLE_PATH)/, $(wildcard src/*.cc)) - -# lwip sources -SRC_C += \ - lib/lwip/src/core/altcp.c \ - lib/lwip/src/core/altcp_alloc.c \ - lib/lwip/src/core/altcp_tcp.c \ - lib/lwip/src/core/def.c \ - lib/lwip/src/core/dns.c \ - lib/lwip/src/core/inet_chksum.c \ - lib/lwip/src/core/init.c \ - lib/lwip/src/core/ip.c \ - lib/lwip/src/core/mem.c \ - lib/lwip/src/core/memp.c \ - lib/lwip/src/core/netif.c \ - lib/lwip/src/core/pbuf.c \ - lib/lwip/src/core/raw.c \ - lib/lwip/src/core/stats.c \ - lib/lwip/src/core/sys.c \ - lib/lwip/src/core/tcp.c \ - lib/lwip/src/core/tcp_in.c \ - lib/lwip/src/core/tcp_out.c \ - lib/lwip/src/core/timeouts.c \ - lib/lwip/src/core/udp.c \ - lib/lwip/src/core/ipv4/autoip.c \ - lib/lwip/src/core/ipv4/dhcp.c \ - lib/lwip/src/core/ipv4/etharp.c \ - lib/lwip/src/core/ipv4/icmp.c \ - lib/lwip/src/core/ipv4/igmp.c \ - lib/lwip/src/core/ipv4/ip4.c \ - lib/lwip/src/core/ipv4/ip4_addr.c \ - lib/lwip/src/core/ipv4/ip4_frag.c \ - lib/lwip/src/core/ipv6/dhcp6.c \ - lib/lwip/src/core/ipv6/ethip6.c \ - lib/lwip/src/core/ipv6/icmp6.c \ - lib/lwip/src/core/ipv6/inet6.c \ - lib/lwip/src/core/ipv6/ip6.c \ - lib/lwip/src/core/ipv6/ip6_addr.c \ - lib/lwip/src/core/ipv6/ip6_frag.c \ - lib/lwip/src/core/ipv6/mld6.c \ - lib/lwip/src/core/ipv6/nd6.c \ - lib/lwip/src/netif/ethernet.c \ - lib/lwip/src/netif/slipif.c \ - lib/lwip/src/apps/http/httpd.c \ - lib/lwip/src/apps/http/fs.c \ - lib/networking/dhserver.c \ - lib/networking/dnserver.c \ - lib/networking/rndis_reports.c - -include ../../rules.mk +get-deps: + python3 $(TOP)/tools/get_deps.py
diff --git a/test/fuzz/device/net_ncm/Makefile b/test/fuzz/device/net_ncm/Makefile index df5a613..1482aaa 100644 --- a/test/fuzz/device/net_ncm/Makefile +++ b/test/fuzz/device/net_ncm/Makefile
@@ -1,43 +1,7 @@ -# Focused fuzz harness for the NCM receive path (recv_validate_datagram). -# -# It is self-contained: it #includes src/class/net/ncm_device.c and stubs the -# few usbd/glue symbols, so it does not link the whole device stack. -# -# make # build the libFuzzer target: _build/net_ncm -# make regression # build + replay the crafted seed under ASan (no engine) -# make clean +include ../../fuzz.mk -TOP := ../../../.. -BUILD := _build +.PHONY: regression -CC ?= clang - -INC += \ - $(TOP)/src \ - . - -CFLAGS += $(addprefix -I,$(INC)) -g -O1 -fsanitize=address - -FUZZ_FLAGS := -fsanitize=fuzzer - -.PHONY: all regression clean get-deps - -all: $(BUILD)/net_ncm - -# No external dependencies (the harness only needs the in-tree NCM driver). -get-deps: - -# libFuzzer supplies its own main(), so drop the standalone driver here. -$(BUILD)/net_ncm: fuzz.c - mkdir -p $(@D) - $(CC) $(CFLAGS) $(FUZZ_FLAGS) -DNO_MAIN -o $@ $< - -# Standalone build with a plain main(): replays the crafted seed and any extra -# corpus files given as arguments. Used as a deterministic regression. -regression: fuzz.c - mkdir -p $(BUILD) - $(CC) $(CFLAGS) -o $(BUILD)/net_ncm_regression $< - ./$(BUILD)/net_ncm_regression - -clean: - $(RM) -rf $(BUILD) *.dSYM +# replays the seed archive (the crafted out-of-bounds NTB) once under ASan +regression: all + ctest --test-dir $(BUILD) --output-on-failure --no-tests=error -R '^net_ncm_seed_replay$$'
diff --git a/test/fuzz/device/net_ncm/fuzz.c b/test/fuzz/device/net_ncm/fuzz.c index 3052636..7f6247e 100644 --- a/test/fuzz/device/net_ncm/fuzz.c +++ b/test/fuzz/device/net_ncm/fuzz.c
@@ -21,7 +21,6 @@ // this reproduces that out-of-bounds read under -fsanitize=address. #include <stdint.h> -#include <stdio.h> #include <stdlib.h> #include <string.h> @@ -88,56 +87,3 @@ free(ntb); return 0; } - -//--------------------------------------------------------------------+ -// Standalone driver (built when there is no libFuzzer engine): replays the -// crafted NTB, or any corpus files passed on the command line. -//--------------------------------------------------------------------+ -#ifndef NO_MAIN -static void run_crafted_seed(void) { - uint8_t buf[64]; - memset(buf, 0, sizeof(buf)); - - // NTH16: signature, wHeaderLength = sizeof(nth16_t), wSequence, wBlockLength, wNdpIndex - uint32_t nth_sig = NTH16_SIGNATURE; - memcpy(buf + 0, &nth_sig, 4); - uint16_t v; - v = sizeof(nth16_t); memcpy(buf + 4, &v, 2); - v = 0; memcpy(buf + 6, &v, 2); - v = sizeof(buf); memcpy(buf + 8, &v, 2); - v = sizeof(nth16_t); memcpy(buf + 10, &v, 2); - - // NDP16 at wNdpIndex: signature, wLength (unbounded pre-fix), wNextNdpIndex - uint32_t ndp_sig = NDP16_SIGNATURE_NCM0; - memcpy(buf + sizeof(nth16_t) + 0, &ndp_sig, 4); - v = 0xFFF0; memcpy(buf + sizeof(nth16_t) + 4, &v, 2); - v = 0; memcpy(buf + sizeof(nth16_t) + 6, &v, 2); - - LLVMFuzzerTestOneInput(buf, sizeof(buf)); -} - -int main(int argc, char **argv) { - if (argc < 2) { - run_crafted_seed(); - return 0; - } - for (int i = 1; i < argc; i++) { - FILE *f = fopen(argv[i], "rb"); - if (f == NULL) { - continue; - } - fseek(f, 0, SEEK_END); - long len = ftell(f); - fseek(f, 0, SEEK_SET); - if (len > 0) { - uint8_t *buf = (uint8_t *) malloc((size_t) len); - if (buf != NULL && fread(buf, 1, (size_t) len, f) == (size_t) len) { - LLVMFuzzerTestOneInput(buf, (size_t) len); - } - free(buf); - } - fclose(f); - } - return 0; -} -#endif
diff --git a/test/fuzz/device/net_ncm/tusb_config.h b/test/fuzz/device/net_ncm/tusb_config.h index b5e3a1f..b26cd76 100644 --- a/test/fuzz/device/net_ncm/tusb_config.h +++ b/test/fuzz/device/net_ncm/tusb_config.h
@@ -13,6 +13,7 @@ // because the harness drives recv_validate_datagram() directly. #define CFG_TUSB_MCU OPT_MCU_NONE #define CFG_TUSB_OS OPT_OS_NONE +#define TUP_DCD_ENDPOINT_MAX 8 // no controller; tusb_mcu.h would warn and default to 8 #define CFG_TUD_ENABLED 1 #define CFG_TUD_ENDPOINT0_SIZE 64
diff --git a/test/fuzz/fuzz.h b/test/fuzz/fuzz.h index 6aa4949..cacca8d 100644 --- a/test/fuzz/fuzz.h +++ b/test/fuzz/fuzz.h
@@ -24,6 +24,7 @@ */ #pragma once +#include <stddef.h> #include <stdint.h> #ifdef __cplusplus
diff --git a/test/fuzz/fuzz.mk b/test/fuzz/fuzz.mk new file mode 100644 index 0000000..5bb31e8 --- /dev/null +++ b/test/fuzz/fuzz.mk
@@ -0,0 +1,25 @@ +# Make entry points kept for OSS-Fuzz's projects/tinyusb/build.sh, which runs `make -C <harness> get-deps all` and +# copies _build/<harness>; the build itself is test/fuzz/CMakeLists.txt. +TOP := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/../..) +HARNESS := $(notdir $(CURDIR)) +BUILD := _build + +# make's built-in cc must not reach CMake; an environment or command-line CC still wins +ifeq ($(origin CC),default) +export CC := clang +endif +ifeq ($(origin CXX),default) +export CXX := clang++ +endif + +.PHONY: all get-deps clean + +# --fresh so CC/CXX/CFLAGS/CXXFLAGS/LDFLAGS changes are never masked by CMake's cache +all: + cmake --fresh -S $(TOP)/test/fuzz -B $(BUILD) -DTINYUSB_FUZZ_TARGET=$(HARNESS) + cmake --build $(BUILD) --target $(HARNESS) --parallel + +get-deps: + +clean: + cmake -E rm -rf $(BUILD)
diff --git a/test/fuzz/make.mk b/test/fuzz/make.mk deleted file mode 100644 index 733a571..0000000 --- a/test/fuzz/make.mk +++ /dev/null
@@ -1,128 +0,0 @@ -# --------------------------------------- -# Common make definition for all examples -# --------------------------------------- - -#-------------- TOP and EXAMPLE_PATH ------------ - -# Set TOP to be the path to get from the current directory (where make was -# invoked) to the top of the tree. $(lastword $(MAKEFILE_LIST)) returns -# the name of this makefile relative to where make was invoked. -THIS_MAKEFILE := $(lastword $(MAKEFILE_LIST)) - -# strip off /tools/top.mk to get for example ../../.. -# and Set TOP to an absolute path -TOP = $(abspath $(subst make.mk,../..,$(THIS_MAKEFILE))) - -# Set EXAMPLE_PATH to the relative path from TOP to the current directory, ie examples/device/cdc_msc_freertos -EXAMPLE_PATH = $(subst $(TOP)/,,$(abspath .)) - -# Detect whether shell style is windows or not -# https://stackoverflow.com/questions/714100/os-detecting-makefile/52062069#52062069 -ifeq '$(findstring ;,$(PATH))' ';' -# PATH contains semicolon - so we're definitely on Windows. -CMDEXE := 1 - -# makefile shell commands should use syntax for DOS CMD, not unix sh -# Unfortunately, SHELL may point to sh or bash, which can't accept DOS syntax. -# We can't just use sh, because while sh and/or bash shell may be available, -# many Windows environments won't have utilities like realpath used below, so... -# Force DOS command shell on Windows. -SHELL := cmd.exe -endif - -# Build directory -BUILD := _build -PROJECT := $(notdir $(CURDIR)) - -# Handy check parameter function -check_defined = \ - $(strip $(foreach 1,$1, \ - $(call __check_defined,$1,$(strip $(value 2))))) -__check_defined = \ - $(if $(value $1),, \ - $(error Undefined make flag: $1$(if $2, ($2)))) - -#-------------- Fuzz harness compiler ------------ - -CC ?= clang -CXX ?= clang++ -GDB ?= gdb -OBJCOPY = objcopy -SIZE = size -MKDIR = mkdir - -ifeq ($(CMDEXE),1) - CP = copy - RM = del - PYTHON = python -else - SED = sed - CP = cp - RM = rm - PYTHON = python3 -endif - -#-------------- Fuzz harness flags ------------ -COVERAGE_FLAGS ?= -fsanitize-coverage=trace-pc-guard -SANITIZER_FLAGS ?= -fsanitize=fuzzer \ - -fsanitize=address - -CFLAGS += $(COVERAGE_FLAGS) $(SANITIZER_FLAGS) - -#-------------- Source files and compiler flags -------------- -INC += $(TOP)/test - -# Compiler Flags -CFLAGS += \ - -ggdb \ - -fdata-sections \ - -ffunction-sections \ - -fno-strict-aliasing \ - -Wall \ - -Wextra \ - -Werror \ - -Wfatal-errors \ - -Wdouble-promotion \ - -Wstrict-prototypes \ - -Wstrict-overflow \ - -Werror-implicit-function-declaration \ - -Wfloat-equal \ - -Wundef \ - -Wshadow \ - -Wwrite-strings \ - -Wsign-compare \ - -Wmissing-format-attribute \ - -Wunreachable-code \ - -Wcast-align \ - -Wcast-qual \ - -Wnull-dereference \ - -Wuninitialized \ - -Wunused \ - -Wredundant-decls \ - -O1 - -CFLAGS += \ - -Wno-error=unreachable-code \ - -DOPT_MCU_FUZZ=1 \ - -DCFG_TUSB_MCU=OPT_MCU_FUZZ \ - -D_FUZZ - -CXXFLAGS += \ - -xc++ \ - -Wno-c++11-narrowing \ - -fno-implicit-templates - -# conversion is too strict for most mcu driver, may be disable sign/int/arith-conversion -# -Wconversion - -# Debugging/Optimization -ifeq ($(DEBUG), 1) - CFLAGS += -Og -else - CFLAGS += $(CFLAGS_OPTIMIZED) -endif - -# Log level is mapped to TUSB DEBUG option -ifneq ($(LOG),) - CFLAGS += -DCFG_TUSB_DEBUG=$(LOG) -endif
diff --git a/test/fuzz/rules.mk b/test/fuzz/rules.mk deleted file mode 100644 index c143303..0000000 --- a/test/fuzz/rules.mk +++ /dev/null
@@ -1,160 +0,0 @@ -# --------------------------------------- -# Common make rules for all examples -# --------------------------------------- - -# Set all as default goal -.DEFAULT_GOAL := all - -# --------------------------------------- -# Compiler Flags -# --------------------------------------- - -LIBS_GCC ?= -lm - -# libc -LIBS += $(LIBS_GCC) - -ifneq ($(BOARD), spresense) -LIBS += -lc -Wl,-Bstatic -lc++ -Wl,-Bdynamic -endif - -# TinyUSB Stack source -SRC_C += \ - src/tusb.c \ - src/common/tusb_fifo.c \ - src/device/usbd.c \ - src/class/audio/audio_device.c \ - src/class/cdc/cdc_device.c \ - src/class/dfu/dfu_device.c \ - src/class/dfu/dfu_rt_device.c \ - src/class/hid/hid_device.c \ - src/class/midi/midi_device.c \ - src/class/msc/msc_device.c \ - src/class/mtp/mtp_device.c \ - src/class/printer/printer_device.c \ - src/class/net/ecm_rndis_device.c \ - src/class/net/ncm_device.c \ - src/class/usbtmc/usbtmc_device.c \ - src/class/video/video_device.c \ - src/class/vendor/vendor_device.c - - -# Fuzzers are c++ -SRC_CXX += \ - test/fuzz/dcd_fuzz.cc \ - test/fuzz/fuzz.cc \ - test/fuzz/msc_fuzz.cc \ - test/fuzz/net_fuzz.cc \ - test/fuzz/usbd_fuzz.cc - -# TinyUSB stack include -INC += $(TOP)/src - -CFLAGS += $(addprefix -I,$(INC)) -CXXFLAGS += -std=c++17 - -# LTO makes it difficult to analyze map file for optimizing size purpose -# We will run this option in ci -ifeq ($(NO_LTO),1) -CFLAGS := $(filter-out -flto,$(CFLAGS)) -endif - -ifneq ($(LD_FILE),) -LDFLAGS_LD_FILE ?= -Wl,-T,$(TOP)/$(LD_FILE) -endif - -LDFLAGS += $(CFLAGS) $(LDFLAGS_LD_FILE) -fuse-ld=lld -Wl,-Map=$@.map -Wl,--cref -Wl,-gc-sections -ifneq ($(SKIP_NANOLIB), 1) -endif - -ASFLAGS += $(CFLAGS) - -# Assembly files can be name with upper case .S, convert it to .s -SRC_S := $(SRC_S:.S=.s) - -# Due to GCC LTO bug https://bugs.launchpad.net/gcc-arm-embedded/+bug/1747966 -# assembly file should be placed first in linking order -# '_asm' suffix is added to object of assembly file -OBJ += $(addprefix $(BUILD)/obj/, $(SRC_S:.s=_asm.o)) -OBJ += $(addprefix $(BUILD)/obj/, $(SRC_C:.c=.o)) -OBJ += $(addprefix $(BUILD)/obj/, $(SRC_CXX:.cc=_cxx.o)) - -# Verbose mode -ifeq ("$(V)","1") -$(info CFLAGS $(CFLAGS) ) $(info ) -$(info LDFLAGS $(LDFLAGS)) $(info ) -$(info ASFLAGS $(ASFLAGS)) $(info ) -endif - -# --------------------------------------- -# Rules -# --------------------------------------- - -all: $(BUILD)/$(PROJECT) - -OBJ_DIRS = $(sort $(dir $(OBJ))) -$(OBJ): | $(OBJ_DIRS) -$(OBJ_DIRS): -ifeq ($(CMDEXE),1) - @$(MKDIR) $(subst /,\,$@) -else - @$(MKDIR) -p $@ -endif - -$(BUILD)/$(PROJECT): $(OBJ) - @echo LINK $@ - @ $(CXX) -o $@ $(LIB_FUZZING_ENGINE) $^ $(LIBS) $(LDFLAGS) - -# We set vpath to point to the top of the tree so that the source files -# can be located. By following this scheme, it allows a single build rule -# to be used to compile all .c files. -vpath %.c . $(TOP) -$(BUILD)/obj/%.o: %.c - @echo CC $(notdir $@) - @$(CC) $(CFLAGS) -c -MD -o $@ $< - -# All cpp srcs -vpath %.cc . $(TOP) -$(BUILD)/obj/%_cxx.o: %.cc - @echo CXX $(notdir $@) - @$(CXX) $(CFLAGS) $(CXXFLAGS) -c -MD -o $@ $< - -# ASM sources lower case .s -vpath %.s . $(TOP) -$(BUILD)/obj/%_asm.o: %.s - @echo AS $(notdir $@) - @$(CC) -x assembler-with-cpp $(ASFLAGS) -c -o $@ $< - -# ASM sources upper case .S -vpath %.S . $(TOP) -$(BUILD)/obj/%_asm.o: %.S - @echo AS $(notdir $@) - @$(CC) -x assembler-with-cpp $(ASFLAGS) -c -o $@ $< - -.PHONY: clean -clean: -ifeq ($(CMDEXE),1) - rd /S /Q $(subst /,\,$(BUILD)) -else - $(RM) -rf $(BUILD) -endif -# ---------------- GNU Make End ----------------------- - -# get depenecies -.PHONY: get-deps -get-deps: - $(PYTHON) $(TOP)/tools/get_deps.py $(FAMILY) - -size: $(BUILD)/$(PROJECT) - -@echo '' - @$(SIZE) $< - -@echo '' - -# linkermap must be install previously at https://github.com/hathach/linkermap -linkermap: $(BUILD)/$(PROJECT) - @linkermap -v $<.map - -# Print out the value of a make variable. -# https://stackoverflow.com/questions/16467718/how-to-print-out-a-variable-in-makefile -print-%: - @echo $* = $($*)