LibGC: Visit the edges of the cells that must survive garbage collection

Previously, we would only keep the cell that must survive alive, but
none of it's edges.

This cropped up with a GC UAF in must_survive_garbage_collection of
WebSocket in .NET's SignalR frontend implementation, where an
out-of-scope WebSocket had it's underlying EventTarget properties
garbage collected, and must_survive_garbage_collection read from the
destroyed EventTarget properties.

See: https://github.com/dotnet/aspnetcore/blob/main/src/SignalR/clients/ts/signalr/src/WebSocketTransport.ts#L81
Found on https://www.formula1.com/ during a live session.

Co-Authored-By: Tim Flynn <trflynn89@pm.me>
This commit is contained in:
Luke Wilde 2025-02-27 14:50:22 +00:00 committed by Tim Flynn
commit 5146bbe296
Notes: github-actions[bot] 2025-02-27 19:36:28 +00:00
3 changed files with 32 additions and 2 deletions

View file

@ -430,6 +430,14 @@ void Heap::mark_live_cells(HashMap<Cell*, HeapRoot> const& roots)
for (auto& inverse_root : m_uprooted_cells)
inverse_root->set_marked(false);
for_each_block([&](auto& block) {
block.template for_each_cell_in_state<Cell::State::Live>([&](Cell* cell) {
if (!cell->is_marked() && cell_must_survive_garbage_collection(*cell))
cell->visit_edges(visitor);
});
return IterationDecision::Continue;
});
m_uprooted_cells.clear();
}
@ -444,7 +452,7 @@ void Heap::finalize_unmarked_cells()
{
for_each_block([&](auto& block) {
block.template for_each_cell_in_state<Cell::State::Live>([](Cell* cell) {
if (!cell->is_marked() && !cell_must_survive_garbage_collection(*cell))
if (!cell->is_marked())
cell->finalize();
});
return IterationDecision::Continue;
@ -466,7 +474,7 @@ void Heap::sweep_dead_cells(bool print_report, Core::ElapsedTimer const& measure
bool block_has_live_cells = false;
bool block_was_full = block.is_full();
block.template for_each_cell_in_state<Cell::State::Live>([&](Cell* cell) {
if (!cell->is_marked() && !cell_must_survive_garbage_collection(*cell)) {
if (!cell->is_marked()) {
dbgln_if(HEAP_DEBUG, " ~ {}", cell);
block.deallocate(cell);
++collected_cells;