Skip to content

Commit 23edec0

Browse files
Bryan C. Millsgopherbot
Bryan C. Mills
authored andcommitted
ssh: ensure that handshakeTransport goroutines have finished before Close returns
This fixes a data race in the tests for x/crypto/ssh, which expects to be able to examine a transport's read and write counters without locking after closing it. (Given the number of goroutines, channels, and mutexes used in this package, I wouldn't be surprised if other concurrency bugs remain. I would suggest simplifying the concurrency in this package, but I don't intend to follow up on that myself at the moment.) Fixes golang/go#56957. Change-Id: Ib1f1390b66707c66a3608e48f3f52483cff3c1f5 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/456758 Reviewed-by: Roland Shoemaker <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Auto-Submit: Bryan Mills <[email protected]> Run-TryBot: Bryan Mills <[email protected]>
1 parent f495dc3 commit 23edec0

File tree

1 file changed

+30
-18
lines changed

1 file changed

+30
-18
lines changed

ssh/handshake.go

+30-18
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,13 @@ type handshakeTransport struct {
5858
incoming chan []byte
5959
readError error
6060

61-
mu sync.Mutex
62-
writeError error
63-
sentInitPacket []byte
64-
sentInitMsg *kexInitMsg
65-
pendingPackets [][]byte // Used when a key exchange is in progress.
61+
mu sync.Mutex
62+
writeError error
63+
sentInitPacket []byte
64+
sentInitMsg *kexInitMsg
65+
pendingPackets [][]byte // Used when a key exchange is in progress.
66+
writePacketsLeft uint32
67+
writeBytesLeft int64
6668

6769
// If the read loop wants to schedule a kex, it pings this
6870
// channel, and the write loop will send out a kex
@@ -71,7 +73,8 @@ type handshakeTransport struct {
7173

7274
// If the other side requests or confirms a kex, its kexInit
7375
// packet is sent here for the write loop to find it.
74-
startKex chan *pendingKex
76+
startKex chan *pendingKex
77+
kexLoopDone chan struct{} // closed (with writeError non-nil) when kexLoop exits
7578

7679
// data for host key checking
7780
hostKeyCallback HostKeyCallback
@@ -86,12 +89,10 @@ type handshakeTransport struct {
8689
// Algorithms agreed in the last key exchange.
8790
algorithms *algorithms
8891

92+
// Counters exclusively owned by readLoop.
8993
readPacketsLeft uint32
9094
readBytesLeft int64
9195

92-
writePacketsLeft uint32
93-
writeBytesLeft int64
94-
9596
// The session ID or nil if first kex did not complete yet.
9697
sessionID []byte
9798
}
@@ -108,7 +109,8 @@ func newHandshakeTransport(conn keyingTransport, config *Config, clientVersion,
108109
clientVersion: clientVersion,
109110
incoming: make(chan []byte, chanSize),
110111
requestKex: make(chan struct{}, 1),
111-
startKex: make(chan *pendingKex, 1),
112+
startKex: make(chan *pendingKex),
113+
kexLoopDone: make(chan struct{}),
112114

113115
config: config,
114116
}
@@ -340,16 +342,17 @@ write:
340342
t.mu.Unlock()
341343
}
342344

345+
// Unblock reader.
346+
t.conn.Close()
347+
343348
// drain startKex channel. We don't service t.requestKex
344349
// because nobody does blocking sends there.
345-
go func() {
346-
for init := range t.startKex {
347-
init.done <- t.writeError
348-
}
349-
}()
350+
for request := range t.startKex {
351+
request.done <- t.getWriteError()
352+
}
350353

351-
// Unblock reader.
352-
t.conn.Close()
354+
// Mark that the loop is done so that Close can return.
355+
close(t.kexLoopDone)
353356
}
354357

355358
// The protocol uses uint32 for packet counters, so we can't let them
@@ -545,7 +548,16 @@ func (t *handshakeTransport) writePacket(p []byte) error {
545548
}
546549

547550
func (t *handshakeTransport) Close() error {
548-
return t.conn.Close()
551+
// Close the connection. This should cause the readLoop goroutine to wake up
552+
// and close t.startKex, which will shut down kexLoop if running.
553+
err := t.conn.Close()
554+
555+
// Wait for the kexLoop goroutine to complete.
556+
// At that point we know that the readLoop goroutine is complete too,
557+
// because kexLoop itself waits for readLoop to close the startKex channel.
558+
<-t.kexLoopDone
559+
560+
return err
549561
}
550562

551563
func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error {

0 commit comments

Comments
 (0)