From 4a7f07402b50e43b41795032941aa01ce5f01ae3 Mon Sep 17 00:00:00 2001 From: ntomsic Date: Thu, 21 May 2026 10:19:21 -0500 Subject: [PATCH] =?UTF-8?q?fix(audit):=20pass=203=20=E2=80=94=20frame()=20?= =?UTF-8?q?per-window=20QPointer=20guards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A render-driven close that destroys an entire window mid-frame would have UAF'd the QList 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 --- qt/src/MainWindow.cpp | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/qt/src/MainWindow.cpp b/qt/src/MainWindow.cpp index 88999f295..89cb69418 100644 --- a/qt/src/MainWindow.cpp +++ b/qt/src/MainWindow.cpp @@ -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 windows = s_windows; - for (MainWindow *w : windows) { - const QList 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> windows; + windows.reserve(s_windows.size()); + for (MainWindow *w : s_windows) windows.append(w); + for (const QPointer &wp : windows) { + if (!wp) continue; + QList> surfaces; + surfaces.reserve(wp->m_surfaces.size()); + for (GhosttySurface *s : wp->m_surfaces) surfaces.append(s); + for (const QPointer &sp : surfaces) { + if (!wp || !sp) continue; + sp->renderIfDirty(); } } }