From patchwork Sun Dec 12 15:16:58 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Evgeny Vereshchagin X-Patchwork-Id: 48853 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 67D463858429 for ; Mon, 13 Dec 2021 02:32:39 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 67D463858429 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1639362759; bh=TaBlI24RvW1T37ZKUm7LO9XVZtRv1OFirJl9+jIMug8=; h=To:Subject:Date:List-Id:List-Unsubscribe:List-Archive:List-Help: List-Subscribe:From:Reply-To:Cc:From; b=Amqa+6RpKV0GFH2hu4ow36W8+5x7Dabqe7zz2OjjX76URXFT2/MCTQr9cZpmvrS3M 5WeWxJuB/zpi7cgI1HIeFR6MAfYJa12pZztWbmWjhQi8AYgdnWS/jKkA+WVDCjlFrF Z4HhlO5kWAcJIYNmi8EZ7QowyP1FqnPNIyWZzLW4= X-Original-To: elfutils-devel@sourceware.org Delivered-To: elfutils-devel@sourceware.org Received: from forward106p.mail.yandex.net (forward106p.mail.yandex.net [IPv6:2a02:6b8:0:1472:2741:0:8b7:109]) by sourceware.org (Postfix) with ESMTPS id A1F4E3858401 for ; Mon, 13 Dec 2021 02:32:25 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org A1F4E3858401 Received: from iva8-4654c25a7801.qloud-c.yandex.net (iva8-4654c25a7801.qloud-c.yandex.net [IPv6:2a02:6b8:c0c:7784:0:640:4654:c25a]) by forward106p.mail.yandex.net (Yandex) with ESMTP id 55A2A2FC001B for ; Mon, 13 Dec 2021 05:32:23 +0300 (MSK) Received: from iva5-057a0d1fbbd8.qloud-c.yandex.net (iva5-057a0d1fbbd8.qloud-c.yandex.net [2a02:6b8:c0c:7f1c:0:640:57a:d1f]) by iva8-4654c25a7801.qloud-c.yandex.net (mxback/Yandex) with ESMTP id WU1j0P3vRw-WNeuYbcZ; Mon, 13 Dec 2021 05:32:23 +0300 Received: by iva5-057a0d1fbbd8.qloud-c.yandex.net (smtp/Yandex) with ESMTPSA id b6wvIS7EFN-WMP8YsWK; Mon, 13 Dec 2021 05:32:22 +0300 (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) (Client certificate not present) X-Yandex-Fwd: 2 To: elfutils-devel@sourceware.org Subject: [PATCH] tests: integrate fuzz-dwfl-core into elfutils Date: Sun, 12 Dec 2021 15:16:58 +0000 Message-Id: <20211212151658.60269-1-evvers@ya.ru> X-Mailer: git-send-email 2.33.1 MIME-Version: 1.0 X-Spam-Status: No, score=-9.7 required=5.0 tests=BAYES_00, DATE_IN_PAST_06_12, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, FREEMAIL_FROM, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_LOW, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on server2.sourceware.org X-BeenThere: elfutils-devel@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Elfutils-devel mailing list List-Unsubscribe: , List-Archive: List-Help: List-Subscribe: , X-Patchwork-Original-From: Evgeny Vereshchagin via Elfutils-devel From: Evgeny Vereshchagin Reply-To: Evgeny Vereshchagin Cc: Evgeny Vereshchagin Errors-To: elfutils-devel-bounces+patchwork=sourceware.org@sourceware.org Sender: "Elfutils-devel" 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 --- 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 + + * Makefile.am: Integrate fuzz-dwfl-core into the testsuite and add a + script linking it against libFuzzer. + 2021-12-09 Frank Ch. Eigler * 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|4fEZ9VLJUX8$ju7;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 +#include +#include +#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 +#include +#include +#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 +#include + +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