Bug 66514 - UBSAN: Add -fsanitize=lifetime
Summary: UBSAN: Add -fsanitize=lifetime
Status: UNCONFIRMED
Alias: None
Product: gcc
Classification: Unclassified
Component: sanitizer (show other bugs)
Version: 5.0
: P3 enhancement
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2015-06-11 15:59 UTC by Martin Liška
Modified: 2023-01-06 12:22 UTC (History)
8 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Martin Liška 2015-06-11 15:59:43 UTC
As discussed in PR66487, C++ started to emit a clobber class contruction and destruction. It would be really nice to have such support in UBSAN, where we can catch all places which for instance access a member of a class after dtor is called.

Ideas how to implement that?

Thanks,
Martin
Comment 1 Jakub Jelinek 2015-06-12 09:33:15 UTC
There is some minimal support in -fsanitize=vptr, but that catches only destructed objects with virtual methods (by disabling the clobbers and clearing the vptr).
Other than that, this is something that is more in line with the address sanitizer (which also has very limited support for file scope objects, but only makes the objects unavailable during construction of each TU, so catches constructor ordering issues within a single TU).  Other than that, the concept of making a chunk of memory available at certain point and unavailable at another point is something -fsanitize=address is able to do.  The question is what can be done with operator new, e.g. if you have a char buffer in some class and construct something else at that spot, then destructing it; reading those bytes afterwards is supposedly UB, but storing there something say with memcpy shouldn't be invalid.
Comment 3 Martin Liška 2015-06-15 11:29:41 UTC
(In reply to Jakub Jelinek from comment #1)
> There is some minimal support in -fsanitize=vptr, but that catches only
> destructed objects with virtual methods (by disabling the clobbers and
> clearing the vptr).

I see.

> Other than that, this is something that is more in line with the address
> sanitizer (which also has very limited support for file scope objects, but
> only makes the objects unavailable during construction of each TU, so
> catches constructor ordering issues within a single TU).  Other than that,
> the concept of making a chunk of memory available at certain point and
> unavailable at another point is something -fsanitize=address is able to do. 
> The question is what can be done with operator new, e.g. if you have a char
> buffer in some class and construct something else at that spot, then
> destructing it; reading those bytes afterwards is supposedly UB, but storing
> there something say with memcpy shouldn't be invalid.

Ok, after reading your caution and test-cases mentioned in the ASAN tracker, I think emitting a poison memory call in a dtor for instances that does not use placement new can be beneficial. However, I can't evaluate if getting such kind of information in doable in GCC?
Comment 4 Jason Merrill 2015-06-15 18:08:12 UTC
(In reply to Martin Liška from comment #3)
> Ok, after reading your caution and test-cases mentioned in the ASAN tracker,
> I think emitting a poison memory call in a dtor for instances that does not
> use placement new can be beneficial. However, I can't evaluate if getting
> such kind of information in doable in GCC?

We could certainly emit the poison call where we currently have the clobber.
Comment 5 Jakub Jelinek 2015-06-16 08:32:29 UTC
The thing is that if you poison at the end of destructor, you need to unpoison it again somewhere, except for file scope variables that when they are destructed supposedly can't be constructed again.
For automatic variables I guess it depends on whether at runtime use-after-return is enabled or not (if it is enabled, then the variables are allocated in a heap object that is completely poisoned afterwards anyway, so that would work too.  But if use-after-return is disabled, they are allocated in the normal stack frame and we'd need to unpoison those objects (together with unpoisoning the guards around them).  And of course we'd need to ensure the stack space is not reused for other variables.
Then there are objects constructed/destructed in heap space, those are supposedly fine too, at least I hope a free poisons the memory.  But what about
objects destructed in e.g. mmap allocated area?  And finally objects placement new constructed in some other variable, there we'd need to unpoison on the first store to that area (or placement new construction).  That is very much non-trivial though, at least in the asan framework.
Comment 6 Martin Liška 2015-06-16 09:14:59 UTC
(In reply to Jakub Jelinek from comment #5)
> The thing is that if you poison at the end of destructor, you need to
> unpoison it again somewhere, except for file scope variables that when they
> are destructed supposedly can't be constructed again.
> For automatic variables I guess it depends on whether at runtime
> use-after-return is enabled or not (if it is enabled, then the variables are
> allocated in a heap object that is completely poisoned afterwards anyway, so
> that would work too.  But if use-after-return is disabled, they are
> allocated in the normal stack frame and we'd need to unpoison those objects
> (together with unpoisoning the guards around them).  And of course we'd need
> to ensure the stack space is not reused for other variables.
> Then there are objects constructed/destructed in heap space, those are
> supposedly fine too, at least I hope a free poisons the memory.  But what
> about
> objects destructed in e.g. mmap allocated area?  And finally objects
> placement new constructed in some other variable, there we'd need to
> unpoison on the first store to that area (or placement new construction). 
> That is very much non-trivial though, at least in the asan framework.

I see the problem, what if we start with all cases that are safe because a poisoned memory should not be reused? From the list of cases you described, we should be able to catch heap-allocated instances. You are right that following case is already covered by asan (heap-use-after-free):

#include <new>

struct A
{
  A (int _m): m(_m) {}
  int m;
};

int main()
{
  /* Test A */
  A *a = new A(12);
  delete a;

  return a->m == 234;
}

But we miss:
#include <new>

struct A
{
  A (int _m): m(_m) {}
  int m;
};

int main()
{
  /* Test A */
  A *a = new A(12);
  a->~A();

  return a->m == 234;
}

And second doable category should be file scope variables. The rest, including automatic variables and all these placement new stuff, can be left for future?
What do you think?
Martin