Skip to content

Commit

Permalink
ssh: prevent double kex at connection start, 2nd try
Browse files Browse the repository at this point in the history
The previous attempt would fail in the following scenario:

* select picks "first" kex from requestKex

* read loop receives a remote kex, posts on requestKex (which is now
  empty) [*] for sending out a response, and sends pendingKex on startKex.

* select picks pendingKex from startKex, and proceeds to run the key
  exchange.

* the posting on requestKex in [*] now triggers a second key exchange.

Fixes #18861. 

Change-Id: I443e82f1d04c7f17d1485fdb87072b9feec26aa8
Reviewed-on: https://go-review.googlesource.com/36055
Run-TryBot: Han-Wen Nienhuys <hanwen@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Han-Wen Nienhuys <hanwen@google.com>
  • Loading branch information
hanwen committed Feb 6, 2017
1 parent 611bde5 commit c530296
Showing 1 changed file with 27 additions and 20 deletions.
47 changes: 27 additions & 20 deletions ssh/handshake.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ type handshakeTransport struct {

// If the read loop wants to schedule a kex, it pings this
// channel, and the write loop will send out a kex
// message. The boolean is whether this is the first request or not.
requestKex chan bool
// message.
requestKex chan struct{}

// If the other side requests or confirms a kex, its kexInit
// packet is sent here for the write loop to find it.
Expand Down Expand Up @@ -102,14 +102,14 @@ func newHandshakeTransport(conn keyingTransport, config *Config, clientVersion,
serverVersion: serverVersion,
clientVersion: clientVersion,
incoming: make(chan []byte, chanSize),
requestKex: make(chan bool, 1),
requestKex: make(chan struct{}, 1),
startKex: make(chan *pendingKex, 1),

config: config,
}

// We always start with a mandatory key exchange.
t.requestKex <- true
t.requestKex <- struct{}{}
return t
}

Expand Down Expand Up @@ -166,6 +166,7 @@ func (t *handshakeTransport) printPacket(p []byte, write bool) {
if write {
action = "sent"
}

if p[0] == msgChannelData || p[0] == msgChannelExtendedData {
log.Printf("%s %s data (packet %d bytes)", t.id(), action, len(p))
} else {
Expand Down Expand Up @@ -230,14 +231,13 @@ func (t *handshakeTransport) recordWriteError(err error) {

func (t *handshakeTransport) requestKeyExchange() {
select {
case t.requestKex <- false:
case t.requestKex <- struct{}{}:
default:
// something already requested a kex, so do nothing.
}
}

func (t *handshakeTransport) kexLoop() {
firstSent := false

write:
for t.getWriteError() == nil {
Expand All @@ -251,26 +251,15 @@ write:
if !ok {
break write
}
case requestFirst := <-t.requestKex:
// For the first key exchange, both
// sides will initiate a key exchange,
// and both channels will fire. To
// avoid doing two key exchanges in a
// row, ignore our own request for an
// initial kex if we have already sent
// it out.
if firstSent && requestFirst {

continue
}
case <-t.requestKex:
break
}

if !sent {
if err := t.sendKexInit(); err != nil {
t.recordWriteError(err)
break
}
firstSent = true
sent = true
}
}
Expand All @@ -287,7 +276,8 @@ write:

// We're not servicing t.startKex, but the remote end
// has just sent us a kexInitMsg, so it can't send
// another key change request.
// another key change request, until we close the done
// channel on the pendingKex request.

err := t.enterKeyExchange(request.otherInit)

Expand All @@ -301,6 +291,23 @@ write:
} else if t.algorithms != nil {
t.writeBytesLeft = t.algorithms.w.rekeyBytes()
}

// we have completed the key exchange. Since the
// reader is still blocked, it is safe to clear out
// the requestKex channel. This avoids the situation
// where: 1) we consumed our own request for the
// initial kex, and 2) the kex from the remote side
// caused another send on the requestKex channel,
clear:
for {
select {
case <-t.requestKex:
//
default:
break clear
}
}

request.done <- t.writeError

// kex finished. Push packets that we received while
Expand Down

0 comments on commit c530296

Please sign in to comment.