core: Acquire renderer state mutex before calling processLinks

Holding the renderer state mutex is a documented precondition of
`processLinks`, but `mouseButtonCallback` previously called
the function without the mutex.

This creates a race with the I/O thread's `processOutput`, which can
prune scrollback pages while `processLinks` is reading them, resulting
in a use-after-free segfault.  See
https://github.com/ghostty-org/ghostty/discussions/12409 (Linux: crash
while selecting text).

57b5e1e250/src/Surface.zig (L4354-L4355)

57b5e1e250/src/Surface.zig (L3822-L3824)

995e4e375 (os: open) changed the body of `processLinks` to be
non-trivial and documented the precondition, but the lock was not held
at the call site.
This commit is contained in:
Jesse Rosenstock
2026-04-25 15:30:10 +02:00
parent 57b5e1e250
commit 28f4676b5d

View File

@@ -3821,6 +3821,8 @@ pub fn mouseButtonCallback(
// clicked link will swallow the event.
if (self.mouse.over_link) {
const pos = try self.rt_surface.getCursorPos();
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
if (self.processLinks(pos)) |processed| {
if (processed) return true;
} else |err| {