Skip to content

Commit 0ba5e26

Browse files
committed
ci: add ClusterFuzzLite
1 parent 2736e29 commit 0ba5e26

File tree

11 files changed

+355
-0
lines changed

11 files changed

+355
-0
lines changed

.clusterfuzzlite/Dockerfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
FROM gcr.io/oss-fuzz-base/base-builder:v1
2+
RUN apt-get update && apt-get install -y make autoconf automake libtool
3+
COPY . $SRC/zforth
4+
WORKDIR $SRC/zforth
5+
COPY .clusterfuzzlite/build.sh $SRC/

.clusterfuzzlite/build.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/bash -eu
2+
3+
shopt -s nullglob
4+
5+
make -C src/fuzz
6+
7+
cp src/fuzz/zforth_fuzzer src/fuzz/*.dict "$OUT"/

.clusterfuzzlite/project.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
language: c

.github/workflows/fuzz_daily.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: ClusterFuzzLite batch fuzzing
2+
3+
on:
4+
schedule:
5+
- cron: "0 0 * * *"
6+
7+
permissions: read-all
8+
9+
jobs:
10+
fuzz:
11+
name: Daily fuzzing
12+
runs-on: ubuntu-latest
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
sanitizer:
17+
- address
18+
19+
# XXX: UBSan fails due to SHR operator being able to exceed word size
20+
# - undefined
21+
22+
# FIXME: MSan fail due to zf_ctx not being fully initialized by zf_init
23+
# - memory
24+
25+
steps:
26+
- name: Build Fuzzers (${{ matrix.sanitizer }})
27+
uses: google/clusterfuzzlite/actions/build_fuzzers@v1
28+
with:
29+
language: c
30+
github-token: ${{ github.token }}
31+
sanitizer: ${{ matrix.sanitizer }}
32+
33+
- name: Run Fuzzers (${{ matrix.sanitizer }})
34+
uses: google/clusterfuzzlite/actions/run_fuzzers@v1
35+
with:
36+
github-token: ${{ github.token }}
37+
fuzz-seconds: 3600
38+
mode: code-change
39+
parallel-fuzzing: true
40+
output-sarif: true
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: ClusterFuzzLite periodic tasks
2+
3+
on:
4+
schedule:
5+
- cron: "0 0 * * 0"
6+
7+
permissions: read-all
8+
9+
jobs:
10+
pruning:
11+
name: Fuzz corpus pruning
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Build Fuzzers (${{ matrix.sanitizer }})
16+
uses: google/clusterfuzzlite/actions/build_fuzzers@v1
17+
with:
18+
language: c
19+
github-token: ${{ github.token }}
20+
21+
- name: Run Fuzzers (${{ matrix.sanitizer }})
22+
uses: google/clusterfuzzlite/actions/run_fuzzers@v1
23+
with:
24+
github-token: ${{ github.token }}
25+
fuzz-seconds: 600
26+
mode: "prune"
27+
report-timeouts: false # This is likely due to infinite loops
28+
output-sarif: true
29+
30+
coverage:
31+
name: Generate fuzzing coverage
32+
runs-on: ubuntu-latest
33+
34+
steps:
35+
- name: Build Fuzzers (${{ matrix.sanitizer }})
36+
uses: google/clusterfuzzlite/actions/build_fuzzers@v1
37+
with:
38+
language: c
39+
github-token: ${{ github.token }}
40+
sanitizer: coverage
41+
42+
- name: Run Fuzzers (${{ matrix.sanitizer }})
43+
uses: google/clusterfuzzlite/actions/run_fuzzers@v1
44+
with:
45+
github-token: ${{ github.token }}
46+
fuzz-seconds: 600
47+
mode: coverage
48+
sanitizer: coverage
49+
report-timeouts: false # This is likely due to infinite loops
50+
output-sarif: true

.github/workflows/fuzz_pr.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: ClusterFuzzLite PR fuzzing
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- "**"
7+
8+
permissions: read-all
9+
10+
jobs:
11+
fuzz:
12+
name: Fuzz test PR
13+
runs-on: ubuntu-latest
14+
concurrency:
15+
group: ${{ github.workflows }}-${{ matrix.sanitizer }}-${{ github.ref }}
16+
cancel-in-progress: true
17+
18+
strategy:
19+
fail-fast: false
20+
matrix:
21+
sanitizer:
22+
- address
23+
24+
# XXX: UBSan fails due to SHR operator being able to exceed word size
25+
# - undefined
26+
27+
# FIXME: MSan fail due to zf_ctx not being fully initialized by zf_init
28+
# - memory
29+
30+
steps:
31+
- name: Build Fuzzers (${{ matrix.sanitizer }})
32+
uses: google/clusterfuzzlite/actions/build_fuzzers@v1
33+
with:
34+
language: c
35+
github-token: ${{ github.token }}
36+
sanitizer: ${{ matrix.sanitizer }}
37+
38+
- name: Run Fuzzers (${{ matrix.sanitizer }})
39+
uses: google/clusterfuzzlite/actions/run_fuzzers@v1
40+
with:
41+
github-token: ${{ github.token }}
42+
fuzz-seconds: 300
43+
mode: code-change
44+
parallel-fuzzing: true
45+
output-sarif: true

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ go
44
*.swp
55
*.swo
66
zforth
7+
zforth_fuzzer
78
zforth.save
89
.zforth.hist

src/fuzz/Makefile

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
BIN := zforth_fuzzer
2+
SRC := main.c zforth.c
3+
4+
OBJS := $(subst .c,.o, $(SRC))
5+
DEPS := $(subst .c,.d, $(SRC))
6+
7+
ifeq ($(CC),cc)
8+
CC := clang
9+
endif
10+
11+
CC ?= clang
12+
13+
VPATH := ../zforth
14+
# For local build only, CI build supplies its own CFLAGS
15+
CFLAGS ?= -Os -g -fsanitize=fuzzer,address -dict=zforth_fuzzer.dict -Wno-unused-command-line-argument
16+
17+
# Required flags
18+
CFLAGS += -I. -I../zforth
19+
CFLAGS += -pedantic -MMD
20+
CFLAGS += -Wall -Wextra -Wno-unused-parameter -Wno-unused-result
21+
22+
LDFLAGS += $(CFLAGS) $(LIB_FUZZING_ENGINE)
23+
24+
LIBS += -lm
25+
26+
$(BIN): $(OBJS)
27+
$(CC) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)
28+
29+
clean:
30+
rm -f $(BIN) $(OBJS) $(DEPS)
31+
32+
-include $(DEPS)
33+

src/fuzz/main.c

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
2+
#include <stdio.h>
3+
#include <stdlib.h>
4+
#include <string.h>
5+
6+
#include "zforth.h"
7+
8+
/*
9+
* Sys callback function
10+
*/
11+
12+
zf_input_state zf_host_sys(zf_ctx *ctx, zf_syscall_id id, const char *input) {
13+
switch ((int)id) {
14+
15+
/* The core system callbacks */
16+
17+
case ZF_SYSCALL_EMIT:
18+
case ZF_SYSCALL_PRINT:
19+
zf_pop(ctx);
20+
break;
21+
22+
case ZF_SYSCALL_TELL: {
23+
zf_cell len = zf_pop(ctx);
24+
zf_cell addr = zf_pop(ctx);
25+
if (addr >= ZF_DICT_SIZE - len) {
26+
zf_abort(ctx, ZF_ABORT_OUTSIDE_MEM);
27+
}
28+
} break;
29+
30+
default:
31+
break;
32+
}
33+
34+
return ZF_INPUT_INTERPRET;
35+
}
36+
37+
/*
38+
* Parse number
39+
*/
40+
41+
zf_cell zf_host_parse_num(zf_ctx *ctx, const char *buf) {
42+
zf_cell v;
43+
int n = 0;
44+
int r = sscanf(buf, ZF_SCAN_FMT "%n", &v, &n);
45+
if (r != 1 || buf[n] != '\0') {
46+
zf_abort(ctx, ZF_ABORT_NOT_A_WORD);
47+
}
48+
return v;
49+
}
50+
51+
/*
52+
* Main
53+
*/
54+
55+
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
56+
zf_ctx ctx;
57+
58+
zf_init(&ctx, 0);
59+
zf_bootstrap(&ctx);
60+
61+
/* Turn input into NUL-terminated string */
62+
char *buf = malloc(size + 1);
63+
if (buf == NULL) {
64+
return 0;
65+
}
66+
memcpy(buf, data, size);
67+
buf[size] = 0;
68+
69+
zf_eval(&ctx, buf);
70+
free(buf);
71+
72+
return 0;
73+
}
74+
75+
/*
76+
* End
77+
*/

src/fuzz/zfconf.h

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#ifndef zfconf
2+
#define zfconf
3+
4+
/* Set to 1 to add tracing support for debugging and inspection. Requires the
5+
* zf_host_trace() function to be implemented. Adds about one kB to .text and
6+
* .rodata, dramatically reduces speed, but is very useful. Make sure to enable
7+
* tracing at run time when calling zf_init() or by setting the 'trace' user
8+
* variable to 1 */
9+
10+
#define ZF_ENABLE_TRACE 0
11+
12+
/* Set to 1 to add boundary checks to stack operations. Increases .text size
13+
* by approx 100 bytes */
14+
15+
#define ZF_ENABLE_BOUNDARY_CHECKS 1
16+
17+
/* Set to 1 to enable bootstrapping of the forth dictionary by adding the
18+
* primitives and user veriables. On small embedded systems you may choose to
19+
* leave this out and start by loading a cross-compiled dictionary instead.
20+
* Enabling adds a few hundred bytes to the .text and .rodata segments */
21+
22+
#define ZF_ENABLE_BOOTSTRAP 1
23+
24+
/* Set to 1 to enable typed access to memory. This allows memory read and write
25+
* of signed and unsigned memory of 8, 16 and 32 bits width, as well as the
26+
* zf_cell type. This adds a few hundred bytes of .text. Check the memaccess.zf
27+
* file for examples how to use these operations */
28+
29+
#define ZF_ENABLE_TYPED_MEM_ACCESS 1
30+
31+
/* Type to use for the basic cell, data stack and return stack. Choose a signed
32+
* integer type that suits your needs, or 'float' or 'double' if you need
33+
* floating point numbers */
34+
35+
typedef float zf_cell;
36+
#define ZF_CELL_FMT "%.14g"
37+
#define ZF_SCAN_FMT "%f"
38+
39+
/* zf_int use for bitops, some arch int type width is less than register width,
40+
it will cause sign fill, so we need manual specify it */
41+
typedef int zf_int;
42+
43+
/* True is defined as the bitwise complement of false. */
44+
#define ZF_FALSE ((zf_cell)0)
45+
#define ZF_TRUE ((zf_cell) ~(zf_int)ZF_FALSE)
46+
47+
/* The type to use for pointers and addresses. 'unsigned int' is usually a good
48+
* choice for best performance and smallest code size */
49+
50+
typedef unsigned int zf_addr;
51+
#define ZF_ADDR_FMT "%04x"
52+
53+
/* Memory region sizes: dictionary size is given in bytes, stack sizes are
54+
* number of elements of type zf_cell */
55+
56+
#define ZF_DICT_SIZE 4096
57+
#define ZF_DSTACK_SIZE 32
58+
#define ZF_RSTACK_SIZE 32
59+
60+
#endif

0 commit comments

Comments
 (0)