BSDs and wl_client_get_credentials

Simon McVittie smcv at collabora.com
Mon Jan 21 11:35:12 UTC 2019


On Mon, 21 Jan 2019 at 12:40:11 +0200, Pekka Paalanen wrote:
> I don't think we can fix wl_client_get_credentials(), the semantics are
> very explicitly tied to the SO_PEERCRED behaviour. What I think we
> should do instead is to look into making a new API using
> SCM_CREDENTIALS.

D-Bus uses (and needs) similar functionality, so if you learn from
what D-Bus did, you can fast-forward past a lot of tedious portability
discussions that we already had.

There are basically two kernel API families that do this sort of thing:

* A socket option or function for the server to query the client's
  credentials without the client's help. This is GLib
  g_socket_get_credentials(). Known implementations in libdbus
  (GLib supports most of these, but I think not all of them):

  - Linux SO_PEERCRED
  - OpenBSD SO_PEERCRED (like Linux but with a different struct name)
  - NetBSD LOCAL_PEEREID (like Linux and OpenBSD but with different naming)
  - Solaris getpeerucred()
  - getpeereid() (available on multiple OSs but doesn't tell you the pid)

* A socket option or function for the client to send credentials attached
  to a message, which the server can trust to have been validated by the
  kernel. This is GLib's higher-level g_unix_connection_send_credentials() and
  g_unix_connection_receive_credentials(), or lower-level
  GUnixCredentialsMessage. There's only one implementation in libdbus,
  which I think is also in GLib:

  - FreeBSD/DragonflyBSD SOL_SOCKET/SCM_CREDS

See libdbus dbus/dbus-sysdeps-unix.c or GLib gio/gsocket.c,
gio/gunixconnection.c, gio/gunixcredentialsmessage.c for reasonably portable
versions of both of those, and in particular a good amount of the research for
which OSs support which APIs.

I think it's reasonable for SO_PEERCRED, LOCAL_PEEREID etc. to be combined into
one portable abstraction (as long as it has a way to say "unknown" for each
field, to account for protocols that give a superset or subset of what Linux
does) but I agree that SCM_CREDENTIALS makes more sense as a separate
abstraction. With hindsight, GLib gets this right where libdbus didn't.

Linux also has SO_PEERGROUPS which returns the supplementary groups (as
far as I know it is not documented whether the primary group is included,
so you have to get the primary group from SO_PEERCRED and combine the two).
libdbus is careful to distinguish between "we know the primary group is 42
and there might be other groups" (on older Linux or non-Linux, when
SO_PEERGROUPS didn't work) and "we know the process has exactly one group,
and it is 42" (on recent Linux, when SO_PEERGROUPS did work). In
application-level API we only expose group information in the SO_PEERGROUPS
case, where we can guarantee that we have the full list.

> But could we make it return obviously invalid values?

libdbus uses (pid_t) -1, (uid_t) -1 and (gid_t) -1 as the obviously-invalid
values. On POSIX systems, the -1 values for uid_t and gid_t are not suitable
for normal use due to their special meaning in setreuid() and setregid(), and
negative PIDs are not suitable for normal use due to their special meaning in
kill(), so that seems safe.

> As for new API, we could have wl_client_get_scm_credentials() or
> something, and automate the sending and receiving of SCM_CREDENTIALS at
> the first message to go through the Wayland connection. Does
> SCM_CREDENTIALS require at least one byte of actual data like
> SCM_RIGHTS does?

Yes it does, on at least some OSs. That's why the D-Bus wire protocol starts
with a single '\0', before the (ASCII) SASL handshake: on OSs that use
SCM_CREDENTIALS, the \0 has the credentials attached to it.

> Currently I have no clear opinion on what might be best. PID, UID and
> GID are quite poor for authorization anyway, I wish we could identify
> some more... fine-grained? At the application level? But is there even
> a useful definition of "an application" from the kernel point of view?

Not really. To implement that, you'd need a race-free way to determine what
"application" your peer is part of. The security boundary between unconfined
processes with the same uid is sufficiently nonexistent that this is unlikely
to be viable without using LSMs (like Snap's use of AppArmor) and/or containers
(like Flatpak's use of various namespaces).

Note that deriving information from the pid is easy to defeat if you have
access to a mechanism like setuid or filesystem capabilities, which
escalates capabilities while preserving the pid.
See <https://bugs.freedesktop.org/show_bug.cgi?id=83499>.
It can also be defeated by pid reuse, although that's a harder attack.

In Linux, SO_PEERCGROUP has been proposed but not accepted; if that
existed then you'd be able to tell which cgroup the peer was in, for
example on systemd systems.

On systems with LSMs (SELinux, AppArmor, etc.) you can use SO_PEERSEC
to get the peer's LSM label, which is useful if your app framework uses
LSMs (for example Snap does, but only on systems booted with AppArmor
enabled).

Flatpak uses /proc/$pid/root/.flatpak-info as a roundabout way to discover
which mount namespace the peer is in, which is not *perfectly* race-free
(it can be defeated by pid reuse) but is believed to be good enough. It
helps that Flatpak's D-Bus proxy is not under the control of the app,
so it's hard (hopefully impossible?) to induce it to exit at just the
right moment.

The D-Bus Containers1 interface (which I'm slowly working on as a replacement
for Flatpak's D-Bus proxy) uses a separate listening socket per
(app-)container, and requires the container framework to ensure that the
confined app can't connect to the "global" D-Bus socket (as Flatpak already
does).

    smcv


More information about the wayland-devel mailing list