Discussion:
feature suggestion: implement SO_PEERCRED on local AF_INET/AF_INET6 sockets (allow uid-based identification on localhost)
Andy Lutomirski
2014-10-15 14:41:48 UTC
Permalink
Given an AF_UNIX socket, the getsockopt(, SOL_SOCKET, SO_PEERCRED,,)
call allows one endpoint to authenticate the other endpoint's pid, uid
and gid.
The call is valid on AF_INET and AF_INET6 sockets but returns no data
(pid=0, uid=-1, gid=-1). Obviously it is meaningless to try to get
such credentials from a INET/INET6 socket in general, but there is one
case where it would make sense: namely, when the endpoint is local
(i.e., when the socket is a connection to the same machine, e.g., when
connecting to 127.0.0.0/8 or ::1/32).
Being able to authenticate local INET/INET6 sockets would be immensely
valuable for a number of programs, to provide some kind of access
control to local sockets. For example, ssh allows port forwarding
using the -L and -D options: by default or by option (cf. the
GatewayPorts option of ssh), these port-forwarding sockets can be
restricted to localhost, but of course they cannot be restricted to
the user running ssh, which makes them a huge security problem. Many
programs suffer from the same problem (they restrict some kind of
connection to localhost, but they of course cannot make a restriction
on which user will be able to connect).
One cannot simply retort "these programs should be using Unix-domain
sockets instead": I don't think many browsers support using a SOCKS
proxy or an HTTP proxy over a Unix-domain socket, and in the latter
case I'm not even sure it would make sense (protocol-wise).
http://www.lehman.cuny.edu/cgi-bin/man-cgi?getpeerucred+3
("The system currently supports both sides of connection end-points
for local AF_UNIX, AF_INET, and AF_INET6 sockets"), Solaris, or at
least some version thereof, support authentication of local AF_INET
and AF_INET6 sockets.
I think it would be wonderful if Linux had this. I'm willing to work
on the implementation if it is considered *a priori* acceptable for
inclusion.
The data seems to be available, since it is exposed in /proc/net/tcp
and /proc/net/tcp6 and whatnots (implementation details left aside, it
is merely a question of matching a line with opposite endpoints to the
current socket and returning it).
[In principle, a userland program can parse /proc/net/tcp so it does
not need the feature I am suggesting, but in practice parsing a text
file to communicate with the kernel is yucky at best, and probably not
very robust (e.g., /proc might not be mounted), and it would be very
difficult to convince, say, the OpenSSH authors to include code that
parses the Linux /proc/net/tcp format (or even link with a library
having this under a more standard getsockpot() interface is cleaner
and opens at least some kind of hope that programs would agree to use
it.]
Question number 1: If this feature were implemented, would it be
considered acceptable for inclusion in the kernel? (If there is some
reason why it can't be accepted, I'd like to know in advance, to avoid
working in vain.)
I will object to adding it as described, for the same reason that I
object to anything that extends the current model of socket-based
credential passing. Ideally, credentials would *never* be implicitly
captured by socket syscalls. We live in the real world, and SO_****CRED
exists, so I think the best we can do is to try to minimize its use.

I can elaborate further, or you can IIRC search the archives for
SCM_IDENTITY, and you can also look at CVE-2013-1979 for a nasty example
of why this model is broken.

That being said, if anyone ever finished the SCM_IDENTITY work, I think
I'd be okay with allowing it over TCP. I can't speak for the network
people, though, and you should ask on netdev (cc'd).
Question number 2: A priori, how difficult would it be to implement
this? (As mentioned above, it seems trivial in principle to merely go
through the local endpoints to find a matching connection, but maybe
there are locking issues that I don't understand that make it much
more difficult than it would seem.) Any guidelines on implementation?
(I imagine one should try to fill sk->sk_peer_cred at connect time,
but I don't really know how difficult this might turn out.)
Dunno.

--Andy
David Madore
2014-10-15 22:30:33 UTC
Permalink
Post by Andy Lutomirski
Given an AF_UNIX socket, the getsockopt(, SOL_SOCKET, SO_PEERCRED,,)
call allows one endpoint to authenticate the other endpoint's pid, uid
and gid.
The call is valid on AF_INET and AF_INET6 sockets but returns no data
(pid=0, uid=-1, gid=-1). Obviously it is meaningless to try to get
such credentials from a INET/INET6 socket in general, but there is one
case where it would make sense: namely, when the endpoint is local
(i.e., when the socket is a connection to the same machine, e.g., when
connecting to 127.0.0.0/8 or ::1/32).
I will object to adding it as described, for the same reason that I
object to anything that extends the current model of socket-based
credential passing. Ideally, credentials would *never* be implicitly
captured by socket syscalls. We live in the real world, and SO_****CRED
exists, so I think the best we can do is to try to minimize its use.
I can elaborate further, or you can IIRC search the archives for
SCM_IDENTITY, and you can also look at CVE-2013-1979 for a nasty example
of why this model is broken.
From what I understand, what was broken is mainly that the credentials
were evaluated when the write() system call took place rather than
when socket() or bind(): this violates the Unix security model
(privilege control occurs when the file descriptor is created, not
when it is used). On the contrary, it is conform to Unix security
principles that credentials are checked implicitly when binding a
socket (this happens when permissions are being checked on the path
when binding or connecting on a Unix domain socket; and to allow
binding to secure ports in the INET domain; and so on). It seems to
me that a suid program that is willing to create or bind a socket on
behalf of its caller without knowing exactly what it will be
connecting to, it should intrinsically be treated as a security
vulnerability, even when it is not obviously exploitable.

Also, to go along the real world examples, identd exists and is used
for identification on local networks (e.g. localhost), so the capture
of credentials already takes place. Unix programmers are aware of
this, and know that a privileged program should not bind a socket if
they don't want to leak privileges. (Another example is the use of -m
owner in iptables.)

And, of course, if Solaris already has this feature, there is some
experience for it. Has there been any documented vulnerability
relating to the fact that Solaris allows getpeerucred() to
authenticate locally connected AF_INET sockets?

Note that since the possibility of using SO_PEERCRED on AF_INET
sockets does not hitherto exist on Linux, we can be sure that nobody
uses it, so it's not like it might open vulnerabilities in existing
code. If you think it's insecure, it can be documented as such (by
comparing it with identd): I still think it's better than having no
control at all when binding to localhost, which is the present
situation (causing, e.g., CVE-2014-2914).

Because SO_PEERCRED currently returns {pid=0,uid=-1,gid=-1} on
AF_INET, we might still return this value if there is any risk that
the endpoint would be unwilling to share its credentials: for example,
this value might be returned if the other endpoint is not ptraceable
by the caller - this would still cover the essential use case, which
is for unprivileged users to authenticate the connections from their
own processes. Would this limitation assuage your worries about the
proposed feature?

The thing is, I don't see any other way the ssh port forwarding mess
can ever be improved. Do you have another solution in mind that?

Any attempt to have some kind of authentication of local sockets that
required participation on the client (authenticatee)'s part is doomed:
if modifying the protocol and/or client code is an option, we might as
well use some form of crypto / TLS. Or Unix-domain sockets. But what
are we supposed to do when modifying the client (to make it send
credentials, use crypto or connect on AF_UNIX) is not an option?
--
David A. Madore
( http://www.madore.org/~david/ )
Andy Lutomirski
2014-10-15 22:54:08 UTC
Permalink
Post by Andy Lutomirski
Post by Andy Lutomirski
Given an AF_UNIX socket, the getsockopt(, SOL_SOCKET, SO_PEERCRED,,)
call allows one endpoint to authenticate the other endpoint's pid, uid
and gid.
The call is valid on AF_INET and AF_INET6 sockets but returns no data
(pid=0, uid=-1, gid=-1). Obviously it is meaningless to try to get
such credentials from a INET/INET6 socket in general, but there is one
case where it would make sense: namely, when the endpoint is local
(i.e., when the socket is a connection to the same machine, e.g., when
connecting to 127.0.0.0/8 or ::1/32).
I will object to adding it as described, for the same reason that I
object to anything that extends the current model of socket-based
credential passing. Ideally, credentials would *never* be implicitly
captured by socket syscalls. We live in the real world, and SO_****CRED
exists, so I think the best we can do is to try to minimize its use.
I can elaborate further, or you can IIRC search the archives for
SCM_IDENTITY, and you can also look at CVE-2013-1979 for a nasty example
of why this model is broken.
From what I understand, what was broken is mainly that the credentials
were evaluated when the write() system call took place rather than
when socket() or bind(): this violates the Unix security model
(privilege control occurs when the file descriptor is created, not
when it is used). On the contrary, it is conform to Unix security
principles that credentials are checked implicitly when binding a
socket (this happens when permissions are being checked on the path
when binding or connecting on a Unix domain socket; and to allow
binding to secure ports in the INET domain; and so on). It seems to
me that a suid program that is willing to create or bind a socket on
behalf of its caller without knowing exactly what it will be
connecting to, it should intrinsically be treated as a security
vulnerability, even when it is not obviously exploitable.
socket has little precedent for checking credentials and none in
POSIX. And you're talking about connect, not bind.
Post by Andy Lutomirski
Also, to go along the real world examples, identd exists and is used
for identification on local networks (e.g. localhost), so the capture
of credentials already takes place. Unix programmers are aware of
this, and know that a privileged program should not bind a socket if
they don't want to leak privileges. (Another example is the use of -m
owner in iptables.)
Ugh.

Identd is completely insecure. Quite a few years ago personally broke
Stanford's entire single sign-on mechanism by exploiting it, against a
hardened, kerberized version, so less. And iptables -m owner is all
about what you can connect to, not what is assumed by the recipient.
(And it's probably insecure, too, in many cases.)
Post by Andy Lutomirski
And, of course, if Solaris already has this feature, there is some
experience for it. Has there been any documented vulnerability
relating to the fact that Solaris allows getpeerucred() to
authenticate locally connected AF_INET sockets?
Does it matter? CVE-2013-1979 existed for many years before anyone noticed.
Post by Andy Lutomirski
Note that since the possibility of using SO_PEERCRED on AF_INET
sockets does not hitherto exist on Linux, we can be sure that nobody
uses it, so it's not like it might open vulnerabilities in existing
code. If you think it's insecure, it can be documented as such (by
comparing it with identd): I still think it's better than having no
control at all when binding to localhost, which is the present
situation (causing, e.g., CVE-2014-2914).
This doesn't follow. *Everybody* uses connect on AF_INET.

IMO anything that sends a caller's credentials needs to be explicit and opt-in.
Post by Andy Lutomirski
Because SO_PEERCRED currently returns {pid=0,uid=-1,gid=-1} on
AF_INET, we might still return this value if there is any risk that
the endpoint would be unwilling to share its credentials: for example,
this value might be returned if the other endpoint is not ptraceable
by the caller - this would still cover the essential use case, which
is for unprivileged users to authenticate the connections from their
own processes. Would this limitation assuage your worries about the
proposed feature?
The thing is, I don't see any other way the ssh port forwarding mess
can ever be improved. Do you have another solution in mind that?
UNIX sockets. Firewall rules. An opt-in mechanism like SCM_IDENTITY
that has explicit support in OpenSSH and doesn't happen unless the
administrator actually requests it in sshd_config.
Post by Andy Lutomirski
Any attempt to have some kind of authentication of local sockets that
if modifying the protocol and/or client code is an option, we might as
well use some form of crypto / TLS. Or Unix-domain sockets. But what
are we supposed to do when modifying the client (to make it send
credentials, use crypto or connect on AF_UNIX) is not an option?
Exactly.

I believe that there is no secure way to authenticate clients that
currently don't authenticate themselves without changing the clients.
That's the whole point: currently-secure are written under the
assumption that they are not exercising their credentials. You can't
safely change that without making it opt-in.

--Andy
David Madore
2014-10-16 00:07:05 UTC
Permalink
Post by Andy Lutomirski
Post by David Madore
Note that since the possibility of using SO_PEERCRED on AF_INET
sockets does not hitherto exist on Linux, we can be sure that nobody
uses it, so it's not like it might open vulnerabilities in existing
code. If you think it's insecure, it can be documented as such (by
comparing it with identd): I still think it's better than having no
control at all when binding to localhost, which is the present
situation (causing, e.g., CVE-2014-2914).
This doesn't follow. *Everybody* uses connect on AF_INET.
IMO anything that sends a caller's credentials needs to be explicit and opt-in.
I'm confused as to whether you mean "opt-in" on the side of the caller
(=process requesting the endpoint's credentials), or on that of the
endpoint (=authenticated process). On the one hand I don't understand
what it could mean on the caller side, on the other hand you mention
explicit support in OpenSSH, which would be the caller in my scenario.

So, in case I haven't been clear enough, the situation I have in mind
is: on "thishost", I run "ssh -L 14321:remotehost:4321 somehost" to
forward connexions on from the local port 14321 of thishost (where ssh
listens on the loopback) to the port 4321 of remotehost.
Unfortunately, now everyone with an acccount on thishost can connect
to port 14321 and effectively emit a connection from somehost to
remotehost on my behalf. I think everyone agrees that this is a huge
problem. But I don't understand how you propose to remedy this.

Patching ssh is an option, but I don't see how to do it (ssh needs to
make sure that the connections it receives on 14321 are from the same
uid, and this seems impossible without the feature I'm discussing).
Patching the kernel is an option. Patching clients that connect to
14321, on the other hand, is not, because there are many different
ones, and their protocol is defined by immutable Internet standards,
so we have no latitude there (for example, we can't ask a Web browser
to connect to Unix domain sockets: there simply isn't a URL scheme to
refer to them). Adding iptables rules is not an option if I'm not the
system administrator on thishost.

So, how can we solve this problem securely?
Post by Andy Lutomirski
I believe that there is no secure way to authenticate clients that
currently don't authenticate themselves without changing the clients.
That's the whole point: currently-secure are written under the
assumption that they are not exercising their credentials. You can't
safely change that without making it opt-in.
Then what are we to do, given that modifying the clients is
impossible?

What about my proposal that user credentials would be returned only if
they refer to the same user as the caller user and that the caller is
permitted to ptrace the endpoint? This answers your objection of
leaking credentials: the caller could do anything at all with the
other side since it could ptrace it - we're just permitting a user to
authenticate their own sockets. A further sysctl could enable the use
of the call in more general cases, for those administrators who think
it should be allowed.
--
David A. Madore
( http://www.madore.org/~david/ )
Andy Lutomirski
2014-10-16 00:11:53 UTC
Permalink
Post by David Madore
Post by Andy Lutomirski
Post by David Madore
Note that since the possibility of using SO_PEERCRED on AF_INET
sockets does not hitherto exist on Linux, we can be sure that nobody
uses it, so it's not like it might open vulnerabilities in existing
code. If you think it's insecure, it can be documented as such (by
comparing it with identd): I still think it's better than having no
control at all when binding to localhost, which is the present
situation (causing, e.g., CVE-2014-2914).
This doesn't follow. *Everybody* uses connect on AF_INET.
IMO anything that sends a caller's credentials needs to be explicit and opt-in.
I'm confused as to whether you mean "opt-in" on the side of the caller
(=process requesting the endpoint's credentials), or on that of the
endpoint (=authenticated process). On the one hand I don't understand
what it could mean on the caller side, on the other hand you mention
explicit support in OpenSSH, which would be the caller in my scenario.
I mean the authenticated process, not the process doing the authentication.
Post by David Madore
So, in case I haven't been clear enough, the situation I have in mind
is: on "thishost", I run "ssh -L 14321:remotehost:4321 somehost" to
forward connexions on from the local port 14321 of thishost (where ssh
listens on the loopback) to the port 4321 of remotehost.
Unfortunately, now everyone with an acccount on thishost can connect
to port 14321 and effectively emit a connection from somehost to
remotehost on my behalf. I think everyone agrees that this is a huge
problem. But I don't understand how you propose to remedy this.
Unfortunately, I think that you need client changes. These could be
semi-transparent (using LD_PRELOAD) or almost completely transparent
(using network namespaces).

Actually, a network namespace-based proxying tool could be very useful.
Post by David Madore
Patching ssh is an option, but I don't see how to do it (ssh needs to
make sure that the connections it receives on 14321 are from the same
uid, and this seems impossible without the feature I'm discussing).
Patching the kernel is an option. Patching clients that connect to
14321, on the other hand, is not, because there are many different
ones, and their protocol is defined by immutable Internet standards,
so we have no latitude there (for example, we can't ask a Web browser
to connect to Unix domain sockets: there simply isn't a URL scheme to
refer to them). Adding iptables rules is not an option if I'm not the
system administrator on thishost.
I misunderstood. I though that you wanted a server-side solution.
Post by David Madore
So, how can we solve this problem securely?
Post by Andy Lutomirski
I believe that there is no secure way to authenticate clients that
currently don't authenticate themselves without changing the clients.
That's the whole point: currently-secure are written under the
assumption that they are not exercising their credentials. You can't
safely change that without making it opt-in.
Then what are we to do, given that modifying the clients is
impossible?
What about my proposal that user credentials would be returned only if
they refer to the same user as the caller user and that the caller is
permitted to ptrace the endpoint? This answers your objection of
leaking credentials: the caller could do anything at all with the
other side since it could ptrace it - we're just permitting a user to
authenticate their own sockets. A further sysctl could enable the use
of the call in more general cases, for those administrators who think
it should be allowed.
Ugh.

That's probably safe, but it's quite disgusting IMO.

--Andy

Loading...