Merge pull request #1378 from lightpanda-io/performance_observer_use_after_free

Fix potential use-after-free with PerformanceObserver.
This commit is contained in:
Pierre Tachoire
2026-01-18 17:03:36 +01:00
committed by GitHub
3 changed files with 56 additions and 1 deletions

View File

@@ -112,3 +112,28 @@
});
});
</script>
<script id="microtask_access_to_records">
testing.async(async () => {
let savedRecords;
const promise = new Promise((resolve) => {
const element = document.createElement('div');
const observer = new MutationObserver((records) => {
// Save the records array itself
savedRecords = records;
resolve();
observer.disconnect();
});
observer.observe(element, { attributes: true });
element.setAttribute('test', 'value');
});
await promise;
// Force arena reset by making a Zig call
document.getElementsByTagName('*');
testing.expectEqual(1, savedRecords.length);
testing.expectEqual('attributes', savedRecords[0].type);
testing.expectEqual('test', savedRecords[0].attributeName);
});
</script>

View File

@@ -36,3 +36,31 @@
performance.mark("operationEnd", { startTime: 34.0 });
}
</script>
<script id="microtask_access_to_list">
{
let savedList;
const promise = new Promise((resolve) => {
const observer = new PerformanceObserver((list, observer) => {
savedList = list;
resolve();
observer.disconnect();
});
observer.observe({ type: "mark" });
performance.mark("testMark");
});
testing.async(async () => {
await promise;
// force a call_depth reset, which will clear the call_arena
document.getElementsByTagName('*');
const entries = savedList.getEntries();
testing.expectEqual(true, entries instanceof Array, {script_id: 'microtask_access_to_list'});
testing.expectEqual(1, entries.length);
testing.expectEqual("testMark", entries[0].name);
testing.expectEqual("mark", entries[0].entryType);
});
}
</script>

View File

@@ -126,7 +126,9 @@ pub fn disconnect(self: *PerformanceObserver, page: *Page) void {
/// Returns the current list of PerformanceEntry objects
/// stored in the performance observer, emptying it out.
pub fn takeRecords(self: *PerformanceObserver, page: *Page) ![]*Performance.Entry {
const records = try page.call_arena.dupe(*Performance.Entry, self._entries.items);
// Use page.arena instead of call_arena because this slice is wrapped in EntryList
// and may be accessed later.
const records = try page.arena.dupe(*Performance.Entry, self._entries.items);
self._entries.clearRetainingCapacity();
return records;
}