Commit e17ffa8d authored by Samuel Groß's avatar Samuel Groß Committed by V8 LUCI CQ

[sandbox] Make the ExternalPointerTable cooperate with LSan

When the LeakSanitizer (LSan) runs, it scans all reachable memory
looking for pointers to other (live) objects, then reports all objects
that are still allocated but not reachable as leaked.
When the external pointer table is used, the pointers stored in it do
unfortunately not look like pointers to LSan as they will have some of
the top bits set. As such, LSan ignores them and may afterwards
incorrectly report some referenced objects as leaked.
To fix this, we now use a "shadow table" when LSan is active which
contains the raw pointer for every (tagged) pointer stored in the real
table. LSan can then scan this table and find all references.

Bug: v8:10391
Change-Id: If0c8b042fdd775ac3c8025d5688e62df37532ec3
Cq-Include-Trybots: luci.v8.try:v8_linux64_heap_sandbox_dbg_ng,v8_linux_arm64_sim_heap_sandbox_dbg_ng
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3779915
Commit-Queue: Samuel Groß <saelo@chromium.org>
Reviewed-by: 's avatarIgor Sheludko <ishell@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81972}
parent 3064727d
......@@ -21,8 +21,18 @@ void ExternalPointerTable::Init(Isolate* isolate) {
VirtualAddressSpace* root_space = GetPlatformVirtualAddressSpace();
DCHECK(IsAligned(kExternalPointerTableReservationSize,
root_space->allocation_granularity()));
size_t reservation_size = kExternalPointerTableReservationSize;
#if defined(LEAK_SANITIZER)
// When LSan is active, we use a "shadow table" which contains the raw
// pointers stored in this external pointer table so that LSan can scan them.
// This is necessary to avoid false leak reports. The shadow table is located
// right after the real table in memory. See also lsan_record_ptr().
reservation_size *= 2;
#endif // LEAK_SANITIZER
buffer_ = root_space->AllocatePages(
VirtualAddressSpace::kNoHint, kExternalPointerTableReservationSize,
VirtualAddressSpace::kNoHint, reservation_size,
root_space->allocation_granularity(), PagePermissions::kNoAccess);
if (!buffer_) {
V8::FatalProcessOutOfMemory(
......@@ -36,6 +46,17 @@ void ExternalPointerTable::Init(Isolate* isolate) {
isolate, "Failed to allocate mutex for ExternalPointerTable");
}
#if defined(LEAK_SANITIZER)
// Make the shadow table accessible.
if (!root_space->SetPagePermissions(
buffer_ + kExternalPointerTableReservationSize,
kExternalPointerTableReservationSize, PagePermissions::kReadWrite)) {
V8::FatalProcessOutOfMemory(isolate,
"Failed to allocate memory for the "
"ExternalPointerTable LSan shadow table");
}
#endif // LEAK_SANITIZER
// Allocate the initial block. Mutex must be held for that.
base::MutexGuard guard(mutex_);
Grow();
......@@ -49,8 +70,12 @@ void ExternalPointerTable::Init(Isolate* isolate) {
void ExternalPointerTable::TearDown() {
DCHECK(is_initialized());
GetPlatformVirtualAddressSpace()->FreePages(
buffer_, kExternalPointerTableReservationSize);
size_t reservation_size = kExternalPointerTableReservationSize;
#if defined(LEAK_SANITIZER)
reservation_size *= 2;
#endif // LEAK_SANITIZER
GetPlatformVirtualAddressSpace()->FreePages(buffer_, reservation_size);
delete mutex_;
buffer_ = kNullAddress;
......
......@@ -144,6 +144,19 @@ class V8_EXPORT_PRIVATE ExternalPointerTable {
return buffer_ + index * sizeof(Address);
}
// When LeakSanitizer is enabled, this method will write the untagged (raw)
// pointer into the shadow table (located after the real table) at the given
// index. This is necessary because LSan is unable to scan the pointers in
// the main table due to the pointer tagging scheme (the values don't "look
// like" pointers). So instead it can scan the pointers in the shadow table.
inline void lsan_record_ptr(uint32_t index, Address value) {
#if defined(LEAK_SANITIZER)
base::Memory<Address>(entry_address(index) +
kExternalPointerTableReservationSize) =
value & ~kExternalPointerTagMask;
#endif // LEAK_SANITIZER
}
// Loads the value at the given index. This method is non-atomic, only use it
// when no other threads can currently access the table.
inline Address load(uint32_t index) const {
......@@ -153,6 +166,7 @@ class V8_EXPORT_PRIVATE ExternalPointerTable {
// Stores the provided value at the given index. This method is non-atomic,
// only use it when no other threads can currently access the table.
inline void store(uint32_t index, Address value) {
lsan_record_ptr(index, value);
base::Memory<Address>(entry_address(index)) = value;
}
......@@ -164,12 +178,14 @@ class V8_EXPORT_PRIVATE ExternalPointerTable {
// Atomically stores the provided value at the given index.
inline void store_atomic(uint32_t index, Address value) {
lsan_record_ptr(index, value);
auto addr = reinterpret_cast<base::Atomic64*>(entry_address(index));
base::Relaxed_Store(addr, value);
}
// Atomically exchanges the value at the given index with the provided value.
inline Address exchange_atomic(uint32_t index, Address value) {
lsan_record_ptr(index, value);
auto addr = reinterpret_cast<base::Atomic64*>(entry_address(index));
return static_cast<Address>(base::Relaxed_AtomicExchange(addr, value));
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment