0

DanglingPtr: Add an option to list dangling ptr.

To track progress, I would need a way to list all the tasks. That is to
say, every occurrences of dangling pointers. I would need to produce tables
similar to this one:
https://docs.google.com/spreadsheets/d/1BAfz1uHhMkFe2sK2vyTHsJ733VQUhY9v2M4HxOwaM7E/edit?resourcekey=0-H9SwkJTMbBxQPKUEuIrwow#gid=0

Behind the feature flag "LogDanglingPtr", this patch turns crash into logging the signature of the occurrence. From the StackTraces, the
relevant callers of free() and raw_ptr.reset() are extracted. Those
are the "signature" of the event. This is easier to process,
compared to the full pair of StackTraces.

This patch add the option "LogDanglingPtr", which makes Chrome to stop
crashing when it encounter a dangling raw_ptr. Instead it prints the
signature of the dangling ptr::
- where it was memory was `free()`.
- where the dangling raw_ptr was released.

A documentation in `docs/dangling_ptr.md` is created.

Bug: 1291138
Change-Id: I05f08dc1c6ab62f82243cadbb8e862260ec54c98
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3575947
Reviewed-by: Bartek Nowierski <bartekn@chromium.org>
Commit-Queue: Arthur Sonzogni <arthursonzogni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1003214}
This commit is contained in:
Arthur Sonzogni
2022-05-13 18:01:14 +00:00
committed by Chromium LUCI CQ
parent 264facf628
commit c59a2cc9f9
4 changed files with 156 additions and 12 deletions

@ -11,6 +11,11 @@
namespace base {
namespace features {
// When set, instead of crashing when encountering a dangling raw_ptr, the
// signatures of the two stacktraces are logged. This is meant to be used only
// by Chromium developers. See /docs/dangling_ptr.md
const BASE_EXPORT Feature kPartitionAllocDanglingPtrRecord{
"PartitionAllocDanglingPtrRecord", FEATURE_DISABLED_BY_DEFAULT};
#if defined(PA_ALLOW_PCSCAN)
// If enabled, PCScan is turned on by default for all partitions that don't
// disable it explicitly.

@ -17,6 +17,7 @@ struct Feature;
namespace features {
extern const BASE_EXPORT Feature kPartitionAllocDanglingPtrRecord;
#if defined(PA_ALLOW_PCSCAN)
extern const BASE_EXPORT Feature kPartitionAllocPCScan;
#endif // defined(PA_ALLOW_PCSCAN)

@ -30,6 +30,8 @@
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/thread_annotations.h"
#include "base/threading/platform_thread.h"
@ -374,6 +376,7 @@ DanglingRawPtrBuffer g_stack_trace_buffer GUARDED_BY(g_stack_trace_buffer_lock);
void DanglingRawPtrDetected(uintptr_t id) {
// This is called from inside the allocator. No allocation is allowed.
internal::PartitionAutoLock guard(g_stack_trace_buffer_lock);
#if DCHECK_IS_ON()
@ -392,37 +395,101 @@ void DanglingRawPtrDetected(uintptr_t id) {
// enough.
}
// From the StackTrace recorded in |DanglingRawPtrDetected|, extract the one
// whose id match |id|. Return nullopt if not found.
absl::optional<debug::StackTrace> TakeStackTrace(uintptr_t id) {
internal::PartitionAutoLock guard(g_stack_trace_buffer_lock);
for (absl::optional<StackTraceWithID>& entry : g_stack_trace_buffer) {
if (entry && entry->id == id) {
debug::StackTrace stack_trace = std::move(entry->stack_trace);
entry = absl::nullopt;
return stack_trace;
}
}
return absl::nullopt;
}
// Extract from the StackTrace output, the signature of the pertinent caller.
// This function is meant to be used only by Chromium developers, to list what
// are all the dangling raw_ptr occurrences in a table.
std::string ExtractDanglingPtrSignature(std::string stacktrace) {
LOG(ERROR) << stacktrace;
std::vector<StringPiece> lines = SplitStringPiece(
stacktrace, "\r\n", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
// We are looking for the callers of the function releasing the raw_ptr and
// freeing memory:
const StringPiece callees[] = {
"internal::BackupRefPtrImpl<>::ReleaseInternal()",
"internal::PartitionFree()",
"base::(anonymous namespace)::FreeFn()",
};
size_t caller_index = 0;
for (size_t i = 0; i < lines.size(); ++i) {
for (const auto& callee : callees) {
if (lines[i].find(callee) != StringPiece::npos) {
caller_index = i + 1;
}
}
}
if (caller_index >= lines.size()) {
return "undefined";
}
StringPiece caller = lines[caller_index];
// |callers| follows the following format:
//
// #4 0x56051fe3404b content::GeneratedCodeCache::DidCreateBackend()
// -- -------------- -----------------------------------------------
// Depth Address Function
size_t address_start = caller.find(' ');
size_t function_start = caller.find(' ', address_start + 1);
if (address_start == caller.npos || function_start == caller.npos) {
return "undefined";
}
return std::string(caller.substr(function_start + 1));
}
void DanglingRawPtrReleased(uintptr_t id) {
// This is called from raw_ptr<>'s release operation. Making allocations is
// allowed. In particular, symbolizing and printing the StackTraces may
// allocate memory.
internal::PartitionAutoLock guard(g_stack_trace_buffer_lock);
debug::StackTrace stack_trace_release;
absl::optional<debug::StackTrace> stack_trace_free = TakeStackTrace(id);
absl::optional<std::string> stack_trace_free;
std::string stack_trace_release = base::debug::StackTrace().ToString();
for (absl::optional<StackTraceWithID>& entry : g_stack_trace_buffer) {
if (entry && entry->id == id) {
stack_trace_free = entry->stack_trace.ToString();
entry = absl::nullopt;
break;
if (FeatureList::IsEnabled(features::kPartitionAllocDanglingPtrRecord)) {
if (stack_trace_free) {
LOG(ERROR) << StringPrintf(
"[DanglingSignature]\t%s\t%s",
ExtractDanglingPtrSignature(stack_trace_release.ToString()).c_str(),
ExtractDanglingPtrSignature(stack_trace_free->ToString()).c_str());
} else {
LOG(ERROR) << StringPrintf(
"[DanglingSignature]\t%s\tmissing-stacktrace",
ExtractDanglingPtrSignature(stack_trace_release.ToString()).c_str());
}
return;
}
if (stack_trace_free) {
LOG(ERROR) << base::StringPrintf(
LOG(ERROR) << StringPrintf(
"Detected dangling raw_ptr with id=0x%016" PRIxPTR
":\n\n"
"The memory was freed at:\n%s\n"
"The dangling raw_ptr was released at:\n%s",
id, stack_trace_free->c_str(), stack_trace_release.c_str());
id, stack_trace_free->ToString().c_str(),
stack_trace_release.ToString().c_str());
} else {
LOG(ERROR) << base::StringPrintf(
LOG(ERROR) << StringPrintf(
"Detected dangling raw_ptr with id=0x%016" PRIxPTR
":\n\n"
"It was not recorded where the memory was freed.\n\n"
"The dangling raw_ptr was released at:\n%s",
id, stack_trace_release.c_str());
id, stack_trace_release.ToString().c_str());
}
IMMEDIATE_CRASH();
}

71
docs/dangling_ptr.md Normal file

@ -0,0 +1,71 @@
# Danling pointer detector.
Dangling pointers are not a problem unless they are dereferenced and used.
However, they are a source of UaF bugs and highly discouraged unless you are
100% confident that they are never dereferenced after the pointed-to objects are
freed.
Behind build flags, Chrome implements a dangling pointer detector. It causes
Chrome to crash, whenever a raw_ptr becomes dangling:
```cpp
raw_ptr<T> ptr_never_dangling;
```
On the other hand, we cannot simply ban all the usage of dangling pointers
because there are valid use cases. The `DisableDanglingPtrDetection` option can
be used to annotate "intentional-and-safe" dangling pointers. It is meant to be
used as a last resort, only if there is no better way to re-architecture the
code.
```cpp
raw_ptr<T, DisableDanglingPtrDetection> ptr_may_dangle;
```
# How to check for dangling pointers?
It is gated behind both build and runtime flags:
## Build flags
```bash
gn args ./out/dangling/
```
```gn
use_goma = true
is_debug = false
dcheck_always_on = true
use_backup_ref_ptr = true
use_partition_alloc = true
enable_dangling_raw_ptr_checks = true
```
## Runtime flags
```bash
./out/dangling/content_shell --enable-features=PartitionAllocBackupRefPtr
```
Chrome will crash on the first dangling raw_ptr detected.
# Record a list of signatures.
Instead of immediately crashing, you can list all the dangling raw_ptr
occurrences. This is gated behind the `PartitionAllocDanglingPtrRecord` feature.
For instance:
```bash
./out/dangling/content_shell --enable-features=PartitionAllocBackupRefPtr,PartitionAllocDanglingPtrRecord |& tee output
```
The logs can be filtered and transformed into a tab separated table:
```bash
cat output \
| grep "DanglingSignature" \
| cut -f2,3 \
| sort \
| uniq -c \
| sed -E 's/^ *//; s/ /\t/' \
| sort -rn
```
This is used to list issues and track progresses.