Skip to content

Commit

Permalink
ci: add ClusterFuzzLite
Browse files Browse the repository at this point in the history
  • Loading branch information
alaviss committed Dec 17, 2024
1 parent 2736e29 commit d6affd9
Show file tree
Hide file tree
Showing 11 changed files with 353 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .clusterfuzzlite/Dockerfile
Original file line number Diff line number Diff line change
@@ -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/
7 changes: 7 additions & 0 deletions .clusterfuzzlite/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash -eu

shopt -s nullglob

make -C src/fuzz

cp src/fuzz/zforth_fuzzer src/fuzz/*.dict "$OUT"/
1 change: 1 addition & 0 deletions .clusterfuzzlite/project.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
language: c
40 changes: 40 additions & 0 deletions .github/workflows/fuzz_daily.yml
Original file line number Diff line number Diff line change
@@ -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
48 changes: 48 additions & 0 deletions .github/workflows/fuzz_periodic.yml
Original file line number Diff line number Diff line change
@@ -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
45 changes: 45 additions & 0 deletions .github/workflows/fuzz_pr.yml
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ go
*.swp
*.swo
zforth
zforth_fuzzer
zforth.save
.zforth.hist
33 changes: 33 additions & 0 deletions src/fuzz/Makefile
Original file line number Diff line number Diff line change
@@ -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)

77 changes: 77 additions & 0 deletions src/fuzz/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#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
*/
60 changes: 60 additions & 0 deletions src/fuzz/zfconf.h
Original file line number Diff line number Diff line change
@@ -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
36 changes: 36 additions & 0 deletions src/fuzz/zforth_fuzzer.dict
Original file line number Diff line number Diff line change
@@ -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="( )"

0 comments on commit d6affd9

Please sign in to comment.