From 798f67cc54cfa778b6182571092550e0d79628b7 Mon Sep 17 00:00:00 2001 From: Alan Ortega Date: Sat, 6 Jun 2026 18:58:47 +0200 Subject: [PATCH] ssh: enforce source-address critical option for all auth methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CVE-2026-46595 added the source-address critical-option check to the publickey path's VerifiedPublicKeyCallback branch. The other auth methods that return *Permissions — "none", "password", "keyboard-interactive" and "gssapi-with-mic" — still returned their Permissions to the caller without validating the remote address, so a server that set Permissions.CriticalOptions["source-address"] from any of those callbacks got no enforcement, contrary to the documented behavior of the Permissions struct. Move the enforcement to a single check after the auth-method switch in userAuthLoop so it applies uniformly. The existing publickey-path checks are left in place as defense in depth. --- ssh/server.go | 14 +++++++++ ssh/server_test.go | 74 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/ssh/server.go b/ssh/server.go index 0192a67503..31310ed5b3 100644 --- a/ssh/server.go +++ b/ssh/server.go @@ -896,6 +896,20 @@ userAuthLoop: authErr = fmt.Errorf("ssh: unknown method %q", userAuthReq.Method) } + // Enforce the "source-address" critical option for every auth + // method. CVE-2026-46595 added this check to the publickey path's + // VerifiedPublicKeyCallback branch, but the other callback-returning + // methods ("none", "password", "keyboard-interactive" and + // "gssapi-with-mic") returned their Permissions to the caller without + // validating the remote address against the restriction. + if authErr == nil && perms != nil && perms.CriticalOptions != nil { + if saco := perms.CriticalOptions[sourceAddressCriticalOption]; saco != "" { + if err := checkSourceAddress(s.RemoteAddr(), saco); err != nil { + authErr = err + } + } + } + authErrs = append(authErrs, authErr) if config.AuthLogCallback != nil { diff --git a/ssh/server_test.go b/ssh/server_test.go index 502a25b29f..4cebbbd6e4 100644 --- a/ssh/server_test.go +++ b/ssh/server_test.go @@ -714,6 +714,80 @@ func TestVerifiedPubKeyCallbackSourceAddress(t *testing.T) { } } +func TestSourceAddressCriticalOptionNonPublicKey(t *testing.T) { + // CVE-2026-46595 added source-address enforcement to the publickey + // path. The same critical option must also be enforced for the other + // auth methods that hand back *Permissions. Each callback below + // restricts access to 192.168.99.99, which never matches the loopback + // address used by netPipe, so every authentication must be rejected. + const mismatchingSourceAddr = "192.168.99.99" + restrictedPerms := func() (*Permissions, error) { + return &Permissions{ + CriticalOptions: map[string]string{ + sourceAddressCriticalOption: mismatchingSourceAddr, + }, + }, nil + } + + for _, tt := range []struct { + name string + serverConf *ServerConfig + auth []AuthMethod + }{ + { + name: "none", + serverConf: &ServerConfig{ + NoClientAuth: true, + NoClientAuthCallback: func(ConnMetadata) (*Permissions, error) { + return restrictedPerms() + }, + }, + auth: nil, + }, + { + name: "password", + serverConf: &ServerConfig{ + PasswordCallback: func(ConnMetadata, []byte) (*Permissions, error) { + return restrictedPerms() + }, + }, + auth: []AuthMethod{Password("password")}, + }, + { + name: "keyboard-interactive", + serverConf: &ServerConfig{ + KeyboardInteractiveCallback: func(ConnMetadata, KeyboardInteractiveChallenge) (*Permissions, error) { + return restrictedPerms() + }, + }, + auth: []AuthMethod{KeyboardInteractive(func(string, string, []string, []bool) ([]string, error) { + return nil, nil + })}, + }, + } { + t.Run(tt.name, func(t *testing.T) { + c1, c2, err := netPipe() + if err != nil { + t.Fatalf("netPipe: %v", err) + } + defer c1.Close() + defer c2.Close() + + tt.serverConf.AddHostKey(testSigners["rsa"]) + go NewServerConn(c1, tt.serverConf) + + clientConf := &ClientConfig{ + User: "user", + Auth: tt.auth, + HostKeyCallback: InsecureIgnoreHostKey(), + } + if _, _, _, err := NewClientConn(c2, "", clientConf); err == nil { + t.Fatalf("client login succeeded via %s auth with a callback returning a mismatching source-address", tt.name) + } + }) + } +} + func TestVerifiedPublicCallbackPartialSuccessBadUsage(t *testing.T) { c1, c2, err := netPipe() if err != nil {