From 81edf9f0da682b8e39f20633f4d6a3e1ebabf61f Mon Sep 17 00:00:00 2001 From: Frantisek Sumsal Date: Fri, 29 Jan 2021 22:01:02 +0100 Subject: [PATCH] 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. --- .github/workflows/cibuild.sh | 34 ++++++++++++++++++++++++- libfdisk/src/Makemodule.am | 2 +- libmount/src/Makemodule.am | 2 +- login-utils/Makemodule.am | 1 + tests/functions.sh | 14 ++++++++++ tests/ts/eject/umount | 8 ++++++ tests/ts/fuzzers/test_fdisk_script_fuzz | 3 +++ tests/ts/fuzzers/test_last_fuzz | 3 +++ tests/ts/fuzzers/test_mount_fuzz | 3 +++ 9 files changed, 67 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cibuild.sh b/.github/workflows/cibuild.sh index 80583c513..7476395dc 100755 --- a/.github/workflows/cibuild.sh +++ b/.github/workflows/cibuild.sh @@ -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) diff --git a/libfdisk/src/Makemodule.am b/libfdisk/src/Makemodule.am index 3615c9f52..9bd64c11a 100644 --- a/libfdisk/src/Makemodule.am +++ b/libfdisk/src/Makemodule.am @@ -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 diff --git a/libmount/src/Makemodule.am b/libmount/src/Makemodule.am index 32fdd3f24..0b6e6c4cf 100644 --- a/libmount/src/Makemodule.am +++ b/libmount/src/Makemodule.am @@ -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 diff --git a/login-utils/Makemodule.am b/login-utils/Makemodule.am index 2ee832083..d4e56d70a 100644 --- a/login-utils/Makemodule.am +++ b/login-utils/Makemodule.am @@ -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 diff --git a/tests/functions.sh b/tests/functions.sh index 148496a58..7c8916f19 100644 --- a/tests/functions.sh +++ b/tests/functions.sh @@ -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 +} diff --git a/tests/ts/eject/umount b/tests/ts/eject/umount index c12d06997..a829d46c0 100755 --- a/tests/ts/eject/umount +++ b/tests/ts/eject/umount @@ -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 diff --git a/tests/ts/fuzzers/test_fdisk_script_fuzz b/tests/ts/fuzzers/test_fdisk_script_fuzz index 6b7af42d4..708d372e8 100755 --- a/tests/ts/fuzzers/test_fdisk_script_fuzz +++ b/tests/ts/fuzzers/test_fdisk_script_fuzz @@ -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 diff --git a/tests/ts/fuzzers/test_last_fuzz b/tests/ts/fuzzers/test_last_fuzz index 4cc86fd06..5d92d0de2 100755 --- a/tests/ts/fuzzers/test_last_fuzz +++ b/tests/ts/fuzzers/test_last_fuzz @@ -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 diff --git a/tests/ts/fuzzers/test_mount_fuzz b/tests/ts/fuzzers/test_mount_fuzz index 610686301..c9349071c 100755 --- a/tests/ts/fuzzers/test_mount_fuzz +++ b/tests/ts/fuzzers/test_mount_fuzz @@ -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