* [PATCH] tests: integrate fuzz-dwfl-core into elfutils @ 2021-12-12 15:16 Evgeny Vereshchagin 2021-12-17 10:46 ` Mark Wielaard 0 siblings, 1 reply; 6+ messages in thread From: Evgeny Vereshchagin @ 2021-12-12 15:16 UTC (permalink / raw) To: elfutils-devel; +Cc: Evgeny Vereshchagin The fuzz target was integrated into OSS-Fuzz in https://github.com/google/oss-fuzz/pull/6944 and since then it has been running there continously (uncovering various issues along the way). It's all well and good but since OSS-Fuzz is far from the elfutils repository it's unnecessarily hard to build the fuzz target locally, verify patches and more generally test new code to make sure that it doesn't introduce new issues ( or reintroduce regressions). This patch aims to address all those issues by moving the fuzz target into the elfutils repository, integrating it into the testsuite and also providing a script that can be used to build full-fledged fuzzers utilizing libFuzzer. With this patch applied `make check` can be used to make sure that files kept in tests/fuzz-dwfl-core-corpus don't crash the code on various architecures. `--enable-sanitize-{address,undefined}` and/or `--enable-valgrind` can additionally be used to uncover issues like https://sourceware.org/bugzilla/show_bug.cgi?id=28685 that don't always manifest themselves in simple segfaults. On top of all that now the fuzz target can be built and linked against libFuzzer locally by just running `./tests/build-fuzzers.sh`. The patch was tested in https://github.com/evverx/elfutils/pull/49: * the testsuite was run on aarch64, armhfp, i386, ppc64le, s390x and x86_64 * Fedora packages were built on those architectures; * elfutils was built with both clang and gcc with and without sanitizers to make sure the tests pass there; * `make distcheck` passed; * coverage reports were built to make sure "static" builds are intact; * the fuzz target was built and run with ClusterFuzzLite to make sure it's still compatible with OSS-Fuzz; * the code was analyzed by various static analyzers to make sure new alerts aren't introduced. Signed-off-by: Evgeny Vereshchagin <evvers@ya.ru> --- tests/.gitignore | 1 + tests/ChangeLog | 5 ++ tests/Makefile.am | 20 ++++- tests/build-fuzzers.sh | 95 +++++++++++++++++++++ tests/fuzz-dwfl-core-corpus/empty | 0 tests/fuzz-dwfl-core-corpus/oss-fuzz-41566 | Bin 0 -> 1553 bytes tests/fuzz-dwfl-core-corpus/oss-fuzz-41570 | Bin 0 -> 1233 bytes tests/fuzz-dwfl-core.c | 50 +++++++++++ tests/fuzz-main.c | 43 ++++++++++ tests/fuzz.h | 9 ++ tests/run-fuzz-dwfl-core.sh | 11 +++ 11 files changed, 232 insertions(+), 2 deletions(-) create mode 100755 tests/build-fuzzers.sh create mode 100644 tests/fuzz-dwfl-core-corpus/empty create mode 100644 tests/fuzz-dwfl-core-corpus/oss-fuzz-41566 create mode 100644 tests/fuzz-dwfl-core-corpus/oss-fuzz-41570 create mode 100644 tests/fuzz-dwfl-core.c create mode 100644 tests/fuzz-main.c create mode 100644 tests/fuzz.h create mode 100755 tests/run-fuzz-dwfl-core.sh diff --git a/tests/.gitignore b/tests/.gitignore index 99d04819..c5429d0a 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -66,6 +66,7 @@ /find-prologues /funcretval /funcscopes +/fuzz-dwfl-core /get-aranges /get-files /get-lines diff --git a/tests/ChangeLog b/tests/ChangeLog index 82061c6e..511c12cf 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -1,3 +1,8 @@ +2021-12-13 Evgeny Vereshchagin <evvers@ya.ru> + + * Makefile.am: Integrate fuzz-dwfl-core into the testsuite and add a + script linking it against libFuzzer. + 2021-12-09 Frank Ch. Eigler <fche@redhat.com> * debuginfod-subr.sh (xfail): New proc. diff --git a/tests/Makefile.am b/tests/Makefile.am index b2da2c83..6e1dd699 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -62,6 +62,7 @@ check_PROGRAMS = arextract arsymtest newfile saridx scnnames sectiondump \ getphdrnum leb128 read_unaligned \ msg_tst system-elf-libelf-test \ nvidia_extended_linemap_libdw \ + fuzz-dwfl-core \ $(asm_TESTS) asm_TESTS = asm-tst1 asm-tst2 asm-tst3 asm-tst4 asm-tst5 \ @@ -197,7 +198,8 @@ TESTS = run-arextract.sh run-arsymtest.sh run-ar.sh newfile test-nlist \ msg_tst system-elf-libelf-test \ $(asm_TESTS) run-disasm-bpf.sh run-low_high_pc-dw-form-indirect.sh \ run-nvidia-extended-linemap-libdw.sh run-nvidia-extended-linemap-readelf.sh \ - run-readelf-dw-form-indirect.sh run-strip-largealign.sh + run-readelf-dw-form-indirect.sh run-strip-largealign.sh \ + run-fuzz-dwfl-core.sh if !BIARCH export ELFUTILS_DISABLE_BIARCH = 1 @@ -580,7 +582,11 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \ run-readelf-dw-form-indirect.sh testfile-dw-form-indirect.bz2 \ run-nvidia-extended-linemap-libdw.sh run-nvidia-extended-linemap-readelf.sh \ testfile_nvidia_linemap.bz2 \ - testfile-largealign.o.bz2 run-strip-largealign.sh + testfile-largealign.o.bz2 run-strip-largealign.sh \ + run-fuzz-dwfl-core.sh \ + fuzz-dwfl-core-corpus/empty \ + fuzz-dwfl-core-corpus/oss-fuzz-41566 \ + fuzz-dwfl-core-corpus/oss-fuzz-41570 if USE_VALGRIND @@ -755,6 +761,16 @@ leb128_LDADD = $(libelf) $(libdw) read_unaligned_LDADD = $(libelf) $(libdw) nvidia_extended_linemap_libdw_LDADD = $(libelf) $(libdw) +# Fuzz targets are split into two files so that they can be +# compatible with the test suite and OSS-Fuzz. OSS-Fuzz takes +# files containing LLVMFuzzerTestOneInput and links them against +# libFuzzer, AFL++ and honggfuzz. The testsuite links them against +# fuzz-main.c (which is a local driver reading files into buffers +# and passing those buffers to LLVMFuzzerTestOneInput). +noinst_HEADERS=fuzz.h +fuzz_dwfl_core_SOURCES = fuzz-main.c fuzz-dwfl-core.c +fuzz_dwfl_core_LDADD = $(libelf) $(libdw) + # We want to test the libelf header against the system elf.h header. # Don't include any -I CPPFLAGS. Except when we install our own elf.h. if !INSTALL_ELFH diff --git a/tests/build-fuzzers.sh b/tests/build-fuzzers.sh new file mode 100755 index 00000000..1fbab1f7 --- /dev/null +++ b/tests/build-fuzzers.sh @@ -0,0 +1,95 @@ +#!/bin/bash -eu + +# This script is supposed to be compatible with OSS-Fuzz, i.e. it has to use +# environment variables like $CC, $CFLAGS and $OUT, link the fuzz targets with CXX +# (even though the project is written in C) and so on: +# https://google.github.io/oss-fuzz/getting-started/new-project-guide/#buildsh + +# The fuzz targets it builds can't make any assumptions about +# their runtime environment apart from /tmp being writable: +# https://google.github.io/oss-fuzz/further-reading/fuzzer-environment/ . +# Even though it says there that it's possible to link fuzz targets against +# their dependencies dynamically by moving them to $OUT and changing +# rpath, it tends to break coverage reports from time to time https://github.com/google/oss-fuzz/issues/6524 +# so all the dependencies are linked statically here. + +# This script is configured via https://github.com/google/oss-fuzz/blob/master/projects/elfutils/project.yaml +# and used to build the elfutils project on OSS-Fuzz with three fuzzing engines +# (libFuzzer, AFL++ and honggfuzz) on two architectures (x86_64 and i386) +# with three sanitizers (ASan, UBSan and MSan) with coverage reports on top of +# all that: https://oss-fuzz.com/coverage-report/job/libfuzzer_asan_elfutils/latest +# so before changing anything ideally it should be tested with the OSS-Fuzz toolchain +# described at https://google.github.io/oss-fuzz/advanced-topics/reproducing/#building-using-docker +# by running something like: +# +# ./infra/helper.py pull_images +# ./infra/helper.py build_image --no-pull elfutils +# for sanitizer in address undefined memory; do +# for engine in libfuzzer afl honggfuzz; do +# ./infra/helper.py build_fuzzers --clean --sanitizer=$sanitizer --engine=$engine elfutils PATH/TO/ELFUTILS +# ./infra/helper.py check_build --sanitizer=$sanitizer --engine=$engine -e ALLOWED_BROKEN_TARGETS_PERCENTAGE=0 elfutils +# done +# done +# +# ./infra/helper.py build_fuzzers --clean --architecture=i386 elfutils PATH/TO/ELFUTILS +# ./infra/helper.py check_build --architecture=i386 -e ALLOWED_BROKEN_TARGETS_PERCENTAGE=0 elfutils +# +# ./infra/helper.py build_fuzzers --clean --sanitizer=coverage elfutils PATH/TO/ELFUTILS +# ./infra/helper.py coverage --no-corpus-download --fuzz-target=fuzz-dwfl-core --corpus-dir=PATH/TO/ELFUTILS/tests/fuzz-dwfl-core-corpus/ elfutils +# +# It should be possible to eventually automate that with ClusterFuzzLite https://google.github.io/clusterfuzzlite/ +# but it doesn't seem to be compatible with buildbot currently. + +# The script can also be used to build and run the fuzz target locally without Docker. +# After installing clang and the build dependencies of libelf by running something +# like `dnf build-dep elfutils-devel` on Fedora or `apt-get build-dep libelf-dev` +# on Debian/Ubuntu, the following commands should be run: +# +# $ ./tests/oss-fuzz.sh +# $ ./out/fuzz-dwfl-core tests/fuzz-dwfl-core-corpus/ + +set -eux + +cd "$(dirname -- "$0")/.." + +SANITIZER=${SANITIZER:-address} +flags="-O1 -fno-omit-frame-pointer -g -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fsanitize=$SANITIZER -fsanitize=fuzzer-no-link" + +export CC=${CC:-clang} +export CFLAGS=${CFLAGS:-$flags} + +export CXX=${CXX:-clang++} +export CXXFLAGS=${CXXFLAGS:-$flags} + +export OUT=${OUT:-"$(pwd)/out"} +mkdir -p "$OUT" + +export LIB_FUZZING_ENGINE=${LIB_FUZZING_ENGINE:--fsanitize=fuzzer} + +make clean || true + +# ASan isn't compatible with -Wl,--no-undefined: https://github.com/google/sanitizers/issues/380 +find -name Makefile.am | xargs sed -i 's/,--no-undefined//' + +# ASan isn't compatible with -Wl,-z,defs either: +# https://clang.llvm.org/docs/AddressSanitizer.html#usage +sed -i 's/^\(ZDEFS_LDFLAGS=\).*/\1/' configure.ac + +autoreconf -i -f +if ! ./configure --enable-maintainer-mode --disable-debuginfod --disable-libdebuginfod \ + --without-bzlib --without-lzma --without-zstd \ + CC="$CC" CFLAGS="-Wno-error $CFLAGS" CXX="-Wno-error $CXX" CXXFLAGS="$CXXFLAGS" LDFLAGS="$CFLAGS"; then + cat config.log + exit 1 +fi + +ASAN_OPTIONS=detect_leaks=0 make -j$(nproc) V=1 + +$CC $CFLAGS \ + -D_GNU_SOURCE -DHAVE_CONFIG_H \ + -I. -I./lib -I./libelf -I./libebl -I./libdw -I./libdwelf -I./libdwfl -I./libasm \ + -c tests/fuzz-dwfl-core.c -o fuzz-dwfl-core.o +$CXX $CXXFLAGS $LIB_FUZZING_ENGINE fuzz-dwfl-core.o \ + ./libdw/libdw.a ./libelf/libelf.a -l:libz.a \ + -o "$OUT/fuzz-dwfl-core" +zip -r -j "$OUT/fuzz-dwfl-core_seed_corpus.zip" tests/fuzz-dwfl-core-corpus diff --git a/tests/fuzz-dwfl-core-corpus/empty b/tests/fuzz-dwfl-core-corpus/empty new file mode 100644 index 00000000..e69de29b diff --git a/tests/fuzz-dwfl-core-corpus/oss-fuzz-41566 b/tests/fuzz-dwfl-core-corpus/oss-fuzz-41566 new file mode 100644 index 0000000000000000000000000000000000000000..a4181e6572fea9d568e787b0a4b57bf5defe4563 GIT binary patch literal 1553 zcmb<-^>JfjWK@8F|4<r1IWRx~LIlYA|Np-L14M)cC=Fr>fEZ9VL<T}KGT{)1sfFv4 zUU)AQVivOgk-<g|cVzZp@;xxpL4YbwB4i4>JUX8$ju<rrO9()6D<mHh!)664a$sa& zU<1<5{z0w`Xl5eE95NeC848yfYVm&sNSOdFA{Z>7;vhRg07Vl897w_@)^+$TB4i4> TJctkTAH-MG^A~=X;*bRZ0NBqd literal 0 HcmV?d00001 diff --git a/tests/fuzz-dwfl-core-corpus/oss-fuzz-41570 b/tests/fuzz-dwfl-core-corpus/oss-fuzz-41570 new file mode 100644 index 0000000000000000000000000000000000000000..4052572f5a484963b7bf03bb350368877671e7b1 GIT binary patch literal 1233 zcmb<-^>JfjWK_Tf7#Sb{Ro;R@j6vZ)5TFXvi#KpK)60jW_Kb!A{ty6VOppp_{$x;q U@IXR1zy$|x8Hg$z3WkL+07HkLIsgCw literal 0 HcmV?d00001 diff --git a/tests/fuzz-dwfl-core.c b/tests/fuzz-dwfl-core.c new file mode 100644 index 00000000..8fea6f51 --- /dev/null +++ b/tests/fuzz-dwfl-core.c @@ -0,0 +1,50 @@ +#include <assert.h> +#include <config.h> +#include <stdlib.h> +#include ELFUTILS_HEADER(dwfl) +#include "fuzz.h" +#include "system.h" + +static const Dwfl_Callbacks core_callbacks = + { + .find_elf = dwfl_build_id_find_elf, + .find_debuginfo = dwfl_standard_find_debuginfo, + }; + +int +LLVMFuzzerTestOneInput (const uint8_t *data, size_t size) +{ + char name[] = "/tmp/fuzz-dwfl-core.XXXXXX"; + int fd = -1; + off_t offset; + ssize_t n; + Elf *core = NULL; + Dwfl *dwfl = NULL; + + fd = mkstemp (name); + assert (fd >= 0); + + n = write_retry (fd, data, size); + assert (n >= 0); + + offset = lseek (fd, 0, SEEK_SET); + assert (offset == 0); + + elf_version (EV_CURRENT); + core = elf_begin (fd, ELF_C_READ_MMAP, NULL); + if (core == NULL) + goto cleanup; + dwfl = dwfl_begin (&core_callbacks); + assert(dwfl != NULL); + if (dwfl_core_file_report (dwfl, core, NULL) < 0) + goto cleanup; + if (dwfl_report_end (dwfl, NULL, NULL) != 0) + goto cleanup; + +cleanup: + dwfl_end (dwfl); + elf_end (core); + close (fd); + unlink (name); + return 0; +} diff --git a/tests/fuzz-main.c b/tests/fuzz-main.c new file mode 100644 index 00000000..35573792 --- /dev/null +++ b/tests/fuzz-main.c @@ -0,0 +1,43 @@ +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include "fuzz.h" + +int +main (int argc, char **argv) +{ + for (int i = 1; i < argc; i++) + { + fprintf (stderr, "Running: %s\n", argv[i]); + + FILE *f = fopen (argv[i], "r"); + assert (f); + + int p = fseek (f, 0, SEEK_END); + assert (p >= 0); + + long len = ftell (f); + assert (len >= 0); + + p = fseek (f, 0, SEEK_SET); + assert (p >= 0); + + void *buf = malloc (len); + assert (buf != NULL || len == 0); + + size_t n_read = fread (buf, 1, len, f); + assert (n_read == (size_t) len); + + (void) fclose (f); + + int r = LLVMFuzzerTestOneInput (buf, len); + + /* Non-zero return values are reserved by LibFuzzer for future use + https://llvm.org/docs/LibFuzzer.html#fuzz-target */ + assert (r == 0); + + free (buf); + + fprintf (stderr, "Done: %s: (%zd bytes)\n", argv[i], n_read); + } +} diff --git a/tests/fuzz.h b/tests/fuzz.h new file mode 100644 index 00000000..c8fe7a3a --- /dev/null +++ b/tests/fuzz.h @@ -0,0 +1,9 @@ +#ifndef _FUZZ_H +#define _FUZZ_H 1 + +#include <stddef.h> +#include <stdint.h> + +int LLVMFuzzerTestOneInput (const uint8_t *data, size_t size); + +#endif /* fuzz.h */ diff --git a/tests/run-fuzz-dwfl-core.sh b/tests/run-fuzz-dwfl-core.sh new file mode 100755 index 00000000..d7c0cea5 --- /dev/null +++ b/tests/run-fuzz-dwfl-core.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +. $srcdir/test-subr.sh + +exit_status=0 +for file in ${abs_srcdir}/fuzz-dwfl-core-corpus/*; do + testrun ${abs_builddir}/fuzz-dwfl-core $file || + { echo "*** failure in $file"; exit_status=1; } +done + +exit $exit_status -- 2.33.1 ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] tests: integrate fuzz-dwfl-core into elfutils 2021-12-12 15:16 [PATCH] tests: integrate fuzz-dwfl-core into elfutils Evgeny Vereshchagin @ 2021-12-17 10:46 ` Mark Wielaard 2021-12-17 12:23 ` Evgeny Vereshchagin 2021-12-19 19:23 ` [PATCH] tests: integrate fuzz-dwfl-core into the test suite Evgeny Vereshchagin 0 siblings, 2 replies; 6+ messages in thread From: Mark Wielaard @ 2021-12-17 10:46 UTC (permalink / raw) To: Evgeny Vereshchagin; +Cc: elfutils-devel Hi Evgeny, On Sun, Dec 12, 2021 at 03:16:58PM +0000, Evgeny Vereshchagin via Elfutils-devel wrote: > The fuzz target was integrated into OSS-Fuzz in > https://github.com/google/oss-fuzz/pull/6944 and since then it > has been running there continously (uncovering various issues > along the way). It's all well and good but since OSS-Fuzz > is far from the elfutils repository it's unnecessarily hard > to build the fuzz target locally, verify patches and more generally > test new code to make sure that it doesn't introduce new issues ( > or reintroduce regressions). This patch aims to address all those > issues by moving the fuzz target into the elfutils repository, > integrating it into the testsuite and also providing a script > that can be used to build full-fledged fuzzers utilizing libFuzzer. > With this patch applied `make check` can be used to make sure > that files kept in tests/fuzz-dwfl-core-corpus don't crash the > code on various architecures. > `--enable-sanitize-{address,undefined}` and/or `--enable-valgrind` > can additionally be used to uncover issues like > https://sourceware.org/bugzilla/show_bug.cgi?id=28685 > that don't always manifest themselves in simple segfaults. On top > of all that now the fuzz target can be built and linked against > libFuzzer locally by just running `./tests/build-fuzzers.sh`. I like the general idea of this. I have been using src/stack as fuzz target locally, but that is not ideal since it does too much. Having specific fuzz target binaries is much better. I also like the idea of making those fuzz-targets into regular testsuite targets. I am still trying to wrap my head around the LLVMFuzzerTestOneInput and libfuzzer requirements. I have experience with afl and honggfuzz which don't have any external library requirement. Also the LLVMFuzzerTestOneInput seems backwards. Shouldn't there be a more generic name for a function that is called by a fuzzer? Maybe it seems upside down because you translate from data stream to byte array and back and then read in the reconstructed stream again. Once you have the bytes you can simply call Elf *elf_memory (char *__image, size_t __size), There is no need to first write out the image to disk and then use a file descriptor to read it back in. One thing I struggle with is the initial seed (corpus). It needs to be as small as possible, but also contain some valid ELF (core) files, so that the fuzzer knows which valid paths there are to try out. How did you construct the initial corpus? I normally try to create at least four (little|big) endian and (32|64) bit minimal valid ELF files, but that is not always easy. Finally I wonder if we cannot integrate the logic in build-fuzzers.sh in the normal auto* build and create a "make fuzz" target that simply uses CC=afl-gcc or CC=hfuzz-gcc and runs afl-fuzz or honggfuzz for a couple of minutes if installed/detected by configure. The build-fuzzers.sh script seems very specific to a google setup which most people won't have locally and which seems somewhat tricky to replicate on other CI builders. Cheers, Mark ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] tests: integrate fuzz-dwfl-core into elfutils 2021-12-17 10:46 ` Mark Wielaard @ 2021-12-17 12:23 ` Evgeny Vereshchagin 2021-12-19 19:23 ` [PATCH] tests: integrate fuzz-dwfl-core into the test suite Evgeny Vereshchagin 1 sibling, 0 replies; 6+ messages in thread From: Evgeny Vereshchagin @ 2021-12-17 12:23 UTC (permalink / raw) To: Mark Wielaard; +Cc: elfutils-devel Hi Mark, > Once you > have the bytes you can simply call Elf *elf_memory (char *__image, > size_t __size), There is no need to first write out the image to disk > and then use a file descriptor to read it back in. I think I should have mentioned in the commit message that the fuzz target came from systemd where elfutils is hidden behind functions receiving filenames and file descriptors and I wanted to cover that code. If I had switched to elf_memory I couldn't have covered the code paths used by systemd. I agree that in other fuzz targets I'm planning to add elf_memory should be used instead but it would be great if it was possible to keep this kind of systemd-specific target. > One thing I struggle with is the initial seed (corpus). It needs to be > as small as possible, but also contain some valid ELF (core) files, so > that the fuzzer knows which valid paths there are to try out. How did > you construct the initial corpus? I normally try to create at least > four (little|big) endian and (32|64) bit minimal valid ELF files, but > that is not always easy. I think the name of that directory is a misnomer because it isn't a seed corpus. I should have probably called it "fuzz-dwfl-core-crashes" or "fuzz-dwfl-core-regressions" because I simply put files that have triggered various issues there. I think initially I just added a valid core file used in the systemd testsuite and let OSS-Fuzz deal with the rest. More generally, with OSS-Fuzz I kind of use brute force to get corpora covering as much code as possible with as few files as possible and with them it usually takes about 5-10 seconds to figure out whether new bugs are introduced or not. Eventually those corpora can be downloaded, unpacked and used in test scripts. elfutils hasn't been there long enough to get links to those corpora but for example one of systemd corpora can be downloaded from https://storage.googleapis.com/systemd-backup.clusterfuzz-external.appspot.com/corpus/libFuzzer/systemd_fuzz-varlink/public.zip > Finally I wonder if we cannot integrate the logic in build-fuzzers.sh > in the normal auto* build and create a "make fuzz" target that simply > uses CC=afl-gcc or CC=hfuzz-gcc and runs afl-fuzz or honggfuzz for a > couple of minutes if installed/detected by configure. I think it's a good idea but I'm not sure how to make that all compatible with OSS-Fuzz. I'll try to figure out how to do that. > The > build-fuzzers.sh script seems very specific to a google setup which > most people won't have locally and which seems somewhat tricky to > replicate on other CI builders. I tried to decouple it from OSS-Fuzz as much as I could so in its current form to build the fuzzer with LibFuzzer it should be enough to install clang and run the script. ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH] tests: integrate fuzz-dwfl-core into the test suite 2021-12-17 10:46 ` Mark Wielaard 2021-12-17 12:23 ` Evgeny Vereshchagin @ 2021-12-19 19:23 ` Evgeny Vereshchagin 2021-12-21 1:49 ` [PATCH v3] " Evgeny Vereshchagin 1 sibling, 1 reply; 6+ messages in thread From: Evgeny Vereshchagin @ 2021-12-19 19:23 UTC (permalink / raw) To: elfutils-devel; +Cc: mark, Evgeny Vereshchagin [v2] 1) At https://sourceware.org/pipermail/elfutils-devel/2021q4/004541.html it was pointed out that build-fuzzers.sh is too tied to OSS-Fuzz and while it was kind of decoupled from it as much as possible in the sense that it was enough to install clang and run the script to build the fuzz target with libFuzzer it's true that it can't be integrated smoothly into buildbot for example where gcc is used and various configure options control what exactly is tested. To address that, `--enable-honggfuzz` is introduced. It looks for hfuzz-gcc, hfuzz-g++ and honggfuzz and if they exist elfutils is built with those wrappers and the fuzz target is additionally run for a few minutes under honggfuzz to make regression testing more effective. It was tested on Fedora 35 and in https://github.com/evverx/elfutils/pull/49 on Ubuntu Focal with both gcc and clang with and without sanitizers/Valgrind and with two versions of honggfuzz (including the latest stable version). To make it work on Ubuntu the following commands should be run ``` apt-get install libbfd-dev libunwind8-dev git clone https://github.com/google/honggfuzz cd honggfuzz git checkout 2.4 make make PREFIX=/usr install cd PATH/TO/ELFUTILS autoreconf -i -f ./configure --enable-maintainer-mode --enable-honggfuzz make check V=1 VERBOSE=1 # FUZZ_TIME can be optionally passed ``` If hongfuzz is installed elsewhere it's possible to point configure to it with CC, CXX and HONGGFUZZ ``` ./configure CC=path-to-hfuzz-gcc CXX=path-to-hfuzz-g++ HONGGFUZZ=path-to-honggfuzz ``` I decided to use honggfuzz instead of AFL because AFL doesn't seem to be maintained actively anymore. Other than that I can't seem to make it work with various combinations of compilers, sanitizers and so on. But thanks to the way the fuzz target is written it should be possible to add it eventually by analogy with honggfuzz. 2) fuzz-dwfl-core-corpus was renamed to fuzz-dwfl-core-crashes to make it more clear that it isn't exaclty a seed corpus. 3) run-strip-g.sh and run-strip-nothing.sh started to compile test programs using temporary files instead of gcc -xc -. It should address https://github.com/google/honggfuzz/issues/431 but more generally autoconf uses temporary files to make sure compiler works so it seems in general it's safer to rely on compiler features that are known to work. 4) A comment was added where I tried to expand on why the fuzz target is written that way. [v1] The fuzz target was integrated into OSS-Fuzz in https://github.com/google/oss-fuzz/pull/6944 and since then it has been running there continously (uncovering various issues along the way). It's all well and good but since OSS-Fuzz is far from the elfutils repository it's unnecessarily hard to build the fuzz target locally, verify patches and more generally test new code to make sure that it doesn't introduce new issues ( or reintroduce regressions). This patch aims to address all those issues by moving the fuzz target into the elfutils repository, integrating it into the testsuite and also providing a script that can be used to build full-fledged fuzzers utilizing libFuzzer. With this patch applied `make check` can be used to make sure that files kept in tests/fuzz-dwfl-core-corpus don't crash the code on various architecures. `--enable-sanitize-{address,undefined}` and/or `--enable-valgrind` can additionally be used to uncover issues like https://sourceware.org/bugzilla/show_bug.cgi?id=28685 that don't always manifest themselves in simple segfaults. On top of all that now the fuzz target can be built and linked against libFuzzer locally by just running `./tests/build-fuzzers.sh`. The patch was tested in https://github.com/evverx/elfutils/pull/49 : * the testsuite was run on aarch64, armhfp, i386, ppc64le, s390x and x86_64 * Fedora packages were built on those architectures; * elfutils was built with both clang and gcc with and without sanitizers to make sure the tests pass there; * `make distcheck` passed; * coverage reports were built to make sure "static" builds are intact; * the fuzz target was built and run with ClusterFuzzLite to make sure it's still compatible with OSS-Fuzz; * the code was analyzed by various static analyzers to make sure new alerts aren't introduced. Signed-off-by: Evgeny Vereshchagin <evvers@ya.ru> --- ChangeLog | 4 + configure.ac | 17 ++++ tests/.gitignore | 1 + tests/ChangeLog | 8 ++ tests/Makefile.am | 30 ++++++- tests/build-fuzzers.sh | 95 ++++++++++++++++++++ tests/fuzz-dwfl-core-crashes/bugzilla-28660 | Bin 0 -> 20890 bytes tests/fuzz-dwfl-core-crashes/empty | 0 tests/fuzz-dwfl-core-crashes/oss-fuzz-41566 | Bin 0 -> 1553 bytes tests/fuzz-dwfl-core-crashes/oss-fuzz-41570 | Bin 0 -> 1233 bytes tests/fuzz-dwfl-core-crashes/oss-fuzz-41572 | Bin 0 -> 4872 bytes tests/fuzz-dwfl-core.c | 58 ++++++++++++ tests/fuzz-main.c | 43 +++++++++ tests/fuzz.h | 9 ++ tests/run-fuzz-dwfl-core.sh | 65 ++++++++++++++ tests/run-strip-g.sh | 7 +- tests/run-strip-nothing.sh | 7 +- 17 files changed, 337 insertions(+), 7 deletions(-) create mode 100755 tests/build-fuzzers.sh create mode 100644 tests/fuzz-dwfl-core-crashes/bugzilla-28660 create mode 100644 tests/fuzz-dwfl-core-crashes/empty create mode 100644 tests/fuzz-dwfl-core-crashes/oss-fuzz-41566 create mode 100644 tests/fuzz-dwfl-core-crashes/oss-fuzz-41570 create mode 100644 tests/fuzz-dwfl-core-crashes/oss-fuzz-41572 create mode 100644 tests/fuzz-dwfl-core.c create mode 100644 tests/fuzz-main.c create mode 100644 tests/fuzz.h create mode 100755 tests/run-fuzz-dwfl-core.sh diff --git a/ChangeLog b/ChangeLog index f00db17b..2ab883ae 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2021-12-19 Evgeny Vereshchagin <evvers@ya.ru> + + * configure.ac: Add --enable-honggfuzz. + 2021-12-04 Mark Wielaard <mark@klomp.org> * configure.ac: Add --enable-sanitize-address. diff --git a/configure.ac b/configure.ac index 48071165..b7547e6d 100644 --- a/configure.ac +++ b/configure.ac @@ -87,6 +87,23 @@ AS_IF([test "$use_locks" = yes], AH_TEMPLATE([USE_LOCKS], [Defined if libraries should be thread-safe.]) +AC_ARG_ENABLE([honggfuzz], +AS_HELP_STRING([--enable-honggfuzz],[build binaries with honggfuzz]), [use_honggfuzz=$enableval], [use_honggfuzz=no]) +if test "$use_honggfuzz" = yes; then + AC_CHECK_PROG(CC, hfuzz-gcc, hfuzz-gcc) + if test -z "$CC"; then + AC_MSG_ERROR([failed to find hfuzz-gcc]) + fi + AC_CHECK_PROG(CXX, hfuzz-g++, hfuzz-g++) + if test -z "$CXX"; then + AC_MSG_ERROR([failed to find hfuzz-g++]) + fi + AC_CHECK_PROG(HONGGFUZZ, honggfuzz, honggfuzz) + if test -z "$HONGGFUZZ"; then + AC_MSG_ERROR([failed to find honggfuzz]) + fi +fi + AC_PROG_CC_C99 AC_PROG_RANLIB AC_PROG_YACC diff --git a/tests/.gitignore b/tests/.gitignore index 99d04819..c5429d0a 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -66,6 +66,7 @@ /find-prologues /funcretval /funcscopes +/fuzz-dwfl-core /get-aranges /get-files /get-lines diff --git a/tests/ChangeLog b/tests/ChangeLog index c97ed52e..627c765d 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -1,3 +1,11 @@ +2021-12-19 Evgeny Vereshchagin <evvers@ya.ru> + + * Makefile.am: Integrate fuzz-dwfl-core into the testsuite and add a + script linking it against libFuzzer. + * run-strip-g.sh,run-strip-nothing.sh: Switch to temporary files + instead of using CC -xc - to get around a honggfuzz issue mentioned at + https://github.com/google/honggfuzz/issues/431 . + 2021-12-17 Mark Wielaard <mark@klomp.org> * run-debuginfod-query-retry.sh: Use /bin/sh instead of /bin/ls. diff --git a/tests/Makefile.am b/tests/Makefile.am index b2da2c83..af1359e9 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -62,6 +62,7 @@ check_PROGRAMS = arextract arsymtest newfile saridx scnnames sectiondump \ getphdrnum leb128 read_unaligned \ msg_tst system-elf-libelf-test \ nvidia_extended_linemap_libdw \ + fuzz-dwfl-core \ $(asm_TESTS) asm_TESTS = asm-tst1 asm-tst2 asm-tst3 asm-tst4 asm-tst5 \ @@ -197,7 +198,8 @@ TESTS = run-arextract.sh run-arsymtest.sh run-ar.sh newfile test-nlist \ msg_tst system-elf-libelf-test \ $(asm_TESTS) run-disasm-bpf.sh run-low_high_pc-dw-form-indirect.sh \ run-nvidia-extended-linemap-libdw.sh run-nvidia-extended-linemap-readelf.sh \ - run-readelf-dw-form-indirect.sh run-strip-largealign.sh + run-readelf-dw-form-indirect.sh run-strip-largealign.sh \ + run-fuzz-dwfl-core.sh if !BIARCH export ELFUTILS_DISABLE_BIARCH = 1 @@ -580,14 +582,19 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \ run-readelf-dw-form-indirect.sh testfile-dw-form-indirect.bz2 \ run-nvidia-extended-linemap-libdw.sh run-nvidia-extended-linemap-readelf.sh \ testfile_nvidia_linemap.bz2 \ - testfile-largealign.o.bz2 run-strip-largealign.sh + testfile-largealign.o.bz2 run-strip-largealign.sh \ + run-fuzz-dwfl-core.sh \ + fuzz-dwfl-core-crashes/empty \ + fuzz-dwfl-core-crashes/bugzilla-28660 \ + fuzz-dwfl-core-crashes/oss-fuzz-41566 \ + fuzz-dwfl-core-crashes/oss-fuzz-41570 \ + fuzz-dwfl-core-crashes/oss-fuzz-41572 if USE_VALGRIND valgrind_cmd=valgrind -q --leak-check=full --error-exitcode=1 endif - installed_TESTS_ENVIRONMENT = libdir='$(DESTDIR)$(libdir)'; \ bindir='$(DESTDIR)$(bindir)'; \ LC_ALL=C; LANG=C; \ @@ -595,9 +602,11 @@ installed_TESTS_ENVIRONMENT = libdir='$(DESTDIR)$(libdir)'; \ abs_srcdir='$(abs_srcdir)'; \ abs_builddir='$(abs_builddir)'; \ abs_top_builddir='$(abs_top_builddir)'; \ + honggfuzz='$(HONGGFUZZ)'; \ export abs_srcdir; export abs_builddir; \ export abs_top_builddir; \ export libdir; export bindir; \ + export honggfuzz; \ export LC_ALL; export LANG; export VALGRIND_CMD; \ unset DEBUGINFOD_URLS; \ NM='$(NM)'; export NM; \ @@ -609,8 +618,10 @@ TESTS_ENVIRONMENT = LC_ALL=C; LANG=C; VALGRIND_CMD='$(valgrind_cmd)'; \ abs_srcdir='$(abs_srcdir)'; \ abs_builddir='$(abs_builddir)'; \ abs_top_builddir='$(abs_top_builddir)'; \ + honggfuzz='$(HONGGFUZZ)'; \ export abs_srcdir; export abs_builddir; \ export abs_top_builddir; \ + export honggfuzz; \ export LC_ALL; export LANG; export VALGRIND_CMD; \ unset DEBUGINFOD_URLS; \ NM='$(NM)'; export NM; \ @@ -755,6 +766,19 @@ leb128_LDADD = $(libelf) $(libdw) read_unaligned_LDADD = $(libelf) $(libdw) nvidia_extended_linemap_libdw_LDADD = $(libelf) $(libdw) +# Fuzz targets are split into two files so that they can be +# compatible with the test suite, OSS-Fuzz and various fuzzing +# engines. OSS-Fuzz takes files containing LLVMFuzzerTestOneInput +# and links them against libFuzzer, AFL++ and honggfuzz. +# The testsuite links them against fuzz-main.c (which is a local driver reading +# files into buffers and passing those buffers to LLVMFuzzerTestOneInput). +# And various fuzzing engines working with binaries reading files +# they generate can be used as well because fuzz-main.c provides +# exactly what they expect. +noinst_HEADERS=fuzz.h +fuzz_dwfl_core_SOURCES = fuzz-main.c fuzz-dwfl-core.c +fuzz_dwfl_core_LDADD = $(libelf) $(libdw) + # We want to test the libelf header against the system elf.h header. # Don't include any -I CPPFLAGS. Except when we install our own elf.h. if !INSTALL_ELFH diff --git a/tests/build-fuzzers.sh b/tests/build-fuzzers.sh new file mode 100755 index 00000000..4ba1fc41 --- /dev/null +++ b/tests/build-fuzzers.sh @@ -0,0 +1,95 @@ +#!/bin/bash -eu + +# This script is supposed to be compatible with OSS-Fuzz, i.e. it has to use +# environment variables like $CC, $CFLAGS and $OUT, link the fuzz targets with CXX +# (even though the project is written in C) and so on: +# https://google.github.io/oss-fuzz/getting-started/new-project-guide/#buildsh + +# The fuzz targets it builds can't make any assumptions about +# their runtime environment apart from /tmp being writable: +# https://google.github.io/oss-fuzz/further-reading/fuzzer-environment/ . +# Even though it says there that it's possible to link fuzz targets against +# their dependencies dynamically by moving them to $OUT and changing +# rpath, it tends to break coverage reports from time to time https://github.com/google/oss-fuzz/issues/6524 +# so all the dependencies are linked statically here. + +# This script is configured via https://github.com/google/oss-fuzz/blob/master/projects/elfutils/project.yaml +# and used to build the elfutils project on OSS-Fuzz with three fuzzing engines +# (libFuzzer, AFL++ and honggfuzz) on two architectures (x86_64 and i386) +# with three sanitizers (ASan, UBSan and MSan) with coverage reports on top of +# all that: https://oss-fuzz.com/coverage-report/job/libfuzzer_asan_elfutils/latest +# so before changing anything ideally it should be tested with the OSS-Fuzz toolchain +# described at https://google.github.io/oss-fuzz/advanced-topics/reproducing/#building-using-docker +# by running something like: +# +# ./infra/helper.py pull_images +# ./infra/helper.py build_image --no-pull elfutils +# for sanitizer in address undefined memory; do +# for engine in libfuzzer afl honggfuzz; do +# ./infra/helper.py build_fuzzers --clean --sanitizer=$sanitizer --engine=$engine elfutils PATH/TO/ELFUTILS +# ./infra/helper.py check_build --sanitizer=$sanitizer --engine=$engine -e ALLOWED_BROKEN_TARGETS_PERCENTAGE=0 elfutils +# done +# done +# +# ./infra/helper.py build_fuzzers --clean --architecture=i386 elfutils PATH/TO/ELFUTILS +# ./infra/helper.py check_build --architecture=i386 -e ALLOWED_BROKEN_TARGETS_PERCENTAGE=0 elfutils +# +# ./infra/helper.py build_fuzzers --clean --sanitizer=coverage elfutils PATH/TO/ELFUTILS +# ./infra/helper.py coverage --no-corpus-download --fuzz-target=fuzz-dwfl-core --corpus-dir=PATH/TO/ELFUTILS/tests/fuzz-dwfl-core-crashes/ elfutils +# +# It should be possible to eventually automate that with ClusterFuzzLite https://google.github.io/clusterfuzzlite/ +# but it doesn't seem to be compatible with buildbot currently. + +# The script can also be used to build and run the fuzz target locally without Docker. +# After installing clang and the build dependencies of libelf by running something +# like `dnf build-dep elfutils-devel` on Fedora or `apt-get build-dep libelf-dev` +# on Debian/Ubuntu, the following commands should be run: +# +# $ ./tests/build-fuzzers.sh +# $ ./out/fuzz-dwfl-core tests/fuzz-dwfl-core-crashes/ + +set -eux + +cd "$(dirname -- "$0")/.." + +SANITIZER=${SANITIZER:-address} +flags="-O1 -fno-omit-frame-pointer -g -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fsanitize=$SANITIZER -fsanitize=fuzzer-no-link" + +export CC=${CC:-clang} +export CFLAGS=${CFLAGS:-$flags} + +export CXX=${CXX:-clang++} +export CXXFLAGS=${CXXFLAGS:-$flags} + +export OUT=${OUT:-"$(pwd)/out"} +mkdir -p "$OUT" + +export LIB_FUZZING_ENGINE=${LIB_FUZZING_ENGINE:--fsanitize=fuzzer} + +make clean || true + +# ASan isn't compatible with -Wl,--no-undefined: https://github.com/google/sanitizers/issues/380 +find -name Makefile.am | xargs sed -i 's/,--no-undefined//' + +# ASan isn't compatible with -Wl,-z,defs either: +# https://clang.llvm.org/docs/AddressSanitizer.html#usage +sed -i 's/^\(ZDEFS_LDFLAGS=\).*/\1/' configure.ac + +autoreconf -i -f +if ! ./configure --enable-maintainer-mode --disable-debuginfod --disable-libdebuginfod \ + --without-bzlib --without-lzma --without-zstd \ + CC="$CC" CFLAGS="-Wno-error $CFLAGS" CXX="-Wno-error $CXX" CXXFLAGS="$CXXFLAGS" LDFLAGS="$CFLAGS"; then + cat config.log + exit 1 +fi + +ASAN_OPTIONS=detect_leaks=0 make -j$(nproc) V=1 + +$CC $CFLAGS \ + -D_GNU_SOURCE -DHAVE_CONFIG_H \ + -I. -I./lib -I./libelf -I./libebl -I./libdw -I./libdwelf -I./libdwfl -I./libasm \ + -c tests/fuzz-dwfl-core.c -o fuzz-dwfl-core.o +$CXX $CXXFLAGS $LIB_FUZZING_ENGINE fuzz-dwfl-core.o \ + ./libdw/libdw.a ./libelf/libelf.a -l:libz.a \ + -o "$OUT/fuzz-dwfl-core" +zip -r -j "$OUT/fuzz-dwfl-core_seed_corpus.zip" tests/fuzz-dwfl-core-crashes diff --git a/tests/fuzz-dwfl-core-crashes/bugzilla-28660 b/tests/fuzz-dwfl-core-crashes/bugzilla-28660 new file mode 100644 index 0000000000000000000000000000000000000000..b687b5601a6d4c20352a7a738fc6f99c06a0e748 GIT binary patch literal 20890 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 literal 0 HcmV?d00001 diff --git a/tests/fuzz-dwfl-core-crashes/empty b/tests/fuzz-dwfl-core-crashes/empty new file mode 100644 index 00000000..e69de29b diff --git a/tests/fuzz-dwfl-core-crashes/oss-fuzz-41566 b/tests/fuzz-dwfl-core-crashes/oss-fuzz-41566 new file mode 100644 index 0000000000000000000000000000000000000000..a4181e6572fea9d568e787b0a4b57bf5defe4563 GIT binary patch literal 1553 zcmb<-^>JfjWK@8F|4<r1IWRx~LIlYA|Np-L14M)cC=Fr>fEZ9VL<T}KGT{)1sfFv4 zUU)AQVivOgk-<g|cVzZp@;xxpL4YbwB4i4>JUX8$ju<rrO9()6D<mHh!)664a$sa& zU<1<5{z0w`Xl5eE95NeC848yfYVm&sNSOdFA{Z>7;vhRg07Vl897w_@)^+$TB4i4> TJctkTAH-MG^A~=X;*bRZ0NBqd literal 0 HcmV?d00001 diff --git a/tests/fuzz-dwfl-core-crashes/oss-fuzz-41570 b/tests/fuzz-dwfl-core-crashes/oss-fuzz-41570 new file mode 100644 index 0000000000000000000000000000000000000000..4052572f5a484963b7bf03bb350368877671e7b1 GIT binary patch literal 1233 zcmb<-^>JfjWK_Tf7#Sb{Ro;R@j6vZ)5TFXvi#KpK)60jW_Kb!A{ty6VOppp_{$x;q U@IXR1zy$|x8Hg$z3WkL+07HkLIsgCw literal 0 HcmV?d00001 diff --git a/tests/fuzz-dwfl-core-crashes/oss-fuzz-41572 b/tests/fuzz-dwfl-core-crashes/oss-fuzz-41572 new file mode 100644 index 0000000000000000000000000000000000000000..534d611b0c606003019d026f1028f248d2f1d656 GIT binary patch literal 4872 zcmeHJUrSU$6hE42VVcHLra{*S=^y*YEb$@YMy(<UEk=+Kv}Ir$W{aEPoAwa&G1hOe z&q3u!@DubL^wML{!*<Wi?{}RUE!ylw+<|+~pE<vK&YU@O&ukRuraL=2griGt%dR96 zbHbsqOvzcuU7!QU9Rr8m>2m~!RUZZ$K^cu~eR=2@41f=);*6^T<_iIipV$Xy{1MmR zI&Np_m`EwW1%3pw#@Vg0#QI7``R{Qi&XR>mG}e1z1H2OYD|K3><Im)!Y}Hb|<l~^0 zMrIGr_#-a5wLh+ItWy7JjS#mT?~h}=PMn^<od7qEKntJ_PV?Chwa6XcAMHs4oNNPF z2ykWSNgbStGr~&i_k4e_OZUUU+4=;o5#ZoXWbghNe+A;EeSgGlf^qzLxOm3L{UJ_U zpYT^|5my4P+#;?FT=h(*>D4v!L2B2j)@FUx<m~C#9`<pu(e9ozCog6n8o<ejkBfjK z1x_)#H1&J;{(Mobdi90j^3*N89h6V!MM$X+Y5jDlX-$stuZtdKytN=>(u2=6wu*iY zKtI%DYya|s#SejEN2G~ybKL%`CV)#CgyHD-8ualy`@J8)LH%1oKbqtB$)h{N9Lqdl z-LYZ5CX@Owr=*LkPirq=sw9^;n#fTdh}GCOlV^5j_5ow#o-_UfIrs;##uO*c^}I5s z(eyjEu6fLxKfis}DRZ`!!Nj2Q$#`yw$-T0%>ATH7bH);R3iIky2iMNp68r~G*y;3r z5*%xkINd`Xrz|mhsw}SsoacX?@;TU(B*)8)g~zsbhat1}FGBvlVdr_&DzL|NN_2fM zlh}%Lv(wBebI6<t?<C<JgK})oNm>_>c}HMusnh?>@GereaisnrOV#SJ1(Mz+!n*-w z^!_o%$-4l0dzA}iuSf6xKz-WF?SCQFvsa_{exyBnYS<$wH*622xj>eJ?Kz{2lRXbT z><ablY3NZ`q0hg0RkgnjX^CB{t>%|2&+_Y2lTRjZWS1)|YwOvil{Hl^X0PWa#&fmT hlK0IROPjVc(9Xco%Rq8BVsAToi*K*Eoq@xVfnU&q=l%cy literal 0 HcmV?d00001 diff --git a/tests/fuzz-dwfl-core.c b/tests/fuzz-dwfl-core.c new file mode 100644 index 00000000..c067fb40 --- /dev/null +++ b/tests/fuzz-dwfl-core.c @@ -0,0 +1,58 @@ +#include <assert.h> +#include <config.h> +#include <stdlib.h> +#include ELFUTILS_HEADER(dwfl) +#include "fuzz.h" +#include "system.h" + +/* This fuzz target was initially used to fuzz systemd and + there elfutils is hidden behind functions receiving file + names and file descriptors. To cover that code the fuzz + target converts bytes it receives into temporary files + and passes their file descriptors to elf_begin instead of calling + something like elf_memory (which can process bytes directly). + New fuzzers covering elfutils should avoid this pattern. */ + +static const Dwfl_Callbacks core_callbacks = + { + .find_elf = dwfl_build_id_find_elf, + .find_debuginfo = dwfl_standard_find_debuginfo, + }; + +int +LLVMFuzzerTestOneInput (const uint8_t *data, size_t size) +{ + char name[] = "/tmp/fuzz-dwfl-core.XXXXXX"; + int fd = -1; + off_t offset; + ssize_t n; + Elf *core = NULL; + Dwfl *dwfl = NULL; + + fd = mkstemp (name); + assert (fd >= 0); + + n = write_retry (fd, data, size); + assert (n >= 0); + + offset = lseek (fd, 0, SEEK_SET); + assert (offset == 0); + + elf_version (EV_CURRENT); + core = elf_begin (fd, ELF_C_READ_MMAP, NULL); + if (core == NULL) + goto cleanup; + dwfl = dwfl_begin (&core_callbacks); + assert(dwfl != NULL); + if (dwfl_core_file_report (dwfl, core, NULL) < 0) + goto cleanup; + if (dwfl_report_end (dwfl, NULL, NULL) != 0) + goto cleanup; + +cleanup: + dwfl_end (dwfl); + elf_end (core); + close (fd); + unlink (name); + return 0; +} diff --git a/tests/fuzz-main.c b/tests/fuzz-main.c new file mode 100644 index 00000000..35573792 --- /dev/null +++ b/tests/fuzz-main.c @@ -0,0 +1,43 @@ +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include "fuzz.h" + +int +main (int argc, char **argv) +{ + for (int i = 1; i < argc; i++) + { + fprintf (stderr, "Running: %s\n", argv[i]); + + FILE *f = fopen (argv[i], "r"); + assert (f); + + int p = fseek (f, 0, SEEK_END); + assert (p >= 0); + + long len = ftell (f); + assert (len >= 0); + + p = fseek (f, 0, SEEK_SET); + assert (p >= 0); + + void *buf = malloc (len); + assert (buf != NULL || len == 0); + + size_t n_read = fread (buf, 1, len, f); + assert (n_read == (size_t) len); + + (void) fclose (f); + + int r = LLVMFuzzerTestOneInput (buf, len); + + /* Non-zero return values are reserved by LibFuzzer for future use + https://llvm.org/docs/LibFuzzer.html#fuzz-target */ + assert (r == 0); + + free (buf); + + fprintf (stderr, "Done: %s: (%zd bytes)\n", argv[i], n_read); + } +} diff --git a/tests/fuzz.h b/tests/fuzz.h new file mode 100644 index 00000000..c8fe7a3a --- /dev/null +++ b/tests/fuzz.h @@ -0,0 +1,9 @@ +#ifndef _FUZZ_H +#define _FUZZ_H 1 + +#include <stddef.h> +#include <stdint.h> + +int LLVMFuzzerTestOneInput (const uint8_t *data, size_t size); + +#endif /* fuzz.h */ diff --git a/tests/run-fuzz-dwfl-core.sh b/tests/run-fuzz-dwfl-core.sh new file mode 100755 index 00000000..b281e479 --- /dev/null +++ b/tests/run-fuzz-dwfl-core.sh @@ -0,0 +1,65 @@ +#!/bin/sh + +. $srcdir/test-subr.sh + +# honggfuzz sets ASAN and UBSAN options compatible with it +# so they are reset early to prevent the environment from +# affecting the test +unset ASAN_OPTIONS +unset UBSAN_OPTIONS + +# run_one is used to process files without honggfuzz +# to get backtraces that otherwise can be borked in honggfuzz runs +# so it has to set ASAN and UBSAN options itself +run_one() +{ + testrun env \ + ASAN_OPTIONS=allocator_may_return_null=1 \ + UBSAN_OPTIONS=print_stacktrace=1:print_summary=1:halt_on_error=1 \ + ${abs_builddir}/fuzz-dwfl-core "$1" +} + +# Here the fuzz target processes files one by one to be able +# to catch memory leaks and other issues that can't be discovered +# with honggfuzz. This part can be run under ASan/UBSan/Valgrind +# so it is never skipped +exit_status=0 +for file in ${abs_srcdir}/fuzz-dwfl-core-crashes/*; do + run_one $file || { echo "*** failure in $file"; exit_status=1; } +done + +# Here Valgrind is turned off because +# hongfuzz keeps track of processes and signals they receive +# and valgrind shouldn't interfer with that +unset VALGRIND_CMD + +if [ -n "$honggfuzz" ]; then + tempfiles log + + testrun $honggfuzz --run_time ${FUZZ_TIME:-180} -n 1 -v --exit_upon_crash \ + -i ${abs_srcdir}/fuzz-dwfl-core-crashes/ \ + -t 30 \ + -o OUT \ + --logfile log \ + -- ${abs_builddir}/fuzz-dwfl-core ___FILE___ + + rm -rf OUT + + # hongfuzz always exits successfully so to tell "success" and "failure" apart + # it's necessary to look for reports it leaves when processes it monitors crash. + # Eventually it will be possible to pass --exit_code_upon_crash, which combined + # with --exit_upon_crash can be used to get honggfuzz to fail, but it hasn't been + # released yet. Initially it was used but on machines with the latest stable release + # tests that should have failed passed, which led to https://github.com/google/honggfuzz/pull/432 + if [ -f HONGGFUZZ.REPORT.TXT ]; then + tail -n 25 log + cat HF.sanitizer.log* || true + cat HONGGFUZZ.REPORT.TXT + for crash in $(sed -n 's/^FUZZ_FNAME: *//p' HONGGFUZZ.REPORT.TXT); do + run_one $crash || true + done + exit_status=1 + fi +fi + +exit $exit_status diff --git a/tests/run-strip-g.sh b/tests/run-strip-g.sh index 15921215..8b480ec0 100755 --- a/tests/run-strip-g.sh +++ b/tests/run-strip-g.sh @@ -22,10 +22,13 @@ # debug sections and so there should not be a copy in the debug file # except for a NOBITS one. -tempfiles a.out strip.out debug.out readelf.out +tmpfile=main.c + +tempfiles $tmpfile a.out strip.out debug.out readelf.out echo Create debug a.out. -echo "int main() { return 1; }" | ${CC} -g -xc - +echo "int main() { return 1; }" >$tmpfile +${CC} -g $tmpfile echo strip -g to file with debug file testrun ${abs_top_builddir}/src/strip -g -o strip.out -f debug.out || diff --git a/tests/run-strip-nothing.sh b/tests/run-strip-nothing.sh index 710c200d..e725297a 100755 --- a/tests/run-strip-nothing.sh +++ b/tests/run-strip-nothing.sh @@ -20,10 +20,13 @@ # If there is nothing to strip then -o output should be identical to input. # And there should not be an (empty) -f debug file. -tempfiles a.out strip.out debug.out +tmpfile=main.c + +tempfiles $tmpfile a.out strip.out debug.out # Create no-debug a.out. -echo "int main() { return 1; }" | ${CC} -s -xc - +echo "int main() { return 1; }" >$tmpfile +${CC} -s $tmpfile # strip to file testrun ${abs_top_builddir}/src/strip -g -o strip.out || -- 2.33.1 ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v3] tests: integrate fuzz-dwfl-core into the test suite 2021-12-19 19:23 ` [PATCH] tests: integrate fuzz-dwfl-core into the test suite Evgeny Vereshchagin @ 2021-12-21 1:49 ` Evgeny Vereshchagin 2021-12-26 16:03 ` [PATCH v4] " Evgeny Vereshchagin 0 siblings, 1 reply; 6+ messages in thread From: Evgeny Vereshchagin @ 2021-12-21 1:49 UTC (permalink / raw) To: elfutils-devel; +Cc: mark, Evgeny Vereshchagin [v3] The test handles infinite loops much better now. In https://sourceware.org/bugzilla/show_bug.cgi?id=28715#c4 it took it about 5 hours on Packit to discover an infinite loop on 32 bit platforms because it didn't enforce any timeouts. It was fixed by passing --tmout_sigvtalrm to honggfuzz (which treats timeouts as normal crashes) and by additionally running the fuzz target with timeout -s9. [v2] 1) At https://sourceware.org/pipermail/elfutils-devel/2021q4/004541.html it was pointed out that build-fuzzers.sh is too tied to OSS-Fuzz and while it was kind of decoupled from it as much as possible in the sense that it was enough to install clang and run the script to build the fuzz target with libFuzzer it's true that it can't be integrated smoothly into buildbot for example where gcc is used and various configure options control what exactly is testsed. To address that, `--enable-honggfuzz` is introduced. It looks for hfuzz-gcc, hfuzz-g++ and honggfuzz and if they exist elfutils is built with those wrappers and the fuzz target is additionally run for a few minutes under honggfuzz to make regression testing more effective. It was tested on Fedora 35 and in https://github.com/evverx/elfutils/pull/53 on Ubuntu Focal with both gcc and clang with and without sanitizers/Valgrind and with two versions of honggfuzz (including the latest stable version). To make it work on Ubuntu the following commands should be run ``` apt-get install libbfd-dev libunwind8-dev git clone https://github.com/google/honggfuzz cd honggfuzz git checkout 2.4 make make PREFIX=/usr install cd PATH/TO/ELFUTILS autoreconf -i -f ./configure --enable-maintainer-mode --enable-honggfuzz make check V=1 VERBOSE=1 # FUZZ_TIME can be optionally passed ``` If hongfuzz is installed elsewhere it's possible to point configure to it with CC, CXX and HONGGFUZZ ``` ./configure CC=path-to-hfuzz-gcc CXX=path-to-hfuzz-g++ HONGGFUZZ=path-to-honggfuzz ``` I decided to use honggfuzz instead of AFL because AFL doesn't seem to be maintained actively anymore. Other than that I can't seem to make it work with various combinations of compilers, sanitizers and so on. But thanks to the way the fuzz target is written it should be possible to add it eventually by analogy with honggfuzz. 2) fuzz-dwfl-core-corpus was renamed to fuzz-dwfl-core-crashes to make it more clear that it isn't exaclty a seed corpus. 3) run-strip-g.sh and run-strip-nothing.sh started to compile test programs using temporary files instead of gcc -xc -. It should address https://github.com/google/honggfuzz/issues/431 but more generally autoconf uses temporary files to make sure compiler works so it seems in general it's safer to rely on compiler features that are known to work. 4) A comment was added where I tried to expand on why the fuzz target is written that way. [v1] The fuzz target was integrated into OSS-Fuzz in https://github.com/google/oss-fuzz/pull/6944 and since then it has been running there continously (uncovering various issues along the way). It's all well and good but since OSS-Fuzz is far from the elfutils repository it's unnecessarily hard to build the fuzz target locally, verify patches and more generally test new code to make sure that it doesn't introduce new issues ( or reintroduce regressions). This patch aims to address all those issues by moving the fuzz target into the elfutils repository, integrating it into the testsuite and also providing a script that can be used to build full-fledged fuzzers utilizing libFuzzer. With this patch applied `make check` can be used to make sure that files kept in tests/fuzz-dwfl-core-corpus don't crash the code on various architecures. `--enable-sanitize-{address,undefined}` and/or `--enable-valgrind` can additionally be used to uncover issues like https://sourceware.org/bugzilla/show_bug.cgi?id=28685 that don't always manifest themselves in simple segfaults. On top of all that now the fuzz target can be built and linked against libFuzzer locally by just running `./tests/build-fuzzers.sh`. The patch was tested in https://github.com/evverx/elfutils/pull/53 : * the testsuite was run on aarch64, armhfp, i386, ppc64le, s390x and x86_64 * Fedora packages were built on those architectures; * elfutils was built with both clang and gcc with and without sanitizers to make sure the tests pass there; * `make distcheck` passed; * coverage reports were built to make sure "static" builds are intact; * the fuzz target was built and run with ClusterFuzzLite to make sure it's still compatible with OSS-Fuzz; * the code was analyzed by various static analyzers to make sure new alerts aren't introduced. Signed-off-by: Evgeny Vereshchagin <evvers@ya.ru> --- ChangeLog | 4 + configure.ac | 17 ++++ tests/.gitignore | 1 + tests/ChangeLog | 8 ++ tests/Makefile.am | 30 ++++++- tests/build-fuzzers.sh | 95 ++++++++++++++++++++ tests/fuzz-dwfl-core-crashes/bugzilla-28660 | Bin 0 -> 20890 bytes tests/fuzz-dwfl-core-crashes/empty | 0 tests/fuzz-dwfl-core-crashes/oss-fuzz-41566 | Bin 0 -> 1553 bytes tests/fuzz-dwfl-core-crashes/oss-fuzz-41570 | Bin 0 -> 1233 bytes tests/fuzz-dwfl-core-crashes/oss-fuzz-41572 | Bin 0 -> 4872 bytes tests/fuzz-dwfl-core.c | 58 ++++++++++++ tests/fuzz-main.c | 43 +++++++++ tests/fuzz.h | 9 ++ tests/run-fuzz-dwfl-core.sh | 74 +++++++++++++++ tests/run-strip-g.sh | 7 +- tests/run-strip-nothing.sh | 7 +- 17 files changed, 346 insertions(+), 7 deletions(-) create mode 100755 tests/build-fuzzers.sh create mode 100644 tests/fuzz-dwfl-core-crashes/bugzilla-28660 create mode 100644 tests/fuzz-dwfl-core-crashes/empty create mode 100644 tests/fuzz-dwfl-core-crashes/oss-fuzz-41566 create mode 100644 tests/fuzz-dwfl-core-crashes/oss-fuzz-41570 create mode 100644 tests/fuzz-dwfl-core-crashes/oss-fuzz-41572 create mode 100644 tests/fuzz-dwfl-core.c create mode 100644 tests/fuzz-main.c create mode 100644 tests/fuzz.h create mode 100755 tests/run-fuzz-dwfl-core.sh diff --git a/ChangeLog b/ChangeLog index f00db17b..b7d80558 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2021-12-21 Evgeny Vereshchagin <evvers@ya.ru> + + * configure.ac: Add --enable-honggfuzz. + 2021-12-04 Mark Wielaard <mark@klomp.org> * configure.ac: Add --enable-sanitize-address. diff --git a/configure.ac b/configure.ac index 48071165..b7547e6d 100644 --- a/configure.ac +++ b/configure.ac @@ -87,6 +87,23 @@ AS_IF([test "$use_locks" = yes], AH_TEMPLATE([USE_LOCKS], [Defined if libraries should be thread-safe.]) +AC_ARG_ENABLE([honggfuzz], +AS_HELP_STRING([--enable-honggfuzz],[build binaries with honggfuzz]), [use_honggfuzz=$enableval], [use_honggfuzz=no]) +if test "$use_honggfuzz" = yes; then + AC_CHECK_PROG(CC, hfuzz-gcc, hfuzz-gcc) + if test -z "$CC"; then + AC_MSG_ERROR([failed to find hfuzz-gcc]) + fi + AC_CHECK_PROG(CXX, hfuzz-g++, hfuzz-g++) + if test -z "$CXX"; then + AC_MSG_ERROR([failed to find hfuzz-g++]) + fi + AC_CHECK_PROG(HONGGFUZZ, honggfuzz, honggfuzz) + if test -z "$HONGGFUZZ"; then + AC_MSG_ERROR([failed to find honggfuzz]) + fi +fi + AC_PROG_CC_C99 AC_PROG_RANLIB AC_PROG_YACC diff --git a/tests/.gitignore b/tests/.gitignore index 99d04819..c5429d0a 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -66,6 +66,7 @@ /find-prologues /funcretval /funcscopes +/fuzz-dwfl-core /get-aranges /get-files /get-lines diff --git a/tests/ChangeLog b/tests/ChangeLog index c97ed52e..c3d2c188 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -1,3 +1,11 @@ +2021-12-21 Evgeny Vereshchagin <evvers@ya.ru> + + * Makefile.am: Integrate fuzz-dwfl-core into the testsuite and add a + script linking it against libFuzzer. + * run-strip-g.sh,run-strip-nothing.sh: Switch to temporary files + instead of using CC -xc - to get around a honggfuzz issue mentioned at + https://github.com/google/honggfuzz/issues/431 . + 2021-12-17 Mark Wielaard <mark@klomp.org> * run-debuginfod-query-retry.sh: Use /bin/sh instead of /bin/ls. diff --git a/tests/Makefile.am b/tests/Makefile.am index b2da2c83..af1359e9 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -62,6 +62,7 @@ check_PROGRAMS = arextract arsymtest newfile saridx scnnames sectiondump \ getphdrnum leb128 read_unaligned \ msg_tst system-elf-libelf-test \ nvidia_extended_linemap_libdw \ + fuzz-dwfl-core \ $(asm_TESTS) asm_TESTS = asm-tst1 asm-tst2 asm-tst3 asm-tst4 asm-tst5 \ @@ -197,7 +198,8 @@ TESTS = run-arextract.sh run-arsymtest.sh run-ar.sh newfile test-nlist \ msg_tst system-elf-libelf-test \ $(asm_TESTS) run-disasm-bpf.sh run-low_high_pc-dw-form-indirect.sh \ run-nvidia-extended-linemap-libdw.sh run-nvidia-extended-linemap-readelf.sh \ - run-readelf-dw-form-indirect.sh run-strip-largealign.sh + run-readelf-dw-form-indirect.sh run-strip-largealign.sh \ + run-fuzz-dwfl-core.sh if !BIARCH export ELFUTILS_DISABLE_BIARCH = 1 @@ -580,14 +582,19 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \ run-readelf-dw-form-indirect.sh testfile-dw-form-indirect.bz2 \ run-nvidia-extended-linemap-libdw.sh run-nvidia-extended-linemap-readelf.sh \ testfile_nvidia_linemap.bz2 \ - testfile-largealign.o.bz2 run-strip-largealign.sh + testfile-largealign.o.bz2 run-strip-largealign.sh \ + run-fuzz-dwfl-core.sh \ + fuzz-dwfl-core-crashes/empty \ + fuzz-dwfl-core-crashes/bugzilla-28660 \ + fuzz-dwfl-core-crashes/oss-fuzz-41566 \ + fuzz-dwfl-core-crashes/oss-fuzz-41570 \ + fuzz-dwfl-core-crashes/oss-fuzz-41572 if USE_VALGRIND valgrind_cmd=valgrind -q --leak-check=full --error-exitcode=1 endif - installed_TESTS_ENVIRONMENT = libdir='$(DESTDIR)$(libdir)'; \ bindir='$(DESTDIR)$(bindir)'; \ LC_ALL=C; LANG=C; \ @@ -595,9 +602,11 @@ installed_TESTS_ENVIRONMENT = libdir='$(DESTDIR)$(libdir)'; \ abs_srcdir='$(abs_srcdir)'; \ abs_builddir='$(abs_builddir)'; \ abs_top_builddir='$(abs_top_builddir)'; \ + honggfuzz='$(HONGGFUZZ)'; \ export abs_srcdir; export abs_builddir; \ export abs_top_builddir; \ export libdir; export bindir; \ + export honggfuzz; \ export LC_ALL; export LANG; export VALGRIND_CMD; \ unset DEBUGINFOD_URLS; \ NM='$(NM)'; export NM; \ @@ -609,8 +618,10 @@ TESTS_ENVIRONMENT = LC_ALL=C; LANG=C; VALGRIND_CMD='$(valgrind_cmd)'; \ abs_srcdir='$(abs_srcdir)'; \ abs_builddir='$(abs_builddir)'; \ abs_top_builddir='$(abs_top_builddir)'; \ + honggfuzz='$(HONGGFUZZ)'; \ export abs_srcdir; export abs_builddir; \ export abs_top_builddir; \ + export honggfuzz; \ export LC_ALL; export LANG; export VALGRIND_CMD; \ unset DEBUGINFOD_URLS; \ NM='$(NM)'; export NM; \ @@ -755,6 +766,19 @@ leb128_LDADD = $(libelf) $(libdw) read_unaligned_LDADD = $(libelf) $(libdw) nvidia_extended_linemap_libdw_LDADD = $(libelf) $(libdw) +# Fuzz targets are split into two files so that they can be +# compatible with the test suite, OSS-Fuzz and various fuzzing +# engines. OSS-Fuzz takes files containing LLVMFuzzerTestOneInput +# and links them against libFuzzer, AFL++ and honggfuzz. +# The testsuite links them against fuzz-main.c (which is a local driver reading +# files into buffers and passing those buffers to LLVMFuzzerTestOneInput). +# And various fuzzing engines working with binaries reading files +# they generate can be used as well because fuzz-main.c provides +# exactly what they expect. +noinst_HEADERS=fuzz.h +fuzz_dwfl_core_SOURCES = fuzz-main.c fuzz-dwfl-core.c +fuzz_dwfl_core_LDADD = $(libelf) $(libdw) + # We want to test the libelf header against the system elf.h header. # Don't include any -I CPPFLAGS. Except when we install our own elf.h. if !INSTALL_ELFH diff --git a/tests/build-fuzzers.sh b/tests/build-fuzzers.sh new file mode 100755 index 00000000..4ba1fc41 --- /dev/null +++ b/tests/build-fuzzers.sh @@ -0,0 +1,95 @@ +#!/bin/bash -eu + +# This script is supposed to be compatible with OSS-Fuzz, i.e. it has to use +# environment variables like $CC, $CFLAGS and $OUT, link the fuzz targets with CXX +# (even though the project is written in C) and so on: +# https://google.github.io/oss-fuzz/getting-started/new-project-guide/#buildsh + +# The fuzz targets it builds can't make any assumptions about +# their runtime environment apart from /tmp being writable: +# https://google.github.io/oss-fuzz/further-reading/fuzzer-environment/ . +# Even though it says there that it's possible to link fuzz targets against +# their dependencies dynamically by moving them to $OUT and changing +# rpath, it tends to break coverage reports from time to time https://github.com/google/oss-fuzz/issues/6524 +# so all the dependencies are linked statically here. + +# This script is configured via https://github.com/google/oss-fuzz/blob/master/projects/elfutils/project.yaml +# and used to build the elfutils project on OSS-Fuzz with three fuzzing engines +# (libFuzzer, AFL++ and honggfuzz) on two architectures (x86_64 and i386) +# with three sanitizers (ASan, UBSan and MSan) with coverage reports on top of +# all that: https://oss-fuzz.com/coverage-report/job/libfuzzer_asan_elfutils/latest +# so before changing anything ideally it should be tested with the OSS-Fuzz toolchain +# described at https://google.github.io/oss-fuzz/advanced-topics/reproducing/#building-using-docker +# by running something like: +# +# ./infra/helper.py pull_images +# ./infra/helper.py build_image --no-pull elfutils +# for sanitizer in address undefined memory; do +# for engine in libfuzzer afl honggfuzz; do +# ./infra/helper.py build_fuzzers --clean --sanitizer=$sanitizer --engine=$engine elfutils PATH/TO/ELFUTILS +# ./infra/helper.py check_build --sanitizer=$sanitizer --engine=$engine -e ALLOWED_BROKEN_TARGETS_PERCENTAGE=0 elfutils +# done +# done +# +# ./infra/helper.py build_fuzzers --clean --architecture=i386 elfutils PATH/TO/ELFUTILS +# ./infra/helper.py check_build --architecture=i386 -e ALLOWED_BROKEN_TARGETS_PERCENTAGE=0 elfutils +# +# ./infra/helper.py build_fuzzers --clean --sanitizer=coverage elfutils PATH/TO/ELFUTILS +# ./infra/helper.py coverage --no-corpus-download --fuzz-target=fuzz-dwfl-core --corpus-dir=PATH/TO/ELFUTILS/tests/fuzz-dwfl-core-crashes/ elfutils +# +# It should be possible to eventually automate that with ClusterFuzzLite https://google.github.io/clusterfuzzlite/ +# but it doesn't seem to be compatible with buildbot currently. + +# The script can also be used to build and run the fuzz target locally without Docker. +# After installing clang and the build dependencies of libelf by running something +# like `dnf build-dep elfutils-devel` on Fedora or `apt-get build-dep libelf-dev` +# on Debian/Ubuntu, the following commands should be run: +# +# $ ./tests/build-fuzzers.sh +# $ ./out/fuzz-dwfl-core tests/fuzz-dwfl-core-crashes/ + +set -eux + +cd "$(dirname -- "$0")/.." + +SANITIZER=${SANITIZER:-address} +flags="-O1 -fno-omit-frame-pointer -g -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fsanitize=$SANITIZER -fsanitize=fuzzer-no-link" + +export CC=${CC:-clang} +export CFLAGS=${CFLAGS:-$flags} + +export CXX=${CXX:-clang++} +export CXXFLAGS=${CXXFLAGS:-$flags} + +export OUT=${OUT:-"$(pwd)/out"} +mkdir -p "$OUT" + +export LIB_FUZZING_ENGINE=${LIB_FUZZING_ENGINE:--fsanitize=fuzzer} + +make clean || true + +# ASan isn't compatible with -Wl,--no-undefined: https://github.com/google/sanitizers/issues/380 +find -name Makefile.am | xargs sed -i 's/,--no-undefined//' + +# ASan isn't compatible with -Wl,-z,defs either: +# https://clang.llvm.org/docs/AddressSanitizer.html#usage +sed -i 's/^\(ZDEFS_LDFLAGS=\).*/\1/' configure.ac + +autoreconf -i -f +if ! ./configure --enable-maintainer-mode --disable-debuginfod --disable-libdebuginfod \ + --without-bzlib --without-lzma --without-zstd \ + CC="$CC" CFLAGS="-Wno-error $CFLAGS" CXX="-Wno-error $CXX" CXXFLAGS="$CXXFLAGS" LDFLAGS="$CFLAGS"; then + cat config.log + exit 1 +fi + +ASAN_OPTIONS=detect_leaks=0 make -j$(nproc) V=1 + +$CC $CFLAGS \ + -D_GNU_SOURCE -DHAVE_CONFIG_H \ + -I. -I./lib -I./libelf -I./libebl -I./libdw -I./libdwelf -I./libdwfl -I./libasm \ + -c tests/fuzz-dwfl-core.c -o fuzz-dwfl-core.o +$CXX $CXXFLAGS $LIB_FUZZING_ENGINE fuzz-dwfl-core.o \ + ./libdw/libdw.a ./libelf/libelf.a -l:libz.a \ + -o "$OUT/fuzz-dwfl-core" +zip -r -j "$OUT/fuzz-dwfl-core_seed_corpus.zip" tests/fuzz-dwfl-core-crashes diff --git a/tests/fuzz-dwfl-core-crashes/bugzilla-28660 b/tests/fuzz-dwfl-core-crashes/bugzilla-28660 new file mode 100644 index 0000000000000000000000000000000000000000..b687b5601a6d4c20352a7a738fc6f99c06a0e748 GIT binary patch literal 20890 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 literal 0 HcmV?d00001 diff --git a/tests/fuzz-dwfl-core-crashes/empty b/tests/fuzz-dwfl-core-crashes/empty new file mode 100644 index 00000000..e69de29b diff --git a/tests/fuzz-dwfl-core-crashes/oss-fuzz-41566 b/tests/fuzz-dwfl-core-crashes/oss-fuzz-41566 new file mode 100644 index 0000000000000000000000000000000000000000..a4181e6572fea9d568e787b0a4b57bf5defe4563 GIT binary patch literal 1553 zcmb<-^>JfjWK@8F|4<r1IWRx~LIlYA|Np-L14M)cC=Fr>fEZ9VL<T}KGT{)1sfFv4 zUU)AQVivOgk-<g|cVzZp@;xxpL4YbwB4i4>JUX8$ju<rrO9()6D<mHh!)664a$sa& zU<1<5{z0w`Xl5eE95NeC848yfYVm&sNSOdFA{Z>7;vhRg07Vl897w_@)^+$TB4i4> TJctkTAH-MG^A~=X;*bRZ0NBqd literal 0 HcmV?d00001 diff --git a/tests/fuzz-dwfl-core-crashes/oss-fuzz-41570 b/tests/fuzz-dwfl-core-crashes/oss-fuzz-41570 new file mode 100644 index 0000000000000000000000000000000000000000..4052572f5a484963b7bf03bb350368877671e7b1 GIT binary patch literal 1233 zcmb<-^>JfjWK_Tf7#Sb{Ro;R@j6vZ)5TFXvi#KpK)60jW_Kb!A{ty6VOppp_{$x;q U@IXR1zy$|x8Hg$z3WkL+07HkLIsgCw literal 0 HcmV?d00001 diff --git a/tests/fuzz-dwfl-core-crashes/oss-fuzz-41572 b/tests/fuzz-dwfl-core-crashes/oss-fuzz-41572 new file mode 100644 index 0000000000000000000000000000000000000000..534d611b0c606003019d026f1028f248d2f1d656 GIT binary patch literal 4872 zcmeHJUrSU$6hE42VVcHLra{*S=^y*YEb$@YMy(<UEk=+Kv}Ir$W{aEPoAwa&G1hOe z&q3u!@DubL^wML{!*<Wi?{}RUE!ylw+<|+~pE<vK&YU@O&ukRuraL=2griGt%dR96 zbHbsqOvzcuU7!QU9Rr8m>2m~!RUZZ$K^cu~eR=2@41f=);*6^T<_iIipV$Xy{1MmR zI&Np_m`EwW1%3pw#@Vg0#QI7``R{Qi&XR>mG}e1z1H2OYD|K3><Im)!Y}Hb|<l~^0 zMrIGr_#-a5wLh+ItWy7JjS#mT?~h}=PMn^<od7qEKntJ_PV?Chwa6XcAMHs4oNNPF z2ykWSNgbStGr~&i_k4e_OZUUU+4=;o5#ZoXWbghNe+A;EeSgGlf^qzLxOm3L{UJ_U zpYT^|5my4P+#;?FT=h(*>D4v!L2B2j)@FUx<m~C#9`<pu(e9ozCog6n8o<ejkBfjK z1x_)#H1&J;{(Mobdi90j^3*N89h6V!MM$X+Y5jDlX-$stuZtdKytN=>(u2=6wu*iY zKtI%DYya|s#SejEN2G~ybKL%`CV)#CgyHD-8ualy`@J8)LH%1oKbqtB$)h{N9Lqdl z-LYZ5CX@Owr=*LkPirq=sw9^;n#fTdh}GCOlV^5j_5ow#o-_UfIrs;##uO*c^}I5s z(eyjEu6fLxKfis}DRZ`!!Nj2Q$#`yw$-T0%>ATH7bH);R3iIky2iMNp68r~G*y;3r z5*%xkINd`Xrz|mhsw}SsoacX?@;TU(B*)8)g~zsbhat1}FGBvlVdr_&DzL|NN_2fM zlh}%Lv(wBebI6<t?<C<JgK})oNm>_>c}HMusnh?>@GereaisnrOV#SJ1(Mz+!n*-w z^!_o%$-4l0dzA}iuSf6xKz-WF?SCQFvsa_{exyBnYS<$wH*622xj>eJ?Kz{2lRXbT z><ablY3NZ`q0hg0RkgnjX^CB{t>%|2&+_Y2lTRjZWS1)|YwOvil{Hl^X0PWa#&fmT hlK0IROPjVc(9Xco%Rq8BVsAToi*K*Eoq@xVfnU&q=l%cy literal 0 HcmV?d00001 diff --git a/tests/fuzz-dwfl-core.c b/tests/fuzz-dwfl-core.c new file mode 100644 index 00000000..c067fb40 --- /dev/null +++ b/tests/fuzz-dwfl-core.c @@ -0,0 +1,58 @@ +#include <assert.h> +#include <config.h> +#include <stdlib.h> +#include ELFUTILS_HEADER(dwfl) +#include "fuzz.h" +#include "system.h" + +/* This fuzz target was initially used to fuzz systemd and + there elfutils is hidden behind functions receiving file + names and file descriptors. To cover that code the fuzz + target converts bytes it receives into temporary files + and passes their file descriptors to elf_begin instead of calling + something like elf_memory (which can process bytes directly). + New fuzzers covering elfutils should avoid this pattern. */ + +static const Dwfl_Callbacks core_callbacks = + { + .find_elf = dwfl_build_id_find_elf, + .find_debuginfo = dwfl_standard_find_debuginfo, + }; + +int +LLVMFuzzerTestOneInput (const uint8_t *data, size_t size) +{ + char name[] = "/tmp/fuzz-dwfl-core.XXXXXX"; + int fd = -1; + off_t offset; + ssize_t n; + Elf *core = NULL; + Dwfl *dwfl = NULL; + + fd = mkstemp (name); + assert (fd >= 0); + + n = write_retry (fd, data, size); + assert (n >= 0); + + offset = lseek (fd, 0, SEEK_SET); + assert (offset == 0); + + elf_version (EV_CURRENT); + core = elf_begin (fd, ELF_C_READ_MMAP, NULL); + if (core == NULL) + goto cleanup; + dwfl = dwfl_begin (&core_callbacks); + assert(dwfl != NULL); + if (dwfl_core_file_report (dwfl, core, NULL) < 0) + goto cleanup; + if (dwfl_report_end (dwfl, NULL, NULL) != 0) + goto cleanup; + +cleanup: + dwfl_end (dwfl); + elf_end (core); + close (fd); + unlink (name); + return 0; +} diff --git a/tests/fuzz-main.c b/tests/fuzz-main.c new file mode 100644 index 00000000..35573792 --- /dev/null +++ b/tests/fuzz-main.c @@ -0,0 +1,43 @@ +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include "fuzz.h" + +int +main (int argc, char **argv) +{ + for (int i = 1; i < argc; i++) + { + fprintf (stderr, "Running: %s\n", argv[i]); + + FILE *f = fopen (argv[i], "r"); + assert (f); + + int p = fseek (f, 0, SEEK_END); + assert (p >= 0); + + long len = ftell (f); + assert (len >= 0); + + p = fseek (f, 0, SEEK_SET); + assert (p >= 0); + + void *buf = malloc (len); + assert (buf != NULL || len == 0); + + size_t n_read = fread (buf, 1, len, f); + assert (n_read == (size_t) len); + + (void) fclose (f); + + int r = LLVMFuzzerTestOneInput (buf, len); + + /* Non-zero return values are reserved by LibFuzzer for future use + https://llvm.org/docs/LibFuzzer.html#fuzz-target */ + assert (r == 0); + + free (buf); + + fprintf (stderr, "Done: %s: (%zd bytes)\n", argv[i], n_read); + } +} diff --git a/tests/fuzz.h b/tests/fuzz.h new file mode 100644 index 00000000..c8fe7a3a --- /dev/null +++ b/tests/fuzz.h @@ -0,0 +1,9 @@ +#ifndef _FUZZ_H +#define _FUZZ_H 1 + +#include <stddef.h> +#include <stdint.h> + +int LLVMFuzzerTestOneInput (const uint8_t *data, size_t size); + +#endif /* fuzz.h */ diff --git a/tests/run-fuzz-dwfl-core.sh b/tests/run-fuzz-dwfl-core.sh new file mode 100755 index 00000000..39149043 --- /dev/null +++ b/tests/run-fuzz-dwfl-core.sh @@ -0,0 +1,74 @@ +#!/bin/sh + +. $srcdir/test-subr.sh + +# Valgrind is turned off because hongfuzz keeps track of +# processes and signals they receive and valgrind shouldn't +# interfer with that. Apart from that it reports memory leaks +# in timeout we aren't interested in: +#==53620== 8 bytes in 1 blocks are definitely lost in loss record 1 of 1 +#==53620== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) +#==53620== by 0x4860959: timer_create@@GLIBC_2.3.3 (timer_create.c:59) +#==53620== by 0x10AFCD: ??? (in /usr/bin/timeout) +#==53620== by 0x10AC18: ??? (in /usr/bin/timeout) +#==53620== by 0x48B00B2: (below main) (libc-start.c:308) +#==53620== +unset VALGRIND_CMD + +# honggfuzz sets ASAN and UBSAN options compatible with it +# so they are reset early to prevent the environment from +# affecting the test +unset ASAN_OPTIONS +unset UBSAN_OPTIONS + +timeout=30 + +# run_one is used to process files without honggfuzz +# to get backtraces that otherwise can be borked in honggfuzz runs +# so it has to set ASAN and UBSAN options itself +run_one() +{ + testrun timeout -s9 $timeout env \ + ASAN_OPTIONS=allocator_may_return_null=1 \ + UBSAN_OPTIONS=print_stacktrace=1:print_summary=1:halt_on_error=1 \ + ${abs_builddir}/fuzz-dwfl-core "$1" +} + +# Here the fuzz target processes files one by one to be able +# to catch memory leaks and other issues that can't be discovered +# with honggfuzz. +exit_status=0 +for file in ${abs_srcdir}/fuzz-dwfl-core-crashes/*; do + run_one $file || { echo "*** failure in $file"; exit_status=1; } +done + +if [ -n "$honggfuzz" ]; then + tempfiles log + + testrun $honggfuzz --run_time ${FUZZ_TIME:-180} -n 1 -v --exit_upon_crash \ + -i ${abs_srcdir}/fuzz-dwfl-core-crashes/ \ + -t $timeout --tmout_sigvtalrm \ + -o OUT \ + --logfile log \ + -- ${abs_builddir}/fuzz-dwfl-core ___FILE___ + + rm -rf OUT + + # hongfuzz always exits successfully so to tell "success" and "failure" apart + # it's necessary to look for reports it leaves when processes it monitors crash. + # Eventually it will be possible to pass --exit_code_upon_crash, which combined + # with --exit_upon_crash can be used to get honggfuzz to fail, but it hasn't been + # released yet. Initially it was used but on machines with the latest stable release + # tests that should have failed passed, which led to https://github.com/google/honggfuzz/pull/432 + if [ -f HONGGFUZZ.REPORT.TXT ]; then + tail -n 25 log + cat HF.sanitizer.log* || true + cat HONGGFUZZ.REPORT.TXT + for crash in $(sed -n 's/^FUZZ_FNAME: *//p' HONGGFUZZ.REPORT.TXT); do + run_one $crash || true + done + exit_status=1 + fi +fi + +exit $exit_status diff --git a/tests/run-strip-g.sh b/tests/run-strip-g.sh index 15921215..8b480ec0 100755 --- a/tests/run-strip-g.sh +++ b/tests/run-strip-g.sh @@ -22,10 +22,13 @@ # debug sections and so there should not be a copy in the debug file # except for a NOBITS one. -tempfiles a.out strip.out debug.out readelf.out +tmpfile=main.c + +tempfiles $tmpfile a.out strip.out debug.out readelf.out echo Create debug a.out. -echo "int main() { return 1; }" | ${CC} -g -xc - +echo "int main() { return 1; }" >$tmpfile +${CC} -g $tmpfile echo strip -g to file with debug file testrun ${abs_top_builddir}/src/strip -g -o strip.out -f debug.out || diff --git a/tests/run-strip-nothing.sh b/tests/run-strip-nothing.sh index 710c200d..e725297a 100755 --- a/tests/run-strip-nothing.sh +++ b/tests/run-strip-nothing.sh @@ -20,10 +20,13 @@ # If there is nothing to strip then -o output should be identical to input. # And there should not be an (empty) -f debug file. -tempfiles a.out strip.out debug.out +tmpfile=main.c + +tempfiles $tmpfile a.out strip.out debug.out # Create no-debug a.out. -echo "int main() { return 1; }" | ${CC} -s -xc - +echo "int main() { return 1; }" >$tmpfile +${CC} -s $tmpfile # strip to file testrun ${abs_top_builddir}/src/strip -g -o strip.out || -- 2.33.1 ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v4] tests: integrate fuzz-dwfl-core into the test suite 2021-12-21 1:49 ` [PATCH v3] " Evgeny Vereshchagin @ 2021-12-26 16:03 ` Evgeny Vereshchagin 0 siblings, 0 replies; 6+ messages in thread From: Evgeny Vereshchagin @ 2021-12-26 16:03 UTC (permalink / raw) To: elfutils-devel; +Cc: mark, Evgeny Vereshchagin [v4] 1) `--enable-afl` was added to make it possible to build and run the fuzz target with afl-gcc/afl-g++/afl-fuzz. It's compatible with both AFL and AFL++ and can be tested on Ubuntu by running the following commands: ``` apt-get install afl++ autoreconf -i -f ./configure --enable-maintainer-mode --enable-afl make -j$(nproc) V=1 make check V=1 VERBOSE=1 TESTS=run-fuzz-dwfl-core.sh FUZZ_TIME=600 ``` It's compatible with ASan/UBsan as well so something like `--enable-sanitize-address` and `--enable-sanitize-undefined` can additionally be passed to run the fuzzer under ASan/UBsan. It was tested on Fedora with AFL (https://github.com/google/AFL) and on Ubuntu with AFL++ (https://github.com/AFLplusplus/AFLplusplus/). 2) Both `use_afl` and `use_honggfuzz` are now shown among the additional test features printed by `./configure` to make it easier to figure out how exactly elfutils is tested. [v3] The test handles infinite loops much better now. In https://sourceware.org/bugzilla/show_bug.cgi?id=28715#c4 it took it about 5 hours on Packit to discover an infinite loop on 32 bit platforms because it didn't enforce any timeouts. It was fixed by passing --tmout_sigvtalrm to honggfuzz (which treats timeouts as normal crashes) and by additionally running the fuzz target with timeout -s9. [v2] 1) In https://sourceware.org/pipermail/elfutils-devel/2021q4/004541.html it was pointed out that build-fuzzers.sh is too tied to OSS-Fuzz and while it was kind of decoupled from it as much as possible in the sense that it was enough to install clang and run the script to build the fuzz target with libFuzzer it's true that it can't be integrated smoothly into buildbot for example where gcc is used and various configure options control what exactly is testsed. To address that, `--enable-honggfuzz` is introduced. It looks for hfuzz-gcc, hfuzz-g++ and honggfuzz and if they exist elfutils is built with those wrappers and the fuzz target is additionally run for a few minutes under honggfuzz to make regression testing more effective. It was tested on Fedora 35 and in https://github.com/evverx/elfutils/pull/53 on Ubuntu Focal with both gcc and clang with and without sanitizers/Valgrind and with two versions of honggfuzz (including the latest stable version). To make it work on Ubuntu the following commands should be run ``` apt-get install libbfd-dev libunwind8-dev git clone https://github.com/google/honggfuzz cd honggfuzz git checkout 2.4 make make PREFIX=/usr install cd PATH/TO/ELFUTILS autoreconf -i -f ./configure --enable-maintainer-mode --enable-honggfuzz make check V=1 VERBOSE=1 # FUZZ_TIME can be optionally passed ``` If hongfuzz is installed elsewhere it's possible to point configure to it with CC, CXX and HONGGFUZZ ``` ./configure CC=path-to-hfuzz-gcc CXX=path-to-hfuzz-g++ HONGGFUZZ=path-to-honggfuzz ``` I decided to use honggfuzz instead of AFL because AFL doesn't seem to be maintained actively anymore. Other than that I can't seem to make it work with various combinations of compilers, sanitizers and so on. But thanks to the way the fuzz target is written it should be possible to add it eventually by analogy with honggfuzz. 2) fuzz-dwfl-core-corpus was renamed to fuzz-dwfl-core-crashes to make it more clear that it isn't exaclty a seed corpus. 3) run-strip-g.sh and run-strip-nothing.sh started to compile test programs using temporary files instead of gcc -xc -. It should address https://github.com/google/honggfuzz/issues/431 but more generally autoconf uses temporary files to make sure compiler works so it seems in general it's safer to rely on compiler features that are known to work. 4) A comment was added where I tried to expand on why the fuzz target is written that way. [v1] The fuzz target was integrated into OSS-Fuzz in https://github.com/google/oss-fuzz/pull/6944 and since then it has been running there continously (uncovering various issues along the way). It's all well and good but since OSS-Fuzz is far from the elfutils repository it's unnecessarily hard to build the fuzz target locally, verify patches and more generally test new code to make sure that it doesn't introduce new issues ( or reintroduce regressions). This patch aims to address all those issues by moving the fuzz target into the elfutils repository, integrating it into the testsuite and also providing a script that can be used to build full-fledged fuzzers utilizing libFuzzer. With this patch applied `make check` can be used to make sure that files kept in tests/fuzz-dwfl-core-corpus don't crash the code on various architecures. `--enable-sanitize-{address,undefined}` and/or `--enable-valgrind` can additionally be used to uncover issues like https://sourceware.org/bugzilla/show_bug.cgi?id=28685 that don't always manifest themselves in simple segfaults. On top of all that now the fuzz target can be built and linked against libFuzzer locally by just running `./tests/build-fuzzers.sh`. The patch was tested in https://github.com/evverx/elfutils/pull/53 : * the testsuite was run on aarch64, armhfp, i386, ppc64le, s390x and x86_64 * Fedora packages were built on those architectures; * elfutils was built with both clang and gcc with and without sanitizers to make sure the tests pass there; * `make distcheck` passed; * coverage reports were built to make sure "static" builds are intact; * the fuzz target was built and run with ClusterFuzzLite to make sure it's still compatible with OSS-Fuzz; * the code was analyzed by various static analyzers to make sure new alerts aren't introduced. Signed-off-by: Evgeny Vereshchagin <evvers@ya.ru> --- ChangeLog | 4 + configure.ac | 39 ++++++++ tests/.gitignore | 1 + tests/ChangeLog | 8 ++ tests/Makefile.am | 34 ++++++- tests/build-fuzzers.sh | 95 ++++++++++++++++++ tests/fuzz-dwfl-core-crashes/bugzilla-28660 | Bin 0 -> 20890 bytes tests/fuzz-dwfl-core-crashes/empty | 0 tests/fuzz-dwfl-core-crashes/oss-fuzz-41566 | Bin 0 -> 1553 bytes tests/fuzz-dwfl-core-crashes/oss-fuzz-41570 | Bin 0 -> 1233 bytes tests/fuzz-dwfl-core-crashes/oss-fuzz-41572 | Bin 0 -> 4872 bytes tests/fuzz-dwfl-core.c | 58 +++++++++++ tests/fuzz-main.c | 43 +++++++++ tests/fuzz.h | 9 ++ tests/run-fuzz-dwfl-core.sh | 102 ++++++++++++++++++++ tests/run-strip-g.sh | 7 +- tests/run-strip-nothing.sh | 7 +- 17 files changed, 400 insertions(+), 7 deletions(-) create mode 100755 tests/build-fuzzers.sh create mode 100644 tests/fuzz-dwfl-core-crashes/bugzilla-28660 create mode 100644 tests/fuzz-dwfl-core-crashes/empty create mode 100644 tests/fuzz-dwfl-core-crashes/oss-fuzz-41566 create mode 100644 tests/fuzz-dwfl-core-crashes/oss-fuzz-41570 create mode 100644 tests/fuzz-dwfl-core-crashes/oss-fuzz-41572 create mode 100644 tests/fuzz-dwfl-core.c create mode 100644 tests/fuzz-main.c create mode 100644 tests/fuzz.h create mode 100755 tests/run-fuzz-dwfl-core.sh diff --git a/ChangeLog b/ChangeLog index f00db17b..c08329bc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2021-12-26 Evgeny Vereshchagin <evvers@ya.ru> + + * configure.ac: Add --enable-afl and --enable-honggfuzz. + 2021-12-04 Mark Wielaard <mark@klomp.org> * configure.ac: Add --enable-sanitize-address. diff --git a/configure.ac b/configure.ac index 48071165..4c7e9ed4 100644 --- a/configure.ac +++ b/configure.ac @@ -87,6 +87,43 @@ AS_IF([test "$use_locks" = yes], AH_TEMPLATE([USE_LOCKS], [Defined if libraries should be thread-safe.]) +AC_ARG_ENABLE([afl], +AS_HELP_STRING([--enable-afl],[run fuzzers with afl]), [use_afl=$enableval], [use_afl=no]) +if test "$use_afl" = yes; then + AC_CHECK_PROG(CC, afl-gcc, afl-gcc) + if test -z "$CC"; then + AC_MSG_ERROR([failed to find afl-gcc]) + fi + AC_CHECK_PROG(CXX, afl-g++, afl-g++) + if test -z "$CXX"; then + AC_MSG_ERROR([failed to find afl-g++]) + fi + AC_CHECK_PROG(AFL_FUZZ, afl-fuzz, afl-fuzz) + if test -z "$AFL_FUZZ"; then + AC_MSG_ERROR([failed to find afl-fuzz]) + fi +fi + +AC_ARG_ENABLE([honggfuzz], +AS_HELP_STRING([--enable-honggfuzz],[run fuzzers with honggfuzz]), [use_honggfuzz=$enableval], [use_honggfuzz=no]) +if test "$use_honggfuzz" = yes; then + if test "$use_afl" = yes; then + AC_MSG_ERROR([cannot enable afl and honggfuzz together]) + fi + AC_CHECK_PROG(CC, hfuzz-gcc, hfuzz-gcc) + if test -z "$CC"; then + AC_MSG_ERROR([failed to find hfuzz-gcc]) + fi + AC_CHECK_PROG(CXX, hfuzz-g++, hfuzz-g++) + if test -z "$CXX"; then + AC_MSG_ERROR([failed to find hfuzz-g++]) + fi + AC_CHECK_PROG(HONGGFUZZ, honggfuzz, honggfuzz) + if test -z "$HONGGFUZZ"; then + AC_MSG_ERROR([failed to find honggfuzz]) + fi +fi + AC_PROG_CC_C99 AC_PROG_RANLIB AC_PROG_YACC @@ -831,6 +868,8 @@ AC_MSG_NOTICE([ debug branch prediction : ${use_debugpred} gprof support : ${use_gprof} gcov support : ${use_gcov} + run fuzzers with honggfuzz : ${use_honggfuzz} + run fuzzers with AFL : ${use_afl} run all tests under valgrind : ${use_valgrind} gcc undefined behaviour sanitizer : ${use_undefined} gcc address sanitizer : ${use_address} diff --git a/tests/.gitignore b/tests/.gitignore index 99d04819..c5429d0a 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -66,6 +66,7 @@ /find-prologues /funcretval /funcscopes +/fuzz-dwfl-core /get-aranges /get-files /get-lines diff --git a/tests/ChangeLog b/tests/ChangeLog index c97ed52e..cff1b952 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -1,3 +1,11 @@ +2021-12-26 Evgeny Vereshchagin <evvers@ya.ru> + + * Makefile.am: Integrate fuzz-dwfl-core into the testsuite and add a + script linking it against libFuzzer. + * run-strip-g.sh,run-strip-nothing.sh: Switch to temporary files + instead of using CC -xc - to get around a honggfuzz issue mentioned at + https://github.com/google/honggfuzz/issues/431 . + 2021-12-17 Mark Wielaard <mark@klomp.org> * run-debuginfod-query-retry.sh: Use /bin/sh instead of /bin/ls. diff --git a/tests/Makefile.am b/tests/Makefile.am index b2da2c83..1a7685c1 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -62,6 +62,7 @@ check_PROGRAMS = arextract arsymtest newfile saridx scnnames sectiondump \ getphdrnum leb128 read_unaligned \ msg_tst system-elf-libelf-test \ nvidia_extended_linemap_libdw \ + fuzz-dwfl-core \ $(asm_TESTS) asm_TESTS = asm-tst1 asm-tst2 asm-tst3 asm-tst4 asm-tst5 \ @@ -197,7 +198,8 @@ TESTS = run-arextract.sh run-arsymtest.sh run-ar.sh newfile test-nlist \ msg_tst system-elf-libelf-test \ $(asm_TESTS) run-disasm-bpf.sh run-low_high_pc-dw-form-indirect.sh \ run-nvidia-extended-linemap-libdw.sh run-nvidia-extended-linemap-readelf.sh \ - run-readelf-dw-form-indirect.sh run-strip-largealign.sh + run-readelf-dw-form-indirect.sh run-strip-largealign.sh \ + run-fuzz-dwfl-core.sh if !BIARCH export ELFUTILS_DISABLE_BIARCH = 1 @@ -580,14 +582,19 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \ run-readelf-dw-form-indirect.sh testfile-dw-form-indirect.bz2 \ run-nvidia-extended-linemap-libdw.sh run-nvidia-extended-linemap-readelf.sh \ testfile_nvidia_linemap.bz2 \ - testfile-largealign.o.bz2 run-strip-largealign.sh + testfile-largealign.o.bz2 run-strip-largealign.sh \ + run-fuzz-dwfl-core.sh \ + fuzz-dwfl-core-crashes/empty \ + fuzz-dwfl-core-crashes/bugzilla-28660 \ + fuzz-dwfl-core-crashes/oss-fuzz-41566 \ + fuzz-dwfl-core-crashes/oss-fuzz-41570 \ + fuzz-dwfl-core-crashes/oss-fuzz-41572 if USE_VALGRIND valgrind_cmd=valgrind -q --leak-check=full --error-exitcode=1 endif - installed_TESTS_ENVIRONMENT = libdir='$(DESTDIR)$(libdir)'; \ bindir='$(DESTDIR)$(bindir)'; \ LC_ALL=C; LANG=C; \ @@ -595,9 +602,13 @@ installed_TESTS_ENVIRONMENT = libdir='$(DESTDIR)$(libdir)'; \ abs_srcdir='$(abs_srcdir)'; \ abs_builddir='$(abs_builddir)'; \ abs_top_builddir='$(abs_top_builddir)'; \ + afl_fuzz='$(AFL_FUZZ)'; \ + honggfuzz='$(HONGGFUZZ)'; \ export abs_srcdir; export abs_builddir; \ export abs_top_builddir; \ export libdir; export bindir; \ + export afl_fuzz; \ + export honggfuzz; \ export LC_ALL; export LANG; export VALGRIND_CMD; \ unset DEBUGINFOD_URLS; \ NM='$(NM)'; export NM; \ @@ -609,8 +620,12 @@ TESTS_ENVIRONMENT = LC_ALL=C; LANG=C; VALGRIND_CMD='$(valgrind_cmd)'; \ abs_srcdir='$(abs_srcdir)'; \ abs_builddir='$(abs_builddir)'; \ abs_top_builddir='$(abs_top_builddir)'; \ + afl_fuzz='$(AFL_FUZZ)'; \ + honggfuzz='$(HONGGFUZZ)'; \ export abs_srcdir; export abs_builddir; \ export abs_top_builddir; \ + export afl_fuzz; \ + export honggfuzz; \ export LC_ALL; export LANG; export VALGRIND_CMD; \ unset DEBUGINFOD_URLS; \ NM='$(NM)'; export NM; \ @@ -755,6 +770,19 @@ leb128_LDADD = $(libelf) $(libdw) read_unaligned_LDADD = $(libelf) $(libdw) nvidia_extended_linemap_libdw_LDADD = $(libelf) $(libdw) +# Fuzz targets are split into two files so that they can be +# compatible with the test suite, OSS-Fuzz and various fuzzing +# engines. OSS-Fuzz takes files containing LLVMFuzzerTestOneInput +# and links them against libFuzzer, AFL++ and honggfuzz. +# The testsuite links them against fuzz-main.c (which is a local driver reading +# files into buffers and passing those buffers to LLVMFuzzerTestOneInput). +# And various fuzzing engines working with binaries reading files +# they generate can be used as well because fuzz-main.c provides +# exactly what they expect. +noinst_HEADERS=fuzz.h +fuzz_dwfl_core_SOURCES = fuzz-main.c fuzz-dwfl-core.c +fuzz_dwfl_core_LDADD = $(libelf) $(libdw) + # We want to test the libelf header against the system elf.h header. # Don't include any -I CPPFLAGS. Except when we install our own elf.h. if !INSTALL_ELFH diff --git a/tests/build-fuzzers.sh b/tests/build-fuzzers.sh new file mode 100755 index 00000000..4ba1fc41 --- /dev/null +++ b/tests/build-fuzzers.sh @@ -0,0 +1,95 @@ +#!/bin/bash -eu + +# This script is supposed to be compatible with OSS-Fuzz, i.e. it has to use +# environment variables like $CC, $CFLAGS and $OUT, link the fuzz targets with CXX +# (even though the project is written in C) and so on: +# https://google.github.io/oss-fuzz/getting-started/new-project-guide/#buildsh + +# The fuzz targets it builds can't make any assumptions about +# their runtime environment apart from /tmp being writable: +# https://google.github.io/oss-fuzz/further-reading/fuzzer-environment/ . +# Even though it says there that it's possible to link fuzz targets against +# their dependencies dynamically by moving them to $OUT and changing +# rpath, it tends to break coverage reports from time to time https://github.com/google/oss-fuzz/issues/6524 +# so all the dependencies are linked statically here. + +# This script is configured via https://github.com/google/oss-fuzz/blob/master/projects/elfutils/project.yaml +# and used to build the elfutils project on OSS-Fuzz with three fuzzing engines +# (libFuzzer, AFL++ and honggfuzz) on two architectures (x86_64 and i386) +# with three sanitizers (ASan, UBSan and MSan) with coverage reports on top of +# all that: https://oss-fuzz.com/coverage-report/job/libfuzzer_asan_elfutils/latest +# so before changing anything ideally it should be tested with the OSS-Fuzz toolchain +# described at https://google.github.io/oss-fuzz/advanced-topics/reproducing/#building-using-docker +# by running something like: +# +# ./infra/helper.py pull_images +# ./infra/helper.py build_image --no-pull elfutils +# for sanitizer in address undefined memory; do +# for engine in libfuzzer afl honggfuzz; do +# ./infra/helper.py build_fuzzers --clean --sanitizer=$sanitizer --engine=$engine elfutils PATH/TO/ELFUTILS +# ./infra/helper.py check_build --sanitizer=$sanitizer --engine=$engine -e ALLOWED_BROKEN_TARGETS_PERCENTAGE=0 elfutils +# done +# done +# +# ./infra/helper.py build_fuzzers --clean --architecture=i386 elfutils PATH/TO/ELFUTILS +# ./infra/helper.py check_build --architecture=i386 -e ALLOWED_BROKEN_TARGETS_PERCENTAGE=0 elfutils +# +# ./infra/helper.py build_fuzzers --clean --sanitizer=coverage elfutils PATH/TO/ELFUTILS +# ./infra/helper.py coverage --no-corpus-download --fuzz-target=fuzz-dwfl-core --corpus-dir=PATH/TO/ELFUTILS/tests/fuzz-dwfl-core-crashes/ elfutils +# +# It should be possible to eventually automate that with ClusterFuzzLite https://google.github.io/clusterfuzzlite/ +# but it doesn't seem to be compatible with buildbot currently. + +# The script can also be used to build and run the fuzz target locally without Docker. +# After installing clang and the build dependencies of libelf by running something +# like `dnf build-dep elfutils-devel` on Fedora or `apt-get build-dep libelf-dev` +# on Debian/Ubuntu, the following commands should be run: +# +# $ ./tests/build-fuzzers.sh +# $ ./out/fuzz-dwfl-core tests/fuzz-dwfl-core-crashes/ + +set -eux + +cd "$(dirname -- "$0")/.." + +SANITIZER=${SANITIZER:-address} +flags="-O1 -fno-omit-frame-pointer -g -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fsanitize=$SANITIZER -fsanitize=fuzzer-no-link" + +export CC=${CC:-clang} +export CFLAGS=${CFLAGS:-$flags} + +export CXX=${CXX:-clang++} +export CXXFLAGS=${CXXFLAGS:-$flags} + +export OUT=${OUT:-"$(pwd)/out"} +mkdir -p "$OUT" + +export LIB_FUZZING_ENGINE=${LIB_FUZZING_ENGINE:--fsanitize=fuzzer} + +make clean || true + +# ASan isn't compatible with -Wl,--no-undefined: https://github.com/google/sanitizers/issues/380 +find -name Makefile.am | xargs sed -i 's/,--no-undefined//' + +# ASan isn't compatible with -Wl,-z,defs either: +# https://clang.llvm.org/docs/AddressSanitizer.html#usage +sed -i 's/^\(ZDEFS_LDFLAGS=\).*/\1/' configure.ac + +autoreconf -i -f +if ! ./configure --enable-maintainer-mode --disable-debuginfod --disable-libdebuginfod \ + --without-bzlib --without-lzma --without-zstd \ + CC="$CC" CFLAGS="-Wno-error $CFLAGS" CXX="-Wno-error $CXX" CXXFLAGS="$CXXFLAGS" LDFLAGS="$CFLAGS"; then + cat config.log + exit 1 +fi + +ASAN_OPTIONS=detect_leaks=0 make -j$(nproc) V=1 + +$CC $CFLAGS \ + -D_GNU_SOURCE -DHAVE_CONFIG_H \ + -I. -I./lib -I./libelf -I./libebl -I./libdw -I./libdwelf -I./libdwfl -I./libasm \ + -c tests/fuzz-dwfl-core.c -o fuzz-dwfl-core.o +$CXX $CXXFLAGS $LIB_FUZZING_ENGINE fuzz-dwfl-core.o \ + ./libdw/libdw.a ./libelf/libelf.a -l:libz.a \ + -o "$OUT/fuzz-dwfl-core" +zip -r -j "$OUT/fuzz-dwfl-core_seed_corpus.zip" tests/fuzz-dwfl-core-crashes diff --git a/tests/fuzz-dwfl-core-crashes/bugzilla-28660 b/tests/fuzz-dwfl-core-crashes/bugzilla-28660 new file mode 100644 index 0000000000000000000000000000000000000000..b687b5601a6d4c20352a7a738fc6f99c06a0e748 GIT binary patch literal 20890 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 literal 0 HcmV?d00001 diff --git a/tests/fuzz-dwfl-core-crashes/empty b/tests/fuzz-dwfl-core-crashes/empty new file mode 100644 index 00000000..e69de29b diff --git a/tests/fuzz-dwfl-core-crashes/oss-fuzz-41566 b/tests/fuzz-dwfl-core-crashes/oss-fuzz-41566 new file mode 100644 index 0000000000000000000000000000000000000000..a4181e6572fea9d568e787b0a4b57bf5defe4563 GIT binary patch literal 1553 zcmb<-^>JfjWK@8F|4<r1IWRx~LIlYA|Np-L14M)cC=Fr>fEZ9VL<T}KGT{)1sfFv4 zUU)AQVivOgk-<g|cVzZp@;xxpL4YbwB4i4>JUX8$ju<rrO9()6D<mHh!)664a$sa& zU<1<5{z0w`Xl5eE95NeC848yfYVm&sNSOdFA{Z>7;vhRg07Vl897w_@)^+$TB4i4> TJctkTAH-MG^A~=X;*bRZ0NBqd literal 0 HcmV?d00001 diff --git a/tests/fuzz-dwfl-core-crashes/oss-fuzz-41570 b/tests/fuzz-dwfl-core-crashes/oss-fuzz-41570 new file mode 100644 index 0000000000000000000000000000000000000000..4052572f5a484963b7bf03bb350368877671e7b1 GIT binary patch literal 1233 zcmb<-^>JfjWK_Tf7#Sb{Ro;R@j6vZ)5TFXvi#KpK)60jW_Kb!A{ty6VOppp_{$x;q U@IXR1zy$|x8Hg$z3WkL+07HkLIsgCw literal 0 HcmV?d00001 diff --git a/tests/fuzz-dwfl-core-crashes/oss-fuzz-41572 b/tests/fuzz-dwfl-core-crashes/oss-fuzz-41572 new file mode 100644 index 0000000000000000000000000000000000000000..534d611b0c606003019d026f1028f248d2f1d656 GIT binary patch literal 4872 zcmeHJUrSU$6hE42VVcHLra{*S=^y*YEb$@YMy(<UEk=+Kv}Ir$W{aEPoAwa&G1hOe z&q3u!@DubL^wML{!*<Wi?{}RUE!ylw+<|+~pE<vK&YU@O&ukRuraL=2griGt%dR96 zbHbsqOvzcuU7!QU9Rr8m>2m~!RUZZ$K^cu~eR=2@41f=);*6^T<_iIipV$Xy{1MmR zI&Np_m`EwW1%3pw#@Vg0#QI7``R{Qi&XR>mG}e1z1H2OYD|K3><Im)!Y}Hb|<l~^0 zMrIGr_#-a5wLh+ItWy7JjS#mT?~h}=PMn^<od7qEKntJ_PV?Chwa6XcAMHs4oNNPF z2ykWSNgbStGr~&i_k4e_OZUUU+4=;o5#ZoXWbghNe+A;EeSgGlf^qzLxOm3L{UJ_U zpYT^|5my4P+#;?FT=h(*>D4v!L2B2j)@FUx<m~C#9`<pu(e9ozCog6n8o<ejkBfjK z1x_)#H1&J;{(Mobdi90j^3*N89h6V!MM$X+Y5jDlX-$stuZtdKytN=>(u2=6wu*iY zKtI%DYya|s#SejEN2G~ybKL%`CV)#CgyHD-8ualy`@J8)LH%1oKbqtB$)h{N9Lqdl z-LYZ5CX@Owr=*LkPirq=sw9^;n#fTdh}GCOlV^5j_5ow#o-_UfIrs;##uO*c^}I5s z(eyjEu6fLxKfis}DRZ`!!Nj2Q$#`yw$-T0%>ATH7bH);R3iIky2iMNp68r~G*y;3r z5*%xkINd`Xrz|mhsw}SsoacX?@;TU(B*)8)g~zsbhat1}FGBvlVdr_&DzL|NN_2fM zlh}%Lv(wBebI6<t?<C<JgK})oNm>_>c}HMusnh?>@GereaisnrOV#SJ1(Mz+!n*-w z^!_o%$-4l0dzA}iuSf6xKz-WF?SCQFvsa_{exyBnYS<$wH*622xj>eJ?Kz{2lRXbT z><ablY3NZ`q0hg0RkgnjX^CB{t>%|2&+_Y2lTRjZWS1)|YwOvil{Hl^X0PWa#&fmT hlK0IROPjVc(9Xco%Rq8BVsAToi*K*Eoq@xVfnU&q=l%cy literal 0 HcmV?d00001 diff --git a/tests/fuzz-dwfl-core.c b/tests/fuzz-dwfl-core.c new file mode 100644 index 00000000..c067fb40 --- /dev/null +++ b/tests/fuzz-dwfl-core.c @@ -0,0 +1,58 @@ +#include <assert.h> +#include <config.h> +#include <stdlib.h> +#include ELFUTILS_HEADER(dwfl) +#include "fuzz.h" +#include "system.h" + +/* This fuzz target was initially used to fuzz systemd and + there elfutils is hidden behind functions receiving file + names and file descriptors. To cover that code the fuzz + target converts bytes it receives into temporary files + and passes their file descriptors to elf_begin instead of calling + something like elf_memory (which can process bytes directly). + New fuzzers covering elfutils should avoid this pattern. */ + +static const Dwfl_Callbacks core_callbacks = + { + .find_elf = dwfl_build_id_find_elf, + .find_debuginfo = dwfl_standard_find_debuginfo, + }; + +int +LLVMFuzzerTestOneInput (const uint8_t *data, size_t size) +{ + char name[] = "/tmp/fuzz-dwfl-core.XXXXXX"; + int fd = -1; + off_t offset; + ssize_t n; + Elf *core = NULL; + Dwfl *dwfl = NULL; + + fd = mkstemp (name); + assert (fd >= 0); + + n = write_retry (fd, data, size); + assert (n >= 0); + + offset = lseek (fd, 0, SEEK_SET); + assert (offset == 0); + + elf_version (EV_CURRENT); + core = elf_begin (fd, ELF_C_READ_MMAP, NULL); + if (core == NULL) + goto cleanup; + dwfl = dwfl_begin (&core_callbacks); + assert(dwfl != NULL); + if (dwfl_core_file_report (dwfl, core, NULL) < 0) + goto cleanup; + if (dwfl_report_end (dwfl, NULL, NULL) != 0) + goto cleanup; + +cleanup: + dwfl_end (dwfl); + elf_end (core); + close (fd); + unlink (name); + return 0; +} diff --git a/tests/fuzz-main.c b/tests/fuzz-main.c new file mode 100644 index 00000000..35573792 --- /dev/null +++ b/tests/fuzz-main.c @@ -0,0 +1,43 @@ +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include "fuzz.h" + +int +main (int argc, char **argv) +{ + for (int i = 1; i < argc; i++) + { + fprintf (stderr, "Running: %s\n", argv[i]); + + FILE *f = fopen (argv[i], "r"); + assert (f); + + int p = fseek (f, 0, SEEK_END); + assert (p >= 0); + + long len = ftell (f); + assert (len >= 0); + + p = fseek (f, 0, SEEK_SET); + assert (p >= 0); + + void *buf = malloc (len); + assert (buf != NULL || len == 0); + + size_t n_read = fread (buf, 1, len, f); + assert (n_read == (size_t) len); + + (void) fclose (f); + + int r = LLVMFuzzerTestOneInput (buf, len); + + /* Non-zero return values are reserved by LibFuzzer for future use + https://llvm.org/docs/LibFuzzer.html#fuzz-target */ + assert (r == 0); + + free (buf); + + fprintf (stderr, "Done: %s: (%zd bytes)\n", argv[i], n_read); + } +} diff --git a/tests/fuzz.h b/tests/fuzz.h new file mode 100644 index 00000000..c8fe7a3a --- /dev/null +++ b/tests/fuzz.h @@ -0,0 +1,9 @@ +#ifndef _FUZZ_H +#define _FUZZ_H 1 + +#include <stddef.h> +#include <stdint.h> + +int LLVMFuzzerTestOneInput (const uint8_t *data, size_t size); + +#endif /* fuzz.h */ diff --git a/tests/run-fuzz-dwfl-core.sh b/tests/run-fuzz-dwfl-core.sh new file mode 100755 index 00000000..84dca1be --- /dev/null +++ b/tests/run-fuzz-dwfl-core.sh @@ -0,0 +1,102 @@ +#!/bin/sh + +. $srcdir/test-subr.sh + +# Valgrind is turned off because hongfuzz and AFL keep track of +# processes and signals they receive and valgrind shouldn't +# interfer with that. Apart from that it reports memory leaks +# in timeout we aren't interested in: +#==53620== 8 bytes in 1 blocks are definitely lost in loss record 1 of 1 +#==53620== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) +#==53620== by 0x4860959: timer_create@@GLIBC_2.3.3 (timer_create.c:59) +#==53620== by 0x10AFCD: ??? (in /usr/bin/timeout) +#==53620== by 0x10AC18: ??? (in /usr/bin/timeout) +#==53620== by 0x48B00B2: (below main) (libc-start.c:308) +#==53620== +unset VALGRIND_CMD + +# honggfuzz sets ASAN and UBSAN options compatible with it +# so they are reset early to prevent the environment from +# affecting the test +unset ASAN_OPTIONS +unset UBSAN_OPTIONS + +fuzz_time=${FUZZ_TIME:-180} +timeout=30 + +# run_one is used to process files without honggfuzz +# to get backtraces that otherwise can be borked in honggfuzz runs +# so it has to set ASAN and UBSAN options itself +run_one() +{ + testrun timeout -s9 $timeout env \ + ASAN_OPTIONS=allocator_may_return_null=1 \ + UBSAN_OPTIONS=print_stacktrace=1:print_summary=1:halt_on_error=1 \ + ${abs_builddir}/fuzz-dwfl-core "$1" +} + +# Here the fuzz target processes files one by one to be able +# to catch memory leaks and other issues that can't be discovered +# with honggfuzz. +find ${abs_srcdir}/fuzz-dwfl-core-crashes/ -type f | while read file; do + run_one $file || { echo "*** failure in $file"; printf "$file\n" >>CRASHES; } +done + +if [ -n "$honggfuzz" ]; then + tempfiles log + + testrun $honggfuzz --run_time $fuzz_time -n 1 -v --exit_upon_crash \ + -i ${abs_srcdir}/fuzz-dwfl-core-crashes/ \ + -t $timeout --tmout_sigvtalrm \ + -o OUT \ + --logfile log \ + -- ${abs_builddir}/fuzz-dwfl-core ___FILE___ + + rm -rf OUT + + # hongfuzz always exits successfully so to tell "success" and "failure" apart + # it's necessary to look for reports it leaves when processes it monitors crash. + # Eventually it will be possible to pass --exit_code_upon_crash, which combined + # with --exit_upon_crash can be used to get honggfuzz to fail, but it hasn't been + # released yet. Initially it was used but on machines with the latest stable release + # tests that should have failed passed, which led to https://github.com/google/honggfuzz/pull/432 + if [ -f HONGGFUZZ.REPORT.TXT ]; then + tail -n 25 log + cat HF.sanitizer.log* || true + cat HONGGFUZZ.REPORT.TXT + for crash in $(sed -n 's/^FUZZ_FNAME: *//p' HONGGFUZZ.REPORT.TXT); do + run_one $crash || { printf "$crash\n" >>CRASHES; } + done + fi +fi + +if [ -n "$afl_fuzz" ]; then + # ASAN_OPTIONS and UBSAN_OPTIONS are compatible with both AFL and AFL++. They were borrowed from + # https://github.com/AFLplusplus/AFLplusplus/blob/74a8f145e09d0361d8f576eb3f2e8881b6116f18/src/afl-forkserver.c#L523 + common_san_opts="abort_on_error=1:malloc_context_size=0:symbolize=0:allocator_may_return_null=1" + handle_san_opts="handle_segv=0:handle_sigbus=0:handle_abort=0:handle_sigfpe=0:handle_sigill=0" + + testrun timeout --preserve-status $fuzz_time \ + env AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 \ + ASAN_OPTIONS="$common_san_opts:$handle_san_opts:detect_leaks=0:detect_odr_violation=0" \ + UBSAN_OPTIONS="$common_san_opts:$handle_san_opts:halt_on_error=1" \ + $afl_fuzz -i ${abs_srcdir}/fuzz-dwfl-core-crashes/ \ + -t $(expr $timeout '*' 1000) \ + -m none \ + -o OUT \ + -- ${abs_builddir}/fuzz-dwfl-core @@ + + find OUT/crashes OUT/hangs -type f | while read file; do + run_one $file || { printf "$file\n" >>CRASHES; } + done + + [ -f CRASHES ] || rm -rf OUT +fi + +if [ -f CRASHES ]; then + printf "Files triggering various crashes:\n" + cat CRASHES + exit 1 +fi + +exit 0 diff --git a/tests/run-strip-g.sh b/tests/run-strip-g.sh index 15921215..8b480ec0 100755 --- a/tests/run-strip-g.sh +++ b/tests/run-strip-g.sh @@ -22,10 +22,13 @@ # debug sections and so there should not be a copy in the debug file # except for a NOBITS one. -tempfiles a.out strip.out debug.out readelf.out +tmpfile=main.c + +tempfiles $tmpfile a.out strip.out debug.out readelf.out echo Create debug a.out. -echo "int main() { return 1; }" | ${CC} -g -xc - +echo "int main() { return 1; }" >$tmpfile +${CC} -g $tmpfile echo strip -g to file with debug file testrun ${abs_top_builddir}/src/strip -g -o strip.out -f debug.out || diff --git a/tests/run-strip-nothing.sh b/tests/run-strip-nothing.sh index 710c200d..e725297a 100755 --- a/tests/run-strip-nothing.sh +++ b/tests/run-strip-nothing.sh @@ -20,10 +20,13 @@ # If there is nothing to strip then -o output should be identical to input. # And there should not be an (empty) -f debug file. -tempfiles a.out strip.out debug.out +tmpfile=main.c + +tempfiles $tmpfile a.out strip.out debug.out # Create no-debug a.out. -echo "int main() { return 1; }" | ${CC} -s -xc - +echo "int main() { return 1; }" >$tmpfile +${CC} -s $tmpfile # strip to file testrun ${abs_top_builddir}/src/strip -g -o strip.out || -- 2.33.1 ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2021-12-27 0:30 UTC | newest] Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2021-12-12 15:16 [PATCH] tests: integrate fuzz-dwfl-core into elfutils Evgeny Vereshchagin 2021-12-17 10:46 ` Mark Wielaard 2021-12-17 12:23 ` Evgeny Vereshchagin 2021-12-19 19:23 ` [PATCH] tests: integrate fuzz-dwfl-core into the test suite Evgeny Vereshchagin 2021-12-21 1:49 ` [PATCH v3] " Evgeny Vereshchagin 2021-12-26 16:03 ` [PATCH v4] " Evgeny Vereshchagin
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).