618 lines
25 KiB
Markdown
618 lines
25 KiB
Markdown
# raw_ptr<T> (aka MiraclePtr, aka BackupRefPtr)
|
||
|
||
`raw_ptr<T>` is a non-owning smart pointer that has improved memory-safety over
|
||
raw pointers. It behaves just like a raw pointer on platforms where
|
||
USE_BACKUP_REF_PTR is off, and almost like one when it's on. The main
|
||
difference is that when USE_BACKUP_REF_PTR is enabled, it's zero-initialized and
|
||
cleared on destruction and move. (You should continue to explicitly initialize
|
||
raw_ptr members to ensure consistent behavior on platforms where USE_BACKUP_REF_PTR
|
||
is disabled.) Unlike `std::unique_ptr<T>`, `base::scoped_refptr<T>`, etc., it
|
||
doesn’t manage ownership or lifetime of an allocated object - you are still
|
||
responsible for freeing the object when no longer used, just as you would
|
||
with a raw C++ pointer.
|
||
|
||
`raw_ptr<T>` is beneficial for security, because it can prevent a significant
|
||
percentage of Use-after-Free
|
||
(UaF) bugs from being exploitable (by poisoning the freed memory and
|
||
quarantining it as long as a dangling `raw_ptr<T>` exists).
|
||
`raw_ptr<T>` has limited impact on stability - dereferencing
|
||
a dangling pointer remains Undefined Behavior (although poisoning may
|
||
lead to earlier, easier to debug crashes).
|
||
Note that the security protection is not yet enabled by default.
|
||
|
||
`raw_ptr<T>` is a part of
|
||
[the MiraclePtr project](https://docs.google.com/document/d/1pnnOAIz_DMWDI4oIOFoMAqLnf_MZ2GsrJNb_dbQ3ZBg/edit?usp=sharing)
|
||
and currently implements
|
||
[the BackupRefPtr algorithm](https://docs.google.com/document/d/1m0c63vXXLyGtIGBi9v6YFANum7-IRC3-dmiYBCWqkMk/edit?usp=sharing).
|
||
If needed, please reach out to
|
||
[memory-safety-dev@chromium.org](https://groups.google.com/u/1/a/chromium.org/g/memory-safety-dev)
|
||
or (Google-internal)
|
||
[chrome-memory-safety@google.com](https://groups.google.com/a/google.com/g/chrome-memory-safety)
|
||
with questions or concerns.
|
||
|
||
[TOC]
|
||
|
||
## When to use |raw_ptr<T>|
|
||
|
||
[The Chromium C++ Style Guide](../../styleguide/c++/c++.md#non_owning-pointers-in-class-fields)
|
||
asks to use `raw_ptr<T>` for class and struct fields in place of
|
||
a raw C++ pointer `T*` whenever possible, except in Renderer-only code.
|
||
This guide offers more details.
|
||
|
||
The usage guidelines are *not* enforced currently (the MiraclePtr team will turn
|
||
on enforcement via Chromium Clang Plugin after confirming performance results
|
||
via Stable channel experiments). Afterwards we plan to allow
|
||
exclusions via:
|
||
- [manual-paths-to-ignore.txt](../../tools/clang/rewrite_raw_ptr_fields/manual-paths-to-ignore.txt)
|
||
to exclude at a directory level. Examples:
|
||
- Renderer-only code (i.e. code in paths that contain `/renderer/` or
|
||
`third_party/blink/public/web/`)
|
||
- Code that cannot depend on `//base`
|
||
- Code in `//ppapi`
|
||
- `RAW_PTR_EXCLUSION` C++ attribute to exclude individual fields. Examples:
|
||
- Cases where `raw_ptr<T>` won't compile (e.g. cases covered in
|
||
[the "Unsupported cases leading to compile errors" section](#Unsupported-cases-leading-to-compile-errors)).
|
||
Make sure to also look at
|
||
[the "Recoverable compile-time problems" section](#Recoverable-compile_time-problems).
|
||
- Cases where the pointer always points outside of PartitionAlloc
|
||
(e.g. literals, stack allocated memory, shared memory, mmap'ed memory,
|
||
V8/Oilpan/Java heaps, TLS, etc.).
|
||
- (Very rare) cases that cause regression on perf bots.
|
||
- (Very rare) cases where `raw_ptr<T>` can lead to runtime errors.
|
||
Make sure to look at
|
||
[the "Extra pointer rules" section](#Extra-pointer-rules)
|
||
before resorting to this exclusion.
|
||
- No explicit exclusions will be needed for:
|
||
- `const char*`, `const wchar_t*`, etc.
|
||
- Function pointers
|
||
- ObjC pointers
|
||
|
||
## Examples of using |raw_ptr<T>| instead of raw C++ pointers
|
||
|
||
Consider an example struct that uses raw C++ pointer fields:
|
||
|
||
```cpp
|
||
struct Example {
|
||
int* int_ptr;
|
||
void* void_ptr;
|
||
SomeClass* object_ptr;
|
||
const SomeClass* ptr_to_const;
|
||
SomeClass* const const_ptr;
|
||
};
|
||
```
|
||
|
||
When using `raw_ptr<T>` the struct above would look as follows:
|
||
|
||
```cpp
|
||
#include "base/memory/raw_ptr.h"
|
||
|
||
struct Example {
|
||
raw_ptr<int> int_ptr;
|
||
raw_ptr<void> void_ptr;
|
||
raw_ptr<SomeClass> object_ptr;
|
||
raw_ptr<const SomeClass> ptr_to_const;
|
||
const raw_ptr<SomeClass> const_ptr;
|
||
};
|
||
```
|
||
|
||
In most cases, only the type in the field declaration needs to change.
|
||
In particular, `raw_ptr<T>` implements
|
||
`operator->`, `operator*` and other operators
|
||
that one expects from a raw pointer.
|
||
Cases where other code needs to be modified are described in
|
||
[the "Recoverable compile-time problems" section](#Recoverable-compile_time-problems)
|
||
below.
|
||
|
||
## Performance
|
||
|
||
### Performance impact of using |raw_ptr<T>| instead of |T\*|
|
||
|
||
Compared to a raw C++ pointer, on platforms where USE_BACKUP_REF_PTR is on,
|
||
`raw_ptr<T>` incurs additional runtime
|
||
overhead for initialization, destruction, and assignment (including
|
||
`ptr++` and `ptr += ...`).
|
||
There is no overhead when dereferencing or extracting a pointer (including
|
||
`*ptr`, `ptr->foobar`, `ptr.get()`, or implicit conversions to a raw C++
|
||
pointer).
|
||
Finally, `raw_ptr<T>` has exactly the same memory footprint as `T*`
|
||
(i.e. `sizeof(raw_ptr<T>) == sizeof(T*)`).
|
||
|
||
One source of the performance overhead is
|
||
a check whether a pointer `T*` points to a protected memory pool.
|
||
This happens in `raw_ptr<T>`'s
|
||
constructor, destructor, and assignment operators.
|
||
If the pointed memory is unprotected,
|
||
then `raw_ptr<T>` behaves just like a `T*`
|
||
and the runtime overhead is limited to the extra check.
|
||
(The security protection incurs additional overhead
|
||
described in
|
||
[the "Performance impact of enabling Use-after-Free protection" section](#Performance-impact-of-enabling-Use_after_Free-protection)
|
||
below.)
|
||
|
||
Some additional overhead comes from setting `raw_ptr<T>` to `nullptr`
|
||
when default-constructed, destructed, or moved.
|
||
|
||
During
|
||
[the "Big Rewrite"](https://groups.google.com/a/chromium.org/g/chromium-dev/c/vAEeVifyf78/m/SkBUc6PhBAAJ)
|
||
most Chromium `T*` fields have been rewritten to `raw_ptr<T>`
|
||
(excluding fields in Renderer-only code).
|
||
The cumulative performance impact of such rewrite
|
||
has been measured by earlier A/B binary experiments.
|
||
There was no measurable impact, except that 32-bit platforms
|
||
have seen a slight increase in jankiness metrics
|
||
(for more detailed results see
|
||
[the document here](https://docs.google.com/document/d/1MfDT-JQh_UIpSQw3KQttjbQ_drA7zw1gQDwU3cbB6_c/edit?usp=sharing)).
|
||
|
||
### Performance impact of enabling Use-after-Free protection
|
||
|
||
When the Use-after-Free protection is enabled, then `raw_ptr<T>` has some
|
||
additional performance overhead. This protection is currently disabled
|
||
by default. We will enable the protection incrementally, starting with
|
||
more non-Renderer processes first.
|
||
|
||
The protection can increase memory usage:
|
||
- For each memory allocation Chromium's allocator (PartitionAlloc)
|
||
allocates extra 16 bytes (4 bytes to store the BackupRefPtr's
|
||
ref-count associated with the allocation, the rest to maintain
|
||
alignment requirements).
|
||
- Freed memory is quarantined and not available for reuse as long
|
||
as dangling `raw_ptr<T>` pointers exist.
|
||
- Enabling protection requires additional partitions in PartitionAlloc,
|
||
which increases memory fragmentation.
|
||
|
||
The protection can increase runtime costs - `raw_ptr<T>`'s constructor,
|
||
destructor, and assignment operators (including `ptr++` and `ptr += ...`) need
|
||
to maintain BackupRefPtr's ref-count.
|
||
|
||
## When it is okay to continue using raw C++ pointers
|
||
|
||
### Unsupported cases leading to compile errors
|
||
|
||
Using raw_ptr<T> in the following scenarios will lead to build errors.
|
||
Continue to use raw C++ pointers in those cases:
|
||
- Function pointers
|
||
- Pointers to Objective-C objects
|
||
- Pointer fields in classes/structs that are used as global or static variables
|
||
(see more details in the
|
||
[Rewrite exclusion statistics](https://docs.google.com/document/d/1uAsWnwy8HfIJhDPSh1efohnqfGsv2LJmYTRBj0JzZh8/edit#heading=h.dg4eebu87wg9)
|
||
)
|
||
- Pointer fields that require non-null, constexpr initialization
|
||
(see more details in the
|
||
[Rewrite exclusion statistics](https://docs.google.com/document/d/1uAsWnwy8HfIJhDPSh1efohnqfGsv2LJmYTRBj0JzZh8/edit#heading=h.dg4eebu87wg9)
|
||
)
|
||
- Pointer fields in classes/structs that have to be trivially constructible or
|
||
destructible
|
||
- Code that doesn’t depend on `//base` (including non-Chromium repositories and
|
||
third party libraries)
|
||
- Code in `//ppapi`
|
||
|
||
### Pointers to unprotected memory (performance optimization)
|
||
|
||
Using `raw_ptr<T>` offers no security benefits (no UaF protection) for pointers
|
||
that don’t point to protected memory (only PartitionAlloc-managed heap allocations
|
||
in non-Renderer processes are protected).
|
||
Therefore in the following cases raw C++ pointers may be used instead of
|
||
`raw_ptr<T>`:
|
||
- Pointer fields that can only point outside PartitionAlloc, including literals,
|
||
stack allocated memory, shared memory, mmap'ed memory, V8/Oilpan/Java heaps,
|
||
TLS, etc.
|
||
- `const char*` (and `const wchar_t*`) pointer fields, unless you’re convinced
|
||
they can point to a heap-allocated object, not just a string literal
|
||
- Pointer fields that can only point to aligned allocations (requested via
|
||
PartitionAlloc’s `AlignedAlloc` or `memalign` family of functions, with
|
||
alignment higher than `base::kAlignment`)
|
||
- Pointer fields in Renderer-only code. (This might change in the future
|
||
as we explore expanding `raw_ptr<T>` usage in https://crbug.com/1273204.)
|
||
|
||
### Other perf optimizations
|
||
|
||
As a performance optimization, raw C++ pointers may be used instead of
|
||
`raw_ptr<T>` if it would have a significant
|
||
[performance impact](#Performance).
|
||
|
||
### Pointers in locations other than fields
|
||
|
||
Use raw C++ pointers instead of `raw_ptr<T>` in the following scenarios:
|
||
- Pointers in local variables and function/method parameters.
|
||
This includes pointer fields in classes/structs that are used only on the stack.
|
||
(We plan to enforce this in the Chromium Clang Plugin. Using `raw_ptr<T>`
|
||
here would cumulatively lead to performance regression and the security
|
||
benefit of UaF protection is lower for such short-lived pointers.)
|
||
- Pointer fields in unions. (Naive usage this will lead to
|
||
[a C++ compile
|
||
error](https://docs.google.com/document/d/1uAsWnwy8HfIJhDPSh1efohnqfGsv2LJmYTRBj0JzZh8/edit#heading=h.fvvnv6htvlg3).
|
||
Avoiding the error requires the `raw_ptr<T>` destructor to be explicitly
|
||
called before destroying the union, if the field is holding a value. Doing
|
||
this manual destruction wrong might lead to leaks or double-dereferences.)
|
||
- Pointers whose addresses are used only as identifiers and which are
|
||
never dereferenced (e.g. keys in a map). There is a performance gain
|
||
by not using `raw_ptr` in this case; prefer to use `uintptr_t` to
|
||
emphasize that the entity can dangle and must not be dereferenced.
|
||
|
||
You don’t have to, but may use `raw_ptr<T>`, in the following scenarios:
|
||
- Pointers that are used as an element type of collections/wrappers. E.g.
|
||
`std::vector<T*>` and `std::vector<raw_ptr<T>>` are both okay, but prefer the
|
||
latter if the collection is a class field (note that some of the perf
|
||
optimizations above might still apply and argue for using a raw C++ pointer).
|
||
|
||
|
||
## Extra pointer rules
|
||
|
||
`raw_ptr<T>` requires following some extra rules compared to a raw C++ pointer:
|
||
- Don’t assign invalid, non-null addresses (this includes previously valid and
|
||
now freed memory,
|
||
[Win32 handles](https://crbug.com/1262017), and more). You can only assign an
|
||
address of memory that is allocated at the time of assignment. Exceptions:
|
||
- a pointer to the end of a valid allocation (but not even 1 byte further)
|
||
- a pointer to the last page of the address space, e.g. for sentinels like
|
||
`reinterpret_cast<void*>(-1)`
|
||
- Don’t initialize or assign `raw_ptr<T>` memory directly
|
||
(e.g. `reinterpret_cast<ClassWithRawPtr*>(buffer)` or
|
||
`memcpy(reinterpret_cast<void*>(&obj_with_raw_ptr), buffer)`.
|
||
- Don’t assign to a `raw_ptr<T>` concurrently, even if the same value.
|
||
- Don’t rely on moved-from pointers to keep their old value. Unlike raw
|
||
pointers, `raw_ptr<T>` is cleared upon moving.
|
||
- Don't use the pointer after it is destructed. Unlike raw pointers,
|
||
`raw_ptr<T>` is cleared upon destruction. This may happen e.g. when fields are
|
||
ordered such that the pointer field is destructed before the class field whose
|
||
destructor uses that pointer field (e.g. see
|
||
[Esoteric Issues](https://docs.google.com/document/d/14Ol_adOdNpy4Ge-XReI7CXNKMzs_LL5vucDQIERDQyg/edit#heading=h.yoba1l8bnfmv)).
|
||
- Don’t assign to a `raw_ptr<T>` until its constructor has run. This may happen
|
||
when a base class’s constructor uses a not-yet-initialized field of a derived
|
||
class (e.g. see
|
||
[Applying MiraclePtr](https://docs.google.com/document/d/1cnpd5Rwesq7DCZiD8FIJfPGHvQN3-Gul6xib_4hwfBg/edit?ts=5ed2d317#heading=h.4ry5d9a6fuxs)).
|
||
|
||
Some of these would result in undefined behavior (UB) even in the world without
|
||
`raw_ptr<T>` (e.g. see
|
||
[Field destruction order](https://groups.google.com/a/chromium.org/g/memory-safety-dev/c/3sEmSnFc61I/m/ZtaeWGslAQAJ)),
|
||
but you’d likely get away without any consequences. In the `raw_ptr<T>` world,
|
||
an obscure crash may occur. Those crashes often manifest themselves as SEGV or
|
||
`CHECK` inside `RawPtrBackupRefImpl::AcquireInternal()` or
|
||
`RawPtrBackupRefImpl::ReleaseInternal()`, but you may also experience memory
|
||
corruption or a silent drop of UaF protection.
|
||
|
||
## Recoverable compile-time problems
|
||
|
||
### Explicit |raw_ptr.get()| might be needed
|
||
|
||
If a raw pointer is needed, but an implicit cast from `raw_ptr<SomeClass>` to
|
||
`SomeClass*` doesn't work, then the raw pointer needs to be obtained by explicitly
|
||
calling `.get()`. Examples:
|
||
- `auto* raw_ptr_var = wrapped_ptr_.get()` (`auto*` requires the initializer to
|
||
be a raw pointer)
|
||
- Alternatively you can change `auto*` to `auto&`. Avoid using `auto` as it’ll
|
||
copy the pointer, which incurs a performance overhead.
|
||
- `return condition ? raw_ptr : wrapped_ptr_.get();` (ternary operator needs
|
||
identical types in both branches)
|
||
- `base::WrapUniquePtr(wrapped_ptr_.get());` (implicit cast doesn't kick in for
|
||
arguments in templates)
|
||
- `printf("%p", wrapped_ptr_.get());` (can't pass class type arguments to
|
||
variadic functions)
|
||
- `reinterpret_cast<SomeClass*>(wrapped_ptr_.get())` (`const_cast` and
|
||
`reinterpret_cast` sometimes require their argument to be a raw pointer;
|
||
`static_cast` should "Just Work")
|
||
- `T2 t2 = t1_wrapped_ptr_.get();` (where there is an implicit conversion
|
||
constructor `T2(T1*)` the compiler can handle one implicit conversion, but not
|
||
two)
|
||
- In general, when type is inferred by a compiler and then used in a context
|
||
where a pointer is expected.
|
||
|
||
### Out-of-line constructor/destructor might be needed
|
||
|
||
Out-of-line constructor/destructor may be newly required by the chromium style
|
||
clang plugin. Error examples:
|
||
- `error: [chromium-style] Complex class/struct needs an explicit out-of-line
|
||
destructor.`
|
||
- `error: [chromium-style] Complex class/struct needs an explicit out-of-line
|
||
constructor.`
|
||
|
||
`raw_ptr<T>` uses a non-trivial constructor/destructor, so classes that used to
|
||
be POD or have a trivial destructor may require an out-of-line
|
||
constructor/destructor to satisfy the chromium style clang plugin.
|
||
|
||
|
||
### In-out arguments need to be refactored
|
||
|
||
Due to implementation difficulties,
|
||
`raw_ptr<T>` doesn't support an address-of operator.
|
||
This means that the following code will not compile:
|
||
|
||
```cpp
|
||
void GetSomeClassPtr(SomeClass** out_arg) {
|
||
*out_arg = ...;
|
||
}
|
||
|
||
struct MyStruct {
|
||
void Example() {
|
||
GetSomeClassPtr(&wrapped_ptr_); // <- won't compile
|
||
}
|
||
|
||
raw_ptr<SomeClass> wrapped_ptr_;
|
||
};
|
||
```
|
||
|
||
The typical fix is to change the type of the out argument:
|
||
|
||
```cpp
|
||
void GetSomeClassPtr(raw_ptr<SomeClass>* out_arg) {
|
||
*out_arg = ...;
|
||
}
|
||
```
|
||
|
||
Similarly this code:
|
||
|
||
```cpp
|
||
void FillPtr(SomeClass*& out_arg) {
|
||
out_arg = ...;
|
||
}
|
||
```
|
||
|
||
would have to be changed to this:
|
||
|
||
```cpp
|
||
void FillPtr(raw_ptr<SomeClass>& out_arg) {
|
||
out_arg = ...;
|
||
}
|
||
```
|
||
|
||
Similarly this code:
|
||
|
||
```cpp
|
||
SomeClass*& GetPtr() {
|
||
return wrapper_ptr_;
|
||
}
|
||
```
|
||
|
||
would have to be changed to this:
|
||
|
||
```cpp
|
||
raw_ptr<SomeClass>& GetPtr() {
|
||
return wrapper_ptr_;
|
||
}
|
||
```
|
||
|
||
### Modern |nullptr| is required
|
||
|
||
As recommended by the Google C++ Style Guide,
|
||
[use nullptr instead of NULL](https://google.github.io/styleguide/cppguide.html#0_and_nullptr/NULL) -
|
||
the latter might result in compile-time errors when used with `raw_ptr<T>`.
|
||
|
||
Example:
|
||
|
||
```cpp
|
||
struct SomeStruct {
|
||
raw_ptr<int> ptr_field;
|
||
};
|
||
|
||
void bar() {
|
||
SomeStruct some_struct;
|
||
some_struct.ptr_field = NULL;
|
||
}
|
||
```
|
||
|
||
Error:
|
||
```err
|
||
../../base/memory/checked_ptr_unittest.cc:139:25: error: use of overloaded
|
||
operator '=' is ambiguous (with operand types raw_ptr<int>' and 'long')
|
||
some_struct.ptr_field = NULL;
|
||
~~~~~~~~~~~~~~~~~~~~~ ^ ~~~~
|
||
../../base/memory/raw_ptr.h:369:29: note: candidate function
|
||
ALWAYS_INLINE raw_ptr& operator=(std::nullptr_t) noexcept {
|
||
^
|
||
../../base/memory/raw_ptr.h:374:29: note: candidate function
|
||
ALWAYS_INLINE raw_ptr& operator=(T* p)
|
||
noexcept {
|
||
```
|
||
|
||
### [rare] Explicit overload or template specialization for |raw_ptr<T>|
|
||
|
||
In rare cases, the default template code won’t compile when `raw_ptr<...>` is
|
||
substituted for a template argument. In such cases, it might be necessary to
|
||
provide an explicit overload or template specialization for `raw_ptr<T>`.
|
||
|
||
Example (more details in
|
||
[Applying MiraclePtr](https://docs.google.com/document/d/1cnpd5Rwesq7DCZiD8FIJfPGHvQN3-Gul6xib_4hwfBg/edit?ts=5ed2d317#heading=h.o2pf3fg0zzf) and the
|
||
[Add CheckedPtr support for cbor_extract::Element](https://chromium-review.googlesource.com/c/chromium/src/+/2224954)
|
||
CL):
|
||
|
||
```cpp
|
||
// An explicit overload (taking raw_ptr<T> as an argument)
|
||
// was needed below:
|
||
template <typename S>
|
||
constexpr StepOrByte<S> Element(
|
||
const Is required,
|
||
raw_ptr<const std::string> S::*member, // <- HERE
|
||
uintptr_t offset) {
|
||
return ElementImpl<S>(required, offset, internal::Type::kString);
|
||
}
|
||
```
|
||
|
||
## AddressSanitizer support
|
||
|
||
For years, AddressSanitizer has been the main tool for diagnosing memory
|
||
corruption issues in Chromium. MiraclePtr alters the security properties of some
|
||
of some such issues, so ideally it should be integrated with ASan. That way an
|
||
engineer would be able to check whether a given use-after-free vulnerability is
|
||
covered by the protection without having to switch between ASan and non-ASan
|
||
builds.
|
||
|
||
Unfortunately, MiraclePtr relies heavily on PartitionAlloc, and ASan needs its
|
||
own allocator to work. As a result, the default implementation of `raw_ptr<T>`
|
||
can't be used with ASan builds. Instead, a special version of `raw_ptr<T>` has
|
||
been implemented, which is based on the ASan quarantine and acts as a
|
||
sufficiently close approximation for diagnostic purposes. At crash time, the
|
||
tool will tell the user if the dangling pointer access would have been protected
|
||
by MiraclePtr *in a regular build*.
|
||
|
||
You can configure the diagnostic tool by modifying the parameters of the feature
|
||
flag `PartitionAllocBackupRefPtr`. For example, launching Chromium as follows:
|
||
|
||
```
|
||
path/to/chrome --enable-features=PartitionAllocBackupRefPtr:enabled-processes/browser-only/asan-enable-dereference-check/true/asan-enable-extraction-check/true/asan-enable-instantiation-check/true
|
||
```
|
||
|
||
activates all available checks in the browser process.
|
||
|
||
### Available checks
|
||
|
||
MiraclePtr provides ASan users with three kinds of security checks, which differ
|
||
in when a particular check occurs:
|
||
|
||
#### Dereference
|
||
|
||
This is the basic check type that helps diagnose regular heap-use-after-free
|
||
bugs. It's enabled by default.
|
||
|
||
#### Extraction
|
||
|
||
The user will be warned if a dangling pointer is extracted from a `raw_ptr<T>`
|
||
variable. If the pointer is then dereferenced, an ASan error report will follow.
|
||
In some cases, extra work on the reproduction case is required to reach the
|
||
faulty memory access. However, even without memory corruption, relying on the
|
||
value of a dangling pointer may lead to problems. For example, it's a common
|
||
(anti-)pattern in Chromium to use a raw pointer as a key in a container.
|
||
Consider the following example:
|
||
|
||
```
|
||
std::map<T*, std::unique_ptr<Ext>> g_map;
|
||
|
||
struct A {
|
||
A() {
|
||
g_map[this] = std::make_unique<Ext>(this);
|
||
}
|
||
|
||
~A() {
|
||
g_map.erase(this);
|
||
}
|
||
};
|
||
|
||
raw_ptr<A> dangling = new A;
|
||
// ...
|
||
delete dangling.get();
|
||
A* replacement = new A;
|
||
// ...
|
||
auto it = g_map.find(dangling);
|
||
if (it == g_map.end())
|
||
return 0;
|
||
it->second.DoStuff();
|
||
```
|
||
|
||
Depending on whether the allocator reuses the same memory region for the second
|
||
`A` object, the program may inadvertently call `DoStuff()` on the wrong `Ext`
|
||
instance. This, in turn, may corrupt the state of the program or bypass security
|
||
controls if the two `A` objects belong to different security contexts.
|
||
|
||
Given the proportion of false positives reported in the mode, it is disabled by
|
||
default. It's mainly intended to be used by security researchers who are willing
|
||
to spend a significant amount of time investigating these early warnings.
|
||
|
||
#### Instantiation
|
||
|
||
This check detects violations of the rule that when instantiating a `raw_ptr<T>`
|
||
from a `T*` , it is only allowed if the `T*` is a valid (i.e. not dangling)
|
||
pointer. This rule exists to help avoid an issue called "pointer laundering"
|
||
which can result in unsafe `raw_ptr<T>` instances that point to memory that is
|
||
no longer in quarantine. This is important, since subsequent use of these
|
||
`raw_ptr<T>` might appear to be safe.
|
||
|
||
In order for "pointer laundering" to occur, we need (1) a dangling `T*`
|
||
(pointing to memory that has been freed) to be assigned to a `raw_ptr<T>`, while
|
||
(2) there is no other `raw_ptr<T>` pointing to the same object/allocation at the
|
||
time of assignment.
|
||
|
||
The check only detects (1), a dangling `T*` being assigned to a `raw_ptr<T>`, so
|
||
in order to determine whether "pointer laundering" has occurred, we need to
|
||
determine whether (2) could plausibly occur, not just in the specific
|
||
reproduction testcase, but in the more general case.
|
||
|
||
In the absence of thorough reasoning about (2), the assumption here should be
|
||
that any failure of this check is a security issue of the same severity as an
|
||
unprotected use-after-free.
|
||
|
||
### Protection status
|
||
|
||
When ASan generates a heap-use-after-free report, it will include a new section
|
||
near the bottom, which starts with the line `MiraclePtr Status: <status>`. At
|
||
the moment, it has three possible options:
|
||
|
||
#### Protected
|
||
|
||
The system is sufficiently confident that MiraclePtr makes the discovered issue
|
||
unexploitable. In the future, the security severity of such bugs will be
|
||
reduced.
|
||
|
||
#### Manual analysis required
|
||
|
||
Dangling pointer extraction was detected before the crash, but there might be
|
||
extra code between the extraction and dereference. Most of the time, the code in
|
||
question will look similar to the following:
|
||
|
||
```
|
||
struct A {
|
||
raw_ptr<T> dangling_;
|
||
};
|
||
|
||
void trigger(A* a) {
|
||
// ...
|
||
T* local = a->dangling_;
|
||
DoStuff();
|
||
local->DoOtherStuff();
|
||
// ...
|
||
}
|
||
```
|
||
|
||
In this scenario, even though `dangling_` points to freed memory, that memory
|
||
is protected and will stay in quarantine until `dangling_` (and all other
|
||
`raw_ptr<T>` variables pointing to the same region) changes its value or gets
|
||
destroyed. Therefore, the expression `a_->dangling->DoOtherStuff()` wouldn't
|
||
trigger an exploitable use-after-free.
|
||
|
||
You will need to make sure that `DoStuff()` is sufficiently trivial and can't
|
||
(not only for the particular reproduction case, but *even in principle*) make
|
||
`dangling_` change its value or get destroyed. If that's the case, the
|
||
`DoOtherStuff()` call may be considered protected. The tool will provide you
|
||
with the stack trace for both the extraction and dereference events.
|
||
|
||
#### Not protected
|
||
|
||
The dangling `T*` doesn't appear to originate from a `raw_ptr<T>` variable,
|
||
which means MiraclePtr can't prevent this issue from being exploited. In
|
||
practice, there may still be a `raw_ptr<T>` in a different part of the code that
|
||
protects the same allocation indirectly, but such protection won't be considered
|
||
robust enough to impact security-related decisions.
|
||
|
||
### Limitations
|
||
|
||
The main limitation of MiraclePtr in ASan builds is the main limitation of ASan
|
||
itself: the capacity of the quarantine is limited. Eventually, every allocation
|
||
in quarantine will get reused regardless of whether there are still references
|
||
to it.
|
||
|
||
In the context of MiraclePtr combined with ASan, it's a problem when:
|
||
|
||
1. A heap allocation that isn't supported by MiraclePtr is made. At the moment,
|
||
the only such class is allocations made early during the process startup
|
||
before MiraclePtr can be activated.
|
||
2. Its address is assigned to a `raw_ptr<T>` variable.
|
||
3. The allocation gets freed.
|
||
4. A new allocation is made in the same memory region as the first one, but this
|
||
time it is supported.
|
||
5. The second allocation gets freed.
|
||
6. The `raw_ptr<T>` variable is accessed.
|
||
|
||
In this case, MiraclePtr will incorrectly assume the memory access is protected.
|
||
Luckily, considering the small number of unprotected allocations in Chromium,
|
||
the size of the quarantine, and the fact that most reproduction cases take
|
||
relatively short time to run, the odds of this happening are very low.
|
||
|
||
The problem is relatively easy to spot if you look at the ASan report: the
|
||
allocation and deallocation stack traces won't be consistent across runs and
|
||
the allocation type won't match the use stack trace.
|
||
|
||
If you encounter a suspicious ASan report, it may be helpful to re-run Chromium
|
||
with an increased quarantine capacity as follows:
|
||
|
||
```
|
||
ASAN_OPTIONS=quarantine_size_mb=1024 path/to/chrome
|
||
```
|