From d6affd949f000caa7f09420e3c799326d9d9031b Mon Sep 17 00:00:00 2001 From: Leorize Date: Mon, 16 Dec 2024 15:55:14 -0600 Subject: [PATCH] ci: add ClusterFuzzLite --- .clusterfuzzlite/Dockerfile | 5 ++ .clusterfuzzlite/build.sh | 7 +++ .clusterfuzzlite/project.yaml | 1 + .github/workflows/fuzz_daily.yml | 40 +++++++++++++++ .github/workflows/fuzz_periodic.yml | 48 ++++++++++++++++++ .github/workflows/fuzz_pr.yml | 45 +++++++++++++++++ .gitignore | 1 + src/fuzz/Makefile | 33 +++++++++++++ src/fuzz/main.c | 77 +++++++++++++++++++++++++++++ src/fuzz/zfconf.h | 60 ++++++++++++++++++++++ src/fuzz/zforth_fuzzer.dict | 36 ++++++++++++++ 11 files changed, 353 insertions(+) create mode 100644 .clusterfuzzlite/Dockerfile create mode 100755 .clusterfuzzlite/build.sh create mode 100644 .clusterfuzzlite/project.yaml create mode 100644 .github/workflows/fuzz_daily.yml create mode 100644 .github/workflows/fuzz_periodic.yml create mode 100644 .github/workflows/fuzz_pr.yml create mode 100644 src/fuzz/Makefile create mode 100644 src/fuzz/main.c create mode 100644 src/fuzz/zfconf.h create mode 100644 src/fuzz/zforth_fuzzer.dict diff --git a/.clusterfuzzlite/Dockerfile b/.clusterfuzzlite/Dockerfile new file mode 100644 index 0000000..fa52575 --- /dev/null +++ b/.clusterfuzzlite/Dockerfile @@ -0,0 +1,5 @@ +FROM gcr.io/oss-fuzz-base/base-builder:v1 +RUN apt-get update && apt-get install -y make autoconf automake libtool +COPY . $SRC/zforth +WORKDIR $SRC/zforth +COPY .clusterfuzzlite/build.sh $SRC/ diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh new file mode 100755 index 0000000..604e6b6 --- /dev/null +++ b/.clusterfuzzlite/build.sh @@ -0,0 +1,7 @@ +#!/bin/bash -eu + +shopt -s nullglob + +make -C src/fuzz + +cp src/fuzz/zforth_fuzzer src/fuzz/*.dict "$OUT"/ diff --git a/.clusterfuzzlite/project.yaml b/.clusterfuzzlite/project.yaml new file mode 100644 index 0000000..b455aa3 --- /dev/null +++ b/.clusterfuzzlite/project.yaml @@ -0,0 +1 @@ +language: c diff --git a/.github/workflows/fuzz_daily.yml b/.github/workflows/fuzz_daily.yml new file mode 100644 index 0000000..5bb61ce --- /dev/null +++ b/.github/workflows/fuzz_daily.yml @@ -0,0 +1,40 @@ +name: ClusterFuzzLite batch fuzzing + +on: + schedule: + - cron: "0 0 * * *" + +permissions: read-all + +jobs: + fuzz: + name: Daily fuzzing + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sanitizer: + - address + + # XXX: UBSan fails due to SHR operator being able to exceed word size + # - undefined + + # FIXME: MSan fail due to zf_ctx not being fully initialized by zf_init + # - memory + + steps: + - name: Build Fuzzers (${{ matrix.sanitizer }}) + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + with: + language: c + github-token: ${{ github.token }} + sanitizer: ${{ matrix.sanitizer }} + + - name: Run Fuzzers (${{ matrix.sanitizer }}) + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + github-token: ${{ github.token }} + fuzz-seconds: 3600 + mode: code-change + parallel-fuzzing: true + output-sarif: true diff --git a/.github/workflows/fuzz_periodic.yml b/.github/workflows/fuzz_periodic.yml new file mode 100644 index 0000000..6cda480 --- /dev/null +++ b/.github/workflows/fuzz_periodic.yml @@ -0,0 +1,48 @@ +name: ClusterFuzzLite periodic tasks + +on: + schedule: + - cron: "0 0 * * 0" + +permissions: read-all + +jobs: + pruning: + name: Fuzz corpus pruning + runs-on: ubuntu-latest + + steps: + - name: Build Fuzzers (${{ matrix.sanitizer }}) + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + with: + language: c + github-token: ${{ github.token }} + + - name: Run Fuzzers (${{ matrix.sanitizer }}) + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + github-token: ${{ github.token }} + fuzz-seconds: 600 + mode: "prune" + output-sarif: true + + coverage: + name: Generate fuzzing coverage + runs-on: ubuntu-latest + + steps: + - name: Build Fuzzers (${{ matrix.sanitizer }}) + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + with: + language: c + github-token: ${{ github.token }} + sanitizer: coverage + + - name: Run Fuzzers (${{ matrix.sanitizer }}) + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + github-token: ${{ github.token }} + fuzz-seconds: 600 + mode: coverage + sanitizer: coverage + output-sarif: true diff --git a/.github/workflows/fuzz_pr.yml b/.github/workflows/fuzz_pr.yml new file mode 100644 index 0000000..f94bbf4 --- /dev/null +++ b/.github/workflows/fuzz_pr.yml @@ -0,0 +1,45 @@ +name: ClusterFuzzLite PR fuzzing + +on: + pull_request: + paths: + - "**" + +permissions: read-all + +jobs: + fuzz: + name: Fuzz test PR + runs-on: ubuntu-latest + concurrency: + group: ${{ github.workflows }}-${{ matrix.sanitizer }}-${{ github.ref }} + cancel-in-progress: true + + strategy: + fail-fast: false + matrix: + sanitizer: + - address + + # XXX: UBSan fails due to SHR operator being able to exceed word size + # - undefined + + # FIXME: MSan fail due to zf_ctx not being fully initialized by zf_init + # - memory + + steps: + - name: Build Fuzzers (${{ matrix.sanitizer }}) + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + with: + language: c + github-token: ${{ github.token }} + sanitizer: ${{ matrix.sanitizer }} + + - name: Run Fuzzers (${{ matrix.sanitizer }}) + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + github-token: ${{ github.token }} + fuzz-seconds: 300 + mode: code-change + parallel-fuzzing: true + output-sarif: true diff --git a/.gitignore b/.gitignore index 12a5861..db75fbb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,6 @@ go *.swp *.swo zforth +zforth_fuzzer zforth.save .zforth.hist diff --git a/src/fuzz/Makefile b/src/fuzz/Makefile new file mode 100644 index 0000000..40e5114 --- /dev/null +++ b/src/fuzz/Makefile @@ -0,0 +1,33 @@ +BIN := zforth_fuzzer +SRC := main.c zforth.c + +OBJS := $(subst .c,.o, $(SRC)) +DEPS := $(subst .c,.d, $(SRC)) + +ifeq ($(CC),cc) + CC := clang +endif + +CC ?= clang + +VPATH := ../zforth +# For local build only, CI build supplies its own CFLAGS +CFLAGS ?= -Os -g -fsanitize=fuzzer,address -dict=zforth_fuzzer.dict -Wno-unused-command-line-argument + +# Required flags +CFLAGS += -I. -I../zforth +CFLAGS += -pedantic -MMD +CFLAGS += -Wall -Wextra -Wno-unused-parameter -Wno-unused-result + +LDFLAGS += $(CFLAGS) $(LIB_FUZZING_ENGINE) + +LIBS += -lm + +$(BIN): $(OBJS) + $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) + +clean: + rm -f $(BIN) $(OBJS) $(DEPS) + +-include $(DEPS) + diff --git a/src/fuzz/main.c b/src/fuzz/main.c new file mode 100644 index 0000000..745d578 --- /dev/null +++ b/src/fuzz/main.c @@ -0,0 +1,77 @@ + +#include +#include +#include + +#include "zforth.h" + +/* + * Sys callback function + */ + +zf_input_state zf_host_sys(zf_ctx *ctx, zf_syscall_id id, const char *input) { + switch ((int)id) { + + /* The core system callbacks */ + + case ZF_SYSCALL_EMIT: + case ZF_SYSCALL_PRINT: + zf_pop(ctx); + break; + + case ZF_SYSCALL_TELL: { + zf_cell len = zf_pop(ctx); + zf_cell addr = zf_pop(ctx); + if (addr >= ZF_DICT_SIZE - len) { + zf_abort(ctx, ZF_ABORT_OUTSIDE_MEM); + } + } break; + + default: + break; + } + + return ZF_INPUT_INTERPRET; +} + +/* + * Parse number + */ + +zf_cell zf_host_parse_num(zf_ctx *ctx, const char *buf) { + zf_cell v; + int n = 0; + int r = sscanf(buf, ZF_SCAN_FMT "%n", &v, &n); + if (r != 1 || buf[n] != '\0') { + zf_abort(ctx, ZF_ABORT_NOT_A_WORD); + } + return v; +} + +/* + * Main + */ + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + zf_ctx ctx; + + zf_init(&ctx, 0); + zf_bootstrap(&ctx); + + /* Turn input into NUL-terminated string */ + char *buf = malloc(size + 1); + if (buf == NULL) { + return 0; + } + memcpy(buf, data, size); + buf[size] = 0; + + zf_eval(&ctx, buf); + free(buf); + + return 0; +} + +/* + * End + */ diff --git a/src/fuzz/zfconf.h b/src/fuzz/zfconf.h new file mode 100644 index 0000000..025eff5 --- /dev/null +++ b/src/fuzz/zfconf.h @@ -0,0 +1,60 @@ +#ifndef zfconf +#define zfconf + +/* Set to 1 to add tracing support for debugging and inspection. Requires the + * zf_host_trace() function to be implemented. Adds about one kB to .text and + * .rodata, dramatically reduces speed, but is very useful. Make sure to enable + * tracing at run time when calling zf_init() or by setting the 'trace' user + * variable to 1 */ + +#define ZF_ENABLE_TRACE 0 + +/* Set to 1 to add boundary checks to stack operations. Increases .text size + * by approx 100 bytes */ + +#define ZF_ENABLE_BOUNDARY_CHECKS 1 + +/* Set to 1 to enable bootstrapping of the forth dictionary by adding the + * primitives and user veriables. On small embedded systems you may choose to + * leave this out and start by loading a cross-compiled dictionary instead. + * Enabling adds a few hundred bytes to the .text and .rodata segments */ + +#define ZF_ENABLE_BOOTSTRAP 1 + +/* Set to 1 to enable typed access to memory. This allows memory read and write + * of signed and unsigned memory of 8, 16 and 32 bits width, as well as the + * zf_cell type. This adds a few hundred bytes of .text. Check the memaccess.zf + * file for examples how to use these operations */ + +#define ZF_ENABLE_TYPED_MEM_ACCESS 1 + +/* Type to use for the basic cell, data stack and return stack. Choose a signed + * integer type that suits your needs, or 'float' or 'double' if you need + * floating point numbers */ + +typedef float zf_cell; +#define ZF_CELL_FMT "%.14g" +#define ZF_SCAN_FMT "%f" + +/* zf_int use for bitops, some arch int type width is less than register width, + it will cause sign fill, so we need manual specify it */ +typedef int zf_int; + +/* True is defined as the bitwise complement of false. */ +#define ZF_FALSE ((zf_cell)0) +#define ZF_TRUE ((zf_cell) ~(zf_int)ZF_FALSE) + +/* The type to use for pointers and addresses. 'unsigned int' is usually a good + * choice for best performance and smallest code size */ + +typedef unsigned int zf_addr; +#define ZF_ADDR_FMT "%04x" + +/* Memory region sizes: dictionary size is given in bytes, stack sizes are + * number of elements of type zf_cell */ + +#define ZF_DICT_SIZE 4096 +#define ZF_DSTACK_SIZE 32 +#define ZF_RSTACK_SIZE 32 + +#endif diff --git a/src/fuzz/zforth_fuzzer.dict b/src/fuzz/zforth_fuzzer.dict new file mode 100644 index 0000000..a4e7876 --- /dev/null +++ b/src/fuzz/zforth_fuzzer.dict @@ -0,0 +1,36 @@ +prim_exit="exit" +prim_lit="lit" +prim_ltz="<0" +prim_colon=":" +prim_semi=";" +prim_add="+" +prim_sub="-" +prim_mul="*" +prim_div="/" +prim_mod="%" +prim_drop="drop" +prim_dup="dup" +prim_pickr="pickr" +prim_peek="@@" +prim_poke="!!" +prim_swap="swap" +prim_rot="rot" +prim_jmp="jmp" +prim_jmp0="jmp0" +prim_tick="'" +prim_pushr=">r" +prim_popr="r>" +prim_equal="=" +prim_sys="sys" +prim_pick="pick" +prim_comma=",," +prim_key="key" +prim_lits="lits" +prim_len="##" +prim_and="&" +prim_or="|" +prim_xor="^" +prim_shl="<<" +prim_shr=">>" + +comment_block="( )"