| # 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 |
| ``` |