Reputation: 5807
I tried to use UBSAN in a project and run into an issue which seems impossible to fix: The project uses a plugin system implemented via shared libraries. That is each plugin provides a factory method which returns an instance of some abstract class with a plugin-specific derived class. The project then iterates over all shared libraries in a folder, opens them with dlopen
, gets the factory method via dlsym
and creates plugin instance which is then used.
However at usage of any interface method UBSAN throws member call on address 0x... which does not point to an object of type '...'
MWE:
struct Foo{
virtual int g() = 0;
};
extern "C" Foo* create();
#include "foo.h"
struct Bar: Foo{
int g(){ return 42; }
};
Foo* create(){
return new Bar();
}
#include "foo.h"
#include <dlfcn.h>
#include <cassert>
int main(){
void* h = dlopen("libfoo.so", RTLD_GLOBAL | RTLD_NOW);
assert(h);
void* c = dlsym(h, "create");
assert(c);
using create_t = Foo*();
Foo* f = reinterpret_cast<create_t*>(c)();
return f->g() != 42;
}
Compile with:
g++ -shared -fPIC -o libfoo.so foo.cpp
g++ -fsanitize=vptr main.cpp -ldl
./a.out
https://whatofhow.wordpress.com/2015/03/17/odr-rtti-dso explains, that this is due to the RTTI info in the shared library and the binary being different.
A very similar issue happens, when you export a function in the shared library, import it with dlsym
and try to call it. The result will be call to function <...> through pointer to incorrect function type '<...>'
with -fsanitize=function
for clang.
Is there any way to solve this? I'm not using Clang or playing with -fvisibility
so no idea what to do here.
Upvotes: 1
Views: 896
Reputation: 5807
Using clang with -fsanitize=function
does already report a violation for the create
call:
call to function create through pointer to incorrect function type 'Foo ()()'
This again looks like a false positive and occurs only if the shared library AND the executable are compiled with '-fsanitize=...`.
Comparing the typeinfo of the differently compile shared library with nm -C libfoo.so | grep typeinfo
shows that the one with the sanitizer has an additional typeinfo for typeinfo for Foo* ()
.
Why typeinfo? Well UBSAN compares the typeinfo to decide whether the passed pointer (function or class) is the expected one. typeinfo
comparison is done by pointer comparison. This is great for speed but introduces a subtle issue: Even when the actual types are literally the same, they might not share the same typeinfo
.
This is the case here: The typeinfo in the library and executable are not merged, so there are 2 instances of the same typeinfo.
The solution is to pass -rdynamic
when creating the executable. From the GCC manual
Pass the flag -export-dynamic to the ELF linker, on targets that support it. This instructs the linker to add all symbols, not only used ones, to the dynamic symbol table. This option is needed for some uses of dlopen or to allow obtaining backtraces from within a program.
It seems that here we have such a "use of dlopen".
For CMake use the property ENABLE_EXPORTS
on the executable target.
For some more fun with UBSAN problems see the related question Call to function (unknown) through pointer to incorrect function type
Upvotes: 1