tcpListener.Accept, unixListener.Accept, Client.dial and Client.dialStreamLocal only read the main stream of the channels they return. Data sent by the peer on the extended (stderr) stream accumulates in the extPending buffer and its window credit is only returned by ReadExtended, which is never called. Since the window is shared between the two streams, a misbehaving peer can pin up to 2 MiB per channel and eventually stall it; well-behaved peers never send stderr on these channel types. Drain the stderr stream into io.Discard, mirroring CL 783720 which fixed the same issue in the agent forwarders. The drain takes precedence over reading Stderr through a type assertion on the returned net.Conn, which was never a supported use. For comparison, OpenSSH refuses extended data on these channel types and drops the payload without buffering it. Updates golang/go#80333 Change-Id: I4f1445eee9ce1b56ca62cec342812d63cfdd3e13 Reviewed-on: https://go-review.googlesource.com/c/crypto/+/802900 Reviewed-by: Filippo Valsorda <filippo@golang.org> Reviewed-by: Carlos Amedee <carlos@golang.org> Reviewed-by: Cherry Mui <cherryyz@google.com> LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
119 lines
2.9 KiB
Go
119 lines
2.9 KiB
Go
package ssh
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
)
|
|
|
|
// streamLocalChannelOpenDirectMsg is a struct used for SSH_MSG_CHANNEL_OPEN message
|
|
// with "direct-streamlocal@openssh.com" string.
|
|
//
|
|
// See openssh-portable/PROTOCOL, section 2.4. connection: Unix domain socket forwarding
|
|
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL#L235
|
|
type streamLocalChannelOpenDirectMsg struct {
|
|
socketPath string
|
|
reserved0 string
|
|
reserved1 uint32
|
|
}
|
|
|
|
// forwardedStreamLocalPayload is a struct used for SSH_MSG_CHANNEL_OPEN message
|
|
// with "forwarded-streamlocal@openssh.com" string.
|
|
type forwardedStreamLocalPayload struct {
|
|
SocketPath string
|
|
Reserved0 string
|
|
}
|
|
|
|
// streamLocalChannelForwardMsg is a struct used for SSH2_MSG_GLOBAL_REQUEST message
|
|
// with "streamlocal-forward@openssh.com"/"cancel-streamlocal-forward@openssh.com" string.
|
|
type streamLocalChannelForwardMsg struct {
|
|
socketPath string
|
|
}
|
|
|
|
// ListenUnix is similar to ListenTCP but uses a Unix domain socket.
|
|
func (c *Client) ListenUnix(socketPath string) (net.Listener, error) {
|
|
c.handleForwardsOnce.Do(c.handleForwards)
|
|
m := streamLocalChannelForwardMsg{
|
|
socketPath,
|
|
}
|
|
// send message
|
|
ok, _, err := c.SendRequest("streamlocal-forward@openssh.com", true, Marshal(&m))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !ok {
|
|
return nil, errors.New("ssh: streamlocal-forward@openssh.com request denied by peer")
|
|
}
|
|
ch := c.forwards.add("unix", socketPath)
|
|
|
|
return &unixListener{socketPath, c, ch}, nil
|
|
}
|
|
|
|
func (c *Client) dialStreamLocal(socketPath string) (Channel, error) {
|
|
msg := streamLocalChannelOpenDirectMsg{
|
|
socketPath: socketPath,
|
|
}
|
|
ch, in, err := c.OpenChannel("direct-streamlocal@openssh.com", Marshal(&msg))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
go DiscardRequests(in)
|
|
go io.Copy(io.Discard, ch.Stderr())
|
|
return ch, err
|
|
}
|
|
|
|
type unixListener struct {
|
|
socketPath string
|
|
|
|
conn *Client
|
|
in <-chan forward
|
|
}
|
|
|
|
// Accept waits for and returns the next connection to the listener.
|
|
func (l *unixListener) Accept() (net.Conn, error) {
|
|
s, ok := <-l.in
|
|
if !ok {
|
|
return nil, io.EOF
|
|
}
|
|
ch, incoming, err := s.newCh.Accept()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
go DiscardRequests(incoming)
|
|
go io.Copy(io.Discard, ch.Stderr())
|
|
|
|
return &chanConn{
|
|
Channel: ch,
|
|
laddr: &net.UnixAddr{
|
|
Name: l.socketPath,
|
|
Net: "unix",
|
|
},
|
|
raddr: &net.UnixAddr{
|
|
Name: "@",
|
|
Net: "unix",
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// Close closes the listener.
|
|
func (l *unixListener) Close() error {
|
|
// this also closes the listener.
|
|
l.conn.forwards.remove("unix", l.socketPath)
|
|
m := streamLocalChannelForwardMsg{
|
|
l.socketPath,
|
|
}
|
|
ok, _, err := l.conn.SendRequest("cancel-streamlocal-forward@openssh.com", true, Marshal(&m))
|
|
if err == nil && !ok {
|
|
err = errors.New("ssh: cancel-streamlocal-forward@openssh.com failed")
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Addr returns the listener's network address.
|
|
func (l *unixListener) Addr() net.Addr {
|
|
return &net.UnixAddr{
|
|
Name: l.socketPath,
|
|
Net: "unix",
|
|
}
|
|
}
|