Charlie Reis | 6d494327 | 2023-01-25 22:46:31 | [diff] [blame] | 1 | # Dangling Pointer Guide |
Arthur Sonzogni | c66acfe6 | 2022-12-08 14:27:28 | [diff] [blame] | 2 | |
| 3 | A dangling pointer has been found in your patch? This doc will help you fix it. |
| 4 | |
| 5 | > This document can also be commented upon: |
| 6 | https://docs.google.com/document/d/19yyWdCQB5KwYtRE5cwadNf30jNSSWwMbim1KJjc-eZQ/edit# |
| 7 | |
| 8 | See also the general instructions about the dangling pointer detector: |
| 9 | [docs/dangling_ptr.md](./dangling_ptr.md) |
| 10 | |
Tom Sepez | b5932eac | 2023-03-24 00:20:31 | [diff] [blame] | 11 | **Table of contents** |
| 12 | - [What to do about dangling pointers](#what-to-do-about-dangling-pointers) |
| 13 | - [`Case 1` I don’t own the affected component](#i-don_t-own-the-affected-component) |
| 14 | - [`Case 2` The dangling pointer does not own the deleted object.](#the-dangling-pointer-does-not-own-the-deleted-object) |
| 15 | - [Incorrect destruction order](#incorrect-destruction-order) |
| 16 | - [Observer callback](#observer-callback) |
| 17 | - [Challenging lifespan](#challenging-lifespan) |
Tom Sepez | 923050c | 2023-04-03 17:16:25 | [diff] [blame] | 18 | - [Cyclic pointers](#cyclic-pointers) |
Tom Sepez | b5932eac | 2023-03-24 00:20:31 | [diff] [blame] | 19 | - [Fallback solution](#fallback-solution) |
| 20 | - [`Case 3` The pointer manages ownership over the object](#the-pointer-manages-ownership-over-the-object) |
| 21 | - [Smart pointers](#smart-pointers) |
| 22 | - [Object vended from C API](#object-vended-from-c-api) |
| 23 | - [Object conditionally owned](#object-conditionally-owned) |
| 24 | - [Fallback solution](#fallback-solution-1) |
Paul Semel | f6756e8 | 2024-03-08 13:28:22 | [diff] [blame] | 25 | - [What to do about unretained dangling pointers](./unretained_dangling_ptr_guide.md) |
Tom Sepez | ac5ddb3 | 2023-06-29 17:21:45 | [diff] [blame] | 26 | - [I can't figure out which pointer is dangling](I-can_t-figure-out-which-pointer-is-dangling) |
Tom Sepez | b5932eac | 2023-03-24 00:20:31 | [diff] [blame] | 27 | - [FAQ - Why dangling pointers matter](#faq-why-dangling-pointers-matter) |
Arthur Sonzogni | c66acfe6 | 2022-12-08 14:27:28 | [diff] [blame] | 28 | |
Tom Sepez | b5932eac | 2023-03-24 00:20:31 | [diff] [blame] | 29 | ## What to do about dangling pointers |
| 30 | |
| 31 | There are a few common cases here. |
| 32 | |
| 33 | ### `Case 1` I don’t own the affected component |
Arthur Sonzogni | c66acfe6 | 2022-12-08 14:27:28 | [diff] [blame] | 34 | |
| 35 | If you do not directly own the affected component, you **don't need** to solve |
| 36 | the issue yourself… though doing so is a great way to learn about and improve |
| 37 | the codebase. |
| 38 | |
| 39 | Please annotate the pointer with `DanglingUntriaged`, and indicate the test case |
| 40 | that can be used to reproduce. |
| 41 | ```cpp |
| 42 | // Dangling when executing TestSuite.TestCase on Windows: |
| 43 | raw_ptr<T, DanglingUntriaged> ptr_; |
| 44 | ``` |
| 45 | Opening and filling a P2 bug is a nice thing to do, but it is not required. |
| 46 | |
| 47 | **Rationale:** |
| 48 | Engineers might uncover new dangling pointers, by testing new code paths. |
| 49 | Knowing about dangling pointers is a purely positive increment. In some cases, |
| 50 | the affected component belongs to a different team. We don’t want to disrupt |
Charlie Reis | 6d494327 | 2023-01-25 22:46:31 | [diff] [blame] | 51 | engineers achieving their primary goal, if they only “discover” a dangling |
| 52 | pointer. Annotating the pointer makes the issue visible directly in the code, |
Arthur Sonzogni | c66acfe6 | 2022-12-08 14:27:28 | [diff] [blame] | 53 | improving our knowledge of Chrome. |
| 54 | |
Tom Sepez | b5932eac | 2023-03-24 00:20:31 | [diff] [blame] | 55 | ### `Case 2` The dangling pointer does not own the deleted object |
Arthur Sonzogni | c66acfe6 | 2022-12-08 14:27:28 | [diff] [blame] | 56 | |
Tom Sepez | b5932eac | 2023-03-24 00:20:31 | [diff] [blame] | 57 | #### Incorrect destruction order |
Arthur Sonzogni | c66acfe6 | 2022-12-08 14:27:28 | [diff] [blame] | 58 | |
| 59 | This represents ~25% of the dangling pointers. |
| 60 | |
Tom Sepez | 16c9bfa | 2023-04-03 16:30:41 | [diff] [blame] | 61 | In the majority of cases, this happens when dependent objects are destroyed in |
Bartek Nowierski | 2e73203 | 2023-01-19 21:34:41 | [diff] [blame] | 62 | the wrong order in a class, causing the dependency to be released first, thus |
Arthur Sonzogni | c66acfe6 | 2022-12-08 14:27:28 | [diff] [blame] | 63 | creating a dangling pointer in the other. |
| 64 | |
Tom Sepez | 16c9bfa | 2023-04-03 16:30:41 | [diff] [blame] | 65 | Recall that destructors destroy class members in the inverse order of their |
| 66 | appearance. It is usually possible to resolve destruction order issues by |
| 67 | re-ordering member declarations so that members which need to live longer come |
| 68 | first. It is important to order members correctly to prevent pre-existing and |
| 69 | future UAF in destructors. |
Arthur Sonzogni | c66acfe6 | 2022-12-08 14:27:28 | [diff] [blame] | 70 | |
| 71 | See [Fix member declaration order](https://docs.google.com/document/d/11YYsyPF9rQv_QFf982Khie3YuNPXV0NdhzJPojpZfco/edit?resourcekey=0-h1dr1uDzZGU7YWHth5TRAQ#bookmark=id.jgjtzldk9pvc) and [Fix reset ordering](https://docs.google.com/document/d/11YYsyPF9rQv_QFf982Khie3YuNPXV0NdhzJPojpZfco/edit?resourcekey=0-h1dr1uDzZGU7YWHth5TRAQ#bookmark=id.xdam727ioy4q) examples. |
| 72 | |
Tom Sepez | 16c9bfa | 2023-04-03 16:30:41 | [diff] [blame] | 73 | One good practice is make owning members (`unique_ptr<>`, `scoped_refptr<>`) |
| 74 | appear before unowned members (`raw_ptr<>`), and to make the unowned members |
| 75 | appear last in the class, since the unowned members often refer to resources |
| 76 | owned by the owning members or the class itself. |
| 77 | |
Lukasz Anforowicz | d6d7ab9 | 2023-05-23 17:16:08 | [diff] [blame] | 78 | One sub-category of destruction order issues is related to `KeyedService`s which |
| 79 | need to correctly |
| 80 | [declare their dependencies](https://source.chromium.org/chromium/chromium/src/+/main:components/keyed_service/core/keyed_service_base_factory.h;l=60-62;drc=8ba1bad80dc22235693a0dd41fe55c0fd2dbdabd) |
| 81 | and |
| 82 | [are expected to drop references](https://source.chromium.org/chromium/chromium/src/+/main:components/keyed_service/core/keyed_service.h;l=12-13;drc=8ba1bad80dc22235693a0dd41fe55c0fd2dbdabd) |
| 83 | to their dependencies in their |
| 84 | [`Shutdown`](https://source.chromium.org/chromium/chromium/src/+/main:components/keyed_service/core/keyed_service.h;l=36-39;drc=8ba1bad80dc22235693a0dd41fe55c0fd2dbdabd) method |
| 85 | (i.e. before their destructor runs). |
| 86 | |
Tom Sepez | b5932eac | 2023-03-24 00:20:31 | [diff] [blame] | 87 | #### Observer callback |
Arthur Sonzogni | c66acfe6 | 2022-12-08 14:27:28 | [diff] [blame] | 88 | |
| 89 | This represents ~4% of the dangling pointers. |
| 90 | |
| 91 | It is important to clear the pointer when the object is about to be deleted. |
| 92 | Chrome uses the observer pattern heavily. In some cases, the observer does not |
| 93 | clear its pointer toward the observed class when notified of its destruction. |
| 94 | |
Tom Sepez | 923050c | 2023-04-03 17:16:25 | [diff] [blame] | 95 | #### Cyclic pointers |
| 96 | |
| 97 | Sometimes two (or more) objects can have unowned references between each other, |
| 98 | with neither one owning the other. This creates a situation where neither can |
| 99 | be deleted without creating a dangling pointer unless some action is first |
| 100 | taken to break the cycle. In order to create such a cycle in the first place, a |
| 101 | call to a "setter" method or equivalent must have occurred handing one object |
| 102 | a reference to the other. Balance out this call with another call to the same |
| 103 | setter, but passing nullptr instead, before the destroying the other object. |
| 104 | |
Tom Sepez | b5932eac | 2023-03-24 00:20:31 | [diff] [blame] | 105 | #### Challenging lifespan |
Arthur Sonzogni | c66acfe6 | 2022-12-08 14:27:28 | [diff] [blame] | 106 | |
| 107 | It can be challenging to deal with an object's lifespan. Sometimes, the lifetime |
| 108 | of two objects are completely different. |
| 109 | |
| 110 | Removing the pointer may be a good thing to do. Sometimes, it can be replaced |
| 111 | by: |
| 112 | - Passing the pointer as a function argument instead of getting access to it |
| 113 | from a long-lived field. |
| 114 | - A token / ID. For instance |
| 115 | [blink::LocalFrameToken](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/public/common/tokens/tokens.h;drc=898134d0d40dbbcd308e7d51655518ac7c6392b5;l=34), |
| 116 | [content::GlobalRenderFrameHostId](https://source.chromium.org/chromium/chromium/src/+/main:content/public/browser/global_routing_id.h;drc=898134d0d40dbbcd308e7d51655518ac7c6392b5;l=64) |
| 117 | - A [WeakPtr](https://docs.google.com/document/d/11YYsyPF9rQv_QFf982Khie3YuNPXV0NdhzJPojpZfco/edit?resourcekey=0-h1dr1uDzZGU7YWHth5TRAQ#bookmark=id.geuhahom0twd) |
| 118 | - [Calling a function](https://docs.google.com/document/d/11YYsyPF9rQv_QFf982Khie3YuNPXV0NdhzJPojpZfco/edit?resourcekey=0-h1dr1uDzZGU7YWHth5TRAQ#heading=h.wh99ri7bbq23) |
| 119 | |
Tom Sepez | b5932eac | 2023-03-24 00:20:31 | [diff] [blame] | 120 | #### Fallback solution |
Arthur Sonzogni | c66acfe6 | 2022-12-08 14:27:28 | [diff] [blame] | 121 | |
| 122 | As a last resort, when the situation is perfectly understood, and you believe it |
Charlie Reis | 6d494327 | 2023-01-25 22:46:31 | [diff] [blame] | 123 | is better to let the pointer dangle, the raw_ptr can be annotated with |
Arthur Sonzogni | c66acfe6 | 2022-12-08 14:27:28 | [diff] [blame] | 124 | `DisableDanglingPtrDetection`. A comment explaining why this is safe must be |
| 125 | added. |
| 126 | |
| 127 | ```cpp |
| 128 | // DisableDanglingPtrDetection option for raw_ptr annotates |
| 129 | // "intentional-and-safe" dangling pointers. It is meant to be used at the |
| 130 | // margin, only if there is no better way to re-architecture the code. |
| 131 | // |
| 132 | // Usage: |
| 133 | // raw_ptr<T, DisableDanglingPtrDetection> dangling_ptr; |
| 134 | // |
Bartek Nowierski | 2e73203 | 2023-01-19 21:34:41 | [diff] [blame] | 135 | // When using it, please provide a justification about what guarantees that it |
| 136 | // will never be dereferenced after becoming dangling. |
Arthur Sonzogni | c66acfe6 | 2022-12-08 14:27:28 | [diff] [blame] | 137 | ``` |
| 138 | |
| 139 | **In emergency situations**: `DanglingUntriaged` can be used similarly, in case |
| 140 | your patch needs to land as soon as possible. |
| 141 | |
Tom Sepez | b5932eac | 2023-03-24 00:20:31 | [diff] [blame] | 142 | ### `Case 3` The pointer manages ownership over the object |
Arthur Sonzogni | c66acfe6 | 2022-12-08 14:27:28 | [diff] [blame] | 143 | |
| 144 | raw_ptr, just like raw pointers T*, are not meant to keep an object alive. It is |
| 145 | preferable not to manage memory manually using them and new/delete. Calling |
| 146 | delete on a raw_ptr will cause the raw_ptr to become immediately dangling. |
| 147 | |
Tom Sepez | b5932eac | 2023-03-24 00:20:31 | [diff] [blame] | 148 | #### Smart pointers |
Arthur Sonzogni | c66acfe6 | 2022-12-08 14:27:28 | [diff] [blame] | 149 | |
| 150 | First, consider replacing the raw_ptr with a smart pointer: |
| 151 | |
| 152 | - std::unique_ptr (See |
| 153 | [example](https://docs.google.com/document/d/11YYsyPF9rQv_QFf982Khie3YuNPXV0NdhzJPojpZfco/edit?resourcekey=0-h1dr1uDzZGU7YWHth5TRAQ#heading=h.6itq8twigqt3)) |
| 154 | - scoped_refptr |
| 155 | |
Tom Sepez | b5932eac | 2023-03-24 00:20:31 | [diff] [blame] | 156 | #### Object vended from C API |
Arthur Sonzogni | c66acfe6 | 2022-12-08 14:27:28 | [diff] [blame] | 157 | |
| 158 | In some cases, the object is vended by a C API. It means new/delete are not |
| 159 | used, but some equivalent API. In this case, it still makes sense to use a |
| 160 | unique_ptr, but with a custom deleter: |
| 161 | [example](https://chromium-review.googlesource.com/c/chromium/src/+/3650764). |
| 162 | |
| 163 | For most common objects, a wrapper already exists: |
| 164 | [base::ScopedFILE](https://source.chromium.org/chromium/chromium/src/+/main:base/files/scoped_file.h;drc=898134d0d40dbbcd308e7d51655518ac7c6392b5;l=105), |
| 165 | [base::ScopedTempDir](https://source.chromium.org/chromium/chromium/src/+/main:base/files/scoped_temp_dir.h;l=25?q=ScopedTempDir&sq=&ss=chromium%2Fchromium%2Fsrc), |
| 166 | .. |
| 167 | |
Tom Sepez | b5932eac | 2023-03-24 00:20:31 | [diff] [blame] | 168 | #### Object conditionally owned |
Arthur Sonzogni | c66acfe6 | 2022-12-08 14:27:28 | [diff] [blame] | 169 | |
| 170 | In some cases, the raw_ptr conditionally owns the memory, depending on some |
| 171 | logic or some `is_owned` boolean. This can still use a unique_ptr |
| 172 | ([example](https://chromium-review.googlesource.com/c/chromium/src/+/3829302)) |
| 173 | |
| 174 | ```cpp |
| 175 | std::unique_ptr<T> t_owned_; |
| 176 | raw_ptr<T> t_; // Reference `t_owned_` or an external object. |
| 177 | ``` |
| 178 | |
Tom Sepez | b5932eac | 2023-03-24 00:20:31 | [diff] [blame] | 179 | #### Fallback solution |
Arthur Sonzogni | c66acfe6 | 2022-12-08 14:27:28 | [diff] [blame] | 180 | |
| 181 | If no solution with a smart pointer is found: |
| 182 | |
| 183 | You can use `raw_ptr::ClearAndDelete()` or `raw_ptr::ClearAndDeleteArray()` to |
| 184 | clear the pointer and free the object in a single operation. |
| 185 | |
| 186 | |Before|After | |
| 187 | |--|--| |
| 188 | | `delete ptr_` | `ptr_.ClearAndDelete();`| |
| 189 | | `delete array_` | `ptr_.ClearAndDeleteArray();`| |
| 190 | |
| 191 | When delete is not used, but the deletion happens through some C API or some |
| 192 | external manager, the `raw_ptr::ExtractAsDangling()` can be used to clear the |
| 193 | pointer, and return the pointer to be passed to the API. The return value must |
| 194 | not be saved, thus outliving the line where it was called. |
| 195 | |
| 196 | This method should be used wisely, and only if there is no other way to rework |
| 197 | the dangling raw_ptr. |
| 198 | |
| 199 | |Before|After | |
| 200 | |--|--| |
| 201 | |`ExternalAPIDelete(ptr_);`|`ExternalAPIDelete(ptr_.ExtractAsDangling());`| |
Tom Sepez | b5932eac | 2023-03-24 00:20:31 | [diff] [blame] | 202 | |
Tom Sepez | ac5ddb3 | 2023-06-29 17:21:45 | [diff] [blame] | 203 | ## I can't figure out which pointer is dangling |
| 204 | |
| 205 | Usually this is a matter of straightforward reasoning, but should all else |
| 206 | fail, another option is to re-build with the alternative dangling pointer |
| 207 | detector as described in |
| 208 | [docs/dangling_ptr.md](./dangling_ptr.md#alternative-dangling-pointer-detector-experimental). |
| 209 | This will show the stacks for object creation, object destruction, and the |
| 210 | destruction of the object containing the dangling ptr member. |
| 211 | |
Tom Sepez | b5932eac | 2023-03-24 00:20:31 | [diff] [blame] | 212 | ## FAQ - Why dangling pointers matter |
| 213 | |
| 214 | Q. Gee, this is a raw pointer. Does destroying it actually do anything? |
| 215 | |
Tom Sepez | 4e7f2ef | 2023-03-24 17:02:16 | [diff] [blame] | 216 | A. Yes. On some platforms `raw_ptr<T>` is a synonym for `T*`, but on many |
| 217 | platforms (more every day) `raw_ptr<T>` is the interface to PartitionAlloc’s |
| 218 | BackupRefPtr (BRP) Use-after-Free (UaF) protection mechanism. Destroying the |
| 219 | pointer thus actually performs internal bookkeeping and may also null |
| 220 | the pointer on destruction to trap use-after-destruct errors. |
Tom Sepez | b5932eac | 2023-03-24 00:20:31 | [diff] [blame] | 221 | |
| 222 | Q. So BRP mitigates these dangling pointers. What's the problem with just |
| 223 | keeping them if they are not used? |
| 224 | |
| 225 | A. When an object is deleted under BRP, if there are any dangling references |
| 226 | remaining, the object must be quarantined and overwritten with a “zapped” |
| 227 | pattern as opposed to being simply freed. This costs cycles and memory |
| 228 | pressure. Even worse, it is still possible that the raw_ptr is converted to |
Tom Sepez | 4e7f2ef | 2023-03-24 17:02:16 | [diff] [blame] | 229 | `T*` and used with the zap value, or even used after the quarantine is |
Tom Sepez | b5932eac | 2023-03-24 00:20:31 | [diff] [blame] | 230 | gone. Hence we are inventing mechanisms for finding dangling pointers so we |
| 231 | may remove the ones we know about. BRP will then make it harder to write |
| 232 | exploits with the ones we don’t know about in the wild. |
| 233 | |
| 234 | Q. Why do we care if this is “just a test”? |
| 235 | |
| 236 | A. Hitting dangling pointer warnings in tests blocks digging into actual |
| 237 | cases further in the code. |
| 238 | |
| 239 | Q. Why should I think about lifetimes, anyway? |
| 240 | |
| 241 | A. Holding an address that we aren’t allowed to de-reference is a bad |
| 242 | practice. It is a security and stability risk, a source of bugs that are |
| 243 | extremely difficult to diagnose, and a hazard for future coders. Also see |
| 244 | e.g. https://discourse.llvm.org/t/rfc-lifetime-annotations-for-c/61377 for |
| 245 | some ideas about how this might play out in the future. |