Skip to content

Commit 22ea4ea

Browse files
miktcopybara-github
authored andcommitted
[PA] Purge Scheduler-Loop Quarantine on UI Thread Task Completion
This change introduces an observer to the UI thread's task queues. The observer purges the Scheduler-Loop Quarantine after each task completes. The Scheduler-Loop Quarantine is part of the PA/AC (PartitionAlloc with Advanced Checks) experiment, which aims to mitigate use-after-free (UaF) vulnerabilities by quarantining objects freed on the Browser UI thread. By purging the quarantine after task completion, when stack memory is likely empty, this patch should mitigate the memory regression caused by the PA/AC experiment. Bug: 329027914, 351974425 Change-Id: I0193f986bb8eb064914c7bc36ab3db1fd19c6f46 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5528149 Reviewed-by: Arthur Sonzogni <[email protected]> Reviewed-by: Daniel Cheng <[email protected]> Reviewed-by: Keishi Hattori <[email protected]> Commit-Queue: Mikihito Matsuura <[email protected]> Cr-Commit-Position: refs/heads/main@{#1496114} NOKEYCHECK=True GitOrigin-RevId: 541ac7163f78ec14b1e0afc52145d70f2b44b783
1 parent 57104f1 commit 22ea4ea

File tree

4 files changed

+127
-1
lines changed

4 files changed

+127
-1
lines changed

src/partition_alloc/scheduler_loop_quarantine.cc

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ SchedulerLoopQuarantineBranch<thread_bound>::PurgeInternal(
241241
}
242242
}
243243

244-
freed_count++;
244+
++freed_count;
245245
PA_DCHECK(slot_size > 0);
246246
freed_size_in_bytes += slot_size;
247247
branch_size_in_bytes_ -= slot_size;
@@ -254,6 +254,31 @@ SchedulerLoopQuarantineBranch<thread_bound>::PurgeInternal(
254254
root_->count_.fetch_sub(freed_count, std::memory_order_relaxed);
255255
}
256256

257+
template <bool thread_bound>
258+
void SchedulerLoopQuarantineBranch<thread_bound>::AllowScanlessPurge() {
259+
PA_DCHECK(kThreadBound);
260+
// Always thread-bound; no need to lock.
261+
FakeScopedGuard guard(lock_);
262+
263+
PA_CHECK(disallow_scanless_purge_ > 0);
264+
--disallow_scanless_purge_;
265+
if (disallow_scanless_purge_ == 0) {
266+
// Now scanless purge is allowed. Purging at this timing is more performance
267+
// efficient.
268+
PurgeInternal(0);
269+
}
270+
}
271+
272+
template <bool thread_bound>
273+
void SchedulerLoopQuarantineBranch<thread_bound>::DisallowScanlessPurge() {
274+
PA_DCHECK(kThreadBound);
275+
// Always thread-bound; no need to lock.
276+
FakeScopedGuard guard(lock_);
277+
278+
++disallow_scanless_purge_;
279+
PA_CHECK(disallow_scanless_purge_ > 0); // Overflow check.
280+
}
281+
257282
template <bool thread_bound>
258283
const SchedulerLoopQuarantineConfig&
259284
SchedulerLoopQuarantineBranch<thread_bound>::GetConfigurationForTesting() {

src/partition_alloc/scheduler_loop_quarantine.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ class SchedulerLoopQuarantineBranch {
156156
SlotSpanMetadata* slot_span,
157157
uintptr_t slot_start) PA_LOCKS_EXCLUDED(lock_);
158158

159+
void AllowScanlessPurge();
160+
void DisallowScanlessPurge();
161+
159162
const SchedulerLoopQuarantineConfig& GetConfigurationForTesting();
160163

161164
class ScopedQuarantineExclusion {
@@ -223,6 +226,15 @@ class SchedulerLoopQuarantineBranch {
223226
// Using `std::atomic` here so that other threads can update this value.
224227
std::atomic_size_t branch_capacity_in_bytes_ = 0;
225228

229+
// TODO(http://crbug.com/329027914): Implement stack scanning, to be performed
230+
// when this value is non-zero.
231+
//
232+
// Currently, a scanless purge is always performed. However, this value is
233+
// still used as a hint to determine safer purge timings for memory
234+
// optimization.
235+
uint32_t disallow_scanless_purge_ PA_GUARDED_BY(lock_) = 0;
236+
237+
// Debug and testing data.
226238
#if PA_BUILDFLAG(DCHECKS_ARE_ON)
227239
std::atomic_bool being_destructed_ = false;
228240
#endif // PA_BUILDFLAG(DCHECKS_ARE_ON)

src/partition_alloc/scheduler_loop_quarantine_support.cc

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,50 @@ ScopedSchedulerLoopQuarantineExclusion::
2121
ScopedSchedulerLoopQuarantineExclusion::
2222
~ScopedSchedulerLoopQuarantineExclusion() {}
2323

24+
SchedulerLoopQuarantineScanPolicyUpdater::
25+
SchedulerLoopQuarantineScanPolicyUpdater() = default;
26+
27+
SchedulerLoopQuarantineScanPolicyUpdater::
28+
~SchedulerLoopQuarantineScanPolicyUpdater() {
29+
// Ensure all `DisallowScanlessPurge()` calls were followed by
30+
// `AllowScanlessPurge()`.
31+
PA_CHECK(disallow_scanless_purge_calls_ == 0);
32+
}
33+
34+
void SchedulerLoopQuarantineScanPolicyUpdater::DisallowScanlessPurge() {
35+
disallow_scanless_purge_calls_++;
36+
PA_CHECK(0 < disallow_scanless_purge_calls_); // Overflow check.
37+
38+
auto* branch = GetQuarantineBranch();
39+
PA_CHECK(branch);
40+
branch->DisallowScanlessPurge();
41+
}
42+
43+
void SchedulerLoopQuarantineScanPolicyUpdater::AllowScanlessPurge() {
44+
PA_CHECK(0 < disallow_scanless_purge_calls_);
45+
disallow_scanless_purge_calls_--;
46+
47+
auto* branch = GetQuarantineBranch();
48+
PA_CHECK(branch);
49+
branch->AllowScanlessPurge();
50+
}
51+
52+
internal::ThreadBoundSchedulerLoopQuarantineBranch*
53+
SchedulerLoopQuarantineScanPolicyUpdater::GetQuarantineBranch() {
54+
ThreadCache* tcache = ThreadCache::EnsureAndGet();
55+
if (!ThreadCache::IsValid(tcache)) {
56+
return nullptr;
57+
}
58+
59+
uintptr_t current_tcache_addr = reinterpret_cast<uintptr_t>(tcache);
60+
if (tcache_address_ == 0) {
61+
tcache_address_ = current_tcache_addr;
62+
} else {
63+
PA_CHECK(tcache_address_ == current_tcache_addr);
64+
}
65+
return &tcache->GetSchedulerLoopQuarantineBranch();
66+
}
67+
2468
namespace internal {
2569
ScopedSchedulerLoopQuarantineBranchAccessorForTesting::
2670
ScopedSchedulerLoopQuarantineBranchAccessorForTesting(

src/partition_alloc/scheduler_loop_quarantine_support.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
#ifndef PARTITION_ALLOC_SCHEDULER_LOOP_QUARANTINE_SUPPORT_H_
1111
#define PARTITION_ALLOC_SCHEDULER_LOOP_QUARANTINE_SUPPORT_H_
1212

13+
#include <map>
1314
#include <optional>
1415
#include <variant>
1516

1617
#include "partition_alloc/build_config.h"
1718
#include "partition_alloc/buildflags.h"
19+
#include "partition_alloc/partition_alloc_base/compiler_specific.h"
1820
#include "partition_alloc/partition_root.h"
1921
#include "partition_alloc/scheduler_loop_quarantine.h"
2022
#include "partition_alloc/thread_cache.h"
@@ -39,6 +41,49 @@ class PA_COMPONENT_EXPORT(PARTITION_ALLOC)
3941
instance_;
4042
};
4143

44+
// An utility class to update Scheduler-Loop Quarantine's purging strategy for
45+
// the current thread. By default it uses "scanless" purge for best performance.
46+
// However, it also supports stack-scanning before purging to verify there is no
47+
// dangling pointer in stack memory. Stack-scanning comes with some performance
48+
// cost, but there is security benefit. This class can be used to switch between
49+
// these two strategies dynamically.
50+
// An example usage is to allow scanless purge only around "stack bottom".
51+
// We can safely assume there is no dangling pointer if stack memory is barely
52+
// used thus safe to purge quarantine.
53+
// At Chrome layer it is task execution and we expect
54+
// `DisallowScanlessPurge()` to be called before task execution and
55+
// `AllowScanlessPurge()` after. Since there is no unified way to hook
56+
// task execution in Chrome, we provide an abstract utility here.
57+
// This class is not thread-safe.
58+
//
59+
// TODO(http://crbug.com/329027914): stack-scanning is not implemented yet
60+
// and this class is effectively "disallow any purge unless really needed".
61+
// It still gives some hints on purging timing for memory efficiency.
62+
class PA_COMPONENT_EXPORT(PARTITION_ALLOC)
63+
SchedulerLoopQuarantineScanPolicyUpdater {
64+
public:
65+
SchedulerLoopQuarantineScanPolicyUpdater();
66+
~SchedulerLoopQuarantineScanPolicyUpdater();
67+
68+
// Disallows scanless purge and performs stack-scanning when needed.
69+
// Can be called multiple times, but each call to this function must be
70+
// followed by `AllowScanlessPurge()`.
71+
void DisallowScanlessPurge();
72+
73+
// Re-activate scanless purge. `DisallowScanlessPurge()` must be called prior
74+
// to use of this function. This may trigger purge immediately.
75+
void AllowScanlessPurge();
76+
77+
private:
78+
PA_ALWAYS_INLINE internal::ThreadBoundSchedulerLoopQuarantineBranch*
79+
GetQuarantineBranch();
80+
81+
uint32_t disallow_scanless_purge_calls_ = 0;
82+
83+
// An address of `ThreadCache` instance works as a thread ID.
84+
uintptr_t tcache_address_ = 0;
85+
};
86+
4287
namespace internal {
4388

4489
class PA_COMPONENT_EXPORT(PARTITION_ALLOC)

0 commit comments

Comments
 (0)