Code Sanitizers¶
Building Packages with Code Sanitizers¶
Many times CVEs and other security-related bugs that are reported make use of Code Sanitizers in order to identify the defective behavior. A few examples of such tools are AddressSanitizer (ASan), UndefinedBehaviorSanitizer (UBSan), ThreadSanitizer (TSan) and LeakSanitizer (LSan).
Frequently, the reporter of a bug or issue will provide a proof of concept or working example that relies on the program or library having been built with code sanitizers support.
This article describes what is necessary for building a package with code sanitizers support.
Passing Appropriate Flags¶
Generally, what is necessary to enable code sanitizers is to pass the appropriate compiler and linker flags to the build. Here AddressSanitizer will be used as example, but the ideas can be generalized for other types of sanitizers.
Many Debian packages use dpkg-buildflags
and so the build can be
adjusted without modifying debian/rules
by setting environment
variables like this:
export DEB_CFLAGS_APPEND=-fsanitize=address
export DEB_CPPFLAGS_APPEND=-fsanitize=address
export DEB_CXXFLAGS_APPEND=-fsanitize=address
export DEB_LDFLAGS_APPEND='-fsanitize=address -static-libasan'
The point of the -static-libasan
flag is to have GCC statically link
ASAN without also statically linking everything else. Adding that flag
makes installation of the resulting packages easier as they will not
depend on the libasan
shared library. That seems to cause issues with
C++ programs though (“Your application is linked against incompatible
ASan runtimes.”).
There are instances, depending on the build system, particular
compiler, compiler version, and perhaps other factors, where the
-static-libasan
flag might lead to linking failures. The failures in
that case are of the form undefined reference to __asan_[...]
. If
that happens, one possible (hacky?) way to address the failure is by
modifying the LDFLAGS
:
export DEB_LDFLAGS_APPEND='-fsanitize=address -lasan -ldl'
ASan may also disturb the build checks, typically from
./configure
, due to LeakSanitizer (which is enabled with
ASan). You can temporarily disable it with:
ASAN_OPTIONS=detect_leaks=0 dpkg-buildpackage ...
debuild -e ASAN_OPTIONS=detect_leaks=0 ...
# For more options:
ASAN_OPTIONS=help=1 ./asan-executable
There are other flags, like -fsanitize=thread
, -fsanitize=leak
,
-fsanitize=undefined
, along with their corresponding -static-*
flags
which may be useful depending on the nature of the bug report or
vulnerability, for instance with ASAN+UBSAN:
export DEB_CFLAGS_APPEND='-fsanitize=address,undefined'
export DEB_CPPFLAGS_APPEND='-fsanitize=address,undefined'
export DEB_CXXFLAGS_APPEND='-fsanitize=address,undefined'
export DEB_LDFLAGS_APPEND='-fsanitize=address,undefined -static-libasan -static-libubsan'
Note
GCC does not support the unsigned-integer-overflow check, so one needs to
use clang/clang++ to check for that and pass
-fsanitize=unsigned-integer-overflow
in addition to -fsanitize=undefined
.
Note
libtool may filter the LDFLAGS
; you may need a patch
Attention
Beware that FORTIFY_SOURCE
is not officially supported, see FAQ
This approach works for cowbuilder, pbuilder, sbuild, and other build chroot-type environments (including those invoked by git-buildpackage, for example) which will pass the environment variables through.
If the package being built does not properly support dpkg-buildflags or
if a build method is being used which does not properly handle the
environment, then it may be necessary to temporarily modify debian/rules
in order to insert the necessary flags at the appropriate locations.
Depending on the package being updated and whether the source is
available in Git or some other VCS, it might make sense to create a
branch for the temporary modifications to the build.
It is also a good idea to leave the changelog in a state that will
prevent accidental upload of the package built with ASAN. This can be
accomplished by starting a new changelog entry and leaving the suite
set to UNRELEASED
.
Alternatively, valgrind may be used to assess the presence of an invalid memory access before/after patching.
Further Readings¶
- Some links which might provide additional information: