summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon McVittie <smcv@collabora.com>2022-07-24 13:02:51 +0100
committerSimon McVittie <smcv@collabora.com>2022-07-24 13:46:26 +0100
commite0a0749268d58e37d5235a24a9f23fdaf7d56155 (patch)
tree4c1aae960ee5888d230801d103a8e9f2e610204f
parente9c8070bfd2fa93833a23357523c286fb0c3cd83 (diff)
downloadglib-e0a0749268d58e37d5235a24a9f23fdaf7d56155.tar.gz
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>
-rw-r--r--gio/gdbusauth.c7
-rw-r--r--gio/gdbusauth.h1
-rw-r--r--gio/gdbusauthmechanism.h2
-rw-r--r--gio/gdbusauthmechanismanon.c2
-rw-r--r--gio/gdbusauthmechanismexternal.c58
-rw-r--r--gio/gdbusconnection.c1
-rw-r--r--gio/gioenums.h9
7 files changed, 58 insertions, 22 deletions
diff --git a/gio/gdbusauth.c b/gio/gdbusauth.c
index 89cbbf67c..eadecb50d 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 30140fb98..8fa89444a 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 bc4afe74d..f0edd19a3 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 903907f86..5f59d4a61 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 a465862d1..6fe8b1bed 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 f0d50f7d8..454f216bd 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 fb5944996..3ba1d5b75 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;
/**