Before telling you what raw_ptr<T>
is, we'd like you to follow one simple rule: think of it as a raw C++ pointer. In particular:
raw_ptr<T> member_ = nullptr;
form of initialization rather than the so-called uniform initialization form (empty braces) raw_ptr<T> member_{};
whose meaning varies with the implementation.)raw_ptr<T>
will free it for you (it won’t). Unlike std::unique_ptr<T>
, base::scoped_refptr<T>
, etc., it does not manage ownership or lifetime of an allocated object.raw_ptr<T>
will protect you from freeing memory too early (it likely will, but there are gotchas; one of them is that dereferencing will result in other type of undefined behavior).(There are other, much more subtle rules that you should follow, but they're harder to accidentally violate, hence discussed in the further section “Extra pointer rules”.)
raw_ptr<T>
is a part of the MiraclePtr project and currently implements the BackupRefPtr algorithm. If needed, please reach out to memory-safety-dev@chromium.org or (Google-internal) chrome-memory-safety@google.com with questions or concerns.
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_RAW_PTR_BACKUP_REF_IMPL is off, and almost like one when it's on. The main difference is that when USE_RAW_PTR_BACKUP_REF_IMPL is enabled, raw_ptr<T>
is beneficial for security, because it can prevent a significant percentage of Use-after-Free (UaF) bugs from being exploitable. It achieves this by quarantining the freed memory as long as any dangling raw_ptr<T>
pointing to it exists, and poisoning it (with 0xEF..EF pattern).
Note that the sheer act of dereferencing a dangling pointer won't crash, but poisoning increases chances that a subsequent usage of read memory will crash (particularly if the read poison is interpreted as a pointer and dereferenced thereafter), thus giving us a chance to investigate and fix. Having said that, we want to emphasize that dereferencing a dangling pointer remains an Undefined Behavior.
raw_ptr<T>
protection is enabled by default in all non-Renderer processes, on:
In particular, it isn't yet enabled by default on:
For the source of truth, both enable_backup_ref_ptr_support
and enable_backup_ref_ptr_feature_flag
need to enabled. Please refer to the following files: build_overrides/partition_alloc.gni and partition_alloc.gni
The Chromium C++ Style Guide 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 currently enforced via Chromium Clang Plugin. We allow exclusions via:
RAW_PTR_EXCLUSION
C++ attribute to exclude individual fields. Examples:raw_ptr<T>
won't compile (e.g. cases covered in the “Unsupported cases leading to compile errors” section). Make sure to also look at the “Recoverable compile-time problems” section.raw_ptr<T>
can lead to runtime errors. Make sure to look at the “Extra pointer rules” section before resorting to this exclusion./renderer/
or third_party/blink/public/web/
)//base
//ppapi
const char*
, const wchar_t*
, etc.Consider an example struct that uses raw C++ pointer fields:
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:
#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 below.
Compared to a raw C++ pointer, on platforms where USE_RAW_PTR_BACKUP_REF_IMPL is on, raw_ptr<T>
incurs additional runtime overhead for initialization, destruction, and assignment (including ptr++
, ptr += ...
, etc.). 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 that extra check. (The security protection incurs additional overhead described in the “Performance impact of enabling Use-after-Free protection” section below.)
Some additional overhead comes from setting raw_ptr<T>
to nullptr
when default-constructed, destructed, or moved. (Yes, we said above to not rely on it, but to be precise this will always happen when USE_RAW_PTR_BACKUP_REF_IMPL is on; no guarantees otherwise.)
During the “Big Rewrite” 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).
When the Use-after-Free protection is enabled, then raw_ptr<T>
has some additional performance overhead.
The protection can increase memory usage:
raw_ptr<T>
pointers exist. (In practice this overhead has been observed to be low, but on a couple occasions it led to significant memory leaks, fortunately caught early.)The protection increases runtime costs - raw_ptr<T>
‘s constructor, destructor, and assignment operators need to maintain BackupRefPtr’s ref-count (atomic increment/decrement). ptr++
, ptr += ...
, etc. don't need to do that, but instead have to incur the cost of verifying that resulting pointer stays within the same allocation (important for BRP integrity).
Continue to use raw C++ pointers in the following cases, which may otherwise result in compile errors:
thread_local
variables (see more details in the Rewrite exclusion statistics )//base
(including non-Chromium repositories and third party libraries)//ppapi
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>
:
const char*
(and const wchar_t*
) pointer fields, unless you’re convinced they can point to a heap-allocated object, not just a string literalthird_party/blink/renderer/core/ third_party/blink/renderer/platform/heap/ third_party/blink/renderer/platform/wtf/
As a performance optimization, raw C++ pointers may be used instead of raw_ptr<T>
if it would have a significant performance impact.
Use raw C++ pointers instead of raw_ptr<T>
in the following scenarios:
raw_ptr<T>
here would cumulatively lead to performance regression and the security benefit of UaF protection is lower for such short-lived pointers.)absl::variant
+ raw_ptr<T>
. If use of C++ union is absolutely unavoidable, prefer a regular C++ pointer: incorrect management of a raw_ptr<T>
field can easily lead to ref-count corruption.raw_ptr
in this case; prefer to use uintptr_t
to emphasize that the entity can dangle and must not be dereferenced. (NOTE, this is a dangerous practice irrespective of raw_ptr usage, as there is a risk of memory being freed and another pointer allocated with the same address!)You don’t have to, but may use raw_ptr<T>
, in the following scenarios:
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).raw_ptr<T>
assumes that the allocator's data structures are in a consistent state. Signal handlers can interrupt in the middle of an allocation operation; therefore, raw_ptr<T>
should not be used in signal handlers.
raw_ptr<T>
requires following some extra rules compared to a raw C++ pointer:
reinterpret_cast<void*>(-1)
raw_ptr<T>
memory directly (e.g. reinterpret_cast<ClassWithRawPtr*>(buffer)
or memcpy(reinterpret_cast<void*>(&obj_with_raw_ptr), buffer)
.raw_ptr<T>
concurrently, even if the same value.raw_ptr<T>
may be cleared upon moving.raw_ptr<T>
may be 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).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).Some of these would result in undefined behavior (UB) even in the world without raw_ptr<T>
(e.g. see Field destruction order), 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.
In an ideal world, a raw_ptr would point to a single object, rather than to a C-style array of objects accessed via pointer arithmetic, since the latter is best handled via a C++ construct such as base::span<> or std::vector<>. raw_ptrs upon which such operations are performed and for which conversion is desirable have been tagged with the AllowPtrArithmetic trait. That all such pointer are tagged can be enforced by setting the GN build arg enable_pointer_arithmetic_trait_check=true.
When building Chromium, raw_ptrs are always nullptr initialized, either as the result of specific implementation that requires it (e.g. BackupRefPtr), or as the result of build flags (to enforce consistency). However, we provide an opt-out to allow third-party code to skip this step (where possible). Use this trait sparingly.
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)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)TemplatedFunction(wrapped_ptr_.get());
(implicit cast doesn't kick in for T*
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)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.
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 (see also an example CL here):
void GetSomeClassPtr(raw_ptr<SomeClass>* out_arg) { *out_arg = ...; }
Similarly this code:
void FillPtr(SomeClass*& out_arg) { out_arg = ...; }
would have to be changed to this:
void FillPtr(raw_ptr<SomeClass>& out_arg) { out_arg = ...; }
Similarly this code:
SomeClass*& GetPtr() { return wrapper_ptr_; }
would have to be changed to this:
raw_ptr<SomeClass>& GetPtr() { return wrapper_ptr_; }
In case you cannot refactor the in-out arguments (e.g. third party library), you may use raw_ptr.AsEphemeralRawAddr()
to obtain extremely short-lived T**
or T*&
. You should not treat T**
obtained via raw_ptr.AsEphemeralRawAddr()
as a normal pointer pointer, and must follow these requirements.
T**
or T*&
anywhere, even as a local variable.raw_ptr<T>
, T**
or T*&
multiple times within an expression.T**
or T*&
is alive.void GetSomeClassPtr(SomeClass** out_arg) { *out_arg = ...; } void FillPtr(SomeClass*& out_arg) { out_arg = ...; } void Foo() { raw_ptr<SomeClass> ptr; GetSomeClassPtr(&ptr.AsEphemeralRawAddr()); FillPtr(ptr.AsEphemeralRawAddr()); // Implicitly converted into |SomeClass*&|. }
Technically, raw_ptr.AsEphemeralRawAddr()
generates a temporary instance of raw_ptr<T>::EphemeralRawAddr
, which holds a temporary copy of T*
. T**
and T*&
points to a copied version of the original pointer and any modification made via T**
or T*&
is written back on destruction of EphemeralRawAddr
instance. C++ guarantees a temporary object returned by raw_ptr.AsEphemeralRawAddr()
lives until completion of evaluation of “full-expression” (i.e. the outermost expression). This makes it possible to use T**
and T*&
within single expression like in-out param.
struct EphemeralRawAddr { EphemeralRawAddr(raw_ptr& ptr): copy(ptr.get()), original(ptr) {} ~EphemeralRawAddr() { original = copy; copy = nullptr; } T** operator&() { return © } operator T*&() { return copy; } T* copy; raw_ptr& original; // Original pointer. };
As recommended by the Google C++ Style Guide, use nullptr instead of NULL - the latter might result in compile-time errors when used with raw_ptr<T>
.
Example:
struct SomeStruct { raw_ptr<int> ptr_field; }; void bar() { SomeStruct some_struct; some_struct.ptr_field = NULL; }
Error:
../../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 {
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 and the Add CheckedPtr support for cbor_extract::Element CL):
// 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); }
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.
MiraclePtr provides ASan users with three kinds of security checks, which differ in when a particular check occurs:
This is the basic check type that helps diagnose regular heap-use-after-free bugs. It's enabled by default.
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.
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.
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:
The system is sufficiently confident that MiraclePtr makes the discovered issue unexploitable. In the future, the security severity of such bugs will be reduced.
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.
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.
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:
raw_ptr<T>
variable.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
Note that
RawPtrNoOpImpl
is thought to have no overhead. However, this has yet to be verified.
“Inert BackupRefPtr” has overhead - once BRP support is compiled in, every raw_ptr
will (at assignment) perform the check that asks, “is BRP protection active?”
As for general BRP enablement,
BRP is live in most browser tests and Chromium targets.
In unit tests,
raw_ptr
is the no-op impl when the build is ASan.
raw_ptr
is live BRP on bots.
raw_ptr
is inert BRP otherwise (see https://crbug.com/1440658).