Chrome's biggest security problem is a constant stream of exploitable (and exploited) Use-after-Free (UaF) bugs. MiraclePtr
is an unmbrella term for algorithms based on smart-pointer-like wrappers, whose goal is to stop UaFs from being exploitable, by turning them from security bugs to non-security crashes or memory leaks. See go/miracleptr for details.
raw_ptr<T>
(formerly CheckedPtr<T>
) is a smart-pointer-like templated class that wraps a raw pointer, protecting it with one of the MiraclePtr
algorithms from being exploited via UaF. The class name came from the first algorithm that we evaluated, and is sujbect to change. BackupRefPtr
is one of the MiraclePtr
algorithms, based on reference counting, that disarms UaFs by quarantining allocations that have known pointers. It was deemed the most promising one and is the only one under consideration at the moment. In the current world, MiraclePtr
, BackupRefPtr
and raw_ptr<T>
became effectively synonyms.
raw_ptr<T>
is currently considered experimental - please don't use it in production code just yet.
For performance reasons, currently we only consider raw_ptr<T>
to replace raw pointer fields (aka member variables). For example, the following struct that uses raw pointers:
struct Example { int* int_ptr; void* void_ptr; SomeClass* object_ptr; const SomeClass* ptr_to_const; SomeClass* const const_ptr; };
Would look as follows when using raw_ptr<T>
:
#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. A handful of incompatible cases are described in the “Incompatibilities with raw pointers” section below.
TODO: Expand the raw notes below:
raw_ptr<T>
existsmalloc
or new
in Chrome, but not pointers to stack memory, etc.)Eventually, once raw_ptr<T>
is no longer experimental, fields (aka member variables) in Chromium code should use raw_ptr<SomeClass>
rather than raw pointers.
TODO: Expand the raw notes below:
raw_ptr<T>
).In most cases, changing the type of a field (or a variable, or a parameter, etc.) from SomeClass*
to raw_ptr<SomeClass>
shouldn't require any additional changes - all other usage of the pointer should continue to compile and work as expected at runtime.
There are some corner-case scenarios however, where raw_ptr<SomeClass>
is not compatible with a raw pointer. Subsections below enumerate such scenarios and offer guidance on how to work with them. For a more in-depth explanation, please see the “BackupRefPtr Support Coverage” document.
.get()
might be requiredIf 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)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”)Due to implementation difficulties, raw_ptr<T>
doesn't support an address-of operator. This means that the following code will not compile:
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:
void GetSomeClassPtr(raw_ptr<SomeClass>* out_arg) { *out_arg = ...; }
If GetSomeClassPtr
can be invoked both with raw pointers and with raw_ptr<T>
, then both overloads might be needed:
void GetSomeClassPtr(SomeClass** out_arg) { *out_arg = ...; } void GetSomeClassPtr(raw_ptr<SomeClass>* out_arg) { SomeClass* tmp = **out_arg; GetSomeClassPtr(&tmp); *out_arg = tmp; }
-Wexit-time-destructors
disallows triggering custom destructors when global variables are destroyed. Since raw_ptr<T>
has a custom destructor, it cannot be used as a field of structs that are used as global variables. If a pointer needs to be used in a global variable (directly or indirectly - e.g. embedded in an array or struct), then the only solution is avoiding raw_ptr<T>
.
Build error:
error: declaration requires an exit-time destructor [-Werror,-Wexit-time-destructors]
constexpr
for non-null valuesconstexpr
raw pointers can be initialized with pointers to string literals or pointers to global variables. Such initialization doesn‘t work for raw_ptr<T>
which doesn’t have a constexpr
constructor for non-null pointer values.
If constexpr
, non-null initialization is required, then the only solution is avoiding raw_ptr<T>
.
If any member of a union has a non-trivial destructor, then the union will not have a destructor. Because of this raw_ptr<T>
usually cannot be used to replace the type of union members, because raw_ptr<T>
has a non-trivial destructor.
Build error:
error: attempt to use a deleted function note: destructor of 'SomeUnion' is implicitly deleted because variant field 'wrapped_ptr' has a non-trivial destructor
It is unsafe to assign raw_ptr<T>
a raw pointer to freed memory even if the raw_ptr<T>
instance is never dereferenced, i.e. the following snippet will likely cause a crash:
void* ptr = malloc(); free(ptr); [...] raw_ptr<void> wrapped_ptr = ptr;
At the very least, nothing prevents the memory slot, which is additionally used to store the raw_ptr<T>
metadata, from being decommitted. Furthermore, the code pattern might lead to free list corruptions and concurrency issues.
On the other hand, assigning a dangling raw_ptr<T>
to another raw_ptr<T>
is supported because the slot is guaranteed to be kept alive. Therefore, a raw_ptr<T>
instance should be only assigned a valid raw pointer, nullptr
or another raw_ptr<T>
. Note that pointers right past the end of an allocation considered valid in C++.
raw_ptr<T>
maintains an internal ref-count associated with the piece of memory that it points to (see the PartitionRefCount
class). The assignment operator of raw_ptr<T>
takes care to update the ref-count as needed, but the ref-count may become unbalanced if the raw_ptr<T>
value is assigned to without going through the assignment operator. An unbalanced ref-count may lead to crashes or memory leaks.
One way to execute such an incorrect assignment is reinterpret_cast
of a pointer to a raw_ptr<T>
. For example, see https://crbug.com/1154799 where the reintepret_cast
is/was used in the Extract
method here). Simplified example:
raw_ptr<int> wrapped_ptr; int** ptr_to_raw_int_ptr = reinterpret_cast<int**>(&wrapped_ptr); // Incorrect code: the assignment below won't update the ref-count internally // maintained by `wrapped_ptr`. *ptr_to_raw_int_ptr = new int(123);
Another way is to reinterpret_cast
a struct containing raw_ptr<T>
fields. For example, see https://crbug.com/1165613#c5 where reinterpret_cast
was used to treat a buffer
of data as FunctionInfo
struct (where interceptor_address
field might be a raw_ptr<T>
). Simplified example:
struct MyStruct { raw_ptr<int> checked_int_ptr_; }; void foo(void* buffer) { // During the assignment, parts of `buffer` will be interpreted as an // already initialized/constructed `raw_ptr<int>` field. MyStruct* my_struct_ptr = reinterpret_cast<MyStruct*>(buffer); // The assignment below will try to decrement the ref-count of the old // pointee. This may crash if the old pointer is pointing to a // PartitionAlloc-managed allocation that has a ref-count already set to 0. my_struct_ptr->checked_int_ptr_ = nullptr; }
Fields are destructed in the reverse order of their declarations:
struct S { Bar bar_; // Bar is destructed last. raw_ptr<Foo> foo_ptr_; // raw_ptr<Foo> (not Foo) is destructed first. };
If destructor of Bar
has a pointer to S
, then it may try to dereference s->foo_ptr_
after raw_ptr<T>
has been already destructed. In practice this will lead to a null dereference and a crash (e.g. see https://crbug.com/1157988).
Note that this code pattern would have resulted in an Undefined Behavior, even if foo_ptr_
was a raw Foo*
pointer (see the memory-safete-dev@ discussion for more details).
Possible solutions (in no particular order):
bar_
field as the very last field.foo_
field (and other POD or raw-pointer-like fields) before any other fields.S
from the destructor of Bar
(and in general, avoid doing significant work from destructors).Pointers past the end of an allocation are supported only if they point exactly to the end of the allocation. Anything beyond that runs into a risk of modifying ref-count of the next allocation, or in the rare case, confusing the ref-counting logic entirely when an allocation is on the border of GigaCage. This could lead to obscure, hard to debug crashes.
If raw_ptr<T>
is used to store an address in another process. The same address could be used in PA for the current process. Resulting in raw_ptr<T>
trying to increment the ref count that doesn't exist.
sandbox::GetProcessBaseAddress()
was an example of a function that returns an address in another process as void*
, resulting in this issue.
TODO(bartekn): Document runtime errors encountered by BackupRefPtr.
TODO(glazunov): One example is accessing a class' raw_ptr<T>
fields in its base class' constructor: https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/wtf/doubly_linked_list.h;drc=cce44dc1cb55c77f63f2ebec5e7015b8dc851c82;l=52