Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ UBSAN produces false positives with derived objects

I wanted to use UBSAN (undefined behavior sanitizer) but found it completely worthless as it reports to many false positives.

E.g. a simple std::make_shared<int>(42); is enough to trigger warnings like

member access within address 0x00000236de70 which does not point to an object of type '_Sp_counted_base'

Reducing this example to a MWE shows that the problem is more general with base classes and inheritance:

Example:

struct Foo{
    int f(){ return g(); }
    virtual int g() = 0;
};

struct Bar: Foo{
    int g(){ return 42; }
};

int main(){
    auto f = new Bar();
    return f->g();
}

Compile with -fsanitize=undefined and watch

example.cpp:15:16: runtime error: member call on address 0x000000726e70 which does not point to an object of type 'Bar'

0x000000726e70: note: object has invalid vptr

See https://godbolt.org/z/0UiVtu.

How are not even these simple cases properly handled? Did I miss anything? How should I properly use UBSAN to check my code? (This requires [almost] no false positives)

Edit: As it seems the MWE only works on godbolt, the original code looks like this:

#include <boost/iostreams/device/mapped_file.hpp>
#include <boost/iostreams/stream.hpp>
using MMStream = boost::iostreams::stream<boost::iostreams::mapped_file_source>;

int main(){
  MMStream stream;
  stream.open("a.out");
  return !stream;
}

Compile with clang++-8 -fsanitize=undefined -fvisibility=hidden -I /opt/boost_1_64_0/include/ test.cpp /opt/boost_1_64_0/lib/libboost_iostreams.so and run which results in errors like

runtime error: member call on address 0x00000126ef30 which does not point to an object of type 'boost::detail::sp_counted_base'

like image 558
Flamefire Avatar asked Jul 31 '19 15:07

Flamefire


People also ask

How does UBSan work?

UBSAN is a runtime undefined behaviour checker. UBSAN uses compile-time instrumentation to catch undefined behavior (UB). Compiler inserts code that perform certain kinds of checks before operations that may cause UB. If check fails (i.e. UB detected) __ubsan_handle_* function called to print error message.

What is UBSan?

UndefinedBehaviorSanitizer (UBSan) is a fast undefined behavior detector. UBSan modifies the program at compile-time to catch various kinds of undefined behavior during program execution, for example: Array subscript out of bounds, where the bounds can be statically determined.


1 Answers

Trying to answer this myself after the comments and creating another MWE.

TLDR: Make sure all classes containing virtual functions are exported when compiling with -fvisibility=hidden

Consider a shared library Foo with

foo.h

#define EXPORT __attribute__((visibility("default")))

struct Foo{
    virtual int g() = 0;
};

struct Bar: Foo{
    int g(){ return 42; }
};

EXPORT Foo* create();

foo.cpp #include "foo.h"

Foo* create(){
  return new Bar();
}

Compiled with clang++-8 foo.cpp -shared -fPIC -o foo.so

And an executable linked against this using the virtual functions but with -fvisibility:

main.cpp:

#include "foo.h"

int main(){
  Foo* f  = create();
  return f->g() != 42;
}

Compiled with clang++-8 -fsanitize=undefined -fvisibility=hidden main.cpp foo.so

This will report

runtime error: member call on address 0x00000290cea0 which does not point to an object of type 'Foo'

This is the same error as described in https://bugs.llvm.org/show_bug.cgi?id=39191 (thanks @Nikita Petrenko)

Summary: With fvisibility=hidden not exported symbols (functions, classes not decorated with the attribute __attribute__((visibility("default"))) are not considered the same when used in different DSOs (e.g. executable and shared library). Hence the base class Foo in the shared library and the executable are distinct (they have different vtables) which UBSAN detects: The executable "expects" an object witg vtable of Exe::Foo but instead gets Library::Foo

In the boost case the class sp_counted_base is the culprit as it is not exported until Boost 1.69 which adds BOOST_SYMBOL_EXPORT, so switching to Boost 1.69+ fixes the issue.

like image 184
Flamefire Avatar answered Oct 07 '22 22:10

Flamefire