Subject: Unix Domain Socket Forwarding

From: William Ahern <[email-masked]>
Date: Tue, 9 May 2006 13:58:49 -0700

I'd like to solicit opinions on a protocol for Unix domain socket
forwarding.

As a proof-of-concept I wrote a patch to OpenSSH 4.3p2 and added
the following messages:

[email-masked]
[email-masked]
[email-masked]
[email-masked]

I can go into details on the format of those messages if anybody wishes, but
to cut to the chase I'm most interested in hearing what people have to say
about umask settings, since some SysV platforms obey file permissions on
Unix domain sockets.

In my implementation I decided to let each end handle the umask
autonomously, partly for simplictity's sake, partly so that the remote side
is solely responsible for local policy, and lastly with the notion that
"streamlocal" might be translatable on Windows (aside from Cygwin's
compatbility layer) and that umask in such a case likely could have no
useful meaning.

Also, for reverse forwarded connections relayed via the
"forwarded-streamlocal" message, the format is a string path followed by
name=value pairs terminated by an empty string. The idea here being that
interesting information could be relayed, possibly allowing something like
getpeereid(2) to be emulated on the other side (getpeereid(2) and similar
API's allows the effective uid and gid of the connecting peer to be
queried), but only on a best-effort type of basis (since trusting the remote
end is a dubious proposition at best). For this piece, I'd also like to hear
opinions on whether anybody would chuck that bit for simplicity's sake.

The existing OpenSSH patch can be found here:

http://www.25thandclement.com/~william/projects/streamlocal.html

Oh, and another interesting problem is handling unlinking of existing
sockets. Since, unfortunately, Unix domain sockets aren't first class
objects in the Unix namespace model, handling dangling socket paths poses
some headaches. Again, I left this up to each side, independently.

- Bill

---

From: "Timo J. Rinne" <[email-masked]>
Date: Wed, 10 May 2006 01:47:37 +0300

> [email-masked]

We have also implemented an extension to connect domain local streams 
for couple of projects.  To circumvent (or ignore) the problem arising 
from umasks and different local stream implementations and access 
control systems, we only implemented "[email-masked]" and left 
remote forwarding of local streams aside.  In my opinion remote 
forwarding is anyways a feature that is not very often needed but is 
very often considered harmful by corporate security people.

However a few points:

1) In my opinion, this mechanism should be independent of server system 
specific implementation of local streams.
2) Stream identifiers should be opaque strings, which could look like 
pathnames in unix server but in some other system they could look 
something else.
3) Point 2 makes remote forwarding actually somewhat tricky, since the 
client should then know what kind of local stream naming policy the 
server host has.
4) It is not absolutely necessary to make stream identifiers to be full 
pathnames of unix system sockets.  Instead the server could maybe prefix 
the path somehow and maybe do some access control along the way.
5) Some systems don't enforce the access bits to local domain sockets 
but if they otherwise implement unix filesystem security, the access 
control can be implemented by setting the access bits of the parent 
directory to reflect the access policy wanted.

We basically only defined an extension channel type and only one format:

       byte      SSH_MSG_CHANNEL_OPEN
       string    "[email-masked]"
       uint32    sender channel
       uint32    initial window size
       uint32    maximum packet size
       string    opaque local stream endpoint specifier

The confirmation message does not have any channel type specific data.

> about umask settings, since some SysV platforms obey file permissions on

Since they can't be trusted, the only way is to use subdirectories.  I 
still maintain that the client side method should be independent of 
this.  For example in Windows, the logical choice would be to use named 
pipes for "local streams".

Maybe there could be some kind of access control specifier in the remote 
forward request that at least could specify whether the socket should be 
connectable by anybody or only by the user itself.  I haven't thought 
this so much, but to me it seems, that most obvious choice there would 
be using some kind of mapping between the real socket name and the local 
stream id.  For example local stream /tmp/foo/bar could actually be 
presented in the server system as unix domain socket /tmp/foo/bar/sock 
and the permissions of the directory /tmp/foo/bar would be set according 
to the access control parameters in the request and access bits of the 
actual socket would be 666 to make everything work identically in 
systems that enforce the socket node permissions and the ones that do 
not.  Of course all this would be annoying if one wants to listen or 
connect to local domain sockets not using the naming convention above.

> compatbility layer) and that umask in such a case likely could have no

Absolutely.

> some headaches.

Very much so.  This was also a reason why I decided to forget the remote 
forwarding for local streams.  In our implementations, the local stream 
identifier (e.g. unix domain socket pathname) is always discovered by 
the client some other way and then used as a parameter to 
"[email-masked]" request.  In this way, client doesn't need to 
know what kind of semantics (if any) the local stream identifier has. 
Also the "stream listener" removal is something that is never done by 
the server but some other component.  Of course in remote forwards this 
would not be the case.

> Again, I left this up to each side, independently.

I can't see any other way.

One scenario that I think remote forwarding might be implemented in a 
local stream abstraction independent and at least somewhat secure way 
would be as follows:

1) Client requests the forwarding but does not specify the actual 
listener.  Something like:

       byte      SSH_MSG_GLOBAL_REQUEST
       string    "[email-masked]"
       boolean   want reply (always true)
       int32     access policy specifier (to be defined)

2) The server generates the path for socket according to the policy 
using access bits of the socket parent directory.  Of course it also 
creates the directory and the actual socket.  Then it returns the 
identifier of the listener (= unix domain socket path) in:

       byte      SSH_MSG_REQUEST_SUCCESS
       string    opaque local stream endpoint specifier

3) Now client could use this local stream id to connect to the listener 
just created (e.g. "[email-masked]" above).  In practise it would 
not connect to the listener itself but rather deliver the endpoint 
identifier to some other component for connecting either through the 
secsh connection or locally in the server system.

In this scenario the dangling dead sockets would not be a problem, since 
the server can always generate a path that is free.

Regards,
-- 
Timo J. Rinne <[email-masked]>        Valimotie 17       +358 20 500 7000 T
Chief Technology Officer           FIN-00380 Helsinki +358 20 500 7397 F
SSH Communications Security Corp.  Finland            http://www.ssh.com