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
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
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
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
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 ||
