golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
123.95k stars 17.66k forks source link

x/crypto/ssh: listening on remote address blocks indefinitely when address doesn't contain a host part #33227

Open jeffwilliams opened 5 years ago

jeffwilliams commented 5 years ago

What version of Go are you using (go version)?

$ go version
go version go1.11.2 linux/amd64

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/kanobe/.cache/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/kanobe/src/go"
GOPROXY=""
GORACE=""
GOROOT="/home/kanobe/download/go"
GOTMPDIR=""
GOTOOLDIR="/home/kanobe/download/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build496899549=/tmp/go-build -gno-record-gcc-switches"

What did you do?

Created an SSH connection using ssh.Dial, and opened a Listener to listen on a port without specifying a host address. Then connected to that port (i.e. using telnet).

This program demonstrates the issue:

package main

import (
  "io/ioutil"
  "log"
  "os"
  "path"

  "golang.org/x/crypto/ssh"
)

func buildSshConfig(user, keyfile string) (conf *ssh.ClientConfig) {
  key, err := ioutil.ReadFile(keyfile)
  if err != nil {
    log.Fatal("unable to build ssh config: ", err)
  }

  signer, err := ssh.ParsePrivateKey(key)
  if err != nil {
    log.Fatal("unable to build ssh config: ", err)
  }

  conf = &ssh.ClientConfig{
    User: user,
    Auth: []ssh.AuthMethod{
      // Use the PublicKeys method for remote authentication.
      ssh.PublicKeys(signer),
    },
    HostKeyCallback: ssh.InsecureIgnoreHostKey(),
  }

  return
}

func main() {
  config := buildSshConfig(os.Getenv("USER"),
    path.Join(os.Getenv("HOME"), ".ssh/id_rsa"))

  conn, err := ssh.Dial("tcp", "localhost:22", config)
  if err != nil {
    log.Fatal("unable to connect: ", err)
  }
  defer conn.Close()

  // Request the remote side to open port 8080
  l, err := conn.Listen("tcp", ":8080")
  if err != nil {
    log.Fatal("unable to register tcp forward: ", err)
  }
  defer l.Close()

  log.Println("Calling Accept")
  l.Accept()
  log.Println("Accept returned")

}

What did you expect to see?

Calling Accept
Accept Returned

What did you see instead?

Calling Accept

Additional Info

It seems that if the local address is not specified in ssh.Client.Listen, the Accept call blocks indefinitely. For example this line fails in the above program:

  l, err := conn.Listen("tcp", ":8080")

But when changed to this it succeeds:

  l, err := conn.Listen("tcp", "127.0.0.1:8080")

The host may be omitted in net.Listen, so there is precedent for using no host. From net.Listen: "For TCP networks, if the host in the address parameter is empty or a literal unspecified IP address, Listen listens on all available unicast and anycast IP addresses of the local system."

I think at a minimum if host may not be omitted, the documentation should mention that it is not supported, and the Listen call should return an error.

julieqiu commented 5 years ago

/cc @FiloSottile

gopherbot commented 3 months ago

Change https://go.dev/cl/599995 mentions this issue: ssh: allow to bind to a hostname in remote forwarding

drakkan commented 3 months ago

Hello, can you please confirm that the above CL fixes the issue? Thank you