Blame SOURCES/0001-GDBus-prefer-getsockopt-style-credentials-passing-AP.patch

d896e1
From ee502dbbe89a5976c32eb8863c9a9d274ddb60e1 Mon Sep 17 00:00:00 2001
d896e1
From: Simon McVittie <smcv@collabora.com>
d896e1
Date: Mon, 14 Oct 2019 08:47:39 +0100
d896e1
Subject: [PATCH] GDBus: prefer getsockopt()-style credentials-passing APIs
d896e1
d896e1
Conceptually, a D-Bus server is really trying to determine the credentials
d896e1
of (the process that initiated) a connection, not the credentials that
d896e1
the process had when it sent a particular message. Ideally, it does
d896e1
this with a getsockopt()-style API that queries the credentials of the
d896e1
connection's initiator without requiring any particular cooperation from
d896e1
that process, avoiding a class of possible failures.
d896e1
d896e1
The leading '\0' in the D-Bus protocol is primarily a workaround
d896e1
for platforms where the message-based credentials-passing API is
d896e1
strictly better than the getsockopt()-style API (for example, on
d896e1
FreeBSD, SCM_CREDS includes a process ID but getpeereid() does not),
d896e1
or where the getsockopt()-style API does not exist at all. As a result
d896e1
libdbus, the reference implementation of D-Bus, does not implement
d896e1
Linux SCM_CREDENTIALS at all - it has no reason to do so, because the
d896e1
SO_PEERCRED socket option is equally informative.
d896e1
d896e1
This change makes GDBusServer on Linux more closely match the behaviour
d896e1
of libdbus.
d896e1
d896e1
In particular, GNOME/glib#1831 indicates that when a libdbus client
d896e1
connects to a GDBus server, recvmsg() sometimes yields a SCM_CREDENTIALS
d896e1
message with cmsg_data={pid=0, uid=65534, gid=65534}. I think this is
d896e1
most likely a race condition in the early steps to connect:
d896e1
d896e1
        client           server
d896e1
    connect
d896e1
                         accept
d896e1
    send '\0' <- race -> set SO_PASSCRED = 1
d896e1
                         receive '\0'
d896e1
d896e1
If the server wins the race:
d896e1
d896e1
        client           server
d896e1
    connect
d896e1
                         accept
d896e1
                         set SO_PASSCRED = 1
d896e1
    send '\0'
d896e1
                         receive '\0'
d896e1
d896e1
then everything is fine. However, if the client wins the race:
d896e1
d896e1
        client           server
d896e1
    connect
d896e1
                         accept
d896e1
    send '\0'
d896e1
                         set SO_PASSCRED = 1
d896e1
                         receive '\0'
d896e1
d896e1
then the kernel does not record credentials for the message containing
d896e1
'\0' (because SO_PASSCRED was 0 at the time). However, by the time the
d896e1
server receives the message, the kernel knows that credentials are
d896e1
desired. I would have expected the kernel to omit the credentials header
d896e1
in this case, but it seems that instead, it synthesizes a credentials
d896e1
structure with a dummy process ID 0, a dummy uid derived from
d896e1
/proc/sys/kernel/overflowuid and a dummy gid derived from
d896e1
/proc/sys/kernel/overflowgid.
d896e1
d896e1
In an unconfigured GDBusServer, hitting this race condition results in
d896e1
falling back to DBUS_COOKIE_SHA1 authentication, which in practice usually
d896e1
succeeds in authenticating the peer's uid. However, we encourage AF_UNIX
d896e1
servers on Unix platforms to allow only EXTERNAL authentication as a
d896e1
security-hardening measure, because DBUS_COOKIE_SHA1 relies on a series
d896e1
of assumptions including a cryptographically strong PRNG and a shared
d896e1
home directory with no write access by others, which are not necessarily
d896e1
true for all operating systems and users. EXTERNAL authentication will
d896e1
fail if the server cannot determine the client's credentials.
d896e1
d896e1
In particular, this caused a regression when CVE-2019-14822 was fixed
d896e1
in ibus, which appears to be resolved by this commit. Qt clients
d896e1
(which use libdbus) intermittently fail to connect to an ibus server
d896e1
(which uses GDBusServer), because ibus no longer allows DBUS_COOKIE_SHA1
d896e1
authentication or non-matching uids.
d896e1
d896e1
Signed-off-by: Simon McVittie <smcv@collabora.com>
d896e1
Closes: https://gitlab.gnome.org/GNOME/glib/issues/1831
d896e1
---
d896e1
 gio/gcredentialsprivate.h | 18 ++++++++++++++++++
d896e1
 gio/gdbusauth.c           | 27 +++++++++++++++++++++++++--
d896e1
 2 files changed, 43 insertions(+), 2 deletions(-)
d896e1
d896e1
diff --git a/gio/gcredentialsprivate.h b/gio/gcredentialsprivate.h
d896e1
index 06f0aed19..e9ec09b9f 100644
d896e1
--- a/gio/gcredentialsprivate.h
d896e1
+++ b/gio/gcredentialsprivate.h
d896e1
@@ -81,6 +81,18 @@
d896e1
  */
d896e1
 #undef G_CREDENTIALS_SPOOFING_SUPPORTED
d896e1
 
d896e1
+/*
d896e1
+ * G_CREDENTIALS_PREFER_MESSAGE_PASSING:
d896e1
+ *
d896e1
+ * Defined to 1 if the data structure transferred by the message-passing
d896e1
+ * API is strictly more informative than the one transferred by the
d896e1
+ * `getsockopt()`-style API, and hence should be preferred, even for
d896e1
+ * protocols like D-Bus that are defined in terms of the credentials of
d896e1
+ * the (process that opened the) socket, as opposed to the credentials
d896e1
+ * of an individual message.
d896e1
+ */
d896e1
+#undef G_CREDENTIALS_PREFER_MESSAGE_PASSING
d896e1
+
d896e1
 #ifdef __linux__
d896e1
 #define G_CREDENTIALS_SUPPORTED 1
d896e1
 #define G_CREDENTIALS_USE_LINUX_UCRED 1
d896e1
@@ -100,6 +112,12 @@
d896e1
 #define G_CREDENTIALS_NATIVE_SIZE (sizeof (struct cmsgcred))
d896e1
 #define G_CREDENTIALS_UNIX_CREDENTIALS_MESSAGE_SUPPORTED 1
d896e1
 #define G_CREDENTIALS_SPOOFING_SUPPORTED 1
d896e1
+/* GLib doesn't implement it yet, but FreeBSD's getsockopt()-style API
d896e1
+ * is getpeereid(), which is not as informative as struct cmsgcred -
d896e1
+ * it does not tell us the PID. As a result, libdbus prefers to use
d896e1
+ * SCM_CREDS, and if we implement getpeereid() in future, we should
d896e1
+ * do the same. */
d896e1
+#define G_CREDENTIALS_PREFER_MESSAGE_PASSING 1
d896e1
 
d896e1
 #elif defined(__NetBSD__)
d896e1
 #define G_CREDENTIALS_SUPPORTED 1
d896e1
diff --git a/gio/gdbusauth.c b/gio/gdbusauth.c
d896e1
index 752ec23fc..14cc5d70e 100644
d896e1
--- a/gio/gdbusauth.c
d896e1
+++ b/gio/gdbusauth.c
d896e1
@@ -31,6 +31,7 @@
d896e1
 #include "gdbusutils.h"
d896e1
 #include "gioenumtypes.h"
d896e1
 #include "gcredentials.h"
d896e1
+#include "gcredentialsprivate.h"
d896e1
 #include "gdbusprivate.h"
d896e1
 #include "giostream.h"
d896e1
 #include "gdatainputstream.h"
d896e1
@@ -969,9 +970,31 @@ _g_dbus_auth_run_server (GDBusAuth              *auth,
d896e1
 
d896e1
   g_data_input_stream_set_newline_type (dis, G_DATA_STREAM_NEWLINE_TYPE_CR_LF);
d896e1
 
d896e1
-  /* first read the NUL-byte */
d896e1
+  /* read the NUL-byte, possibly with credentials attached */
d896e1
 #ifdef G_OS_UNIX
d896e1
-  if (G_IS_UNIX_CONNECTION (auth->priv->stream))
d896e1
+#ifndef G_CREDENTIALS_PREFER_MESSAGE_PASSING
d896e1
+  if (G_IS_SOCKET_CONNECTION (auth->priv->stream))
d896e1
+    {
d896e1
+      GSocket *sock = g_socket_connection_get_socket (G_SOCKET_CONNECTION (auth->priv->stream));
d896e1
+
d896e1
+      local_error = NULL;
d896e1
+      credentials = g_socket_get_credentials (sock, &local_error);
d896e1
+
d896e1
+      if (credentials == NULL && !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
d896e1
+        {
d896e1
+          g_propagate_error (error, local_error);
d896e1
+          goto out;
d896e1
+        }
d896e1
+      else
d896e1
+        {
d896e1
+          /* Clear the error indicator, so we can retry with
d896e1
+           * g_unix_connection_receive_credentials() if necessary */
d896e1
+          g_clear_error (&local_error);
d896e1
+        }
d896e1
+    }
d896e1
+#endif
d896e1
+
d896e1
+  if (credentials == NULL && G_IS_UNIX_CONNECTION (auth->priv->stream))
d896e1
     {
d896e1
       local_error = NULL;
d896e1
       credentials = g_unix_connection_receive_credentials (G_UNIX_CONNECTION (auth->priv->stream),
d896e1
-- 
d896e1
2.23.0
d896e1