[glib/wip/smcv/gdbus-cross-namespace: 1/2] gdbusauthmechanismexternal: Optionally send empty authorization identity




commit e0a0749268d58e37d5235a24a9f23fdaf7d56155
Author: Simon McVittie <smcv collabora com>
Date:   Sun Jul 24 13:02:51 2022 +0100

    gdbusauthmechanismexternal: Optionally send empty authorization identity
    
    When using a GDBus client in a non-trivial user namespace, the result of
    geteuid() can differ from the uid in the namespace where the server is
    running. This would result in connection attempts being rejected, because
    the identity that the client claims to have does not match the identity
    that the server derives from its credentials.
    
    RFC 4422 allows us to send an empty authorization identity, which means we
    want to authenticate as whatever identity the server can derive from our
    out-of-band credentials. In particular, this resolves the authentication
    failure when crossing between different Linux user namespaces.
    
    Because D-Bus does not have a way to represent an empty initial response
    as distinct from the absence of an initial response, we cannot use the
    initial-response optimization (RFC 4422 ยง4.3.a) in this case, and must
    fall back to waiting for the server to send a challenge.
    
    Unfortunately, GDBus versions older than glib!2826 did not implement
    the server side of this protocol correctly, and would respond to the
    missing initial response in a way that breaks the SASL state machine
    (expecting a response without sending a challenge), causing client and
    server to deadlock with each waiting for the other to respond. Until
    fixed versions of GDBus are widespread, we can't rely on having a server
    that can cope with this, so gate it behind a flag, which can be set for
    connections that are known to cross non-trivial namespace boundaries.
    
    Originally inspired by
    <https://github.com/systemd/systemd/commit/1ed4723d38cd0d1423c8fe650f90fa86007ddf55>,
    and based on earlier work by Giuseppe Scrivano (in which the
    cross-namespace behaviour was unconditional, rather than gated by a
    flag).
    
    Co-authored-by: Giuseppe Scrivano <giuseppe scrivano org>
    Signed-off-by: Simon McVittie <smcv collabora com>

 gio/gdbusauth.c                  |  7 +++++
 gio/gdbusauth.h                  |  1 +
 gio/gdbusauthmechanism.h         |  2 ++
 gio/gdbusauthmechanismanon.c     |  2 ++
 gio/gdbusauthmechanismexternal.c | 58 +++++++++++++++++++++++++---------------
 gio/gdbusconnection.c            |  1 +
 gio/gioenums.h                   |  9 ++++++-
 7 files changed, 58 insertions(+), 22 deletions(-)
---
diff --git a/gio/gdbusauth.c b/gio/gdbusauth.c
index 89cbbf67c6..eadecb50dc 100644
--- a/gio/gdbusauth.c
+++ b/gio/gdbusauth.c
@@ -417,6 +417,7 @@ hexdecode (const gchar  *str,
 static GDBusAuthMechanism *
 client_choose_mech_and_send_initial_response (GDBusAuth           *auth,
                                               GCredentials        *credentials_that_were_sent,
+                                              GDBusConnectionFlags conn_flags,
                                               const gchar* const  *supported_auth_mechs,
                                               GPtrArray           *attempted_auth_mechs,
                                               GDataOutputStream   *dos,
@@ -507,6 +508,7 @@ client_choose_mech_and_send_initial_response (GDBusAuth           *auth,
 
   initial_response_len = 0;
   initial_response = _g_dbus_auth_mechanism_client_initiate (mech,
+                                                             conn_flags,
                                                              &initial_response_len);
 #if 0
   g_printerr ("using auth mechanism with name '%s' of type '%s' with initial response '%s'\n",
@@ -556,6 +558,7 @@ typedef enum
 gchar *
 _g_dbus_auth_run_client (GDBusAuth     *auth,
                          GDBusAuthObserver     *observer,
+                         GDBusConnectionFlags conn_flags,
                          GDBusCapabilityFlags offered_capabilities,
                          GDBusCapabilityFlags *out_negotiated_capabilities,
                          GCancellable  *cancellable,
@@ -574,6 +577,9 @@ _g_dbus_auth_run_client (GDBusAuth     *auth,
   ClientState state;
   GDBusCapabilityFlags negotiated_capabilities;
 
+  g_return_val_if_fail ((connection->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT), NULL);
+  g_return_val_if_fail (!(connection->flags & G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER), NULL);
+
   debug_print ("CLIENT: initiating");
 
   _g_dbus_auth_add_mechs (auth, observer);
@@ -667,6 +673,7 @@ _g_dbus_auth_run_client (GDBusAuth     *auth,
           g_free (line);
           mech = client_choose_mech_and_send_initial_response (auth,
                                                                credentials,
+                                                               conn_flags,
                                                                (const gchar* const *) supported_auth_mechs,
                                                                attempted_auth_mechs,
                                                                dos,
diff --git a/gio/gdbusauth.h b/gio/gdbusauth.h
index 30140fb983..8fa89444a8 100644
--- a/gio/gdbusauth.h
+++ b/gio/gdbusauth.h
@@ -78,6 +78,7 @@ gboolean    _g_dbus_auth_run_server (GDBusAuth             *auth,
 
 gchar      *_g_dbus_auth_run_client (GDBusAuth     *auth,
                                      GDBusAuthObserver     *observer,
+                                     GDBusConnectionFlags   conn_flags,
                                      GDBusCapabilityFlags offered_capabilities,
                                      GDBusCapabilityFlags *out_negotiated_capabilities,
                                      GCancellable  *cancellable,
diff --git a/gio/gdbusauthmechanism.h b/gio/gdbusauthmechanism.h
index bc4afe74db..f0edd19a3e 100644
--- a/gio/gdbusauthmechanism.h
+++ b/gio/gdbusauthmechanism.h
@@ -93,6 +93,7 @@ struct _GDBusAuthMechanismClass
   /* functions for client-side authentication */
   GDBusAuthMechanismState   (*client_get_state)         (GDBusAuthMechanism   *mechanism);
   gchar                    *(*client_initiate)          (GDBusAuthMechanism   *mechanism,
+                                                         GDBusConnectionFlags  conn_flags,
                                                          gsize                *out_initial_response_len);
   void                      (*client_data_receive)      (GDBusAuthMechanism   *mechanism,
                                                          const gchar          *data,
@@ -140,6 +141,7 @@ void                      _g_dbus_auth_mechanism_server_shutdown          (GDBus
 
 GDBusAuthMechanismState   _g_dbus_auth_mechanism_client_get_state         (GDBusAuthMechanism   *mechanism);
 gchar                    *_g_dbus_auth_mechanism_client_initiate          (GDBusAuthMechanism   *mechanism,
+                                                                           GDBusConnectionFlags  conn_flags,
                                                                            gsize                
*out_initial_response_len);
 void                      _g_dbus_auth_mechanism_client_data_receive      (GDBusAuthMechanism   *mechanism,
                                                                            const gchar          *data,
diff --git a/gio/gdbusauthmechanismanon.c b/gio/gdbusauthmechanismanon.c
index 903907f864..5f59d4a61d 100644
--- a/gio/gdbusauthmechanismanon.c
+++ b/gio/gdbusauthmechanismanon.c
@@ -60,6 +60,7 @@ static gchar                   *mechanism_server_get_reject_reason  (GDBusAuthMe
 static void                     mechanism_server_shutdown           (GDBusAuthMechanism   *mechanism);
 static GDBusAuthMechanismState  mechanism_client_get_state          (GDBusAuthMechanism   *mechanism);
 static gchar                   *mechanism_client_initiate           (GDBusAuthMechanism   *mechanism,
+                                                                     GDBusConnectionFlags  conn_flags,
                                                                      gsize                
*out_initial_response_len);
 static void                     mechanism_client_data_receive       (GDBusAuthMechanism   *mechanism,
                                                                      const gchar          *data,
@@ -261,6 +262,7 @@ mechanism_client_get_state (GDBusAuthMechanism   *mechanism)
 
 static gchar *
 mechanism_client_initiate (GDBusAuthMechanism   *mechanism,
+                           GDBusConnectionFlags  conn_flags,
                            gsize                *out_initial_response_len)
 {
   GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism);
diff --git a/gio/gdbusauthmechanismexternal.c b/gio/gdbusauthmechanismexternal.c
index a465862d12..6fe8b1bed3 100644
--- a/gio/gdbusauthmechanismexternal.c
+++ b/gio/gdbusauthmechanismexternal.c
@@ -68,6 +68,7 @@ static gchar                   *mechanism_server_get_reject_reason  (GDBusAuthMe
 static void                     mechanism_server_shutdown           (GDBusAuthMechanism   *mechanism);
 static GDBusAuthMechanismState  mechanism_client_get_state          (GDBusAuthMechanism   *mechanism);
 static gchar                   *mechanism_client_initiate           (GDBusAuthMechanism   *mechanism,
+                                                                     GDBusConnectionFlags  conn_flags,
                                                                      gsize                
*out_initial_response_len);
 static void                     mechanism_client_data_receive       (GDBusAuthMechanism   *mechanism,
                                                                      const gchar          *data,
@@ -360,38 +361,51 @@ mechanism_client_get_state (GDBusAuthMechanism   *mechanism)
 
 static gchar *
 mechanism_client_initiate (GDBusAuthMechanism   *mechanism,
+                           GDBusConnectionFlags  conn_flags,
                            gsize                *out_initial_response_len)
 {
   GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism);
   gchar *initial_response = NULL;
-#if defined(G_OS_UNIX)
-  GCredentials *credentials;
-#endif
 
   g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), NULL);
   g_return_val_if_fail (!m->priv->is_server && !m->priv->is_client, NULL);
 
   m->priv->is_client = TRUE;
-  m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED;
+  m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA;
 
   *out_initial_response_len = 0;
 
-  /* return the uid */
+  if (conn_flags & G_DBUS_CONNECTION_FLAGS_CROSS_NAMESPACE)
+    {
+      /* If backwards-compatibility with GDBus servers < 2.73.3 is not a
+       * concern, we do not send an initial response, because there is
+       * no way to express an empty authorization identity this way.
+       * Instead, we'll reply to the server's first (empty) challenge
+       * with an empty authorization identity in our first response.  */
+      g_debug ("Using cross-namespace EXTERNAL authentication (this will deadlock if server is GDBus < 
2.73.3)");
+    }
+  else
+    {
+      /* Send the Unix uid or Windows SID as an initial response.
+       * This is the only thing that is interoperable with GDBus 2.73.3
+       * servers. */
 #if defined(G_OS_UNIX)
-  credentials = _g_dbus_auth_mechanism_get_credentials (mechanism);
-  g_assert (credentials != NULL);
+      GCredentials *credentials;
+
+      credentials = _g_dbus_auth_mechanism_get_credentials (mechanism);
+      g_assert (credentials != NULL);
 
-  initial_response = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) g_credentials_get_unix_user 
(credentials, NULL));
+      initial_response = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) g_credentials_get_unix_user 
(credentials, NULL));
 #elif defined(G_OS_WIN32)
-  initial_response = _g_win32_current_process_sid_string (NULL);
+      initial_response = _g_win32_current_process_sid_string (NULL);
 #else
-#ifdef __GNUC__
-#pragma GCC diagnostic push
-#pragma GCC diagnostic warning "-Wcpp"
-#warning Dont know how to send credentials on this OS. The EXTERNAL D-Bus authentication mechanism will not 
work.
-#pragma GCC diagnostic pop
-#endif
+      /* GDBus < 2.73.3 servers can't have worked on this platform anyway,
+       * so it isn't a regression to behave as though
+       * G_DBUS_CONNECTION_FLAGS_CROSS_NAMESPACE had been set. */
+      g_debug ("Unknown platform, cannot use initial response in EXTERNAL");
 #endif
+    }
+
   if (initial_response)
     {
       m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED;
@@ -411,8 +425,9 @@ mechanism_client_data_receive (GDBusAuthMechanism   *mechanism,
   g_return_if_fail (m->priv->is_client && !m->priv->is_server);
   g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA);
 
-  /* can never end up here because we are never in the WAITING_FOR_DATA state */
-  g_assert_not_reached ();
+  /* The server sent us a challenge, which should normally
+   * be empty.  We respond with our authorization identity.  */
+  m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND;
 }
 
 static gchar *
@@ -425,10 +440,11 @@ mechanism_client_data_send (GDBusAuthMechanism   *mechanism,
   g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, NULL);
   g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL);
 
-  /* can never end up here because we are never in the HAVE_DATA_TO_SEND state */
-  g_assert_not_reached ();
-
-  return NULL;
+  /* We respond to the server's challenge by sending our
+   * authorization identity, which is the empty string, meaning
+   * whoever the out-of-band credentials say we are.  */
+  *out_data_len = 0;
+  return g_strdup ("");
 }
 
 static void
diff --git a/gio/gdbusconnection.c b/gio/gdbusconnection.c
index f0d50f7d86..454f216bd5 100644
--- a/gio/gdbusconnection.c
+++ b/gio/gdbusconnection.c
@@ -2569,6 +2569,7 @@ initable_init (GInitable     *initable,
       connection->auth = _g_dbus_auth_new (connection->stream);
       connection->guid = _g_dbus_auth_run_client (connection->auth,
                                                   connection->authentication_observer,
+                                                  connection->flags,
                                                   get_offered_capabilities_max (connection),
                                                   &connection->capabilities,
                                                   cancellable,
diff --git a/gio/gioenums.h b/gio/gioenums.h
index fb59449964..3ba1d5b753 100644
--- a/gio/gioenums.h
+++ b/gio/gioenums.h
@@ -1216,6 +1216,12 @@ typedef enum
  * delayed until g_dbus_connection_start_message_processing() is called.
  * @G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER: When authenticating
  * as a server, require the UID of the peer to be the same as the UID of the server. (Since: 2.68)
+ * @G_DBUS_CONNECTION_FLAGS_CROSS_NAMESPACE: When authenticating, try to use
+ *  protocols that work across a Linux user namespace boundary, even if this
+ *  reduces interoperability with older D-Bus implementations. This currently
+ *  affects client-side `EXTERNAL` authentication, for which this flag makes
+ *  connections to a server in another user namespace succeed, but causes
+ *  a deadlock when connecting to a GDBus server older than 2.73.3. Since: 2.74
  *
  * Flags used when creating a new #GDBusConnection.
  *
@@ -1228,7 +1234,8 @@ typedef enum {
   G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS = (1<<2),
   G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION = (1<<3),
   G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING = (1<<4),
-  G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER GLIB_AVAILABLE_ENUMERATOR_IN_2_68 = (1<<5)
+  G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER GLIB_AVAILABLE_ENUMERATOR_IN_2_68 = (1<<5),
+  G_DBUS_CONNECTION_FLAGS_CROSS_NAMESPACE GLIB_AVAILABLE_ENUMERATOR_IN_2_74 = (1<<6)
 } GDBusConnectionFlags;
 
 /**


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