C++ UBSAN produces false positives with derived objects

只愿长相守 提交于 2020-01-24 15:14:47

问题


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'


回答1:


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.



来源:https://stackoverflow.com/questions/57294792/c-ubsan-produces-false-positives-with-derived-objects

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!