bug-hurd
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

On SO_PEERCRED & SCM_CREDS


From: Sergey Bugaev
Subject: On SO_PEERCRED & SCM_CREDS
Date: Mon, 6 Feb 2023 16:09:10 +0300

Hello!

I came across this glibc patch [0] from Svante Signell that is
apparently not upstream, but shipped in Debian. I have also seen this
wiki page [1]. So here is a braindump of how that patch is broken and
why we cannot easily fix it.

[0]: 
https://salsa.debian.org/glibc-team/glibc/-/blob/sid/debian/patches/hurd-i386/tg-sendmsg-SCM_CREDS.diff
[1]: https://www.gnu.org/software/hurd/open_issues/sendmsg_scm_creds.html

I understand that implementing these APIs is important for D-Bus and
Wayland (and X.Org, maybe? I don't know), but I can also see why the
patch can not be upstreamed.

For one thing, the patch uses auth_server_authenticate (newport =
MACH_PORT_NULL), and you know what that means, right?
Man-in-the-middle attacks! Just get root to authenticate to you (could
be from root sending these same SCM_CREDS to you, or from something
else entirely like io_reauth), and forward its rendezvous port to your
peer. Boom, the peer thinks you're root.

I'm posting this publicly because it doesn't look like there is
anything sensitive on D-Bus right now. I've checked on my Debian
GNU/Hurd box, and there's only org.freedesktop.DBus itself, while on
darnassus there does not seem to be a D-Bus socket at all, despite the
daemon running and the socket path being specified in its config as
one would expect. But if we had systemd or Polkit or something
listening on D-Bus and providing privileged services (such as: spawn
this command as root), it'd be trivial to trick them into believing
you're root and getting them to run a program of your choosing as
root.

And I don't think it's possible to fix this, at least not easily.

Fixing this would require making use of the newport mechanism for
sure. Ideally, that'd require us to somehow replace the socket port in
the first peer with a new "reauthenticated" socket port (not actually
reauthenticated as in io_reauth). This leaves the question of what to
do with already queued data unclear -- we can neither trust it (since
it was queued on the old socket port), nor ignore it (that would
surely mess with the peer's expectations) The alternative to replacing
the socket port is some awkward "confirm that you're actually trying
to reauth this socket port" dance, a la what we did with
proc_reauthenticate_complete (), but more complicated (you'd have to
pass the sockaddr as a Mach port). But that would not work either,
because...

it's important that both send() and recv() on the socket remain
asynchronous, i.e. the two calls on the two sides must not need to
actually rendezvous with each other. The first peer must be able to
queue some data (and creds) with a non-blocking send() and go on its
merry way, without waiting for the second peer to recv(). In
particular this means that the first peer cannot synchronously wait
for auth_user_authenticate () to return, since that waits until the
second peer calls recv(). Similarly, once the second peer does get to
recv() the data & creds, it must be able to so without blocking, so
even if we do manage to send some newport to the first peer, we cannot
e.g. wait until it calls some sort of "socket_scm_creds_ack ()" RPC on
it.

-------

What D-Bus and Wayland would really prefer is getsockopt (SO_PEERCRED)
rather than SCM_CREDS. The former gives you the peer's creds without
the peer having to do anything special at all. Importantly, it should
return peer's credentials *at the time of socket creation*, otherwise
there's the possibility of a race-based attack: you create the socket
and queue (send) your request, then you quickly exec ("su"). By the
time dbus-daemon sees the request and checks your credentials, the
process belongs to root.

Since [2], pflocal already caches creator's primary [UG]ID, so I guess
that could be used to implement getsockopt (SO_PEERCRED) without any
client involvement. (Except that it seems to set them to 0 if unknown,
which is not going to work for obvious reasons...)

[2]: 
https://git.savannah.gnu.org/cgit/hurd/hurd.git/commit/?id=594cfb7586089dfefab60574495baf6ed4048c1d

But the issue with *that* is also the usual one when transferring
[UG]IDs as data (and not finding out the [UG]IDs using the auth
protocol); namely the two peers can be in different "UID namespaces",
that is, use different auth servers (think fakeauth). Specifically for
SO_PEERCRED and fakeauth, imagine the two peers both run with the same
fakeauth server, but pflocal does not. So the peer who calls
getsockopt (SO_PEERCRED) will get UID=1000 from pflocal, even though
its peer's UID is 0 as far as it's concerned.

But note that:
* fakeauth [currently] doesn't support this -- it always forwards
authenticate() RPCs to the upstream auth server, and only itself acts
on auth_getids () & friends. So even if a client and a server are both
using the same fakeauth, the server will still see the 'outer'
credentials of the client (i.e. UID=1000). IMO this should be fixed
independently.
* Linux _does_ support using SO_PEERCRED across PID and UID
namespaces, and converts the IDs as appropriate.

Sergey



reply via email to

[Prev in Thread] Current Thread [Next in Thread]