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
<1ed4723d38>,
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>
This commit is contained in:
Simon McVittie
2022-07-24 13:02:51 +01:00
parent e9c8070bfd
commit e0a0749268
7 changed files with 58 additions and 22 deletions

View File

@@ -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;
initial_response = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) g_credentials_get_unix_user (credentials, NULL));
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));
#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