-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
Related to STL Hardening (#5090 implemented by #5274).
Overview
Destructor tombstones are complementary to precondition hardening, which prevents access to non-existent elements. When containers and smart pointers are destroyed, their owned elements/objects no longer exist. To resist use-after-free mistakes, the destructors of containers and smart pointers should set their pointers to some invalid value, instead of continuing to point to deallocated memory.
Choosing a tombstone value
Null is an obvious possibility, and it's certainly safer than nothing, but it could tempt users into making non-Standard assumptions. (We previously encountered cases where users assumed that basic_string could be repeatedly destroyed without harm.)
Windows guarantees that the first 64K of address space is permanently bogus. I believe that decimal 19937 is a good sentinel value, for the following reasons:
- It's associated with the STL (
mt19937), potentially making it easier to figure out "where did this value come from?" during debugging - It's in the middle-ish of 64K, ensuring that moderate-size additions and subtractions remain bogus
- It's somewhat less than the 32K midpoint, leaving more room for addition offsets (which are much more likely when indexing)
- It's odd (prime!), ensuring that it isn't usefully aligned for anything
Some users have expressed interest in customizing this, potentially to a value that's chosen at runtime, to further reduce the temptation for programmer-users to form improper expectations. (I don't see how this would be valuable, given how reliably 19937 stops program execution, but I'm not a user.)
Classes
This is the initial list of classes where I'm implementing and testing tombstones. The goal is to target widely used classes, not literally every class in the STL. Iostreams and concurrency (future etc.) are potential candidates for the future.
- Non-
arraycontainers:vector<T>andvector<bool>dequelistforward_listsetetc. (ordered associative)unordered_setetc. (unordered associative)
- Strings:
basic_string
- Smart pointers:
unique_ptr<T>andunique_ptr<T[]>shared_ptrandweak_ptrexception_ptr(a special kind of smart pointer)
- Can own dynamically allocated function objects:
functionmove_only_function
- Vaguely like a container:
optional- Setting it to empty will work with precondition hardening to prevent access to the object.
- Even though a
T&orT*could have been previously obtained, we should not attempt to scribble overT's bytes. That would be much more expensive, we don't know what bytes are valid forT(indeed, they may all be valid), and we should focus on cheap and significant interventions.
variant- Similarly, this can be set to
valueless_by_exception(), recording that it doesn't hold a value. - Similarly, we shouldn't scribble over the rest of the storage.
- Similarly, this can be set to
any- Can own a dynamically allocated object.
- Can be properly tombstoned, not just set to empty.
- Others:
valarray- Not popular, but container-ish, and easy to tombstone.
regex- Also owns lots of dynamically allocated nodes, and easy to tombstone.
pmr::polymorphic_allocator- Stores a
memory_resource*which is easy to tombstone, and allocation machinery is a common target for attackers.
- Stores a
Macros
Control Macro
_MSVC_STL_DESTRUCTOR_TOMBSTONES
Defining this to 1 enables the feature; defining it to 0 disables the feature.
We plan to initially ship this as disabled-by-default, but plan to change it to enabled-by-default in a future release.
Value Macro
_MSVC_STL_UINTPTR_TOMBSTONE_VALUE
This defaults to uintptr_t{19937}, but can be entirely replaced. If replaced with a function call, it should be ::fully::qualified().
Special considerations
- This is applicable to raw pointers only. We'll use
if constexprto avoid messing with (extremely unusual) fancy pointers. - This is applicable to runtime only. We'll use
_Is_constant_evaluated()to avoid interfering withconstexprevaluation (where we can't usereinterpret_cast). - We'll need to update the
VSO_0000000_wcfb01_idempotent_container_destructorstest. - I verified that MSVC's optimizer doesn't skip such assignments.
- Classes with optimizations to avoid dynamic memory allocation (e.g.
basic_string,function,move_only_function,any) need careful analysis to determine whether we should use "small mode" or "large mode" for the tombstone.