

                             Libc Call Interception


While we can, in principle, compile all code with instrumentation, it is unclear
how difficult this is for the C standard library. The LLVM sanitizers don't try,
even if MSan otherwise requires all code to be instrumented, and we take this as
a sign that there may be unforeseen challenges. For now, we take the same route
as the sanitizers and intercept calls to libc functions, wrapping them with
symbolic handling. For example, the wrapper for "memset" obtains the symbolic
expression for the value to be written in memory and pushes it to the shadow
region of the destination memory. In the future, we may experiment with
compiling (parts of) the libc to avoid the effort of manually defining wrappers.

Initially, we tried the interception mechanism that the LLVM sanitizers use,
implemented in the compiler-rt library. The Linux version basically just defines
a function with the name of the libc function. The dynamic loader resolves
symbols to the first function with the right name that it finds; given an
appropriate link order, the wrapper (or "interceptor" in compiler-rt parlance)
will be called instead of the libc function. Calling the real function is just a
matter of asking the loader for alternative resolutions (i.e., calling "dlsym"
with flag "RTLD_NEXT"). The problem for us is that this approach *globally*
replaces a given libc function, in the executable and in all libraries that it
loads. However, our run-time support library is loaded into the same process and
makes heavy use of libc, so we need the ability to use wrappers in one part of
the program and concrete functions in another. This turned out to complicate the
compiler-rt-based implementation so much that we eventually abandoned the
approach.

Function renaming provided a convenient alternative: we control all code that is
supposed to call wrappers rather than the libc functions properly, so we just rename
the targets of their calls. For example, a call to "memset" in the program under
test is turned into a call to "memset_symbolized", which we can easily define as
a regular function wrapping "memset". Calls from our run-time library, on the
other hand, use the regular function names and thus end up in libc as usual.
