Userspace code coverage - ligurio/openbsd-tests GitHub Wiki

Preparation

Install required packages:

  • Regression suite requirements: pkg_add net-snmp scapy py-libdnet p5-Net-Flow p5-BSD-Socket-Splice p5-IO-Socket-INET6 p5-Socket6 p5-File-Slurp p5-BSD-Resource p5-IO-Socket-SSL p5-ldap openldap-client-- p5-AnyEvent p5-Hash-Merge p5-YAML-1.19 p5-NetPacket p5-Net-Pcap p5-Crypt-Random

  • cmake (to build compiler-rt)

  • llvm (to build compiler-rt)

  • python-2.7

  • git

  • py-pip (gcovr)

  • pip install gcovr

  • the_silver_searcher (for convenience)

  • vim--no_x11 (for convenience)

$ doas pkg_add cmake llvm python-2.7 git py-pip the_silver_searcher vim--no_x11 mc
$ cd /usr/src
$ ftp https://mirror.yandex.ru/pub/OpenBSD/6.2/src.tar.gz
$ ftp https://mirror.yandex.ru/pub/OpenBSD/6.2/sys.tar.gz
$ tar xvzf src.tar.gz
$ tar xvzf sys.tar.gz
or
CVS (https://www.openbsd.org/anoncvs.html)
or
$ git clone https://github.com/openbsd/src
$ ftp -o - https://ftp.eu.openbsd.org/pub/OpenBSD/snapshots/timestamp
$ cd src; git reset --hard $(git rev-list -1 $(git rev-parse --until=TIMESTAMP) master)

/etc/mk.conf

COMPILER_VERSION?=clang

SKIPDIR+=       lib/csu
SKIPDIR+=       libexec/ld.so
SKIPDIR+=       games                   # not interested
SKIPDIR+=       gnu                     # not interested
SKIPDIR+=       sys
SKIPDIR+=       regress/sys             # save time on running tests
SKIPDIR+=       regress/gnu             # save time on running tests
SKIPDIR+=       usr.sbin/bind           # configure: error: C compiler cannot create executables
SKIPDIR+=       usr.sbin/nsd            # configure: error: C compiler cannot create executables
SKIPDIR+=       usr.sbin/unbound        # configure: error: C compiler cannot create executables

.if defined(COVERAGE)

CFLAGS+=        --coverage
CLEANFILES+=    *.gcov *.gcda *.gcno coverage.info

.if ${COMPILER_VERSION} == "clang"
LDADD+=         -lclang_rt.profile
LDFLAGS+=       -lclang_rt.profile
LIBS+=          -lclang_rt.profile
.elif ${COMPILER_VERSION} == "gcc4" || ${COMPILER_VERSION} == "gcc3"
LDADD+=         -lgcov
LDFLAGS+=       -lgcov
LIBS+=          -lgcov
.endif

.endif # COVERAGE

Disable pledge(2)

Since version 5.9 OpenBSD includes pledge() system call. It is a useful for mitigation security attacks but unfortunately it prevents to collecting of code coverage data. You will get SIGABRT in a target testing binary on attempt to run tests with it:

Testing suffix "/usr/bin/gcc" "xx"
===> bc
rm -f *.log t19
t1
Abort trap (core dumped)
*** Error 134 in target 'regress' (ignored)
Abort trap (core dumped)
*** Error 134 in bc (<bsd.regress.mk>:106 'regress': @echo usr.bin/bc/t1 | tee -a /dev/null /dev/null 2>&1 > /dev/null)
*** Error 1 in /usr/src/regress/usr.bin (<bsd.subdir.mk>:48 'all')
# dmesg | tail -2
tee(5031): syscall 5 "rpath"
tee(79812): syscall 5 "rpath"
Abort trap (core dumped)
#

To avoid such issues we need to add additional promises to each program where it absent using script addpromise.py. From my experience only four promises are required: wpath, rpath, cpath and flock.

$ find /usr/src \( -path /usr/src/gnu -o -path /usr/src/sys -o -path /usr/src/games -o -path /usr/src/regress \) -prune -o -name '*.[c,y]' -type f -exec addpromise.py {} \;

For example cat(1) source file will look after this like:

diff --git a/bin/cat/cat.c b/bin/cat/cat.c
index ed28a7f44bf..9a1d17c4a2f 100644
--- a/bin/cat/cat.c
+++ b/bin/cat/cat.c
@@ -63,7 +63,7 @@ main(int argc, char *argv[])
 {
        int ch;

-       if (pledge("stdio rpath", NULL) == -1)
+       if (pledge("stdio rpath cpath wpath", NULL) == -1)
                err(1, "pledge");

        while ((ch = getopt(argc, argv, "benstuv")) != -1)
  • Now we are ready to build a system from sources. All required steps are described in the release(8). But there is one note: don't do 'make obj' because we should place obj files to the same directory where sources are place.

Disable chroot(2)

There is only one way to disable chroot - comment out appropriate lines in a source code.

Disable privilege separation in daemons

There two ways to disable privilege separation:

  • comment out appropriate lines in a source code
setgroups(1, &pw->pw_gid) == -1)
        err(1, "setgroups() failed");
if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1)
        err(1, "setresgid() failed");
if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
        err(1, "setresuid() failed");

I did it for each component in userspace except: unbound, nsd, inetd, lpd, chroot and authpf.

  • give more rights to user account using as separation account

Compiler-specific tweaks in a system

Clang
  • unfortunately libcompiler_rt is in OpenBSD source tree, but without required functions (libprofile)
  • git clone https://github.com/llvm-mirror/compiler-rt
  • echo "set(COMPILER_RT_HAS_PROFILE TRUE)" >> cmake/config-ix.cmake
  • mkdir build; cd build; cmake ../; make install
  • ln -s /usr/local/lib/openbsd/libclang_rt.profile-x86_64.a /usr/lib/libclang_rt.profile.a
GCC
  • create symlink for gcov library: ln -s /usr/lib/gcc-lib/amd64-unknown-openbsd6.2/4.2.1/libgcov.a /usr/lib/libgcov.a
  • use COMPILER_VERSION=gcc4 instead of clang

Code coverage measurement

# cd /usr/src
# COVERAGE=1 COMPILER_VERSION="clang" make clean
# COVERAGE=1 COMPILER_VERSION="clang" make 2>&1 | tee build.log
# COVERAGE=1 COMPILER_VERSION="clang" make install
# ??? COVERAGE=1 COMPILER_VERSION="clang" make REGRESS_LOG=/var/log/regress-tests regress
# COVERAGE=1 COMPILER_VERSION="clang" make regression-tests 2>&1 | tee regress.log  # don't forget to install regress dependencies (https://github.com/ligurio/openbsd-tests/wiki/How-to-run-tests#how-to-run-regress-tests)
# ln lib/libcurses/tinfo/doalloc.c lib/libcurses/doalloc.c
# gcovr --html --html-details --output=coverage.html --verbose --exclude-directories="regress" --print-summary --root `pwd` .
# gcovr --xml --xml-pretty --verbose --exclude-directories="regress" --print-summary --root `pwd` .
$ cd /usr/src
$ COVERAGE=1 COMPILER_VERSION="clang" make 2>&1 | tee build.log
$ COVERAGE=1 COMPILER_VERSION="clang" make install
$ COVERAGE=1 COMPILER_VERSION="clang" make -C regress/sbin/iked/parser/ 2>&1 | tee regress.log
$ COVERAGE=1 COMPILER_VERSION="clang" make -C regress/usr.bin/mandoc/db/dbm_dump 2>&1 | tee regress.log
$ make -C regress 2>&1 | tee regress.log

$ COVERAGE=1 COMPILER_VERSION="clang" make -C libexec 2>&1 | tee -a build.log
$ COVERAGE=1 COMPILER_VERSION="clang" make -C libexec install
$ make -C regress/libexec 2>&1 | tee -a regress.log

$ COVERAGE=1 COMPILER_VERSION="clang" make -C lib 2>&1 | tee -a build.log
$ COVERAGE=1 COMPILER_VERSION="clang" make -C lib install
$ COVERAGE=1 COMPILER_VERSION="clang" make -C regress/lib 2>&1 | tee -a regress.log

$ ln lib/libcurses/tinfo/doalloc.c lib/libcurses/doalloc.c
$ gcovr --html --html-details --output=coverage.html --exclude-directories="regress" --print-summary --root `pwd` .
$ gcovr --xml --xml-pretty --exclude-directories="regress" --print-summary --root `pwd` .