Bug 67515 - crash from ubsan for non-virtual call in initializer list with an invalid vtable
Summary: crash from ubsan for non-virtual call in initializer list with an invalid vtable
Status: REOPENED
Alias: None
Product: gcc
Classification: Unclassified
Component: sanitizer (show other bugs)
Version: 6.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2015-09-09 11:49 UTC by Roger Orr
Modified: 2016-01-18 13:52 UTC (History)
6 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2015-09-09 00:00:00


Attachments
Sample program showing the false positive and the seg fault (337 bytes, text/plain)
2015-09-09 11:49 UTC, Roger Orr
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Roger Orr 2015-09-09 11:49:00 UTC
Created attachment 36312 [details]
Sample program showing the false positive and the seg fault

ubsan produces a warning at runtime from the attached code and then crashes.

The crash is provoked by performing a placement new with a pre-populated buffer, but can occur 'in the wild' depending on what the memory contents are at runtime.

Fails with trunk (as at 2015-09-08 using http://melpon.org/wandbox/) and gcc 5.2.0

(This may possibly be related to pr67258)
Comment 1 Jonathan Wakely 2015-09-09 12:37:12 UTC
12.6.2 [class.base.init] p16 says this is undefined (and has a very similar example).
Comment 2 Jonathan Wakely 2015-09-09 12:40:58 UTC
A message about a vptr is a bit mis-leading for non-virtual call, so maybe that could be improved, but in essence 'this' is not well-defined at that point.
Comment 3 Markus Trippelsdorf 2015-09-09 12:50:55 UTC
markus@x4 tmp % cat ub.ii
extern "C" void memset(void *, int, int);
struct A {
  A(int) {}
};
struct test : A {
  test() : A(m_fn1()) {}
  int m_fn1() { return 0; }
  virtual ~test() {}
};

int a[8];
void *operator new(unsigned long, void *p2) { return p2; }

int main() {
  test b;
  memset(a, '\x7f', sizeof 0);
  new (a) test;
}
markus@x4 tmp % clang++ -fsanitize=undefined -O3 ub.ii -Wall -Wextra
markus@x4 tmp % ./a.out
markus@x4 tmp % g++ -fsanitize=undefined -O3 ub.ii -Wall -Wextra
markus@x4 tmp % ./a.out
ub.ii:6:19: runtime error: member call on address 0x7ffddf2a17b0 which does not point to an object of type 'test'
0x7ffddf2a17b0: note: object has invalid vptr
 00 00 00 00  a0 18 2a df fd 7f 00 00  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  f0 04 42 02
              ^~~~~~~~~~~~~~~~~~~~~~~
              invalid vptr
[1]    31199 segmentation fault  ./a.out


(In reply to Jonathan Wakely from comment #2)
> A message about a vptr is a bit mis-leading for non-virtual call, so maybe
> that could be improved, but in essence 'this' is not well-defined at that
> point.

But it shouldn't segfault in __ubsan::checkDynamicType().
Comment 4 Roger Orr 2015-09-09 13:08:56 UTC
Ah - apologies -- I'd got the example by stripping down a call in boost::format and didn't do a full enough check that the code was well formed: I'll report that UB to boost.

However as Markus says the seg fault remains troublesome.
Comment 5 Markus Trippelsdorf 2015-09-09 13:10:33 UTC
(anonymous namespace)::getVtablePrefix (Object=0x401460 <a>) at ../../../../gcc/libsanitizer/ubsan/ubsan_type_hash.cc:200
200       if (Prefix->Offset > 0 || !Prefix->TypeInfo)
(gdb) bt
#0  (anonymous namespace)::getVtablePrefix (Object=0x401460 <a>) at ../../../../gcc/libsanitizer/ubsan/ubsan_type_hash.cc:200
#1  __ubsan::checkDynamicType (Object=Object@entry=0x401460 <a>, Type=0x400d78 <typeinfo for test>, Hash=17814158270761423139)
    at ../../../../gcc/libsanitizer/ubsan/ubsan_type_hash.cc:219
#2  0x00007ffff72d8203 in HandleDynamicTypeCacheMiss (Data=0x401320, Pointer=4199520, Hash=<optimized out>, Opts=...)
    at ../../../../gcc/libsanitizer/ubsan/ubsan_handlers_cxx.cc:31
#3  0x00007ffff72d8963 in __ubsan::__ubsan_handle_dynamic_type_cache_miss (Data=<optimized out>, Pointer=<optimized out>, Hash=<optimized out>)
    at ../../../../gcc/libsanitizer/ubsan/ubsan_handlers_cxx.cc:74
#4  0x0000000000400a95 in main ()
Comment 6 Jakub Jelinek 2015-09-09 13:21:06 UTC
The problem is that to avoid the segfault, you'd need to significantly slow down the library code (pretty much, instead of
  if (Prefix->Offset > 0 || !Prefix->TypeInfo)
    // This can't possibly be a valid vtable.
    return 0;
you'd need something like write (dev_null_fd, VtablePrefix, sizeof (*VtablePrefix)); first and check if it didn't return -1 / EFAULT (because the library hardly can install segfault handlers).
The library assumes that the virtual table pointers contain either valid, or previously valid vptrs (or NULL).
So, to get rid of some of the segfaults, but not all, it could e.g. write NULL to the virtual table pointer at the start of the constructor, before starting to construct the base classes, or something similar (if -fsanitize=vptr only, of course).
Comment 7 Andrew Pinski 2015-09-09 13:26:42 UTC
Correct the summary.  To some extent this is inside libubsan so it should be reported upstream too.
Comment 8 Jakub Jelinek 2015-09-09 13:32:52 UTC
You can get the same segfault with clang++ e.g. on
struct A
{
  int a;
  A () {}
  int foo () { return 1; }
  virtual ~A () {}
};
alignas (A) char buf[sizeof (A)];

void foo (void *x)
{
  A *y = (A *) x;
  y->foo ();
}

int main ()
{
  __builtin_memset (buf, '\x7f', sizeof 0);
  foo (&buf);
}
(but as in this case it is really called on object not even started to be constructed, there is no other workaround than to slow down the library).
Comment 9 Yury V. Zaytsev 2016-01-15 14:58:56 UTC
Hi Roger, I'm bitten by the same problem. Did you report the issue against boost::format? Is there a workaround that I could make use of? Thanks!
Comment 10 Roger Orr 2016-01-15 21:11:26 UTC
Hello Yury, yes the problem with boost was reported as 
https://svn.boost.org/trac/boost/ticket/11632
and the ticket contains a proposed solution.
Comment 11 Yury V. Zaytsev 2016-01-18 13:52:02 UTC
Hi Roger,

Thank you for the hint! I've tried the solution from the linked ticket, but I'm still getting the same problem, albeit at a different place in the code (not sure why?!). In addition I'm still struggling with undefined behaviors in other parts of boost, like this one: https://svn.boost.org/trac/boost/ticket/11204 ... for now I'll try to remove the use of boost::lexical_cast where possible :-/

Thanks!