mingw-w64-${{matrix.env}}-gcc
pkgconf
- run: CFLAGS="$DEF_CFLAGS" ./tools/make-windows-release --no-docs --no-zip
+
+ fuzz-with-libFuzzer:
+ name: Fuzz with libFuzzer (${{matrix.target}} ${{matrix.sanitizer}})
+ strategy:
+ matrix:
+ include:
+ - target: wim
+ sanitizer:
+ - target: wim
+ sanitizer: --asan --ubsan
+ - target: xml
+ sanitizer:
+ - target: xml
+ sanitizer: --asan --ubsan
+ - target: compress
+ sanitizer:
+ - target: compress
+ sanitizer: --asan --ubsan
+ - target: decompress
+ sanitizer:
+ - target: decompress
+ sanitizer: --asan --ubsan
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y clang $DEPENDENCIES
+ - run: ./bootstrap
+ - name: Fuzz
+ run: |
+ tools/libFuzzer/fuzz.sh --time=120 ${{matrix.sanitizer}} \
+ ${{matrix.target}}
/tests/tree-cmp
/tests/wlfuzz
/tests/wlfuzz.exe
+/tools/libFuzzer/*/fuzz
+/tools/libFuzzer/test-one-input
/wimlib-*-bin/
/wimlib-*.tar
/wimlib-*.tar.*
+++ /dev/null
-SRC := $(wildcard */*.c)
-EXE := $(SRC:.c=)
-
-LDLIBS := -lwim
-LDFLAGS := -L../../.libs
-CPPFLAGS := -I../../include
-
-all:$(EXE)
-
-clean:
- rm -f $(EXE)
+++ /dev/null
-#include <assert.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <wimlib.h>
-
-int main(int argc, char *argv[])
-{
- int fd;
- struct stat stbuf;
- uint8_t ctype;
- uint8_t level;
- struct wimlib_compressor *c;
- struct wimlib_decompressor *d;
- size_t usize, csize;
- void *udata, *cdata, *decompressed;
- int ret;
-
- fd = open(argv[1], O_RDONLY);
- assert(fd >= 0);
- ret = fstat(fd, &stbuf);
- assert(!ret);
-
- if (stbuf.st_size < 2)
- return 0;
- ret = read(fd, &ctype, 1);
- assert(ret == 1);
- ret = read(fd, &level, 1);
- assert(ret == 1);
- ctype = 1 + ((uint8_t)(ctype - 1) % 3); /* 1-3 */
- level = 1 + (level % 100); /* 1-100 */
- usize = stbuf.st_size - 2;
-
- udata = malloc(usize);
- cdata = malloc(usize);
- decompressed = malloc(usize);
-
- ret = read(fd, udata, usize);
- assert(ret == usize);
-
- ret = wimlib_create_compressor(ctype, usize, level, &c);
- if (ret == 0) {
- ret = wimlib_create_decompressor(ctype, usize, &d);
- assert(ret == 0);
-
- csize = wimlib_compress(udata, usize, cdata, usize, c);
- if (csize) {
- ret = wimlib_decompress(cdata, csize,
- decompressed, usize, d);
- assert(ret == 0);
- assert(memcmp(udata, decompressed, usize) == 0);
- }
- wimlib_free_compressor(c);
- wimlib_free_decompressor(d);
- }
- free(udata);
- free(cdata);
- free(decompressed);
- return 0;
-}
+++ /dev/null
-#include <assert.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <wimlib.h>
-
-int main(int argc, char *argv[])
-{
- int fd;
- struct stat stbuf;
- uint8_t ctype;
- size_t csize, uspace;
- void *cdata, *udata;
- struct wimlib_decompressor *d;
- int ret;
-
- fd = open(argv[1], O_RDONLY);
- assert(fd >= 0);
- ret = fstat(fd, &stbuf);
- assert(!ret);
-
- if (stbuf.st_size < 1)
- return 0;
- ret = read(fd, &ctype, 1);
- assert(ret == 1);
- ctype = 1 + ((uint8_t)(ctype - 1) % 3); /* 1-3 */
- csize = stbuf.st_size - 1;
- uspace = csize * 8;
-
- cdata = malloc(csize);
- udata = malloc(uspace);
-
- ret = read(fd, cdata, csize);
- assert(ret == csize);
-
- ret = wimlib_create_decompressor(ctype, uspace, &d);
- if (ret == 0)
- wimlib_decompress(cdata, csize, udata, uspace, d);
-
- free(udata);
- free(cdata);
- wimlib_free_decompressor(d);
- return 0;
-}
+++ /dev/null
-#!/bin/bash
-
-set -e -u -o pipefail
-
-cd "$(dirname "$0")"
-
-read -r -a AVAILABLE_TARGETS < <(echo */fuzz.c | sed 's@/fuzz.c@@g')
-
-usage()
-{
- cat << EOF
-Usage: $0 [OPTION]... [TARGET]...
-
-Fuzz wimlib with afl-fuzz.
-
-Options:
- --asan Enable AddressSanitizer
- --no-resume Don't resume existing afl-fuzz session; start a new one
- --ubsan Enable UndefinedBehaviorSanitizer
-
-Available targets: ${AVAILABLE_TARGETS[*]}
-EOF
-}
-
-die()
-{
- echo "$*" 1>&2
- exit 1
-}
-
-asan=false
-ubsan=false
-may_resume=true
-
-longopts_array=(
-asan
-help
-no-resume
-ubsan
-)
-longopts=$(echo "${longopts_array[@]}" | tr ' ' ',')
-
-if ! options=$(getopt -o "" -l "$longopts" -- "$@"); then
- usage 1>&2
- exit 1
-fi
-eval set -- "$options"
-while (( $# >= 0 )); do
- case "$1" in
- --asan)
- asan=true
- ;;
- --help)
- usage
- exit 0
- ;;
- --no-resume)
- may_resume=false
- ;;
- --ubsan)
- ubsan=true
- ;;
- --)
- shift
- break
- ;;
- *)
- echo 1>&2 "Invalid option: \"$1\""
- usage 1>&2
- exit 1
- esac
- shift
-done
-
-if $asan && $ubsan; then
- die "--asan and --ubsan are mutually exclusive"
-fi
-
-if ! type -P afl-fuzz > /dev/null; then
- die "afl-fuzz is not installed"
-fi
-
-if (( $# == 0 )); then
- targets=("${AVAILABLE_TARGETS[@]}")
-else
- for target; do
- found=false
- for t in "${AVAILABLE_TARGETS[@]}"; do
- if [ "$target" = "$t" ]; then
- found=true
- fi
- done
- if ! $found; then
- echo 1>&2 "Unknown target '$target'"
- echo 1>&2 "Available targets: ${AVAILABLE_TARGETS[*]}"
- exit 1
- fi
- done
- targets=("$@")
-fi
-if (( ${#targets[@]} > 1 )) && ! type -P urxvt > /dev/null; then
- die "urxvt is not installed"
-fi
-
-afl_opts=""
-if $asan; then
- export AFL_USE_ASAN=1
- export CFLAGS="-O2 -m32"
- export CC=afl-clang
- afl_opts+=" -m 800"
-elif $ubsan; then
- export CFLAGS="-fsanitize=undefined -fno-sanitize-recover=undefined"
- export CC=afl-gcc
-else
- export AFL_HARDEN=1
- export CFLAGS="-O2"
- export CC=afl-gcc
-fi
-
-sudo sh -c "echo core > /proc/sys/kernel/core_pattern"
-sudo sh -c "echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor"
-
-NPROC=$(getconf _NPROCESSORS_ONLN)
-
-(
-cd ../../
-./configure CC="$CC" CFLAGS="$CFLAGS"
-make "-j$NPROC"
-)
-make "-j$NPROC" -B
-export LD_LIBRARY_PATH=$PWD/../../.libs
-
-for dir in "${targets[@]}"; do
- workdir=/tmp/wimlib_$dir
- cp -vaT "$dir" "$workdir"
- indir=$workdir/inputs
- outdir=$workdir/outputs
- if [ -e "$outdir" ]; then
- if $may_resume; then
- indir="-"
- else
- rm -rf "${outdir:?}"/*
- fi
- else
- mkdir "$outdir"
- fi
- cmd="afl-fuzz -i $indir -o $outdir -T wimlib_$dir $afl_opts -- $workdir/fuzz @@"
- if (( ${#targets[@]} > 1 )); then
- urxvt -e bash -c "$cmd" &
- else
- $cmd
- fi
-done
-wait
--- /dev/null
+#include "../fuzzer.h"
+
+/* Fuzz the compression and decompression round trip. */
+int LLVMFuzzerTestOneInput(const uint8_t *in, size_t insize)
+{
+ int ctype;
+ int level;
+ struct wimlib_compressor *c;
+ struct wimlib_decompressor *d;
+ size_t csize_avail = insize;
+ uint8_t *cbuf;
+ uint8_t *decompressed;
+ size_t csize;
+ int ret;
+
+ if (insize < 2)
+ return 0;
+ ctype = 1 + ((uint8_t)(in[0] - 1) % 3); /* 1-3 */
+ level = 1 + (in[1] % 100); /* 1-100 */
+ in += 2;
+ insize -= 2;
+
+ cbuf = malloc(csize_avail);
+ decompressed = malloc(insize);
+
+ ret = wimlib_create_compressor(ctype, insize, level, &c);
+ if (ret == 0) {
+ ret = wimlib_create_decompressor(ctype, insize, &d);
+ assert(ret == 0);
+
+ csize = wimlib_compress(in, insize, cbuf, csize_avail, c);
+ if (csize) {
+ ret = wimlib_decompress(cbuf, csize,
+ decompressed, insize, d);
+ assert(ret == 0);
+ assert(memcmp(in, decompressed, insize) == 0);
+ }
+ wimlib_free_compressor(c);
+ wimlib_free_decompressor(d);
+ }
+ free(cbuf);
+ free(decompressed);
+ return 0;
+}
--- /dev/null
+#include "../fuzzer.h"
+
+/* Fuzz decompression. */
+int LLVMFuzzerTestOneInput(const uint8_t *in, size_t insize)
+{
+ int ctype;
+ struct wimlib_decompressor *d;
+ const size_t outsize_avail = 3 * insize;
+ uint8_t *out;
+ int ret;
+
+ if (insize < 1)
+ return 0;
+ ctype = 1 + ((uint8_t)(in[0] - 1) % 3); /* 1-3 */
+ in++;
+ insize--;
+
+ ret = wimlib_create_decompressor(ctype, insize, &d);
+ if (ret == 0) {
+ out = malloc(outsize_avail);
+ wimlib_decompress(in, insize, out, outsize_avail, d);
+ wimlib_free_decompressor(d);
+ free(out);
+ }
+ return 0;
+}
--- /dev/null
+#include "fuzzer.h"
+
+static int64_t num_allocs_remaining;
+
+static void *
+faultinject_malloc(size_t size)
+{
+ if (__atomic_sub_fetch(&num_allocs_remaining, 1, __ATOMIC_RELAXED) <= 0)
+ return NULL;
+ return malloc(size);
+}
+
+static void
+faultinject_free(void *p)
+{
+ free(p);
+}
+
+static void *
+faultinject_realloc(void *p, size_t size)
+{
+ if (__atomic_sub_fetch(&num_allocs_remaining, 1, __ATOMIC_RELAXED) <= 0)
+ return NULL;
+ return realloc(p, size);
+}
+
+bool
+setup_fault_nth(const uint8_t **in, size_t *insize, uint16_t *fault_nth)
+{
+ uint16_t n;
+
+ if (*insize < 2)
+ return false;
+ memcpy(&n, *in, 2);
+ wimlib_set_memory_allocator(faultinject_malloc, faultinject_free,
+ faultinject_realloc);
+ num_allocs_remaining = n ?: INT64_MAX;
+ *in += 2;
+ *insize -= 2;
+ *fault_nth = n;
+ return true;
+}
--- /dev/null
+#!/bin/bash
+
+set -e -u -o pipefail
+
+cd "$(dirname "$0")"
+TOPDIR=../..
+SCRIPTDIR=$PWD
+
+read -r -a AVAILABLE_TARGETS < <(echo */fuzz.c | sed 's@/fuzz.c@@g')
+
+usage()
+{
+ cat << EOF
+Usage: $0 [OPTION]... FUZZ_TARGET
+
+Fuzz wimlib with LLVM's libFuzzer.
+
+Options:
+ --asan Enable AddressSanitizer
+ --input=INPUT Test a single input file only
+ --msan Enable MemorySanitizer
+ --time=SECONDS Stop after the given time has passed
+ --ubsan Enable UndefinedBehaviorSanitizer
+
+Available fuzz targets: ${AVAILABLE_TARGETS[*]}
+EOF
+}
+
+die()
+{
+ echo "$*" 1>&2
+ exit 1
+}
+
+run_cmd()
+{
+ echo "$*"
+ "$@"
+}
+
+EXTRA_SANITIZERS=
+EXTRA_FUZZER_ARGS=()
+INPUT=
+
+longopts_array=(
+asan
+help
+input:
+msan
+time:
+ubsan
+)
+longopts=$(echo "${longopts_array[@]}" | tr ' ' ',')
+
+if ! options=$(getopt -o "" -l "$longopts" -- "$@"); then
+ usage 1>&2
+ exit 1
+fi
+eval set -- "$options"
+while true; do
+ case "$1" in
+ --asan)
+ EXTRA_SANITIZERS+=",address"
+ ;;
+ --help)
+ usage
+ exit 0
+ ;;
+ --input)
+ INPUT=$2
+ shift
+ ;;
+ --time)
+ EXTRA_FUZZER_ARGS+=("-max_total_time=$2")
+ shift
+ ;;
+ --msan)
+ EXTRA_SANITIZERS+=",memory"
+ ;;
+ --ubsan)
+ EXTRA_SANITIZERS+=",undefined"
+ ;;
+ --)
+ shift
+ break
+ ;;
+ *)
+ echo 1>&2 "Invalid option '$1'"
+ usage 1>&2
+ exit 1
+ esac
+ shift
+done
+
+if (( $# != 1 )); then
+ echo 1>&2 "No fuzz target specified!"
+ usage 1>&2
+ exit 1
+fi
+TARGET=$1
+if [ ! -e "$TARGET/fuzz.c" ]; then
+ echo 1>&2 "'$TARGET' is not a valid fuzz target!"
+ usage 1>&2
+ exit 1
+fi
+cd "$TOPDIR"
+cflags="-g -O1 -Wall -Werror"
+cflags+=" -fsanitize=fuzzer-no-link$EXTRA_SANITIZERS"
+if [ -n "$EXTRA_SANITIZERS" ]; then
+ cflags+=" -fno-sanitize-recover=${EXTRA_SANITIZERS#,}"
+fi
+if ! [ -e config.log ] || ! grep -q -- "'CFLAGS=$cflags'" config.log; then
+ run_cmd ./configure --enable-test-support --without-fuse --without-ntfs-3g \
+ CC=clang CFLAGS="$cflags"
+fi
+run_cmd make "-j$(getconf _NPROCESSORS_ONLN)"
+cd "$SCRIPTDIR"
+if [ -n "$INPUT" ]; then
+ run_cmd clang -g -O1 -fsanitize=fuzzer-no-link$EXTRA_SANITIZERS -Wall -Werror \
+ -I "$TOPDIR/include" "$TARGET/fuzz.c" test-one-input.c fault-injection.c \
+ "$TOPDIR/.libs/libwim.a" -o test-one-input
+ run_cmd ./test-one-input "$INPUT"
+else
+ run_cmd clang -g -O1 -fsanitize=fuzzer$EXTRA_SANITIZERS -Wall -Werror \
+ -I "$TOPDIR/include" "$TARGET/fuzz.c" fault-injection.c \
+ "$TOPDIR/.libs/libwim.a" -o "$TARGET/fuzz"
+ run_cmd "$TARGET/fuzz" "${EXTRA_FUZZER_ARGS[@]}" "$TARGET/corpus"
+fi
--- /dev/null
+#define ENABLE_TEST_SUPPORT 1
+#include <assert.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <wimlib.h>
+#include <wimlib/test_support.h>
+
+bool
+setup_fault_nth(const uint8_t **in, size_t *insize, uint16_t *fault_nth);
--- /dev/null
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+int LLVMFuzzerTestOneInput(const uint8_t *in, size_t insize);
+
+int main(int argc, char *argv[])
+{
+ int fd;
+ struct stat stbuf;
+ uint8_t *in;
+
+ fd = open(argv[1], O_RDONLY);
+ if (fd < 0) {
+ perror(argv[1]);
+ return 1;
+ }
+ if (fstat(fd, &stbuf) != 0) {
+ perror("fstat");
+ return 1;
+ }
+ in = malloc(stbuf.st_size);
+ if (read(fd, in, stbuf.st_size) != stbuf.st_size) {
+ perror("read");
+ return 1;
+ }
+ LLVMFuzzerTestOneInput(in, stbuf.st_size);
+ close(fd);
+ free(in);
+ return 0;
+}
--- /dev/null
+#include "../fuzzer.h"
+
+/* Fuzz WIM file reading. */
+int LLVMFuzzerTestOneInput(const uint8_t *in, size_t insize)
+{
+ uint16_t fault_nth;
+ char tmp_wim[128];
+ char tmp_dir[128];
+ int fd;
+ WIMStruct *wim;
+ int ret;
+
+ if (!setup_fault_nth(&in, &insize, &fault_nth))
+ return 0;
+
+ sprintf(tmp_wim, "/tmp/wim-fuzz-%d.wim", getpid());
+ sprintf(tmp_dir, "/tmp/wim-fuzz-%d", getpid());
+
+ fd = open(tmp_wim, O_WRONLY|O_CREAT|O_TRUNC, 0600);
+ assert(fd >= 0);
+ ret = write(fd, in, insize);
+ assert(ret == insize);
+ close(fd);
+
+ ret = wimlib_open_wim(tmp_wim, 0, &wim);
+ if (ret == 0) {
+ wimlib_extract_image(wim, 1, tmp_dir, 0);
+ wimlib_add_image(wim, tmp_dir, "name", NULL, 0);
+ wimlib_free(wim);
+ }
+ return 0;
+}
--- /dev/null
+#include "../fuzzer.h"
+
+/* Fuzz XML parsing and writing. */
+int LLVMFuzzerTestOneInput(const uint8_t *in, size_t insize)
+{
+ uint16_t fault_nth;
+ char *in_str;
+ char *out_str = NULL;
+ int ret;
+
+ if (!setup_fault_nth(&in, &insize, &fault_nth))
+ return 0;
+
+ in_str = malloc(insize + 1);
+ memcpy(in_str, in, insize);
+ in_str[insize] = '\0';
+ ret = wimlib_parse_and_write_xml_doc(in_str, &out_str);
+ if (ret == 0) {
+ char *out2_str = NULL;
+
+ /*
+ * If the first parse+write succeeded, we now should be able to
+ * parse+write the result without changing it further.
+ */
+ ret = wimlib_parse_and_write_xml_doc(out_str, &out2_str);
+ if (ret != 0)
+ assert(ret == WIMLIB_ERR_NOMEM && fault_nth);
+ else
+ assert(strcmp(out_str, out2_str) == 0);
+ free(out2_str);
+ } else {
+ assert(ret == WIMLIB_ERR_XML ||
+ (fault_nth && ret == WIMLIB_ERR_NOMEM));
+ }
+ free(in_str);
+ free(out_str);
+ return 0;
+}