cppgc: Properly clear (Weak)Peristent and WeakMember pointers
The CL addresses two issues with (Weak)Persistent and WeakMember: 1. (Weak)Persistent pointers are cleared on heap teardown. Before this CL the pointers would contain stale values which could lead to UAF. 2. WeakPersistent and WeakMember are cleared using a combination of internal clearing methods and mutable fields which avoids the use of const_cast<>. Bug: chromium:1056170 Change-Id: Ibf2b0f0856771b4f6906608cde13a6d43ebf81f3 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2248190 Reviewed-by: Omer Katz <omerkatz@chromium.org> Reviewed-by: Anton Bikineev <bikineev@chromium.org> Commit-Queue: Michael Lippautz <mlippautz@chromium.org> Cr-Commit-Position: refs/heads/master@{#68394}
This commit is contained in:
parent
a6394f0640
commit
e0c1a349ea
@ -56,6 +56,11 @@ class PersistentNode final {
|
||||
|
||||
bool IsUsed() const { return trace_; }
|
||||
|
||||
void* owner() const {
|
||||
CPPGC_DCHECK(IsUsed());
|
||||
return owner_;
|
||||
}
|
||||
|
||||
private:
|
||||
// PersistentNode acts as a designated union:
|
||||
// If trace_ != nullptr, owner_ points to the corresponding Persistent handle.
|
||||
@ -67,11 +72,13 @@ class PersistentNode final {
|
||||
TraceCallback trace_ = nullptr;
|
||||
};
|
||||
|
||||
class V8_EXPORT PersistentRegion {
|
||||
class V8_EXPORT PersistentRegion final {
|
||||
using PersistentNodeSlots = std::array<PersistentNode, 256u>;
|
||||
|
||||
public:
|
||||
PersistentRegion() = default;
|
||||
// Clears Persistent fields to avoid stale pointers after heap teardown.
|
||||
~PersistentRegion();
|
||||
|
||||
PersistentRegion(const PersistentRegion&) = delete;
|
||||
PersistentRegion& operator=(const PersistentRegion&) = delete;
|
||||
|
@ -49,12 +49,6 @@ class V8_EXPORT LivenessBroker final {
|
||||
TraceTrait<T>::GetTraceDescriptor(object).base_object_payload);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool IsHeapObjectAlive(const WeakMember<T>& weak_member) const {
|
||||
return (weak_member != kSentinelPointer) &&
|
||||
IsHeapObjectAlive<T>(weak_member.Get());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool IsHeapObjectAlive(const UntracedMember<T>& untraced_member) const {
|
||||
return (untraced_member != kSentinelPointer) &&
|
||||
|
@ -19,19 +19,43 @@ class Visitor;
|
||||
|
||||
namespace internal {
|
||||
|
||||
class MemberBase {
|
||||
protected:
|
||||
MemberBase() = default;
|
||||
explicit MemberBase(void* value) : raw_(value) {}
|
||||
|
||||
void* const* GetRawSlot() const { return &raw_; }
|
||||
void* GetRaw() const { return raw_; }
|
||||
void SetRaw(void* value) { raw_ = value; }
|
||||
|
||||
void* GetRawAtomic() const {
|
||||
return reinterpret_cast<const std::atomic<void*>*>(&raw_)->load(
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
void SetRawAtomic(void* value) {
|
||||
reinterpret_cast<std::atomic<void*>*>(&raw_)->store(
|
||||
value, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void ClearFromGC() const { raw_ = nullptr; }
|
||||
|
||||
private:
|
||||
mutable void* raw_ = nullptr;
|
||||
};
|
||||
|
||||
// The basic class from which all Member classes are 'generated'.
|
||||
template <typename T, typename WeaknessTag, typename WriteBarrierPolicy,
|
||||
typename CheckingPolicy>
|
||||
class BasicMember : private CheckingPolicy {
|
||||
class BasicMember final : private MemberBase, private CheckingPolicy {
|
||||
public:
|
||||
using PointeeType = T;
|
||||
|
||||
constexpr BasicMember() = default;
|
||||
constexpr BasicMember(std::nullptr_t) {} // NOLINT
|
||||
BasicMember(SentinelPointer s) : raw_(s) {} // NOLINT
|
||||
BasicMember(T* raw) : raw_(raw) { // NOLINT
|
||||
BasicMember(SentinelPointer s) : MemberBase(s) {} // NOLINT
|
||||
BasicMember(T* raw) : MemberBase(raw) { // NOLINT
|
||||
InitializingWriteBarrier();
|
||||
this->CheckPointer(raw_);
|
||||
this->CheckPointer(Get());
|
||||
}
|
||||
BasicMember(T& raw) : BasicMember(&raw) {} // NOLINT
|
||||
BasicMember(const BasicMember& other) : BasicMember(other.Get()) {}
|
||||
@ -108,7 +132,7 @@ class BasicMember : private CheckingPolicy {
|
||||
|
||||
T* Get() const {
|
||||
// Executed by the mutator, hence non atomic load.
|
||||
return raw_;
|
||||
return static_cast<T*>(MemberBase::GetRaw());
|
||||
}
|
||||
|
||||
void Clear() { SetRawAtomic(nullptr); }
|
||||
@ -120,25 +144,18 @@ class BasicMember : private CheckingPolicy {
|
||||
}
|
||||
|
||||
private:
|
||||
void SetRawAtomic(T* raw) {
|
||||
reinterpret_cast<std::atomic<T*>*>(&raw_)->store(raw,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
T* GetRawAtomic() const {
|
||||
return reinterpret_cast<const std::atomic<T*>*>(&raw_)->load(
|
||||
std::memory_order_relaxed);
|
||||
return static_cast<T*>(MemberBase::GetRawAtomic());
|
||||
}
|
||||
|
||||
void InitializingWriteBarrier() const {
|
||||
WriteBarrierPolicy::InitializingBarrier(
|
||||
reinterpret_cast<const void*>(&raw_), static_cast<const void*>(raw_));
|
||||
WriteBarrierPolicy::InitializingBarrier(GetRawSlot(), GetRaw());
|
||||
}
|
||||
void AssigningWriteBarrier() const {
|
||||
WriteBarrierPolicy::AssigningBarrier(reinterpret_cast<const void*>(&raw_),
|
||||
static_cast<const void*>(raw_));
|
||||
WriteBarrierPolicy::AssigningBarrier(GetRawSlot(), GetRaw());
|
||||
}
|
||||
|
||||
T* raw_ = nullptr;
|
||||
void ClearFromGC() const { MemberBase::ClearFromGC(); }
|
||||
|
||||
friend class cppgc::Visitor;
|
||||
};
|
||||
|
@ -15,14 +15,43 @@
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace cppgc {
|
||||
|
||||
class Visitor;
|
||||
|
||||
namespace internal {
|
||||
|
||||
class PersistentBase {
|
||||
protected:
|
||||
PersistentBase() = default;
|
||||
explicit PersistentBase(void* raw) : raw_(raw) {}
|
||||
|
||||
void* GetValue() const { return raw_; }
|
||||
void SetValue(void* value) { raw_ = value; }
|
||||
|
||||
PersistentNode* GetNode() const { return node_; }
|
||||
void SetNode(PersistentNode* node) { node_ = node; }
|
||||
|
||||
// Performs a shallow clear which assumes that internal persistent nodes are
|
||||
// destroyed elsewhere.
|
||||
void ClearFromGC() const {
|
||||
raw_ = nullptr;
|
||||
node_ = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable void* raw_ = nullptr;
|
||||
mutable PersistentNode* node_ = nullptr;
|
||||
|
||||
friend class PersistentRegion;
|
||||
};
|
||||
|
||||
// The basic class from which all Persistent classes are generated.
|
||||
template <typename T, typename WeaknessPolicy, typename LocationPolicy,
|
||||
typename CheckingPolicy>
|
||||
class BasicPersistent : public LocationPolicy,
|
||||
private WeaknessPolicy,
|
||||
private CheckingPolicy {
|
||||
class BasicPersistent final : public PersistentBase,
|
||||
public LocationPolicy,
|
||||
private WeaknessPolicy,
|
||||
private CheckingPolicy {
|
||||
public:
|
||||
using typename WeaknessPolicy::IsStrongPersistent;
|
||||
using PointeeType = T;
|
||||
@ -38,15 +67,15 @@ class BasicPersistent : public LocationPolicy,
|
||||
|
||||
BasicPersistent( // NOLINT
|
||||
SentinelPointer s, const SourceLocation& loc = SourceLocation::Current())
|
||||
: LocationPolicy(loc), raw_(s) {}
|
||||
: PersistentBase(s), LocationPolicy(loc) {}
|
||||
|
||||
// Raw value contstructors.
|
||||
// Raw value constructors.
|
||||
BasicPersistent(T* raw, // NOLINT
|
||||
const SourceLocation& loc = SourceLocation::Current())
|
||||
: LocationPolicy(loc), raw_(raw) {
|
||||
: PersistentBase(raw), LocationPolicy(loc) {
|
||||
if (!IsValid()) return;
|
||||
node_ = WeaknessPolicy::GetPersistentRegion(raw_).AllocateNode(
|
||||
this, &BasicPersistent::Trace);
|
||||
SetNode(WeaknessPolicy::GetPersistentRegion(GetValue())
|
||||
.AllocateNode(this, &BasicPersistent::Trace));
|
||||
this->CheckPointer(Get());
|
||||
}
|
||||
|
||||
@ -74,13 +103,11 @@ class BasicPersistent : public LocationPolicy,
|
||||
BasicPersistent(
|
||||
BasicPersistent&& other,
|
||||
const SourceLocation& loc = SourceLocation::Current()) noexcept
|
||||
: LocationPolicy(std::move(other)),
|
||||
raw_(std::move(other.raw_)),
|
||||
node_(std::move(other.node_)) {
|
||||
: PersistentBase(std::move(other)), LocationPolicy(std::move(other)) {
|
||||
if (!IsValid()) return;
|
||||
node_->UpdateOwner(this);
|
||||
other.raw_ = nullptr;
|
||||
other.node_ = nullptr;
|
||||
GetNode()->UpdateOwner(this);
|
||||
other.SetValue(nullptr);
|
||||
other.SetNode(nullptr);
|
||||
this->CheckPointer(Get());
|
||||
}
|
||||
|
||||
@ -114,13 +141,12 @@ class BasicPersistent : public LocationPolicy,
|
||||
BasicPersistent& operator=(BasicPersistent&& other) {
|
||||
if (this == &other) return *this;
|
||||
Clear();
|
||||
PersistentBase::operator=(std::move(other));
|
||||
LocationPolicy::operator=(std::move(other));
|
||||
raw_ = std::move(other.raw_);
|
||||
node_ = std::move(other.node_);
|
||||
if (!IsValid()) return *this;
|
||||
node_->UpdateOwner(this);
|
||||
other.raw_ = nullptr;
|
||||
other.node_ = nullptr;
|
||||
GetNode()->UpdateOwner(this);
|
||||
other.SetValue(nullptr);
|
||||
other.SetNode(nullptr);
|
||||
this->CheckPointer(Get());
|
||||
return *this;
|
||||
}
|
||||
@ -156,7 +182,7 @@ class BasicPersistent : public LocationPolicy,
|
||||
T* operator->() const { return Get(); }
|
||||
T& operator*() const { return *Get(); }
|
||||
|
||||
T* Get() const { return raw_; }
|
||||
T* Get() const { return static_cast<T*>(GetValue()); }
|
||||
|
||||
void Clear() { Assign(nullptr); }
|
||||
|
||||
@ -176,29 +202,35 @@ class BasicPersistent : public LocationPolicy,
|
||||
// Ideally, handling kSentinelPointer would be done by the embedder. On the
|
||||
// other hand, having Persistent aware of it is beneficial since no node
|
||||
// gets wasted.
|
||||
return raw_ != nullptr && raw_ != kSentinelPointer;
|
||||
return GetValue() != nullptr && GetValue() != kSentinelPointer;
|
||||
}
|
||||
|
||||
void Assign(T* ptr) {
|
||||
if (IsValid()) {
|
||||
if (ptr && ptr != kSentinelPointer) {
|
||||
// Simply assign the pointer reusing the existing node.
|
||||
raw_ = ptr;
|
||||
SetValue(ptr);
|
||||
this->CheckPointer(ptr);
|
||||
return;
|
||||
}
|
||||
WeaknessPolicy::GetPersistentRegion(raw_).FreeNode(node_);
|
||||
node_ = nullptr;
|
||||
WeaknessPolicy::GetPersistentRegion(GetValue()).FreeNode(GetNode());
|
||||
SetNode(nullptr);
|
||||
}
|
||||
raw_ = ptr;
|
||||
SetValue(ptr);
|
||||
if (!IsValid()) return;
|
||||
node_ = WeaknessPolicy::GetPersistentRegion(raw_).AllocateNode(
|
||||
this, &BasicPersistent::Trace);
|
||||
SetNode(WeaknessPolicy::GetPersistentRegion(GetValue())
|
||||
.AllocateNode(this, &BasicPersistent::Trace));
|
||||
this->CheckPointer(Get());
|
||||
}
|
||||
|
||||
T* raw_ = nullptr;
|
||||
PersistentNode* node_ = nullptr;
|
||||
void ClearFromGC() const {
|
||||
if (IsValid()) {
|
||||
WeaknessPolicy::GetPersistentRegion(GetValue()).FreeNode(GetNode());
|
||||
PersistentBase::ClearFromGC();
|
||||
}
|
||||
}
|
||||
|
||||
friend class cppgc::Visitor;
|
||||
};
|
||||
|
||||
template <typename T1, typename WeaknessPolicy1, typename LocationPolicy1,
|
||||
|
@ -136,12 +136,11 @@ class Visitor {
|
||||
template <typename PointerType>
|
||||
static void HandleWeak(const LivenessBroker& info, const void* object) {
|
||||
const PointerType* weak = static_cast<const PointerType*>(object);
|
||||
// Sentinel values are preserved for weak pointers.
|
||||
if (*weak == kSentinelPointer) return;
|
||||
const auto* raw = weak->Get();
|
||||
if (raw && !info.IsHeapObjectAlive(raw)) {
|
||||
// Object is passed down through the marker as const. Alternatives are
|
||||
// - non-const Trace method;
|
||||
// - mutable pointer in MemberBase;
|
||||
const_cast<PointerType*>(weak)->Clear();
|
||||
if (!info.IsHeapObjectAlive(raw)) {
|
||||
weak->ClearFromGC();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,9 +7,21 @@
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
#include "include/cppgc/persistent.h"
|
||||
|
||||
namespace cppgc {
|
||||
namespace internal {
|
||||
|
||||
PersistentRegion::~PersistentRegion() {
|
||||
for (auto& slots : nodes_) {
|
||||
for (auto& node : *slots) {
|
||||
if (node.IsUsed()) {
|
||||
static_cast<PersistentBase*>(node.owner())->ClearFromGC();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t PersistentRegion::NodesInUse() const {
|
||||
return std::accumulate(
|
||||
nodes_.cbegin(), nodes_.cend(), 0u, [](size_t acc, const auto& slots) {
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "src/heap/cppgc/marker.h"
|
||||
|
||||
#include "include/cppgc/allocation.h"
|
||||
#include "include/cppgc/internal/pointer-policies.h"
|
||||
#include "include/cppgc/member.h"
|
||||
#include "include/cppgc/persistent.h"
|
||||
#include "src/heap/cppgc/heap-object-header-inl.h"
|
||||
@ -245,5 +246,20 @@ TEST_F(MarkerTest, InConstructionObjectIsEventuallyMarkedNonEmptyStack) {
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(MarkerTest, SentinelNotClearedOnWeakPersistentHandling) {
|
||||
Marker marker(Heap::From(GetHeap())->AsBase());
|
||||
Persistent<GCed> root = MakeGarbageCollected<GCed>(GetAllocationHandle());
|
||||
auto* tmp = MakeGarbageCollected<GCed>(GetAllocationHandle());
|
||||
root->SetWeakChild(tmp);
|
||||
static const Marker::MarkingConfig config = {
|
||||
MarkingConfig::CollectionType::kMajor,
|
||||
MarkingConfig::StackState::kNoHeapPointers};
|
||||
marker.StartMarking(config);
|
||||
marker.FinishMarking(config);
|
||||
root->SetWeakChild(kSentinelPointer);
|
||||
marker.ProcessWeakness();
|
||||
EXPECT_EQ(kSentinelPointer, root->weak_child());
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace cppgc
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "include/cppgc/allocation.h"
|
||||
#include "include/cppgc/garbage-collected.h"
|
||||
#include "include/cppgc/internal/pointer-policies.h"
|
||||
#include "include/cppgc/member.h"
|
||||
#include "include/cppgc/type-traits.h"
|
||||
#include "src/heap/cppgc/heap.h"
|
||||
@ -70,6 +71,7 @@ class RootVisitor final : public VisitorBase {
|
||||
private:
|
||||
std::vector<std::pair<WeakCallback, const void*>> weak_callbacks_;
|
||||
};
|
||||
|
||||
class PersistentTest : public testing::TestSupportingAllocationOnly {};
|
||||
|
||||
} // namespace
|
||||
@ -617,6 +619,25 @@ TEST_F(PersistentTest, TraceWeak) {
|
||||
EXPECT_EQ(0u, GetRegion<WeakPersistent>(heap).NodesInUse());
|
||||
}
|
||||
|
||||
TEST_F(PersistentTest, ClearOnHeapDestruction) {
|
||||
Persistent<GCed> persistent;
|
||||
WeakPersistent<GCed> weak_persistent;
|
||||
|
||||
// Create another heap that can be destroyed during the test.
|
||||
auto heap = Heap::Create(GetPlatformHandle());
|
||||
persistent = MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
|
||||
weak_persistent = MakeGarbageCollected<GCed>(heap->GetAllocationHandle());
|
||||
const Persistent<GCed> persistent_sentinel(kSentinelPointer);
|
||||
const WeakPersistent<GCed> weak_persistent_sentinel(kSentinelPointer);
|
||||
heap.reset();
|
||||
|
||||
EXPECT_EQ(nullptr, persistent);
|
||||
EXPECT_EQ(nullptr, weak_persistent);
|
||||
// Sentinel values survive as they do not represent actual heap objects.
|
||||
EXPECT_EQ(kSentinelPointer, persistent_sentinel);
|
||||
EXPECT_EQ(kSentinelPointer, weak_persistent_sentinel);
|
||||
}
|
||||
|
||||
#if CPPGC_SUPPORTS_SOURCE_LOCATION
|
||||
TEST_F(PersistentTest, LocalizedPersistent) {
|
||||
GCed* gced = MakeGarbageCollected<GCed>(GetAllocationHandle());
|
||||
|
@ -22,6 +22,8 @@ class TestWithPlatform : public ::testing::Test {
|
||||
|
||||
TestPlatform& GetPlatform() const { return *platform_; }
|
||||
|
||||
std::shared_ptr<TestPlatform> GetPlatformHandle() const { return platform_; }
|
||||
|
||||
protected:
|
||||
static std::shared_ptr<TestPlatform> platform_;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user