Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x/crypto/ssh: Unable to start subsystem without an exec or shell message #35025

Open
magisterquis opened this issue Oct 21, 2019 · 7 comments
Open
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@magisterquis
Copy link

What version of Go are you using (go version)?

$ go version
go version go1.13.3 openbsd/amd64

Does this issue reproduce with the latest release?

It does.

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/stuart/.cache/go-build"
GOENV="/home/stuart/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="openbsd"
GONOPROXY=""
GONOSUMDB=""
GOOS="openbsd"
GOPATH="/home/stuart/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/home/stuart/.go/1.13.3"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/home/stuart/.go/1.13.3/pkg/tool/openbsd_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="0"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -fmessage-length=0"

What did you do?

Wrote a little server which implemented an SSH subsystem and tried to write a client to use that subsystem.
A minimal server: http://play.golang.org/p/HtAmPnyMpDl
A minimal client: http://play.golang.org/p/VqSuWARBHJT

What did you expect to see?

Output from the subsystem

What did you see instead?

That the session hadn't been started.

When starting a subsystem, a channel is opened and a subsystem request is sent with the name of the subsystem. According to RFC4254 exactly one of shell, exec, or subsystem requests can succeed per channel, though it doesn't say that only one may be sent. Currently, the subsystem request is sent with session.RequestSubsystem, but there is no way to call the non-exported session.start without calling session.Start (which sends an exec request) or session.Shell (which sends a shell request). This means that to start subsystem, the server will first receive a subsystem request and then an exec or shell request. Changing session.RequestSubsystem to call session.start (i.e. to parallel session.Shell and session.Start) causes the server to start the subsystem and delivers output to the client.

The OpenSSH client only sends a subsystem message. The OpenSSH server replies with false exec request following a subsystem request.

@gopherbot gopherbot added this to the Unreleased milestone Oct 21, 2019
@dmitshur dmitshur added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Oct 21, 2019
@dmitshur
Copy link
Contributor

dmitshur commented Oct 21, 2019

/cc @hanwen @FiloSottile per owners.

@skinowski
Copy link

skinowski commented Jun 29, 2021

Due to this bug, we cannot obtain exit status or signal from subsystem as well. This is because we cannot Session.Wait() on the subsystem. Probably the fix is easy, add the forgotten call to Session.start() in session:

--- a/vendor/golang.org/x/crypto/ssh/session.go
+++ b/vendor/golang.org/x/crypto/ssh/session.go
@@ -221,6 +221,9 @@ type subsystemRequestMsg struct {
 // RequestSubsystem requests the association of a subsystem with the session on the remote host.
 // A subsystem is a predefined command that runs in the background when the ssh session is initiated
 func (s *Session) RequestSubsystem(subsystem string) error {
+       if s.started {
+               return errors.New("ssh: session already started")
+       }
        msg := subsystemRequestMsg{
                Subsystem: subsystem,
        }
@@ -228,7 +231,10 @@ func (s *Session) RequestSubsystem(subsystem string) error {
        if err == nil && !ok {
                err = errors.New("ssh: subsystem request failed")
        }
-       return err
+       if err != nil {
+               return err
+       }
+       return s.start()
 }

@achal1012
Copy link

Hi @dmitshur @hanwen @FiloSottile: Did you get a chance to review @skinowski's change above? Appreciate your time!

@hanwen
Copy link
Contributor

hanwen commented Nov 15, 2023

The code in session.go has been unchanged since ~2012, so I wasn't too familiar, but I read your report carefully. I think the analysis is correct, but given that nobody has complained about it in 10 years, I suppose OpenSSH actually accepts the [RequestSubsystem("test"), Start("shell")] sequence? Or has this been broken (ie. unused) all this time?

There is no test for this functionality against OpenSSH. We should add one (I think scp is served through a subsystem; we don't need build a full scp client, just check that we can start the subsystem) before we go any further to make sure we understand the status quo and be sure we fix the problem.

@achal1012
Copy link

achal1012 commented Nov 15, 2023

Thank you @hanwen for the quick response: I did a test where I took Server code from @magisterquis A minimal server: http://play.golang.org/p/HtAmPnyMpDl and used OpenSSH client on my Mac to run the scp command using:

scp -P 2222 file.md localhost: and on the server side I got just one request with Type subsystem and sftp name

So looks like the OpenSSH client handles the starting of the same session as the Subsytem session correctly? i.e. no Start is called before calling RequestSubsystem

@hanwen
Copy link
Contributor

hanwen commented Nov 15, 2023

I was asking

I suppose OpenSSH actually accepts the [RequestSubsystem("test"), Start("shell")] sequence? Or has this been broken (ie. unused) all this time?

I did a test where I took Server code ..

the way to test this is run a x/crypto/ssh client against OpenSSH server, not the reverse.

@kobrineli
Copy link

kobrineli commented May 28, 2024

@magisterquis @achal1012 @hanwen
Hi! Faced with the same problem. It seems like there really must be a private start call inside RequestSubsystem.
Calling Start("shell") after RequestSubsystem doesn't work, because according to RFC 4254 requesting another request from exec, shell and subsystem, must fail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

7 participants