Fix race condition in listen code

Now listen sockets should always be closed. I observed that on rare
occasions, listen sockets were not closed. My assumption is that a
client was sending a listen request, but also closed right after that,
and the closing code ran before the listen request code, so the last
listen request was processed after the listen-shutdown code was run; so
the last one was never freed.

At least, that is my current assumption.
master
Merlijn Wajer 7 years ago committed by Merlijn B. W. Wajer
parent 0a65d24f73
commit 5c5d9bc213
  1. 29
      sshd.go

@ -30,7 +30,8 @@ var (
authorisedkeys = flag.String("authorisedkeys", "authorized_keys", "Authorised keys")
verbose = flag.Bool("verbose", false, "Enable verbose mode")
authmutex sync.Mutex
authmutex sync.Mutex
listenmutex sync.Mutex
)
type sshClient struct {
@ -39,6 +40,7 @@ type sshClient struct {
Listeners map[string]net.Listener
AllowedLocalPorts []uint32
AllowedRemotePorts []uint32
Stopping bool
}
type bindInfo struct {
@ -87,9 +89,9 @@ func main() {
config := &ssh.ServerConfig{
PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
authmutex.Lock()
defer authmutex.Unlock()
if deviceinfo, found := authorisedKeys[string(key.Marshal())]; found {
authmutex.Lock()
defer authmutex.Unlock()
return &ssh.Permissions{
CriticalOptions: map[string]string{"name": deviceinfo.Comment,
"localports": deviceinfo.LocalPorts,
@ -127,7 +129,7 @@ func main() {
return
}
client := sshClient{sshConn.Permissions.CriticalOptions["name"], sshConn, make(map[string]net.Listener), nil, nil}
client := sshClient{sshConn.Permissions.CriticalOptions["name"], sshConn, make(map[string]net.Listener), nil, nil, false}
allowedLocalPorts := sshConn.Permissions.CriticalOptions["localports"]
allowedRemotePorts := sshConn.Permissions.CriticalOptions["remoteports"]
@ -142,17 +144,19 @@ func main() {
go func() {
err := client.Conn.Wait()
listenmutex.Lock()
client.Stopping = true
if *verbose {
log.Printf("SSH connection closed for client %s: %s", client.Name, err)
}
// TODO: Make this safe? Is it impossible for cancel code to be
// running at this point?
for bind, listener := range client.Listeners {
if *verbose {
log.Printf("Closing listener bound to %s", bind)
}
listener.Close()
}
listenmutex.Unlock()
}()
go handleRequest(&client, reqs)
@ -450,16 +454,29 @@ func handleRequest(client *sshClient, reqs <-chan *ssh.Request) {
// RFC4254: 7.1 for forwarding
if req.Type == "tcpip-forward" {
listenmutex.Lock()
/* If we are closing, do not set up a new listener */
if client.Stopping {
listenmutex.Unlock()
req.Reply(false, []byte{})
continue
}
listener, bindinfo, err := handleTcpIpForward(client, req)
if err != nil {
listenmutex.Unlock()
continue
}
client.Listeners[bindinfo.Bound] = listener
listenmutex.Unlock()
go handleListener(client, bindinfo, listener)
continue
} else if req.Type == "cancel-tcpip-forward" {
listenmutex.Lock()
handleTcpIPForwardCancel(client, req)
listenmutex.Unlock()
continue
} else {
// Discard everything else

Loading…
Cancel
Save