]> wimlib.net Git - wimlib/commitdiff
Improve fuzz testing
authorEric Biggers <ebiggers3@gmail.com>
Mon, 27 Mar 2023 00:25:46 +0000 (17:25 -0700)
committerEric Biggers <ebiggers3@gmail.com>
Mon, 27 Mar 2023 00:25:46 +0000 (17:25 -0700)
- Convert fuzzing scripts from afl-fuzz to libFuzzer
- Add xml and wim fuzzers, including malloc failure injection
- Fuzz for 2 minutes as part of the GitHub Actions CI

29 files changed:
.github/workflows/ci.yml
.gitignore
tools/afl-fuzz/.gitignore [deleted file]
tools/afl-fuzz/Makefile [deleted file]
tools/afl-fuzz/compress/fuzz.c [deleted file]
tools/afl-fuzz/decompress/fuzz.c [deleted file]
tools/afl-fuzz/fuzz.sh [deleted file]
tools/libFuzzer/compress/corpus/lzms20 [moved from tools/afl-fuzz/compress/inputs/lzms20 with 100% similarity]
tools/libFuzzer/compress/corpus/lzms50 [moved from tools/afl-fuzz/compress/inputs/lzms50 with 100% similarity]
tools/libFuzzer/compress/corpus/lzms80 [moved from tools/afl-fuzz/compress/inputs/lzms80 with 100% similarity]
tools/libFuzzer/compress/corpus/lzx20 [moved from tools/afl-fuzz/compress/inputs/lzx20 with 100% similarity]
tools/libFuzzer/compress/corpus/lzx50 [moved from tools/afl-fuzz/compress/inputs/lzx50 with 100% similarity]
tools/libFuzzer/compress/corpus/lzx80 [moved from tools/afl-fuzz/compress/inputs/lzx80 with 100% similarity]
tools/libFuzzer/compress/corpus/xpress20 [moved from tools/afl-fuzz/compress/inputs/xpress20 with 100% similarity]
tools/libFuzzer/compress/corpus/xpress50 [moved from tools/afl-fuzz/compress/inputs/xpress50 with 100% similarity]
tools/libFuzzer/compress/corpus/xpress80 [moved from tools/afl-fuzz/compress/inputs/xpress80 with 100% similarity]
tools/libFuzzer/compress/fuzz.c [new file with mode: 0644]
tools/libFuzzer/decompress/corpus/lzms [moved from tools/afl-fuzz/decompress/inputs/lzms with 100% similarity]
tools/libFuzzer/decompress/corpus/lzx [moved from tools/afl-fuzz/decompress/inputs/lzx with 100% similarity]
tools/libFuzzer/decompress/corpus/xpress [moved from tools/afl-fuzz/decompress/inputs/xpress with 100% similarity]
tools/libFuzzer/decompress/fuzz.c [new file with mode: 0644]
tools/libFuzzer/fault-injection.c [new file with mode: 0644]
tools/libFuzzer/fuzz.sh [new file with mode: 0755]
tools/libFuzzer/fuzzer.h [new file with mode: 0644]
tools/libFuzzer/test-one-input.c [new file with mode: 0644]
tools/libFuzzer/wim/corpus/0 [new file with mode: 0644]
tools/libFuzzer/wim/fuzz.c [new file with mode: 0644]
tools/libFuzzer/xml/corpus/0 [new file with mode: 0644]
tools/libFuzzer/xml/fuzz.c [new file with mode: 0644]

index c4718b8feb97bfc2a5fb7cd9ff75241ddbeb157f..29527bba0916c21cfab7c89a4b43d168e6ea0f5c 100644 (file)
@@ -147,3 +147,37 @@ jobs:
           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}}
index feb1eaba93b7c19acbd965f201bbcb1ca216b82b..908421a5c4af52708d12365194a0d4f277730474 100644 (file)
@@ -45,6 +45,8 @@
 /tests/tree-cmp
 /tests/wlfuzz
 /tests/wlfuzz.exe
+/tools/libFuzzer/*/fuzz
+/tools/libFuzzer/test-one-input
 /wimlib-*-bin/
 /wimlib-*.tar
 /wimlib-*.tar.*
diff --git a/tools/afl-fuzz/.gitignore b/tools/afl-fuzz/.gitignore
deleted file mode 100644 (file)
index 24c8d6c..0000000
+++ /dev/null
@@ -1 +0,0 @@
-*/fuzz
diff --git a/tools/afl-fuzz/Makefile b/tools/afl-fuzz/Makefile
deleted file mode 100644 (file)
index cff26bf..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-SRC := $(wildcard */*.c)
-EXE := $(SRC:.c=)
-
-LDLIBS := -lwim
-LDFLAGS := -L../../.libs
-CPPFLAGS := -I../../include
-
-all:$(EXE)
-
-clean:
-       rm -f $(EXE)
diff --git a/tools/afl-fuzz/compress/fuzz.c b/tools/afl-fuzz/compress/fuzz.c
deleted file mode 100644 (file)
index 4c91172..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-#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;
-}
diff --git a/tools/afl-fuzz/decompress/fuzz.c b/tools/afl-fuzz/decompress/fuzz.c
deleted file mode 100644 (file)
index b3d4b90..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-#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;
-}
diff --git a/tools/afl-fuzz/fuzz.sh b/tools/afl-fuzz/fuzz.sh
deleted file mode 100755 (executable)
index ea2bd4e..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-#!/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
diff --git a/tools/libFuzzer/compress/fuzz.c b/tools/libFuzzer/compress/fuzz.c
new file mode 100644 (file)
index 0000000..e87bbba
--- /dev/null
@@ -0,0 +1,44 @@
+#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;
+}
diff --git a/tools/libFuzzer/decompress/fuzz.c b/tools/libFuzzer/decompress/fuzz.c
new file mode 100644 (file)
index 0000000..82782bc
--- /dev/null
@@ -0,0 +1,26 @@
+#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;
+}
diff --git a/tools/libFuzzer/fault-injection.c b/tools/libFuzzer/fault-injection.c
new file mode 100644 (file)
index 0000000..e33c37a
--- /dev/null
@@ -0,0 +1,42 @@
+#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;
+}
diff --git a/tools/libFuzzer/fuzz.sh b/tools/libFuzzer/fuzz.sh
new file mode 100755 (executable)
index 0000000..247587d
--- /dev/null
@@ -0,0 +1,128 @@
+#!/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
diff --git a/tools/libFuzzer/fuzzer.h b/tools/libFuzzer/fuzzer.h
new file mode 100644 (file)
index 0000000..6639060
--- /dev/null
@@ -0,0 +1,13 @@
+#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);
diff --git a/tools/libFuzzer/test-one-input.c b/tools/libFuzzer/test-one-input.c
new file mode 100644 (file)
index 0000000..9b201f8
--- /dev/null
@@ -0,0 +1,34 @@
+#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;
+}
diff --git a/tools/libFuzzer/wim/corpus/0 b/tools/libFuzzer/wim/corpus/0
new file mode 100644 (file)
index 0000000..a2a3f95
Binary files /dev/null and b/tools/libFuzzer/wim/corpus/0 differ
diff --git a/tools/libFuzzer/wim/fuzz.c b/tools/libFuzzer/wim/fuzz.c
new file mode 100644 (file)
index 0000000..4d21e12
--- /dev/null
@@ -0,0 +1,32 @@
+#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;
+}
diff --git a/tools/libFuzzer/xml/corpus/0 b/tools/libFuzzer/xml/corpus/0
new file mode 100644 (file)
index 0000000..d63b464
Binary files /dev/null and b/tools/libFuzzer/xml/corpus/0 differ
diff --git a/tools/libFuzzer/xml/fuzz.c b/tools/libFuzzer/xml/fuzz.c
new file mode 100644 (file)
index 0000000..a16dbe9
--- /dev/null
@@ -0,0 +1,38 @@
+#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;
+}