* 'fuzz' of https://github.com/evverx/util-linux:
  tests: pack testcases into zip archives
  tests: integrate test_last_fuzz into the testsuite
  tests: add a fuzzer for process_wtmp_file
  docs: mention OSS-Fuzz and CIFuzz and how to build fuzz targets locally
  tools: make it possible to set all the fuzzing flags with config-gen
  build-system: make "make distcheck" work
  travis: set CXX correctly
  travis: turn on --enable-fuzzing-engine
  build-sys: add support for --enable-fuzzing-engine
  tests: integrate test_mount_fuzz into the testsuite
  tests: take exit codes into account
  tests: add a fuzzer for mnt_table_parse_stream
This commit is contained in:
Karel Zak 2020-08-10 14:40:44 +02:00
commit c87cce57d8
19 changed files with 301 additions and 13 deletions

View File

@ -40,10 +40,27 @@ shell_session_update() { :; }
function xconfigure
{
local gcc_version clang_version cxx
which "$CC"
"$CC" --version
./configure "$@" $OSX_CONFOPTS
if [[ "$CC" =~ ^clang-([0-9]+)$ ]]; then
clang_version=${BASH_REMATCH[1]}
cxx=clang++-${clang_version}
elif [[ "$CC" =~ ^gcc-([0-9]+)$ ]]; then
gcc_version=${BASH_REMATCH[1]}
cxx=g++-${gcc_version}
elif [[ "$CC" == "clang" ]]; then
cxx=clang++
elif [[ "$CC" == "gcc" ]]; then
cxx=g++
fi
which "$cxx"
"$cxx" --version
CC=$CC CXX=$cxx ./configure "$@" $OSX_CONFOPTS
err=$?
if [ "$DUMP_CONFIG_LOG" = "short" ]; then
grep -B1 -A10000 "^## Output variables" config.log | grep -v "_FALSE="
@ -82,6 +99,10 @@ function check_nonroot
conf_opts="$conf_opts --enable-werror"
fi
if [[ "$CC" =~ "clang" ]]; then
conf_opts="$conf_opts --enable-fuzzing-engine"
fi
xconfigure $conf_opts || return
$MAKE || return
@ -109,6 +130,10 @@ function check_root
conf_opts="$conf_opts --enable-werror"
fi
if [[ "$CC" =~ "clang" ]]; then
conf_opts="$conf_opts --enable-fuzzing-engine"
fi
xconfigure $conf_opts || return
$MAKE || return

View File

@ -67,6 +67,14 @@
Please, be careful and use these tests only for development and never on
production system.
fuzz targets
------------
The fuzz targets can be built and run along with the other tests (after installing
clang):
$ ./tools/config-gen fuzz
$ make check
environment variables
---------------------
@ -136,3 +144,14 @@ Coverity Scan
Fossies codespell report
URL: https://fossies.org/linux/test/util-linux-master.tar.gz/codespell.html
OSS-Fuzz
URL: https://google.github.io/oss-fuzz/
URL: https://oss-fuzz-build-logs.storage.googleapis.com/index.html#util-linux
URL: https://oss-fuzz.com/coverage-report/job/libfuzzer_asan_util-linux/latest
CIFuzz
URL: https://google.github.io/oss-fuzz/getting-started/continuous-integration/
URL: https://github.com/karelzak/util-linux/actions?query=workflow%3ACIFuzz

View File

@ -10,14 +10,24 @@ AM_CPPFLAGS += \
-D_PATH_VENDORDIR=\"${vendordir}\"
endif
if FUZZING_ENGINE
if !OSS_FUZZ
AM_CPPFLAGS += \
-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
endif
endif
AM_CFLAGS = -fsigned-char $(WARN_CFLAGS)
AM_LDFLAGS = $(ASAN_LDFLAGS) $(UBSAN_LDFLAGS)
AM_CXXFLAGS = $(AM_CFLAGS)
AM_LDFLAGS = $(ASAN_LDFLAGS) $(UBSAN_LDFLAGS) $(FUZZING_ENGINE_LDFLAGS)
# Add gettext stuff to the global LDADD for systems with separate libintl
# library. The LTLIBINTL is generated by AM_GNU_GETTEXT macro.
#
LDADD = $(LTLIBINTL)
LIB_FUZZING_ENGINE ?= -fsanitize=fuzzer
# Automake (at least up to 1.10) mishandles dist_man_MANS inside conditionals.
# Unlike with other dist primaries, the files are not distributed if the
# conditional is false.

View File

@ -189,6 +189,21 @@ AS_IF([test "x$enable_ubsan" = xyes], [
])
AC_SUBST([UBSAN_LDFLAGS])
AC_ARG_ENABLE([fuzzing-engine],
AS_HELP_STRING([--enable-fuzzing-engine], [compile with fuzzing engine]),
[], [enable_fuzzing_engine=no]
)
AS_IF([test "x$enable_fuzzing_engine" = xyes -a "x$LIB_FUZZING_ENGINE" = x], [
UL_WARN_ADD([-fno-omit-frame-pointer])
UL_WARN_ADD([-gline-tables-only])
UL_WARN_ADD([-fsanitize=fuzzer-no-link])
FUZZING_ENGINE_LDFLAGS="-fsanitize=fuzzer-no-link"
])
AC_SUBST([FUZZING_ENGINE_LDFLAGS])
AC_PROG_CXX
AM_CONDITIONAL([FUZZING_ENGINE], [test "x$enable_fuzzing_engine" = xyes])
AM_CONDITIONAL([OSS_FUZZ], [test "x$LIB_FUZZING_ENGINE" != x])
dnl libtool-2
LT_INIT

View File

@ -21,6 +21,7 @@ dist_noinst_HEADERS += \
include/exec_shell.h \
include/exitcodes.h \
include/fileutils.h \
include/fuzz.h \
include/idcache.h \
include/ismounted.h \
include/iso9660.h \

9
include/fuzz.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef UTIL_LINUX_FUZZ_H
#define UTIL_LINUX_FUZZ_H
#include <stddef.h>
#include <stdint.h>
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
#endif /* UTIL_LINUX_FUZZ_H */

View File

@ -154,6 +154,19 @@ test_mount_debug_CFLAGS = $(libmount_tests_cflags)
test_mount_debug_LDFLAGS = $(libmount_tests_ldflags)
test_mount_debug_LDADD = $(libmount_tests_ldadd)
if FUZZING_ENGINE
check_PROGRAMS += test_mount_fuzz
test_mount_fuzz_SOURCES = libmount/src/fuzz.c
# https://google.github.io/oss-fuzz/getting-started/new-project-guide/#Requirements
nodist_EXTRA_test_mount_fuzz_SOURCES = dummy.cxx
test_mount_fuzz_CFLAGS = $(libmount_tests_cflags)
test_mount_fuzz_LDFLAGS = $(libmount_tests_ldflags)
test_mount_fuzz_LDADD = $(libmount_tests_ldadd) $(LIB_FUZZING_ENGINE)
endif
endif # BUILD_LIBMOUNT_TESTS

26
libmount/src/fuzz.c Normal file
View File

@ -0,0 +1,26 @@
#include "fuzz.h"
#include "mountP.h"
#include <stddef.h>
#include <stdint.h>
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
struct libmnt_table *tb = NULL;
FILE *f = NULL;
if (size == 0)
return 0;
tb = mnt_new_table();
assert(tb);
f = fmemopen((char*) data, size, "re");
assert(f);
(void) mnt_table_parse_stream(tb, f, "mountinfo");
mnt_unref_table(tb);
fclose(f);
return 0;
}

View File

@ -11,6 +11,18 @@ install-exec-hook-last:
cd $(DESTDIR)$(usrbin_execdir) && ln -sf last lastb
INSTALL_EXEC_HOOKS += install-exec-hook-last
if FUZZING_ENGINE
check_PROGRAMS += test_last_fuzz
# https://google.github.io/oss-fuzz/getting-started/new-project-guide/#Requirements
nodist_EXTRA_test_last_fuzz_SOURCES = dummy.cxx
test_last_fuzz_SOURCES = login-utils/last.c
test_last_fuzz_CFLAGS = $(AM_CFLAGS) -DFUZZ_TARGET
test_last_fuzz_LDADD = $(LDADD) libcommon.la $(LIB_FUZZING_ENGINE)
endif
endif
if BUILD_SULOGIN

View File

@ -53,6 +53,10 @@
#include "timeutils.h"
#include "monotonic.h"
#ifdef FUZZ_TARGET
#include "fuzz.h"
#endif
#ifndef SHUTDOWN_TIME
# define SHUTDOWN_TIME 254
#endif
@ -157,6 +161,7 @@ static unsigned int recsdone; /* Number of records listed */
static time_t lastdate; /* Last date we've seen */
static time_t currentdate; /* date when we started processing the file */
#ifndef FUZZ_TARGET
/* --time-format=option parser */
static int which_time_format(const char *s)
{
@ -168,6 +173,7 @@ static int which_time_format(const char *s)
}
errx(EXIT_FAILURE, _("unknown time format: %s"), s);
}
#endif
/*
* Read one utmp entry, return in new format.
@ -258,6 +264,7 @@ static int uread(FILE *fp, struct utmpx *u, int *quit, const char *filename)
return 1;
}
#ifndef FUZZ_TARGET
/*
* Print a short date.
*/
@ -286,6 +293,7 @@ static void quit_handler(int sig __attribute__((unused)))
warnx(_("Interrupted %s"), showdate());
signal(SIGQUIT, quit_handler);
}
#endif
/*
* Lookup a host with DNS.
@ -564,7 +572,7 @@ static int list(const struct last_control *ctl, struct utmpx *p, time_t logout_t
return 0;
}
#ifndef FUZZ_TARGET
static void __attribute__((__noreturn__)) usage(const struct last_control *ctl)
{
FILE *out = stdout;
@ -599,6 +607,7 @@ static void __attribute__((__noreturn__)) usage(const struct last_control *ctl)
exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
}
#endif
static int is_phantom(const struct last_control *ctl, struct utmpx *ut)
{
@ -660,17 +669,23 @@ static void process_wtmp_file(const struct last_control *ctl,
int quit = 0; /* Flag */
int down = 0; /* Down flag */
#ifndef FUZZ_TARGET
time(&lastdown);
#else
lastdown = 1596001948;
#endif
/*
* Fill in 'lastdate'
*/
lastdate = currentdate = lastrch = lastdown;
#ifndef FUZZ_TARGET
/*
* Install signal handlers
*/
signal(SIGINT, int_handler);
signal(SIGQUIT, quit_handler);
#endif
/*
* Open the utmp file
@ -907,6 +922,36 @@ static void process_wtmp_file(const struct last_control *ctl,
}
}
#ifdef FUZZ_TARGET
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
struct last_control ctl = {
.showhost = TRUE,
.name_len = LAST_LOGIN_LEN,
.time_fmt = LAST_TIMEFTM_SHORT,
.domain_len = LAST_DOMAIN_LEN,
.boot_time = {
.tv_sec = 1595978419,
.tv_usec = 816074
}
};
char name[] = "/tmp/test-last-fuzz.XXXXXX";
int fd;
ssize_t n;
fd = mkostemp(name, O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC);
assert(fd >= 0);
n = write(fd, data, size);
assert(n == (ssize_t) size);
process_wtmp_file(&ctl, name);
close(fd);
unlink(name);
return 0;
}
#else
int main(int argc, char **argv)
{
struct last_control ctl = {
@ -1041,3 +1086,4 @@ int main(int argc, char **argv)
free(files);
return EXIT_SUCCESS;
}
#endif

View File

@ -18,6 +18,7 @@ TS_HELPER_LIBMOUNT_TAB="${ts_helpersdir}test_mount_tab"
TS_HELPER_LIBMOUNT_UPDATE="${ts_helpersdir}test_mount_tab_update"
TS_HELPER_LIBMOUNT_UTILS="${ts_helpersdir}test_mount_utils"
TS_HELPER_LIBMOUNT_DEBUG="${ts_helpersdir}test_mount_debug"
TS_HELPER_LIBMOUNT_FUZZ="${ts_helpersdir}test_mount_fuzz"
TS_HELPER_LIBSMARTCOLS_FROMFILE="${ts_helpersdir}sample-scols-fromfile"
TS_HELPER_LIBSMARTCOLS_TITLE="${ts_helpersdir}sample-scols-title"
TS_HELPER_PYLIBMOUNT_CONTEXT="$top_srcdir/libmount/python/test_mount_context.py"
@ -41,6 +42,7 @@ TS_HELPER_UUID_PARSER="${ts_helpersdir}test_uuid_parser"
TS_HELPER_UUID_NAMESPACE="${ts_helpersdir}test_uuid_namespace"
TS_HELPER_MBSENCODE="${ts_helpersdir}test_mbsencode"
TS_HELPER_CAL="${ts_helpersdir}test_cal"
TS_HELPER_LAST_FUZZ="${ts_helpersdir}test_last_fuzz"
# paths to commands
TS_CMD_ADDPART=${TS_CMD_ADDPART:-"${ts_commandsdir}addpart"}

View File

@ -230,6 +230,7 @@ function ts_init_core_env {
TS_OUTPUT="$TS_OUTDIR/$TS_TESTNAME"
TS_ERRLOG="$TS_OUTDIR/$TS_TESTNAME.err"
TS_VGDUMP="$TS_OUTDIR/$TS_TESTNAME.vgdump"
TS_EXIT_CODE="$TS_OUTDIR/$TS_TESTNAME.exit_code"
TS_DIFF="$TS_DIFFDIR/$TS_TESTNAME"
TS_EXPECTED="$TS_TOPDIR/expected/$TS_NS"
TS_EXPECTED_ERR="$TS_TOPDIR/expected/$TS_NS.err"
@ -241,15 +242,16 @@ function ts_init_core_subtest_env {
TS_OUTPUT="$TS_OUTDIR/$TS_TESTNAME-$TS_SUBNAME"
TS_ERRLOG="$TS_OUTDIR/$TS_TESTNAME-$TS_SUBNAME.err"
TS_VGDUMP="$TS_OUTDIR/$TS_TESTNAME-$TS_SUBNAME.vgdump"
TS_EXIT_CODE="$TS_OUTDIR/$TS_TESTNAME-$TS_SUBNAME.exit_code"
TS_DIFF="$TS_DIFFDIR/$TS_TESTNAME-$TS_SUBNAME"
TS_EXPECTED="$TS_TOPDIR/expected/$TS_NS"
TS_EXPECTED_ERR="$TS_TOPDIR/expected/$TS_NS.err"
TS_MOUNTPOINT="$TS_OUTDIR/${TS_TESTNAME}-${TS_SUBNAME}-mnt"
rm -f $TS_OUTPUT $TS_ERRLOG $TS_VGDUMP
rm -f $TS_OUTPUT $TS_ERRLOG $TS_VGDUMP $TS_EXIT_CODE
[ -d "$TS_OUTDIR" ] || mkdir -p "$TS_OUTDIR"
touch $TS_OUTPUT $TS_ERRLOG
touch $TS_OUTPUT $TS_ERRLOG $TS_EXIT_CODE
[ -n "$TS_VALGRIND_CMD" ] && touch $TS_VGDUMP
}
@ -359,10 +361,10 @@ function ts_init_env {
export BLKID_FILE
rm -f $TS_OUTPUT $TS_ERRLOG $TS_VGDUMP
rm -f $TS_OUTPUT $TS_ERRLOG $TS_VGDUMP $TS_EXIT_CODE
[ -d "$TS_OUTDIR" ] || mkdir -p "$TS_OUTDIR"
touch $TS_OUTPUT $TS_ERRLOG
touch $TS_OUTPUT $TS_ERRLOG $TS_EXIT_CODE
[ -n "$TS_VALGRIND_CMD" ] && touch $TS_VGDUMP
if [ "$TS_VERBOSE" == "yes" ]; then
@ -380,6 +382,7 @@ function ts_init_env {
echo " verbose: $TS_VERBOSE"
echo " output: $TS_OUTPUT"
echo " error log: $TS_ERRLOG"
echo " exit code: $TS_EXIT_CODE"
echo " valgrind: $TS_VGDUMP"
echo " expected: $TS_EXPECTED{.err}"
echo " mountpoint: $TS_MOUNTPOINT"
@ -470,6 +473,7 @@ function ts_run {
fi
"${args[@]}" "$@"
echo $? >$TS_EXIT_CODE
}
function ts_gen_diff_from {
@ -499,6 +503,7 @@ function ts_gen_diff_from {
function ts_gen_diff {
local status_out=0
local status_err=0
local exit_code=0
[ -f "$TS_OUTPUT" ] || return 1
[ -f "$TS_EXPECTED" ] || TS_EXPECTED=/dev/null
@ -509,17 +514,31 @@ function ts_gen_diff {
[ -d "$TS_DIFFDIR" ] || mkdir -p "$TS_DIFFDIR"
ts_gen_diff_from $TS_EXPECTED $TS_OUTPUT $TS_DIFF
status_out=$?
# error log is fully optional
[ -f "$TS_EXPECTED_ERR" ] || TS_EXPECTED_ERR=/dev/null
[ -f "$TS_ERRLOG" ] || TS_ERRLOG=/dev/null
ts_gen_diff_from $TS_EXPECTED_ERR $TS_ERRLOG $TS_DIFF.err
status_err=$?
if [ "$TS_COMPONENT" != "fuzzers" ]; then
ts_gen_diff_from $TS_EXPECTED $TS_OUTPUT $TS_DIFF
status_out=$?
if [ $status_out -ne 0 -o $status_err -ne 0 ]; then
ts_gen_diff_from $TS_EXPECTED_ERR $TS_ERRLOG $TS_DIFF.err
status_err=$?
else
# TS_EXIT_CODE is empty when tests aren't run with ts_run: https://github.com/karelzak/util-linux/issues/1072
# or when ts_finalize is called right after ts_finalize_subtest.
exit_code="$(cat $TS_EXIT_CODE)"
if [ -z "$exit_code" ]; then
exit_code=0
fi
if [ $exit_code -ne 0 ]; then
ts_gen_diff_from $TS_EXPECTED $TS_OUTPUT $TS_DIFF
ts_gen_diff_from $TS_EXPECTED_ERR $TS_ERRLOG $TS_DIFF.err
fi
fi
if [ $status_out -ne 0 -o $status_err -ne 0 -o $exit_code -ne 0 ]; then
return 1
fi
return 0

26
tests/ts/fuzzers/test_last_fuzz Executable file
View File

@ -0,0 +1,26 @@
#!/bin/bash
# This file is part of util-linux.
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This file is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
TS_TOPDIR="${0%/*}/../.."
TS_DESC="test_last_fuzz"
. $TS_TOPDIR/functions.sh
ts_init "$*"
ts_check_test_command "$TS_HELPER_LAST_FUZZ"
mkdir -p ${TS_OUTPUT}_workdir
ts_run $TS_HELPER_LAST_FUZZ ${TS_OUTPUT}_workdir ${TS_SCRIPT}_files -max_total_time=10 >$TS_OUTPUT 2>$TS_ERRLOG
ts_finalize

Binary file not shown.

View File

@ -0,0 +1,26 @@
#!/bin/bash
# This file is part of util-linux.
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This file is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
TS_TOPDIR="${0%/*}/../.."
TS_DESC="test_mount_fuzz"
. $TS_TOPDIR/functions.sh
ts_init "$*"
ts_check_test_command "$TS_HELPER_LIBMOUNT_FUZZ"
mkdir -p ${TS_OUTPUT}_workdir
ts_run $TS_HELPER_LIBMOUNT_FUZZ ${TS_OUTPUT}_workdir ${TS_SCRIPT}_files -max_total_time=10 >$TS_OUTPUT 2>$TS_ERRLOG
ts_finalize

Binary file not shown.

View File

@ -23,6 +23,10 @@ fi
while [ -n "$1" ]; do
opts="$opts $(ul_get_configuration tools/config-gen.d/$1.conf)"
if [ "$1" == "fuzz" ]; then
export CC=${CC:-clang}
export CXX=${CXX:-clang++}
fi
shift
done

View File

@ -0,0 +1,4 @@
include:devel.conf
--enable-ubsan
--enable-fuzzing-engine

31
tools/oss-fuzz.sh Executable file
View File

@ -0,0 +1,31 @@
#!/usr/bin/env bash
set -ex
export LC_CTYPE=C.UTF-8
export CC=${CC:-clang}
export CXX=${CXX:-clang++}
export LIB_FUZZING_ENGINE=${LIB_FUZZING_ENGINE:--fsanitize=fuzzer}
SANITIZER=${SANITIZER:-address -fsanitize-address-use-after-scope}
flags="-O1 -fno-omit-frame-pointer -gline-tables-only -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -fsanitize=$SANITIZER -fsanitize=fuzzer-no-link"
export CFLAGS=${CFLAGS:-$flags}
export CXXFLAGS=${CXXFLAGS:-$flags}
export OUT=${OUT:-$(pwd)/out}
mkdir -p $OUT
./autogen.sh
./configure --disable-all-programs --enable-last --enable-fuzzing-engine --enable-libmount --enable-libblkid
make -j$(nproc) V=1 check-programs
for d in "$(dirname $0)"/../tests/ts/fuzzers/test_*_fuzz_files; do
bd=$(basename "$d")
fuzzer=${bd%_files}
zip -jqr $OUT/${fuzzer}_seed_corpus.zip "$d"
done
find . -maxdepth 1 -type f -executable -name "test_*_fuzz" -exec mv {} $OUT \;
find . -type f -name "fuzz-*.dict" -exec cp {} $OUT \;