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: