fix(audit): pass 3 — frame() per-window QPointer guards

A render-driven close that destroys an entire window mid-frame would
have UAF'd the QList<MainWindow*> snapshot from pass 1+2. Switch
both the outer window list and each per-window surface list to
QPointer snapshots so a destroyed entry shows up as null instead
of a dangling pointer; skip null entries on each iteration.

Build verified.

Co-Authored-By: claude-flow <ruv@ruv.net>
pull/12846/head
ntomsic 2026-05-21 10:19:21 -05:00
parent 49ff7d297f
commit 4a7f07402b
1 changed files with 18 additions and 13 deletions

View File

@ -892,19 +892,24 @@ void MainWindow::frame() {
ghostty_app_tick(s_app);
// Rendering happens only here, so a flood of RENDER actions cannot
// saturate the GUI thread — each surface renders at most once a
// frame. One pass across every window: the shared ghostty_app_t was
// already ticked once above. Iterate snapshots of both the window
// list and each window's surface list — a render can trigger a
// close (renderer-unhealthy chain, child-exited press, etc.) which
// mutates these QLists, invalidating in-place iterators.
const QList<MainWindow *> windows = s_windows;
for (MainWindow *w : windows) {
const QList<GhosttySurface *> surfaces = w->m_surfaces;
for (GhosttySurface *s : surfaces) {
// After the snapshot, the window may already have removed `s`
// from m_surfaces (deferred-deleted). Skip if so.
if (!w->m_surfaces.contains(s)) continue;
s->renderIfDirty();
// frame. One pass across every window: the shared ghostty_app_t
// was already ticked once above.
//
// Iterate via QPointer snapshots so a render-driven close
// (renderer-unhealthy chain, child-exited press, etc.) that
// destroys a window or surface mid-frame can't UAF the iterator
// or the inner-loop receiver.
QList<QPointer<MainWindow>> windows;
windows.reserve(s_windows.size());
for (MainWindow *w : s_windows) windows.append(w);
for (const QPointer<MainWindow> &wp : windows) {
if (!wp) continue;
QList<QPointer<GhosttySurface>> surfaces;
surfaces.reserve(wp->m_surfaces.size());
for (GhosttySurface *s : wp->m_surfaces) surfaces.append(s);
for (const QPointer<GhosttySurface> &sp : surfaces) {
if (!wp || !sp) continue;
sp->renderIfDirty();
}
}
}