1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225
  226
  227
  228
  229
  230
  231
  232
  233
  234
  235
  236
  237
  238
  239
  240
  241
  242
  243
  244
  245

docs / dangling_ptr_guide.md [blame]

# Dangling Pointer Guide

A dangling pointer has been found in your patch? This doc will help you fix it.

> This document can also be commented upon:
https://docs.google.com/document/d/19yyWdCQB5KwYtRE5cwadNf30jNSSWwMbim1KJjc-eZQ/edit#

See also the general instructions about the dangling pointer detector:
[docs/dangling_ptr.md](./dangling_ptr.md)

**Table of contents**
- [What to do about dangling pointers](#what-to-do-about-dangling-pointers)
  - [`Case 1` I don’t own the affected component](#i-don_t-own-the-affected-component)
  - [`Case 2` The dangling pointer does not own the deleted object.](#the-dangling-pointer-does-not-own-the-deleted-object)
    - [Incorrect destruction order](#incorrect-destruction-order)
    - [Observer callback](#observer-callback)
    - [Challenging lifespan](#challenging-lifespan)
    - [Cyclic pointers](#cyclic-pointers)
    - [Fallback solution](#fallback-solution)
  - [`Case 3` The pointer manages ownership over the object](#the-pointer-manages-ownership-over-the-object)
    - [Smart pointers](#smart-pointers)
    - [Object vended from C API](#object-vended-from-c-api)
    - [Object conditionally owned](#object-conditionally-owned)
    - [Fallback solution](#fallback-solution-1)
- [What to do about unretained dangling pointers](./unretained_dangling_ptr_guide.md)
- [I can't figure out which pointer is dangling](I-can_t-figure-out-which-pointer-is-dangling)
- [FAQ - Why dangling pointers matter](#faq-why-dangling-pointers-matter)

## What to do about dangling pointers

There are a few common cases here.

### `Case 1` I don’t own the affected component

If you do not directly own the affected component, you **don't need** to solve
the issue yourself… though doing so is a great way to learn about and improve
the codebase.

Please annotate the pointer with `DanglingUntriaged`, and indicate the test case
that can be used to reproduce.
```cpp
// Dangling when executing TestSuite.TestCase on Windows:
raw_ptr<T, DanglingUntriaged> ptr_;
```
Opening and filling a P2 bug is a nice thing to do, but it is not required.

**Rationale:**
Engineers might uncover new dangling pointers, by testing new code paths.
Knowing about dangling pointers is a purely positive increment. In some cases,
the affected component belongs to a different team. We don’t want to disrupt
engineers achieving their primary goal, if they only “discover” a dangling
pointer. Annotating the pointer makes the issue visible directly in the code,
improving our knowledge of Chrome.

### `Case 2` The dangling pointer does not own the deleted object

#### Incorrect destruction order

This represents ~25% of the dangling pointers.

In the majority of cases, this happens when dependent objects are destroyed in
the wrong order in a class, causing the dependency to be released first, thus
creating a dangling pointer in the other.

Recall that destructors destroy class members in the inverse order of their
appearance. It is usually possible to resolve destruction order issues by
re-ordering member declarations so that members which need to live longer come
first. It is important to order members correctly to prevent pre-existing and
future UAF in destructors.

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.

One good practice is make owning members (`unique_ptr<>`, `scoped_refptr<>`)
appear before unowned members (`raw_ptr<>`), and to make the unowned members
appear last in the class, since the unowned members often refer to resources
owned by the owning members or the class itself.

One sub-category of destruction order issues is related to `KeyedService`s which
need to correctly
[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)
and
[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)
to their dependencies in their
[`Shutdown`](https://source.chromium.org/chromium/chromium/src/+/main:components/keyed_service/core/keyed_service.h;l=36-39;drc=8ba1bad80dc22235693a0dd41fe55c0fd2dbdabd) method
(i.e. before their destructor runs).

#### Observer callback

This represents ~4% of the dangling pointers.

It is important to clear the pointer when the object is about to be deleted.
Chrome uses the observer pattern heavily. In some cases, the observer does not
clear its pointer toward the observed class when notified of its destruction.

#### Cyclic pointers

Sometimes two (or more) objects can have unowned references between each other,
with neither one owning the other. This creates a situation where neither can
be deleted without creating a dangling pointer unless some action is first
taken to break the cycle. In order to create such a cycle in the first place, a
call to a "setter" method or equivalent must have occurred handing one object
a reference to the other. Balance out this call with another call to the same
setter, but passing nullptr instead, before the destroying the other object.

#### Challenging lifespan

It can be challenging to deal with an object's lifespan. Sometimes, the lifetime
of two objects are completely different.

Removing the pointer may be a good thing to do. Sometimes, it can be replaced
by:
-   Passing the pointer as a function argument instead of getting access to it
    from a long-lived field.
-   A token / ID. For instance
    [blink::LocalFrameToken](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/public/common/tokens/tokens.h;drc=898134d0d40dbbcd308e7d51655518ac7c6392b5;l=34),
    [content::GlobalRenderFrameHostId](https://source.chromium.org/chromium/chromium/src/+/main:content/public/browser/global_routing_id.h;drc=898134d0d40dbbcd308e7d51655518ac7c6392b5;l=64)
-   A [WeakPtr](https://docs.google.com/document/d/11YYsyPF9rQv_QFf982Khie3YuNPXV0NdhzJPojpZfco/edit?resourcekey=0-h1dr1uDzZGU7YWHth5TRAQ#bookmark=id.geuhahom0twd)
-   [Calling a function](https://docs.google.com/document/d/11YYsyPF9rQv_QFf982Khie3YuNPXV0NdhzJPojpZfco/edit?resourcekey=0-h1dr1uDzZGU7YWHth5TRAQ#heading=h.wh99ri7bbq23)

#### Fallback solution

As a last resort, when the situation is perfectly understood, and you believe it
is better to let the pointer dangle, the raw_ptr can be annotated with
`DisableDanglingPtrDetection`. A comment explaining why this is safe must be
added.

```cpp
// DisableDanglingPtrDetection option for raw_ptr annotates
// "intentional-and-safe" dangling pointers. It is meant to be used at the
// margin, only if there is no better way to re-architecture the code.
//
// Usage:
// raw_ptr<T, DisableDanglingPtrDetection> dangling_ptr;
//
// When using it, please provide a justification about what guarantees that it
// will never be dereferenced after becoming dangling.
```

**In emergency situations**: `DanglingUntriaged` can be used similarly, in case
your patch needs to land as soon as possible.

### `Case 3` The pointer manages ownership over the object

raw_ptr, just like raw pointers T*, are not meant to keep an object alive. It is
preferable not to manage memory manually using them and new/delete. Calling
delete on a raw_ptr will cause the raw_ptr to become immediately dangling.

#### Smart pointers

First, consider replacing the raw_ptr with a smart pointer:

-   std::unique_ptr (See
    [example](https://docs.google.com/document/d/11YYsyPF9rQv_QFf982Khie3YuNPXV0NdhzJPojpZfco/edit?resourcekey=0-h1dr1uDzZGU7YWHth5TRAQ#heading=h.6itq8twigqt3))
-   scoped_refptr

#### Object vended from C API

In some cases, the object is vended by a C API. It means new/delete are not
used, but some equivalent API. In this case, it still makes sense to use a
unique_ptr, but with a custom deleter:
[example](https://chromium-review.googlesource.com/c/chromium/src/+/3650764).

For most common objects, a wrapper already exists:
[base::ScopedFILE](https://source.chromium.org/chromium/chromium/src/+/main:base/files/scoped_file.h;drc=898134d0d40dbbcd308e7d51655518ac7c6392b5;l=105),
[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),
..

#### Object conditionally owned

In some cases, the raw_ptr conditionally owns the memory, depending on some
logic or some `is_owned` boolean. This can still use a unique_ptr
([example](https://chromium-review.googlesource.com/c/chromium/src/+/3829302))

```cpp
std::unique_ptr<T> t_owned_;
raw_ptr<T> t_; // Reference `t_owned_` or an external object.
```

#### Fallback solution

If no solution with a smart pointer is found:

You can use `raw_ptr::ClearAndDelete()` or `raw_ptr::ClearAndDeleteArray()` to
clear the pointer and free the object in a single operation.

|Before|After |
|--|--|
| `delete ptr_` | `ptr_.ClearAndDelete();`|
| `delete array_` | `ptr_.ClearAndDeleteArray();`|

When delete is not used, but the deletion happens through some C API or some
external manager, the `raw_ptr::ExtractAsDangling()` can be used to clear the
pointer, and return the pointer to be passed to the API. The return value must
not be saved, thus outliving the line where it was called.

This method should be used wisely, and only if there is no other way to rework
the dangling raw_ptr.

|Before|After |
|--|--|
|`ExternalAPIDelete(ptr_);`|`ExternalAPIDelete(ptr_.ExtractAsDangling());`|

## I can't figure out which pointer is dangling

Usually this is a matter of straightforward reasoning, but should all else
fail, another option is to re-build with the alternative dangling pointer
detector as described in
[docs/dangling_ptr.md](./dangling_ptr.md#alternative-dangling-pointer-detector-experimental).
This will show the stacks for object creation, object destruction, and the
destruction of the object containing the dangling ptr member.

## FAQ - Why dangling pointers matter

Q. Gee, this is a raw pointer. Does destroying it actually do anything?

A. Yes. On some platforms `raw_ptr<T>` is a synonym for `T*`, but on many
platforms (more every day) `raw_ptr<T>` is the interface to PartitionAlloc’s
BackupRefPtr (BRP) Use-after-Free (UaF) protection mechanism. Destroying the
pointer thus actually performs internal bookkeeping and may also null
the pointer on destruction to trap use-after-destruct errors.

Q. So BRP mitigates these dangling pointers. What's the problem with just
keeping them if they are not used?

A. When an object is deleted under BRP, if there are any dangling references
remaining, the object must be quarantined and overwritten with a “zapped”
pattern as opposed to being simply freed. This costs cycles and memory
pressure. Even worse, it is still possible that the raw_ptr is converted to
`T*` and used with the zap value, or even used after the quarantine is
gone. Hence we are inventing mechanisms for finding dangling pointers so we
may remove the ones we know about. BRP will then make it harder to write
exploits with the ones we don’t know about in the wild.

Q. Why do we care if this is “just a test”?

A. Hitting dangling pointer warnings in tests blocks digging into actual
cases further in the code.

Q. Why should I think about lifetimes, anyway?

A. Holding an address that we aren’t allowed to de-reference is a bad
practice. It is a security and stability risk, a source of bugs that are
extremely difficult to diagnose, and a hazard for future coders. Also see
e.g. https://discourse.llvm.org/t/rfc-lifetime-annotations-for-c/61377 for
some ideas about how this might play out in the future.