# The fuzz targets of scripts/fuzz.sh (colchunk, bm25, shred; test/fuzz/README.md). No backend is linked: tools/pgshim.c and fz_shim.c stand in for it. # nice make -j4 -C test/fuzz OUT=/tmp/x all build the three targets # make FZ_DRIVER=1 ... the portable coverage-guided driver (fz_driver.c) instead of libFuzzer # # With clang (and its libFuzzer runtime) the targets are libFuzzer binaries; with gcc, or FZ_DRIVER=1, fz_driver.c is main() and the # edges come from -fsanitize-coverage=trace-pc. Both take the libFuzzer command line (-max_total_time=SEC, -runs=N, a corpus # directory), so scripts/fuzz.sh does not care. Always ASan + UBSan, any report aborts (-fno-sanitize-recover=all). No -fwrapv: signed # overflow is a finding here, where the extension itself is built -fwrapv like the server. OUT ?= /tmp/sift-fuzz-build SRC = ../../src include $(SRC)/areas.mk KT = ../../tools/kernels PGINC := $(shell pg_config --includedir-server) PGLIB := -L$(shell pg_config --libdir) -L$(shell pg_config --pkglibdir) ifndef FZ_DRIVER FZ_CC := $(shell command -v clang 2>/dev/null) ifneq ($(FZ_CC),) ifeq ($(shell echo 'int LLVMFuzzerTestOneInput(const unsigned char *d, unsigned long n) { return 0; }' | $(FZ_CC) -fsanitize=fuzzer -x c - -o /dev/null 2>/dev/null && echo ok),ok) FZ_LIBFUZZER = 1 endif endif endif ifdef FZ_LIBFUZZER CC = $(FZ_CC) COV = -fsanitize=fuzzer-no-link LINKFZ = -fsanitize=fuzzer else CC ?= gcc COV = -fsanitize-coverage=trace-pc endif # SAN, DSAN (the driver's) and DEFS/COLZ can be replaced: MSan needs SAN='-fsanitize=memory -fsanitize-memory-track-origins' DEFS= COLZ= (lz4 and zstd are # not instrumented), valgrind needs SAN='-fno-omit-frame-pointer' DSAN= COV= FZ_DRIVER=1 (docs/SAFETY.md) SAN ?= -fsanitize=address,undefined,float-cast-overflow -fno-sanitize-recover=all -fno-omit-frame-pointer DSAN ?= -fsanitize=address,undefined # gcc before 13 miscompiles the AVX-512 kernels at -O1 under pointer-overflow: it spills a 16 bit mask with kmovw and reloads 64 bits ifneq ($(shell printf '\043if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 13\nyes\n\043endif\n' | $(CC) -E -x c - 2>/dev/null | grep -x yes),) SAN += -fno-sanitize=pointer-overflow DSAN += -fno-sanitize=pointer-overflow endif DEFS ?= -DSIFT_HAVE_LZ4 -DSIFT_HAVE_ZSTD COLZ ?= -lzstd -llz4 CFLAGS += -std=gnu11 -O1 -g $(SAN) $(SIFT_BASE_ISA) -ffp-contract=off -Wall -Wno-unused-function -Wno-unused-parameter \ -Wno-sign-compare $(SIFT_INC) -I$(KT) -I. -I$(PGINC) $(SIFT_PGCPP) $(DEFS) -D_GNU_SOURCE LDLIBS = -lm $(COLZ) $(PGLIB) -lpgport -lpgcommon $(SIFT_PGLIBS) # every source the three targets decode with (the portable core, SRC/portable-col.list and portable.list members), and the stand-ins CORE = col/codec/codec col/codec/filter col/codec/get col/codec_policy col/codec_write col/codec_parse col/codec_blob col/codec_kern col/codec_avx2 col/codec_avx512 col/codec_neon col/compress col/kern col/kern_avx2 \ col/kern_avx512 col/kern_neon col/enc col/schema col/dict/dict col/dict/order col/dict/parse col/dict/serialize col/batch col/chunk col/ctest col/unit col/shred col/shred_spec col/shred_kern col/shred_kern_avx2 col/shred_kern_avx512 col/shred_kern_neon \ bm25/kernels bm25/kernels_avx2 bm25/kernels_avx512 bm25/kernels_neon bm25/analyze bm25/analyze_config bm25/analyze_stem bm25/posting bm25/postw bm25/posblk bm25/pos bm25/bitmap bm25/blob bm25/bitmap_scalar \ bm25/bitmap_avx2 bm25/bitmap_avx512 bm25/bitmap_neon core/span OBJ = $(addprefix $(OUT)/obj/,$(addsuffix .o,$(CORE))) $(OUT)/shim.o $(OUT)/pgshim.o $(OUT)/fz_shim.o HDR = $(wildcard $(SRC)/*/*.h $(SRC)/*/*/*.h) fz_shim.h ../../tools/pgshim.h Makefile ifndef FZ_LIBFUZZER DRIVER = $(OUT)/fz_driver.o endif all: $(addprefix $(OUT)/,colchunk bm25 shred) $(OUT)/obj/%.o: $(SRC)/%.c $(HDR) @mkdir -p $(@D) $(CC) $(CFLAGS) $(COV) -c -o $@ $< $(OUT)/obj/%.o: %.c $(HDR) @mkdir -p $(@D) $(CC) $(CFLAGS) $(COV) -c -o $@ $< # the cpu stand-in of tools/kernels; its stub of sift_k would clash with the real one of bm25/bitmap.c $(OUT)/shim.o: $(KT)/shim.c $(HDR) @mkdir -p $(OUT) $(CC) $(CFLAGS) $(COV) -Dsift_k=fz_unused_sift_k -c -o $@ $< # the stand-ins are not instrumented $(OUT)/pgshim.o: ../../tools/pgshim.c $(HDR) @mkdir -p $(OUT) $(CC) $(CFLAGS) -c -o $@ $< $(OUT)/fz_shim.o: fz_shim.c $(HDR) @mkdir -p $(OUT) $(CC) $(CFLAGS) -c -o $@ $< $(OUT)/fz_%.o: fz_%.c $(HDR) @mkdir -p $(OUT) $(CC) $(CFLAGS) $(COV) -c -o $@ $< # the driver is not instrumented: it is the callback $(OUT)/fz_driver.o: fz_driver.c @mkdir -p $(OUT) $(CC) -O1 -g $(DSAN) -c -o $@ $< $(OUT)/colchunk: $(OUT)/fz_colchunk.o $(OBJ) $(DRIVER) $(CC) $(CFLAGS) $(LINKFZ) -o $@ $^ $(LDLIBS) $(OUT)/bm25: $(OUT)/fz_bm25.o $(OBJ) $(DRIVER) $(CC) $(CFLAGS) $(LINKFZ) -o $@ $^ $(LDLIBS) $(OUT)/shred: $(OUT)/fz_shred.o $(OBJ) $(DRIVER) $(CC) $(CFLAGS) $(LINKFZ) -o $@ $^ $(LDLIBS) # every file of regress/ is an input that crashed a target before its fix, named TARGET-what: replays them, fails if one comes back # (FZ_STRICT: an error of a class other than corruption is a failure too) regress: all @for f in regress/*; do t=$${f#regress/}; FZ_STRICT=1 $(OUT)/$${t%%-*} $$f >/dev/null 2>&1 || { echo "FAIL $$f"; exit 1; }; done; echo "regress: ok" clean: rm -rf $(OUT) .PHONY: all clean regress