ci: deal with uninstrumented binaries using instrumented libs

All `eject` tests were failing under ASan, since they call /bin/mount,
which is uninstrumented, but it picks up the instrumented `libblkid`
library, causing ASan to complain:

gcc:
ASan runtime does not come first in initial library list; you should either link runtime to your application or manually preload it with LD_PRELOAD.
eject: unmount of `/home/runner/work/util-linux/util-linux/tests/output/eject/umount-by-disk-mounted-mnt' failed

clang:
/bin/umount: symbol lookup error: /home/runner/work/util-linux/util-linux/.libs/libblkid.so.1: undefined symbol: __sancov_lowest_stack
eject: unmount of `/home/runner/work/util-linux/util-linux/tests/output/eject/umount-by-disk-mounted-mnt' failed

Subsequently, all tests which require the `scsi_debug` module get skipped,
since it's still in use due to the failed umount:

fdisk: align 512/4K                   ... SKIPPED (cannot remove scsi_debug module (rmmod))
fdisk: align 512/4K +alignment_offset ... SKIPPED (cannot remove scsi_debug module (rmmod))
fdisk: align 512/4K +MD               ... SKIPPED (cannot remove scsi_debug module (rmmod))

In case of gcc this can be easily resolved by setting $LD_PRELOAD to the
respective ASan library. clang makes this a bit more difficult, since it
compiles the ASan library statically, so firstly we need to force dynamic
linking (via -shared-asan), and then add the runtime DSO path to the
linker cache, since it's in a non-standard path.
This commit is contained in:
Frantisek Sumsal 2021-01-29 22:01:02 +01:00
parent c40b3cd03d
commit 81edf9f0da
9 changed files with 67 additions and 3 deletions

View File

@ -7,9 +7,13 @@ COMPILER_VERSION="${COMPILER_VERSION}"
if [[ "$COMPILER" == clang ]]; then
CC="clang${COMPILER_VERSION:+-$COMPILER_VERSION}"
CXX="clang++${COMPILER_VERSION:+-$COMPILER_VERSION}"
CFLAGS="-shared-libasan -O1 -g -fno-omit-frame-pointer"
CXXFLAGS="-shared-libasan -O1 -g -fno-omit-frame-pointer"
elif [[ "$COMPILER" == gcc ]]; then
CC="gcc${COMPILER_VERSION:+-$COMPILER_VERSION}"
CXX="g++${COMPILER_VERSION:+-$COMPILER_VERSION}"
CFLAGS="-O1 -g -fno-omit-frame-pointer"
CXXFLAGS="-O1 -g -fno-omit-frame-pointer"
fi
set -ex
@ -35,7 +39,7 @@ for phase in "${PHASES[@]}"; do
sudo -E git clean -xdf
./autogen.sh
CC=$CC CXX=$CXX ./configure $opts
CC=$CC CXX=$CXX CFLAGS="$CFLAGS" CXXFLAGS="$CXXFLAGS" ./configure $opts
;;
MAKE)
make -j
@ -45,6 +49,34 @@ for phase in "${PHASES[@]}"; do
make install DESTDIR=/tmp/dest
;;
CHECK)
# All the following black magic is to make test/eject/umount work, since
# eject execl()s the uninstrumented /bin/umount binary, which confuses
# ASan. The workaround for this is to set $LD_PRELOAD to the ASan's
# runtime DSO, which works well with gcc without any additional hassle.
# However, since clang, by default, links ASan statically, we need to
# explicitly state we want dynamic linking (see -shared-libasan above).
# That, however, introduces another issue - clang's ASan runtime is in
# a non-standard path, so all binaries compiled in such way refuse
# to start. That's what the following blob of code is for - it detects
# the ASan's runtime path and adds the respective directory to
# the dynamic linker cache.
#
# The actual $LD_PRELOAD sheanigans are done directly in
# tests/ts/eject/umount.
asan_rt_name="$(ldd ./kill | awk '/lib.+asan.*.so/ {print $1; exit}')"
asan_rt_path="$($CC --print-file-name "$asan_rt_name")"
echo "Detected ASan runtime: $asan_rt_name ($asan_rt_path)"
if [[ -z "$asan_rt_name" || -z "$asan_rt_path" ]]; then
echo >&2 "Couldn't detect ASan runtime, can't continue"
exit 1
fi
if [[ "$COMPILER" == clang* ]]; then
mkdir -p /etc/ld.so.conf.d/
echo "${asan_rt_path%/*}" > /etc/ld.so.conf.d/99-clang-libasan.conf
ldconfig
fi
./tests/run.sh --show-diff
;;
DISTCHECK)

View File

@ -103,7 +103,7 @@ nodist_EXTRA_test_fdisk_script_fuzz_SOURCES = dummy.cxx
test_fdisk_script_fuzz_SOURCES = libfdisk/src/script.c
test_fdisk_script_fuzz_CFLAGS = -DFUZZ_TARGET $(libfdisk_la_CFLAGS) $(NO_UNUSED_WARN_CFLAGS)
test_fdisk_script_fuzz_LDFLAGS = $(libfdisk_tests_ldflags)
test_fdisk_script_fuzz_LDFLAGS = $(libfdisk_tests_ldflags) -lpthread
test_fdisk_script_fuzz_LDADD = $(libfdisk_tests_ldadd) $(LIB_FUZZING_ENGINE)
endif

View File

@ -163,7 +163,7 @@ test_mount_fuzz_SOURCES = libmount/src/fuzz.c
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_LDFLAGS = $(libmount_tests_ldflags) -lpthread
test_mount_fuzz_LDADD = $(libmount_tests_ldadd) $(LIB_FUZZING_ENGINE)
endif

View File

@ -20,6 +20,7 @@ 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_LDFLAGS = -lpthread
test_last_fuzz_LDADD = $(LDADD) libcommon.la $(LIB_FUZZING_ENGINE)
endif

View File

@ -1078,3 +1078,17 @@ function ts_has_ncurses_support {
echo "no"
fi
}
# Get path to the ASan runtime DSO the given binary was compiled with
function ts_get_asan_rt_path {
local binary="${1?}"
local rt_path
ts_check_prog "ldd"
ts_check_prog "awk"
rt_path="$(ldd "$binary" | awk '/lib.+asan.*.so/ {print $3; exit}')"
if [ -n "$rt_path" -a -f "$rt_path" ]; then
echo "$rt_path"
fi
}

View File

@ -8,6 +8,7 @@ ts_init "$*"
ts_check_test_command "$TS_CMD_FDISK"
ts_check_test_command "$TS_CMD_EJECT"
ts_check_test_command "$TS_CMD_KILL"
ts_check_test_command "$TS_CMD_MOUNT"
ts_skip_nonroot
@ -59,6 +60,13 @@ function deinit_device {
ts_scsi_debug_rmmod
}
# As the eject binary execl()s an uninstrumented /bin/umount binary, we need
# to explicitly $LD_PRELOAD the ASan's runtime DSO, otherwise ASan will complain.
# Since all three utilities used by this test (eject, fdisk, mount) are just
# libtool wrappers, let's check the kill binary instead, which should have
# the needed DSO information.
ASAN_RT_PATH="$(ts_get_asan_rt_path "$TS_CMD_KILL")"
[ -n "$ASAN_RT_PATH" ] && export LD_PRELOAD="$ASAN_RT_PATH:$LD_PRELOAD"
ts_init_subtest "by-disk"
init_device

View File

@ -20,6 +20,9 @@ ts_init "$*"
ts_check_test_command "$TS_HELPER_LIBFDISK_SCRIPT_FUZZ"
ASAN_RT_PATH="$(ts_get_asan_rt_path "$TS_HELPER_LIBFDISK_SCRIPT_FUZZ")"
[ -n "$ASAN_RT_PATH" ] && export LD_PRELOAD="$ASAN_RT_PATH:$LD_PRELOAD"
mkdir -p ${TS_OUTPUT}_workdir
ts_run $TS_HELPER_LIBFDISK_SCRIPT_FUZZ ${TS_OUTPUT}_workdir ${TS_SCRIPT}_files -max_total_time=10 >$TS_OUTPUT 2>$TS_ERRLOG

View File

@ -20,6 +20,9 @@ ts_init "$*"
ts_check_test_command "$TS_HELPER_LAST_FUZZ"
ASAN_RT_PATH="$(ts_get_asan_rt_path "$TS_HELPER_LAST_FUZZ")"
[ -n "$ASAN_RT_PATH" ] && export LD_PRELOAD="$ASAN_RT_PATH:$LD_PRELOAD"
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

View File

@ -20,6 +20,9 @@ ts_init "$*"
ts_check_test_command "$TS_HELPER_LIBMOUNT_FUZZ"
ASAN_RT_PATH="$(ts_get_asan_rt_path "$TS_HELPER_LIBMOUNT_FUZZ")"
[ -n "$ASAN_RT_PATH" ] && export LD_PRELOAD="$ASAN_RT_PATH:$LD_PRELOAD"
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