Skip to content

fix: guard sess.pty against concurrent access - #59

Open
owenthereal wants to merge 1 commit into
charmbracelet:masterfrom
owenthereal:fix-pty-race
Open

fix: guard sess.pty against concurrent access#59
owenthereal wants to merge 1 commit into
charmbracelet:masterfrom
owenthereal:fix-pty-race

Conversation

@owenthereal

Copy link
Copy Markdown

Fixes #58.

Pty() copies *sess.pty while handleRequests writes sess.pty.Window in the window-change case. Neither took the mutex the session already embeds and uses for sigCh and breakCh.

The handler goroutine is started from the shell case and handleRequests keeps running, so a client that resizes right after starting its shell writes sess.pty.Window while the handler reads it. Pty() is the only way for a handler to obtain the window channel, so any handler that supports resizing is exposed.

Takes the lock in both places. The send on sess.winch stays outside it: winch has a capacity of one, so holding the lock across the send would let a slow consumer block every Pty() behind it.

pty-req writes the same field but cannot race a handler, since it is refused once sess.handled is set and handlers only start after that, so it is left alone.

TestPtyResize does not catch this because it waits for the first window delivery before resizing, which orders the two accesses. The new TestPtyResizeRace resizes without synchronising against the handler.

Verified with go test -race, 5 runs each:

TestPtyResizeRace
before race reported on 5 of 5
after clean on 5 of 5

Full suite passes with -race.

Pty() copies *sess.pty while handleRequests writes sess.pty.Window in the
window-change case. Neither took the mutex the session already embeds and
uses for sigCh and breakCh.

The handler goroutine is started from the shell case and handleRequests
keeps running, so a client that resizes right after starting its shell
writes sess.pty.Window while the handler reads it. Pty() is the only way
for a handler to obtain the window channel, so any handler that supports
resizing is exposed.

Takes the lock in both places. The send on sess.winch stays outside it:
winch has a capacity of one, so holding the lock across the send would let
a slow consumer block every Pty() behind it.

pty-req writes the same field but cannot race a handler, since it is
refused once sess.handled is set and handlers only start after that, so it
is left alone.

TestPtyResize does not catch this because it waits for the first window
delivery before resizing, which orders the two accesses. The new test
resizes without synchronising against the handler; it reports a race on
every run before this change and none after.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Data race between Session.Pty() and the window-change handler

1 participant