Merge pull request #12663 from mrc0mmand/clang-asan-improvements
test: improve handling of ASan under clang
This commit is contained in:
@@ -64,5 +64,10 @@ available functionality:
|
||||
of active alerts can be found
|
||||
[here](https://lgtm.com/projects/g/systemd/systemd/alerts/?mode=list).
|
||||
|
||||
15. Each PR is automatically tested with [Address Sanitizer](https://clang.llvm.org/docs/AddressSanitizer.html)
|
||||
and [Undefined Behavior Sanitizer](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html).
|
||||
See [Testing systemd using sanitizers](https://systemd.io/TESTING_WITH_SANITIZERS)
|
||||
for more information.
|
||||
|
||||
Access to Coverity and oss-fuzz reports is limited. Please reach out to the
|
||||
maintainers if you need access.
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: Testing systemd using sanitizers
|
||||
---
|
||||
|
||||
# Testing systemd using sanitizers
|
||||
|
||||
To catch the *nastier* kind of bugs, you can run your code with [Address Sanitizer](https://clang.llvm.org/docs/AddressSanitizer.html)
|
||||
and [Undefined Behavior Sanitizer](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html).
|
||||
This is mostly done automagically by various CI systems for each PR, but you may
|
||||
want to do it locally as well. The process slightly varies depending on the
|
||||
compiler you want to use and which part of the test suite you want to run.
|
||||
|
||||
## gcc
|
||||
gcc compiles in sanitizer libraries dynamically by default, so you need to get
|
||||
the shared libraries first - on Fedora these are shipped as a separate packages
|
||||
(`libasan` for Address Sanitizer and `libubsan` for Undefined Behavior Sanitizer).
|
||||
|
||||
The compilation itself is then a matter of simply adding `-Db_sanitize=address,undefined`
|
||||
to `meson`. That's it - following executions of `meson test` and integrations tests
|
||||
under `test/` subdirectory will run with sanitizers enabled. However, to get
|
||||
truly useful results, you should tweak the runtime configuration of respective
|
||||
sanitizers; e.g. in systemd we set the following environment variables:
|
||||
|
||||
```bash
|
||||
ASAN_OPTIONS=strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1
|
||||
UBSAN_OPTIONS=print_stacktrace=1:print_summary=1:halt_on_error=1
|
||||
```
|
||||
## clang
|
||||
In case of clang things are somewhat different - the sanitizer libraries are
|
||||
compiled in statically by default. This is not an issue if you plan to run
|
||||
only the unit tests, but for integration tests you'll need to convince clang
|
||||
to use the dynamic versions of sanitizer libraries.
|
||||
|
||||
First of all, pass `-shared-libsan` to both `clang` and `clang++`:
|
||||
|
||||
```bash
|
||||
CFLAGS=-shared-libasan
|
||||
CXXFLAGS=-shared-libasan
|
||||
```
|
||||
|
||||
The `CXXFLAGS` are necessary for `src/libsystemd/sd-bus/test-bus-vtable-cc.c`. Compilation
|
||||
is then the same as in case of gcc, simply add `-Db_sanitize=address,undefined`
|
||||
to the `meson` call and use the same environment variables for runtime configuration.
|
||||
|
||||
```bash
|
||||
ASAN_OPTIONS=strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1
|
||||
UBSAN_OPTIONS=print_stacktrace=1:print_summary=1:halt_on_error=1
|
||||
```
|
||||
|
||||
After this, you'll probably notice that all compiled binaries complain about
|
||||
missing `libclang_rt.asan*` library. To fix this, you have to install clang's
|
||||
runtime libraries, usually shipped in the `compiler-rt` package. As these libraries
|
||||
are installed in a non-standard location (non-standard for `ldconfig`), you'll
|
||||
need to manually direct binaries to the respective runtime libraries.
|
||||
|
||||
```
|
||||
# Optionally locate the respective runtime DSO
|
||||
$ ldd build/systemd | grep libclang_rt.asan
|
||||
libclang_rt.asan-x86_64.so => not found
|
||||
libclang_rt.asan-x86_64.so => not found
|
||||
$ find /usr/lib* /usr/local/lib* -type f -name libclang_rt.asan-x86_64.so 2>/dev/null
|
||||
/usr/lib64/clang/7.0.1/lib/libclang_rt.asan-x86_64.so
|
||||
|
||||
# Set the LD_LIBRARY_PATH accordingly
|
||||
export LD_LIBRARY_PATH=/usr/lib64/clang/7.0.1/lib/
|
||||
|
||||
# If the path is correct, the "not found" message should change to an actual path
|
||||
$ ldd build/systemd | grep libclang_rt.asan
|
||||
libclang_rt.asan-x86_64.so => /usr/lib64/clang/7.0.1/lib/libclang_rt.asan-x86_64.so (0x00007fa9752fc000)
|
||||
```
|
||||
|
||||
This should help binaries to correctly find necessary sanitizer DSOs.
|
||||
|
||||
Also, to make the reports useful, `llvm-symbolizer` tool is required (usually
|
||||
part of the `llvm` package).
|
||||
|
||||
## Background notes
|
||||
The reason why you need to force dynamic linking in case of `clang` is that some
|
||||
applications make use of `libsystemd`, which is compiled with sanitizers as well.
|
||||
However, if a *standard* (uninstrumented) application loads an instrumented library,
|
||||
it will immediately fail due to unresolved symbols. To fix/workaround this, you
|
||||
need to pre-load the ASan DSO using `LD_PRELOAD=/path/to/asan/dso`, which will
|
||||
make things work as expected in most cases. This will, obviously, not work with
|
||||
statically linked sanitizer libraries.
|
||||
|
||||
These shenanigans are performed automatically when running the integration test
|
||||
suite (i.e. `test/TEST-??-*`) and are located in `test/test-functions` (mainly,
|
||||
but not only, in the `create_asan_wrapper` function).
|
||||
+55
-4
@@ -54,6 +54,30 @@ if [[ "$IS_BUILT_WITH_ASAN" = "yes" ]]; then
|
||||
PATH_TO_INIT=$ROOTLIBDIR/systemd-under-asan
|
||||
QEMU_MEM="1536M"
|
||||
QEMU_SMP=4
|
||||
|
||||
# We need to correctly distinguish between gcc's and clang's ASan DSOs.
|
||||
if ldd $BUILD_DIR/systemd | grep -q libasan.so; then
|
||||
ASAN_COMPILER=gcc
|
||||
elif ldd $BUILD_DIR/systemd | grep -q libclang_rt.asan; then
|
||||
ASAN_COMPILER=clang
|
||||
|
||||
# As clang's ASan DSO is usually in a non-standard path, let's check if
|
||||
# the environment is set accordingly. If not, warn the user and exit.
|
||||
# We're not setting the LD_LIBRARY_PATH automagically here, because
|
||||
# user should encounter (and fix) the same issue when running the unit
|
||||
# tests (meson test)
|
||||
if ldd "$BUILD_DIR/systemd" | grep -q "libclang_rt.asan.*not found"; then
|
||||
_asan_rt_name="$(ldd $BUILD_DIR/systemd | awk '/libclang_rt.asan/ {print $1; exit}')"
|
||||
_asan_rt_path="$(find /usr/lib* /usr/local/lib* -type f -name "$_asan_rt_name" 2>/dev/null | sed 1q)"
|
||||
echo >&2 "clang's ASan DSO ($_asan_rt_name) is not present in the runtime library path"
|
||||
echo >&2 "Consider setting LD_LIBRARY_PATH=${_asan_rt_path%/*}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo >&2 "systemd is not linked against the ASan DSO"
|
||||
echo >&2 "gcc does this by default, for clang compile with -shared-libasan"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
function find_qemu_bin() {
|
||||
@@ -268,7 +292,7 @@ setup_basic_environment() {
|
||||
install_depmod_files
|
||||
generate_module_dependencies
|
||||
if [[ "$IS_BUILT_WITH_ASAN" = "yes" ]]; then
|
||||
create_asan_wrapper
|
||||
create_asan_wrapper
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -348,7 +372,24 @@ EOF
|
||||
|
||||
create_asan_wrapper() {
|
||||
local _asan_wrapper=$initdir/$ROOTLIBDIR/systemd-under-asan
|
||||
local _asan_rt_pattern
|
||||
ddebug "Create $_asan_wrapper"
|
||||
|
||||
case "$ASAN_COMPILER" in
|
||||
gcc)
|
||||
_asan_rt_pattern="*libasan*"
|
||||
;;
|
||||
clang)
|
||||
_asan_rt_pattern="libclang_rt.asan-*"
|
||||
# Install llvm-symbolizer to generate useful reports
|
||||
# See: https://clang.llvm.org/docs/AddressSanitizer.html#symbolizing-the-reports
|
||||
dracut_install "llvm-symbolizer"
|
||||
;;
|
||||
*)
|
||||
dfail "Unsupported compiler: $ASAN_COMPILER"
|
||||
exit 1
|
||||
esac
|
||||
|
||||
cat >$_asan_wrapper <<EOF
|
||||
#!/bin/bash
|
||||
|
||||
@@ -358,18 +399,28 @@ DEFAULT_ASAN_OPTIONS=strict_string_checks=1:detect_stack_use_after_return=1:chec
|
||||
DEFAULT_UBSAN_OPTIONS=print_stacktrace=1:print_summary=1:halt_on_error=1
|
||||
DEFAULT_ENVIRONMENT="ASAN_OPTIONS=\$DEFAULT_ASAN_OPTIONS UBSAN_OPTIONS=\$DEFAULT_UBSAN_OPTIONS"
|
||||
|
||||
# As right now bash is the PID 1, we can't expect PATH to have a sane value.
|
||||
# Let's make one to prevent unexpected "<bin> not found" issues in the future
|
||||
export PATH="/sbin:/bin:/usr/sbin:/usr/bin"
|
||||
|
||||
mount -t proc proc /proc
|
||||
mount -t sysfs sysfs /sys
|
||||
mount -o remount,rw /
|
||||
|
||||
PATH_TO_ASAN=\$(find / -name '*libasan*' | sed 1q)
|
||||
PATH_TO_ASAN=\$(find / -name '$_asan_rt_pattern' | sed 1q)
|
||||
if [[ "\$PATH_TO_ASAN" ]]; then
|
||||
# A lot of services (most notably dbus) won't start without preloading libasan
|
||||
# See https://github.com/systemd/systemd/issues/5004
|
||||
DEFAULT_ENVIRONMENT="\$DEFAULT_ENVIRONMENT LD_PRELOAD=\$PATH_TO_ASAN"
|
||||
# Let's add the ASan DSO's path to the dynamic linker's cache. This is pretty
|
||||
# unnecessary for gcc & libasan, however, for clang this is crucial, as its
|
||||
# runtime ASan DSO is in a non-standard (library) path.
|
||||
echo \${PATH_TO_ASAN%/*} > /etc/ld.so.conf.d/asan-path-override.conf
|
||||
ldconfig
|
||||
fi
|
||||
echo DefaultEnvironment=\$DEFAULT_ENVIRONMENT >>/etc/systemd/system.conf
|
||||
echo DefaultTimeoutStartSec=180s >>/etc/systemd/system.conf
|
||||
echo DefaultStandardOutput=journal+console >>/etc/systemd/system.conf
|
||||
|
||||
# ASAN and syscall filters aren't compatible with each other.
|
||||
find / -name '*.service' -type f | xargs sed -i 's/^\\(MemoryDeny\\|SystemCall\\)/#\\1/'
|
||||
@@ -475,14 +526,14 @@ get_ldpath() {
|
||||
install_missing_libraries() {
|
||||
# install possible missing libraries
|
||||
for i in $initdir{,/usr}/{sbin,bin}/* $initdir{,/usr}/lib/systemd/{,tests/{,manual/,unsafe/}}*; do
|
||||
LD_LIBRARY_PATH=$(get_ldpath $i) inst_libs $i
|
||||
LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$(get_ldpath $i)" inst_libs $i
|
||||
done
|
||||
}
|
||||
|
||||
create_empty_image() {
|
||||
local _size=500
|
||||
if [[ "$STRIP_BINARIES" = "no" ]]; then
|
||||
_size=$((2*_size))
|
||||
_size=$((4*_size))
|
||||
fi
|
||||
rm -f "$TESTDIR/rootdisk.img"
|
||||
# Create the blank file to use as a root filesystem
|
||||
|
||||
Reference in New Issue
Block a user