From c1a3b97d708a15b7b939b739ba276187844849a2 Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Sun, 5 Jul 2026 19:37:35 +0200 Subject: [PATCH] ssh: document and test source-address behavior for non-IP transports The source-address critical option is a comma-separated list of IP addresses and CIDR blocks, so it can never match a connection whose remote address is not IP-based, such as a Unix domain socket. Change-Id: I45e09fe5c8c13922cb0735278acf33320857322c Reviewed-on: https://go-review.googlesource.com/c/crypto/+/797042 Reviewed-by: David Chase LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Filippo Valsorda Reviewed-by: Junyang Shao --- ssh/server.go | 12 ++++++++++-- ssh/server_test.go | 12 ++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/ssh/server.go b/ssh/server.go index 480eb6b..d1a798a 100644 --- a/ssh/server.go +++ b/ssh/server.go @@ -28,7 +28,11 @@ type Permissions struct { // the given address). The SSH package currently only enforces // the "source-address" critical option: it is validated against // the client's remote address whenever it is present in the - // Permissions returned by any authentication callback. It is up + // Permissions returned by any authentication callback. Its value + // is a comma-separated list of IP addresses and CIDR blocks; + // consistently with OpenSSH, a connection whose remote address is + // not an IP address, such as a Unix domain socket, never matches + // the list and is rejected when the option is present. It is up // to server implementations to enforce other critical options, // such as "force-command", by checking them after the SSH // handshake is successful. In general, SSH servers should reject @@ -444,6 +448,10 @@ func (s *connection) serverHandshake(config *ServerConfig) (*Permissions, error) return perms, err } +// checkSourceAddress matches addr against sourceAddrs, a comma-separated list +// of IP addresses and CIDR blocks. Consistently with OpenSSH, a remote address +// that is not IP-based, such as a Unix domain socket, never matches the list +// and is rejected. func checkSourceAddress(addr net.Addr, sourceAddrs string) error { if addr == nil { return errors.New("ssh: no address known for client, but source-address match required") @@ -451,7 +459,7 @@ func checkSourceAddress(addr net.Addr, sourceAddrs string) error { tcpAddr, ok := addr.(*net.TCPAddr) if !ok { - return fmt.Errorf("ssh: remote address %v is not an TCP address when checking source-address match", addr) + return fmt.Errorf("ssh: remote address %v is not a TCP address when checking source-address match", addr) } for _, sourceAddr := range strings.Split(sourceAddrs, ",") { diff --git a/ssh/server_test.go b/ssh/server_test.go index 81ebd1e..0f84616 100644 --- a/ssh/server_test.go +++ b/ssh/server_test.go @@ -849,6 +849,18 @@ func TestAuthCallbacksSourceAddress(t *testing.T) { } } +func TestCheckSourceAddressNonIP(t *testing.T) { + for _, addr := range []net.Addr{ + &net.UnixAddr{Name: "/run/test.sock", Net: "unix"}, + &net.UnixAddr{}, + nil, + } { + if err := checkSourceAddress(addr, "127.0.0.0/8,::1/128"); err == nil { + t.Errorf("source-address check succeeded for non-IP remote address %v", addr) + } + } +} + func TestVerifiedPublicCallbackPartialSuccessBadUsage(t *testing.T) { c1, c2, err := netPipe() if err != nil {