detached 1cc5bed

fix(transport): reject fd sends before claiming and close the socket on write failure

commit
1cc5bed41b0d5c0503c997c534e4ae9b458f9ef5
author
brice · · 2026-09-28 07:48 UTC
parent
b2fe39a
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +20 -11
M protocol/core/core_test.go +6 -0
@@ -441,6 +441,7 @@ func TestConcurrentDestroySendsOnce(t *testing.T) {
441441 defer d.Close()
442442 receiveRequest(t, p)
443443 const rounds, destroyers, committers = 100, 4, 4
444+ totalCommits := 0
444445 for round := 0; round < rounds; round++ {
445446 surface := core.NewSurface(d.Context())
446447 surface.SetID(d.AllocateID())
@@ -469,6 +470,7 @@ func TestConcurrentDestroySendsOnce(t *testing.T) {
469470 if okDestroy != 1 {
470471 t.Fatalf("round %d: %d destroys succeeded, want 1", round, okDestroy)
471472 }
473+ totalCommits += okCommit
472474 // Decode every frame: exactly okCommit commits (opcode 6), then one
473475 // destroy (opcode 0) for this surface, and nothing after it.
474476 frames := readFrames(t, p, okCommit+1)
@@ -487,6 +489,10 @@ func TestConcurrentDestroySendsOnce(t *testing.T) {
487489 }
488490 p.SetReadDeadline(time.Time{})
489491 }
492+ // Ordering is only exercised when some commits won the race.
493+ if totalCommits == 0 {
494+ t.Fatal("no commit succeeded in any round; ordering was not exercised")
495+ }
490496 }
491497
492498 type wireFrame struct {
@@ -441,6 +441,7 @@ func TestConcurrentDestroySendsOnce(t *testing.T) {
441 defer d.Close()441 defer d.Close()
442 receiveRequest(t, p)442 receiveRequest(t, p)
443 const rounds, destroyers, committers = 100, 4, 4443 const rounds, destroyers, committers = 100, 4, 4
444+ totalCommits := 0
444 for round := 0; round < rounds; round++ {445 for round := 0; round < rounds; round++ {
445 surface := core.NewSurface(d.Context())446 surface := core.NewSurface(d.Context())
446 surface.SetID(d.AllocateID())447 surface.SetID(d.AllocateID())
@@ -469,6 +470,7 @@ func TestConcurrentDestroySendsOnce(t *testing.T) {
469 if okDestroy != 1 {470 if okDestroy != 1 {
470 t.Fatalf("round %d: %d destroys succeeded, want 1", round, okDestroy)471 t.Fatalf("round %d: %d destroys succeeded, want 1", round, okDestroy)
471 }472 }
473+ totalCommits += okCommit
472 // Decode every frame: exactly okCommit commits (opcode 6), then one474 // Decode every frame: exactly okCommit commits (opcode 6), then one
473 // destroy (opcode 0) for this surface, and nothing after it.475 // destroy (opcode 0) for this surface, and nothing after it.
474 frames := readFrames(t, p, okCommit+1)476 frames := readFrames(t, p, okCommit+1)
@@ -487,6 +489,10 @@ func TestConcurrentDestroySendsOnce(t *testing.T) {
487 }489 }
488 p.SetReadDeadline(time.Time{})490 p.SetReadDeadline(time.Time{})
489 }491 }
492+ // Ordering is only exercised when some commits won the race.
493+ if totalCommits == 0 {
494+ t.Fatal("no commit succeeded in any round; ordering was not exercised")
495+ }
490 }496 }
491 497
492 type wireFrame struct {498 type wireFrame struct {
M scm_linux.go +14 -11
@@ -143,25 +143,28 @@ func (d *Display) sendmsgWithFDs(buf []byte, fds []int, guard func() error) erro
143143 if d.closed.Load() {
144144 return net.ErrClosed
145145 }
146+ if len(fds) > 0 && d.unix == nil {
147+ return errors.New("wlturbo: cannot send file descriptors over a non-Unix connection")
148+ }
146149 if guard != nil {
147150 if err := guard(); err != nil {
148151 return err
149152 }
150153 }
154+ var n int
155+ var err error
151156 if len(fds) == 0 {
152- n, err := d.conn.Write(buf)
153- if err == nil && n != len(buf) {
154- return io.ErrShortWrite
155- }
156- return err
157+ n, err = d.conn.Write(buf)
158+ } else {
159+ n, _, err = d.unix.WriteMsgUnix(buf, unix.UnixRights(fds...), nil)
157160 }
158- if d.unix == nil {
159- return errors.New("wlturbo: cannot send file descriptors over a non-Unix connection")
160- }
161-
162- n, _, err := d.unix.WriteMsgUnix(buf, unix.UnixRights(fds...), nil)
163161 if err == nil && n != len(buf) {
164- return io.ErrShortWrite
162+ err = io.ErrShortWrite
163+ }
164+ if err != nil {
165+ // A failed or partial write desynchronizes the stream: shut the socket
166+ // down so every later send and receive fails instead of corrupting it.
167+ _ = d.conn.Close()
165168 }
166169 return err
167170 }
@@ -143,25 +143,28 @@ func (d *Display) sendmsgWithFDs(buf []byte, fds []int, guard func() error) erro
143 if d.closed.Load() {143 if d.closed.Load() {
144 return net.ErrClosed144 return net.ErrClosed
145 }145 }
146+ if len(fds) > 0 && d.unix == nil {
147+ return errors.New("wlturbo: cannot send file descriptors over a non-Unix connection")
148+ }
146 if guard != nil {149 if guard != nil {
147 if err := guard(); err != nil {150 if err := guard(); err != nil {
148 return err151 return err
149 }152 }
150 }153 }
154+ var n int
155+ var err error
151 if len(fds) == 0 {156 if len(fds) == 0 {
152- n, err := d.conn.Write(buf)157+ n, err = d.conn.Write(buf)
153- if err == nil && n != len(buf) {158+ } else {
154- return io.ErrShortWrite159+ n, _, err = d.unix.WriteMsgUnix(buf, unix.UnixRights(fds...), nil)
155- }
156- return err
157 }160 }
158- if d.unix == nil {
159- return errors.New("wlturbo: cannot send file descriptors over a non-Unix connection")
160- }
161-
162- n, _, err := d.unix.WriteMsgUnix(buf, unix.UnixRights(fds...), nil)
163 if err == nil && n != len(buf) {161 if err == nil && n != len(buf) {
164- return io.ErrShortWrite162+ err = io.ErrShortWrite
163+ }
164+ if err != nil {
165+ // A failed or partial write desynchronizes the stream: shut the socket
166+ // down so every later send and receive fails instead of corrupting it.
167+ _ = d.conn.Close()
165 }168 }
166 return err169 return err
167 }170 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 8

↑↓ moveenter openesc close