1 of 35

MiraclePtr status update

Preventing exploitation of Use-after-Free bugs

in Chromium

bartekn@ arthursonzogni@ keishi@ google.com

1

2 of 35

Impact of Use-after-Free (UaF)

2

3 of 35

Renderer sandbox

Sandbox + Site Isolation:

  • Renderer has no access to cross-site data

3

Web Renderer

mail.com

Web Renderer

evil.com

Browser Process

sandbox

ipc

ipc

sandbox

4 of 35

Renderer sandbox

Sandbox + Site Isolation:

  • Renderer has no access to cross-site data
  • Unless it can escape the sandbox
    • By attacking the OS/kernel
    • Or the Browser Process

4

sandbox

syscalls

Web Renderer

mail.com

Web Renderer

evil.com

Browser Process

Operating System

sandbox

ipc

ipc

5 of 35

Renderer sandbox

Sandbox + Site Isolation:

  • Renderer has no access to cross-site data
  • Unless it can escape the sandbox
    • By attacking the OS/kernel
    • Or the Browser Process�via e.g. Use-after-Free (UaF)
  • UaF bugs = high security severity
    • Assuming they can be exploited
    • For more details see an example from Project Zero

5

Web Renderer

mail.com

Web Renderer

evil.com

Browser Process

Operating System

sandbox

sandbox

ipc

ipc

syscalls

UaF

6 of 35

High+ severity bugs impacting Stable

6

2022 figures are extrapolated from early November to the whole year

7 of 35

High+ severity bugs impacting Stable

7

8 of 35

In-the-wild 0-day exploited bugs

8

data source: Project Zero, augmented with metadata and a few more recent bugs

This is just the tip of the iceberg! (see next slide)

9 of 35

In-the-wild 0-day exploited bugs

9

10 of 35

Non-exploitable UaF bugs

What’s that?

Better not assume unless can be 100% proven.

10

11 of 35

MiraclePtr to the rescue!

11

12 of 35

A flow of a UaF attack

Attack outline:

  • Conjure a dangling pointer
  • Inject attacker-controlled data into the same memory
    • Disruption technique: Don’t free the memory�(as long as dangling pointers exist)
      • “free” is postponed, but timing of destructors is not affected
  • Dereference the dangling pointer

12

13 of 35

A flow of a UaF attack

Attack outline:

  • Conjure a dangling pointer
  • Inject attacker-controlled data into the same memory
    • Disruption technique: Don’t free the memory�(as long as dangling pointers exist)
      • “free” is postponed, but timing of destructors is not affected
  • Dereference the dangling pointer

13

This is where�we currently�focus our efforts

14 of 35

What is MiraclePtr?

BackupRefPtr algorithm:

14

Requires support from allocator (PartitionAlloc)

  • ref_cnt=1 in Alloc(), ++ref_cnt in raw_ptr<T> ctor
  • --ref_cnt in Free() & raw_ptr<T> dtor
  • Free(): if ref_cnt>0
    • quarantines memory
    • poisons memory
      • a future read might lead to a crash
    • the last raw_ptr<T> dtor will release memory

raw_ptr<T>

T* ptr

allocated memory

ref_cnt

T

15 of 35

raw_ptr<T>

raw_ptr<T> does not manage the object lifetime.

Please use raw_ptr<T> in place of a raw C++ pointer T* whenever possible, but…

  • in class and struct fields only (no params, locals or globals)
  • not in Renderer-only code

Details are available in the Usage Guideline.

15

16 of 35

Clang plugin enforcement almost there…

We are developing a clang plugin that will show compiler errors when raw_ptr<T> is not used.

16

../../base/trace_event/traced_value.h(103,18): error:� [chromium-rawptr] Use raw_ptr<T> instead of a raw pointer.

TracedValue* value_;

Example of error

Example of exclusion

struct _HEAP_32 {� // `Heap` is not a raw_ptr<...>, because reinterpret_cast of uninitialized

// memory to raw_ptr can cause ref-counting mismatch.

RAW_PTR_EXCLUSION struct _HEAP_32* Heap;

};

17 of 35

Shipping progress

17

18 of 35

MiraclePtr launched on some platforms!

  • June: Launched in the browser process only, on Windows and Android
  • August: Started experiment for Linux/ChromeOS/Mac
  • September: Launched in all processes except renderer, on Windows and Android

18

19 of 35

Protection coverage

  • On supported platforms, MiraclePtr protects our users from ~60% of UaFs[1] in non-renderer processes.

19

[1] stats based on security issues reported since April

20 of 35

Roadmap update (v1)

✅ BackupRefPtr implemented and thoroughly tested

✅ PartitionAlloc-Everywhere now almost everywhere

✅ Evaluate overhead that can't be tested via field trials (binary A/B experiments)

✅ Land the "Big Rewrite" (T* → raw_ptr<T>)

✅ Evaluate overhead that can be tested using field trials

✅ Enable MiraclePtr protection for Windows/Android

✅ Integrate with ASan

🔁 Enable clang plugin to enforce raw_ptr<T> usage

(per-pointer opt-out option available)

20

✅ done

🔁 in progress

not started (but planned)

21 of 35

Roadmap update (beyond v1)

Increase coverage:

Reference field support (raw_ref<T>)

🔁 Enable MiraclePtr protection for Linux/ChromeOS/Mac

🔁 third_party library support (🔁: PDFium; : ANGLE, SwiftShader, Skia, Dawn, …)

🔁 Renderer process support

Pointers inside templated collections (Collection<raw_ptr<T>>)

raw_ptr<const char>

Increase debuggability:

🔁 Dangling Pointer Detector

🔁 MTE-like algorithm

21

✅ done

🔁 in progress

not started (but planned)

22 of 35

Performance

22

23 of 35

Performance overhead

Performance regressions of BRP enabled for non-renderer processes (shipping) vs disabled[2]

23

Memory.Browser.PrivateMemoryFootprint

50P: +5.12% ◆◆◆◆

95P: +5.04% ◆◆◆◆

99P: +4.68% ◆◆◇◇

Memory.Browser.PrivateMemoryFootprint

50P: +5.25% ◆◆◆◆

95P: +5.40% ◆◆◆◆

99P: +6.39% ◆◆◆◆

Memory.Gpu.PrivateMemoryFootprint

50P: +1.07% ◆◆◆◆

95P: +2.11% ◆◆◆◆

99P: +2.14% ◆◆◆◆

Memory.Total.PrivateMemoryFootprint

50P: +3.04% ◆◆◆◆

95P: +1.94% ◆◆◆◆

99P: +1.32% ◆◆◆◇

Browser.Responsiveness.JankyIntervalsPerThirtySeconds[3]

50P: +0.67% ◆◆◆◆

95P: +3.77% ◆◆◆◆

99P: +3.86% ◆◆◆◆

WebVitals.FirstContentfulPaint3

50P: +0.45% ◆◆◆◇

WebVitals.LargestContentfulPaint2

50P: +0.40% ◆◆◆◇

Graphics.Smoothness.PercentDroppedFrames3.AllSequences

99P: +0.16% ◆◆◇◇

EventLatency.GestureScrollUpdate.Touchscreen.TotalLatency

99P: +0.43% ◆◆◇◇

Startup.Android.Cold.TimeToFirstVisibleContent

50P: +0.90% ◆◆◆◆

Windows

Android

[2] data from M107, showing metrics with more than ◆◆

(number of ◆ shows the level of statistical significance)

Android

[3] main thread contention

24 of 35

Memory overhead

There are three sources of memory overhead

  • Quarantined memory (<0.01% PMF)�Memory referenced by a dangling pointer is “quarantined”, meaning it is reserved even after free()
  • Fragmentation caused by splitting the main PartitionAlloc partition (~1% PMF)�Because we have to enable MiraclePtr for just the browser process, we had to prepare a separate partition for allocations before a bit of process initialization.
  • Ref count added to each allocation (the rest)

24

25 of 35

CPU overhead

  • raw_ptr<T> incurs additional runtime overhead for initialization, destruction, and assignment.
    • Find ref-count and increment/decrement it (atomics)
    • We had keep some pointers as T*, to keep the overhead down
  • No overhead when dereferencing or extracting a pointer

25

26 of 35

Memory overhead

Ref count added to each allocation

We tried two ways of storing the ref count.

“before alloc” is the naive solution of having the ref count at the beginning of the allocation. Ref count is 4 bytes, but due to alignment, we need to add 16 bytes to every allocation.

“prev slot” places the ref count in the padding of the previous slot. Because PartitionAlloc is a bucket-based memory allocator, allocations often have wasted padding space at the end. “Prev slot” uses that space when available.

26

27 of 35

Memory overhead

Ref count added to each allocation

Prev slot puts ref count in the wasted padding space so we didn’t think this would be so big.

But it turned out this is the majority of overhead.

  • There are a lot of small allocations.
  • There are a lot of allocations that have a size of exactly power of two.

27

BackupRefPtr disabled

BackupRefPtr enabled

28 of 35

Dangling Pointer Detector (DPD)

28

29 of 35

What is Dangling Pointer Detector?

BackupRefPtr algorithm:

29

raw_ptr<T>

T* ptr

allocated memory

ref_cnt

T

allocated memory

ref_cnt

T

raw_ptr<T>

T* ptr

ref_cnt

raw_ptr<T, DisableDanglingPointerDetector>

T* ptr

30 of 35

Example

30

// |container_host_| is valid throughout lifetime of |this| because it owns |this|.

- const raw_ptr<ServiceWorkerContainerHost> container_host_;

+ const raw_ptr<ServiceWorkerContainerHost, DanglingUntriaged> container_host_;

Invalid assumption!

31 of 35

31

Remove pointer (24%)

  • 14.6% use unique_ptr
  • 3.13% use weak_ptr
  • Etc: RenderFrameHost ID, scoped_refptr, etc..

Fix destruction order (25%)

Reset pointer (39%)

Other (10%)

32 of 35

Roadmap update

PartitionAlloc support

Report useful debugging info: 2 stacktraces: free() and ptr release + stacktrace.

🔁 Annotate pre-existing dangling pointer (Linux ✅, others 🔁)

🔁 Enforce on the commit queue

🔁 Evaluate overhead that can't be tested via field trials (binary A/B experiment)

Experiment against real users and fuzzers

32

✅ done

🔁 in progress

not started (but planned)

33 of 35

Thank you!

33

34 of 35

Thank you to everyone on the team!

MiraclePtr implementation & testing: keishi@, bartekn@, glazunov@, tasak@, lukasza@, arthursonzogni@, ahijazi@, paulsemel@, pmeuleman@, kdlee@, danakj@, lizeb@

Security advisors: glazunov@, markbrand@, adetaylor@, lukasza@, danakj@, tsepez@

Release: sebmarchand@, srinivassista@, govind@, benmason@, pbommana@

Data analysis advisor: mpearson@

PM: dharani@

Shepherding the project: haraken@

34

35 of 35

Questions?

35