C++ runtime error (using -fsanitize=undefined): object has a possibly invalid vptr: abs(offset to top) too big

Viewed 505

My code:

const int N = 1048569;
struct A { };
struct B : virtual public A {
    char space[N];
};
struct C : virtual public A {
    void foo() {};
};
struct D : public B, public C { };

D T;
int main() {
    T.foo();
    return 0;
}

When I compile my code with -fsanitize=undefined, I got the runtime error below:

a.cpp:13:10: runtime error: member call on address 0x560aead1e6a8 which does not point to an object of type 'C'
0x560aead1e6a8: note: object has a possibly invalid vptr: abs(offset to top) too big
 00 00 00 00  b0 dc c1 ea 0a 56 00 00  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00
              ^~~~~~~~~~~~~~~~~~~~~~~
              possibly invalid vptr

The program run successfully if I don't use -fsanitize=undefined option.

What's more, if I set the value of N to 1048568 (just decrease by 1), the program runs successfully in both case.

I wonder if it's my fault or it's just because fsanitize cannot deal with such big size

1 Answers

It's an arbitrary size limit set by Clang's UndefinedBehaviour Sanitizer:

https://github.com/llvm/llvm-project/blob/5745eccef54ddd3caca278d1d292a88b2281528b/compiler-rt/lib/ubsan/ubsan_type_hash.h#L55-L57

/// A sanity check for Vtable. Offsets to top must be reasonably small
/// numbers (by absolute value). It's a weak check for Vtable corruption.
const int VptrMaxOffsetToTop = 1<<20;

"offset-to-top" in the Itanium ABI is a ptrdiff_t value in the vtable for every object (and base class subobject) that, when added to a pointer, gives a pointer to the most derived object (The type you new'd or declared as a variable). This is what's used by dynamic_cast<void*>(pointer_to_base). UBSan expects this to be relatively small since your classes wouldn't be that large and offset anyway. This means the only occasion it would be so large is if you accidentally overrun a buffer and wrote to the vtable directly.

The way to "fix" this if you really needed to do this is to put the large class as your second base class, so it's offset would be relatively small.

Related