blob: d5f97e2996fc0ae1d66a63f116de796c3e517141 [file] [log] [blame] [view]
Charlie Reis6d4943272023-01-25 22:46:311# Dangling Pointer Guide
Arthur Sonzognic66acfe62022-12-08 14:27:282
3A dangling pointer has been found in your patch? This doc will help you fix it.
4
5> This document can also be commented upon:
6https://docs.google.com/document/d/19yyWdCQB5KwYtRE5cwadNf30jNSSWwMbim1KJjc-eZQ/edit#
7
8See also the general instructions about the dangling pointer detector:
9[docs/dangling_ptr.md](./dangling_ptr.md)
10
Tom Sepezb5932eac2023-03-24 00:20:3111**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 Sepez923050c2023-04-03 17:16:2518 - [Cyclic pointers](#cyclic-pointers)
Tom Sepezb5932eac2023-03-24 00:20:3119 - [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 Semelf6756e82024-03-08 13:28:2225- [What to do about unretained dangling pointers](./unretained_dangling_ptr_guide.md)
Tom Sepezac5ddb32023-06-29 17:21:4526- [I can't figure out which pointer is dangling](I-can_t-figure-out-which-pointer-is-dangling)
Tom Sepezb5932eac2023-03-24 00:20:3127- [FAQ - Why dangling pointers matter](#faq-why-dangling-pointers-matter)
Arthur Sonzognic66acfe62022-12-08 14:27:2828
Tom Sepezb5932eac2023-03-24 00:20:3129## What to do about dangling pointers
30
31There are a few common cases here.
32
33### `Case 1` I don’t own the affected component
Arthur Sonzognic66acfe62022-12-08 14:27:2834
35If you do not directly own the affected component, you **don't need** to solve
36the issue yourself… though doing so is a great way to learn about and improve
37the codebase.
38
39Please annotate the pointer with `DanglingUntriaged`, and indicate the test case
40that can be used to reproduce.
41```cpp
42// Dangling when executing TestSuite.TestCase on Windows:
43raw_ptr<T, DanglingUntriaged> ptr_;
44```
45Opening and filling a P2 bug is a nice thing to do, but it is not required.
46
47**Rationale:**
48Engineers might uncover new dangling pointers, by testing new code paths.
49Knowing about dangling pointers is a purely positive increment. In some cases,
50the affected component belongs to a different team. We don’t want to disrupt
Charlie Reis6d4943272023-01-25 22:46:3151engineers achieving their primary goal, if they only “discover” a dangling
52pointer. Annotating the pointer makes the issue visible directly in the code,
Arthur Sonzognic66acfe62022-12-08 14:27:2853improving our knowledge of Chrome.
54
Tom Sepezb5932eac2023-03-24 00:20:3155### `Case 2` The dangling pointer does not own the deleted object
Arthur Sonzognic66acfe62022-12-08 14:27:2856
Tom Sepezb5932eac2023-03-24 00:20:3157#### Incorrect destruction order
Arthur Sonzognic66acfe62022-12-08 14:27:2858
59This represents ~25% of the dangling pointers.
60
Tom Sepez16c9bfa2023-04-03 16:30:4161In the majority of cases, this happens when dependent objects are destroyed in
Bartek Nowierski2e732032023-01-19 21:34:4162the wrong order in a class, causing the dependency to be released first, thus
Arthur Sonzognic66acfe62022-12-08 14:27:2863creating a dangling pointer in the other.
64
Tom Sepez16c9bfa2023-04-03 16:30:4165Recall that destructors destroy class members in the inverse order of their
66appearance. It is usually possible to resolve destruction order issues by
67re-ordering member declarations so that members which need to live longer come
68first. It is important to order members correctly to prevent pre-existing and
69future UAF in destructors.
Arthur Sonzognic66acfe62022-12-08 14:27:2870
71See [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 Sepez16c9bfa2023-04-03 16:30:4173One good practice is make owning members (`unique_ptr<>`, `scoped_refptr<>`)
74appear before unowned members (`raw_ptr<>`), and to make the unowned members
75appear last in the class, since the unowned members often refer to resources
76owned by the owning members or the class itself.
77
Lukasz Anforowiczd6d7ab92023-05-23 17:16:0878One sub-category of destruction order issues is related to `KeyedService`s which
79need 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)
81and
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)
83to 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 Sepezb5932eac2023-03-24 00:20:3187#### Observer callback
Arthur Sonzognic66acfe62022-12-08 14:27:2888
89This represents ~4% of the dangling pointers.
90
91It is important to clear the pointer when the object is about to be deleted.
92Chrome uses the observer pattern heavily. In some cases, the observer does not
93clear its pointer toward the observed class when notified of its destruction.
94
Tom Sepez923050c2023-04-03 17:16:2595#### Cyclic pointers
96
97Sometimes two (or more) objects can have unowned references between each other,
98with neither one owning the other. This creates a situation where neither can
99be deleted without creating a dangling pointer unless some action is first
100taken to break the cycle. In order to create such a cycle in the first place, a
101call to a "setter" method or equivalent must have occurred handing one object
102a reference to the other. Balance out this call with another call to the same
103setter, but passing nullptr instead, before the destroying the other object.
104
Tom Sepezb5932eac2023-03-24 00:20:31105#### Challenging lifespan
Arthur Sonzognic66acfe62022-12-08 14:27:28106
107It can be challenging to deal with an object's lifespan. Sometimes, the lifetime
108of two objects are completely different.
109
110Removing the pointer may be a good thing to do. Sometimes, it can be replaced
111by:
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 Sepezb5932eac2023-03-24 00:20:31120#### Fallback solution
Arthur Sonzognic66acfe62022-12-08 14:27:28121
122As a last resort, when the situation is perfectly understood, and you believe it
Charlie Reis6d4943272023-01-25 22:46:31123is better to let the pointer dangle, the raw_ptr can be annotated with
Arthur Sonzognic66acfe62022-12-08 14:27:28124`DisableDanglingPtrDetection`. A comment explaining why this is safe must be
125added.
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 Nowierski2e732032023-01-19 21:34:41135// When using it, please provide a justification about what guarantees that it
136// will never be dereferenced after becoming dangling.
Arthur Sonzognic66acfe62022-12-08 14:27:28137```
138
139**In emergency situations**: `DanglingUntriaged` can be used similarly, in case
140your patch needs to land as soon as possible.
141
Tom Sepezb5932eac2023-03-24 00:20:31142### `Case 3` The pointer manages ownership over the object
Arthur Sonzognic66acfe62022-12-08 14:27:28143
144raw_ptr, just like raw pointers T*, are not meant to keep an object alive. It is
145preferable not to manage memory manually using them and new/delete. Calling
146delete on a raw_ptr will cause the raw_ptr to become immediately dangling.
147
Tom Sepezb5932eac2023-03-24 00:20:31148#### Smart pointers
Arthur Sonzognic66acfe62022-12-08 14:27:28149
150First, 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 Sepezb5932eac2023-03-24 00:20:31156#### Object vended from C API
Arthur Sonzognic66acfe62022-12-08 14:27:28157
158In some cases, the object is vended by a C API. It means new/delete are not
159used, but some equivalent API. In this case, it still makes sense to use a
160unique_ptr, but with a custom deleter:
161[example](https://chromium-review.googlesource.com/c/chromium/src/+/3650764).
162
163For 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 Sepezb5932eac2023-03-24 00:20:31168#### Object conditionally owned
Arthur Sonzognic66acfe62022-12-08 14:27:28169
170In some cases, the raw_ptr conditionally owns the memory, depending on some
171logic 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
175std::unique_ptr<T> t_owned_;
176raw_ptr<T> t_; // Reference `t_owned_` or an external object.
177```
178
Tom Sepezb5932eac2023-03-24 00:20:31179#### Fallback solution
Arthur Sonzognic66acfe62022-12-08 14:27:28180
181If no solution with a smart pointer is found:
182
183You can use `raw_ptr::ClearAndDelete()` or `raw_ptr::ClearAndDeleteArray()` to
184clear 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
191When delete is not used, but the deletion happens through some C API or some
192external manager, the `raw_ptr::ExtractAsDangling()` can be used to clear the
193pointer, and return the pointer to be passed to the API. The return value must
194not be saved, thus outliving the line where it was called.
195
196This method should be used wisely, and only if there is no other way to rework
197the dangling raw_ptr.
198
199|Before|After |
200|--|--|
201|`ExternalAPIDelete(ptr_);`|`ExternalAPIDelete(ptr_.ExtractAsDangling());`|
Tom Sepezb5932eac2023-03-24 00:20:31202
Tom Sepezac5ddb32023-06-29 17:21:45203## I can't figure out which pointer is dangling
204
205Usually this is a matter of straightforward reasoning, but should all else
206fail, another option is to re-build with the alternative dangling pointer
207detector as described in
208[docs/dangling_ptr.md](./dangling_ptr.md#alternative-dangling-pointer-detector-experimental).
209This will show the stacks for object creation, object destruction, and the
210destruction of the object containing the dangling ptr member.
211
Tom Sepezb5932eac2023-03-24 00:20:31212## FAQ - Why dangling pointers matter
213
214Q. Gee, this is a raw pointer. Does destroying it actually do anything?
215
Tom Sepez4e7f2ef2023-03-24 17:02:16216A. Yes. On some platforms `raw_ptr<T>` is a synonym for `T*`, but on many
217platforms (more every day) `raw_ptr<T>` is the interface to PartitionAlloc’s
218BackupRefPtr (BRP) Use-after-Free (UaF) protection mechanism. Destroying the
219pointer thus actually performs internal bookkeeping and may also null
220the pointer on destruction to trap use-after-destruct errors.
Tom Sepezb5932eac2023-03-24 00:20:31221
222Q. So BRP mitigates these dangling pointers. What's the problem with just
223keeping them if they are not used?
224
225A. When an object is deleted under BRP, if there are any dangling references
226remaining, the object must be quarantined and overwritten with a “zapped”
227pattern as opposed to being simply freed. This costs cycles and memory
228pressure. Even worse, it is still possible that the raw_ptr is converted to
Tom Sepez4e7f2ef2023-03-24 17:02:16229`T*` and used with the zap value, or even used after the quarantine is
Tom Sepezb5932eac2023-03-24 00:20:31230gone. Hence we are inventing mechanisms for finding dangling pointers so we
231may remove the ones we know about. BRP will then make it harder to write
232exploits with the ones we don’t know about in the wild.
233
234Q. Why do we care if this is “just a test”?
235
236A. Hitting dangling pointer warnings in tests blocks digging into actual
237cases further in the code.
238
239Q. Why should I think about lifetimes, anyway?
240
241A. Holding an address that we aren’t allowed to de-reference is a bad
242practice. It is a security and stability risk, a source of bugs that are
243extremely difficult to diagnose, and a hazard for future coders. Also see
244e.g. https://discourse.llvm.org/t/rfc-lifetime-annotations-for-c/61377 for
245some ideas about how this might play out in the future.