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