mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-17 07:50:04 +00:00
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:
parent
da5d4e9f6a
commit
5146bbe296
Notes:
github-actions[bot]
2025-02-27 19:36:28 +00:00
Author: https://github.com/Lubrsi
Commit: 5146bbe296
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3722
3 changed files with 32 additions and 2 deletions
|
@ -430,6 +430,14 @@ void Heap::mark_live_cells(HashMap<Cell*, HeapRoot> const& roots)
|
||||||
for (auto& inverse_root : m_uprooted_cells)
|
for (auto& inverse_root : m_uprooted_cells)
|
||||||
inverse_root->set_marked(false);
|
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();
|
m_uprooted_cells.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -444,7 +452,7 @@ void Heap::finalize_unmarked_cells()
|
||||||
{
|
{
|
||||||
for_each_block([&](auto& block) {
|
for_each_block([&](auto& block) {
|
||||||
block.template for_each_cell_in_state<Cell::State::Live>([](Cell* cell) {
|
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();
|
cell->finalize();
|
||||||
});
|
});
|
||||||
return IterationDecision::Continue;
|
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_has_live_cells = false;
|
||||||
bool block_was_full = block.is_full();
|
bool block_was_full = block.is_full();
|
||||||
block.template for_each_cell_in_state<Cell::State::Live>([&](Cell* cell) {
|
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);
|
dbgln_if(HEAP_DEBUG, " ~ {}", cell);
|
||||||
block.deallocate(cell);
|
block.deallocate(cell);
|
||||||
++collected_cells;
|
++collected_cells;
|
||||||
|
|
1
Tests/LibWeb/Text/expected/WebSocket/WebSocket-gc.txt
Normal file
1
Tests/LibWeb/Text/expected/WebSocket/WebSocket-gc.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
PASS! (Didn't crash)
|
21
Tests/LibWeb/Text/input/WebSocket/WebSocket-gc.html
Normal file
21
Tests/LibWeb/Text/input/WebSocket/WebSocket-gc.html
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
<script>
|
||||||
|
asyncTest((done) => {
|
||||||
|
{
|
||||||
|
const ws = new WebSocket('wss://echo.websocket.org');
|
||||||
|
|
||||||
|
ws.onopen = () => {
|
||||||
|
};
|
||||||
|
ws.onmessage = () => {
|
||||||
|
};
|
||||||
|
ws.onerror = () => {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
internals.gc();
|
||||||
|
println("PASS! (Didn't crash)");
|
||||||
|
done();
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
Add table
Add a link
Reference in a new issue