core: Acquire renderer state mutex before calling processLinks (#12463)

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.
pull/12467/head
Mitchell Hashimoto 2026-04-25 13:21:38 -07:00 committed by GitHub
commit e9ca0f8c9a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 2 additions and 0 deletions

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| {