From 1cfab12a28d97716ad581c30fbbf3e94e4d7f303 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Mon, 14 Oct 2019 08:22:24 +0100 Subject: [PATCH 1/3] gcredentialsprivate: Document the various private macros Signed-off-by: Simon McVittie --- gio/gcredentialsprivate.h | 59 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/gio/gcredentialsprivate.h b/gio/gcredentialsprivate.h index 4d1c420a8..06f0aed19 100644 --- a/gio/gcredentialsprivate.h +++ b/gio/gcredentialsprivate.h @@ -22,6 +22,65 @@ #include "gio/gcredentials.h" #include "gio/gnetworking.h" +/* + * G_CREDENTIALS_SUPPORTED: + * + * Defined to 1 if GCredentials works. + */ +#undef G_CREDENTIALS_SUPPORTED + +/* + * G_CREDENTIALS_USE_LINUX_UCRED, etc.: + * + * Defined to 1 if GCredentials uses Linux `struct ucred`, etc. + */ +#undef G_CREDENTIALS_USE_LINUX_UCRED +#undef G_CREDENTIALS_USE_FREEBSD_CMSGCRED +#undef G_CREDENTIALS_USE_NETBSD_UNPCBID +#undef G_CREDENTIALS_USE_OPENBSD_SOCKPEERCRED +#undef G_CREDENTIALS_USE_SOLARIS_UCRED + +/* + * G_CREDENTIALS_NATIVE_TYPE: + * + * Defined to one of G_CREDENTIALS_TYPE_LINUX_UCRED, etc. + */ +#undef G_CREDENTIALS_NATIVE_TYPE + +/* + * G_CREDENTIALS_NATIVE_SIZE: + * + * Defined to the size of the %G_CREDENTIALS_NATIVE_TYPE + */ +#undef G_CREDENTIALS_NATIVE_SIZE + +/* + * G_CREDENTIALS_UNIX_CREDENTIALS_MESSAGE_SUPPORTED: + * + * Defined to 1 if we have a message-passing API in which credentials + * are attached to a particular message, such as `SCM_CREDENTIALS` on Linux + * or `SCM_CREDS` on FreeBSD. + */ +#undef G_CREDENTIALS_UNIX_CREDENTIALS_MESSAGE_SUPPORTED + +/* + * G_CREDENTIALS_SOCKET_GET_CREDENTIALS_SUPPORTED: + * + * Defined to 1 if we have a `getsockopt()`-style API in which one end of + * a socket connection can directly query the credentials of the process + * that initiated the other end, such as `getsockopt SO_PEERCRED` on Linux + * or `getpeereid()` on multiple operating systems. + */ +#undef G_CREDENTIALS_SOCKET_GET_CREDENTIALS_SUPPORTED + +/* + * G_CREDENTIALS_SPOOFING_SUPPORTED: + * + * Defined to 1 if privileged processes can spoof their credentials when + * using the message-passing API. + */ +#undef G_CREDENTIALS_SPOOFING_SUPPORTED + #ifdef __linux__ #define G_CREDENTIALS_SUPPORTED 1 #define G_CREDENTIALS_USE_LINUX_UCRED 1 -- 2.20.1 From 5f9318af8f19756685c1b79cf8b76f3e66614d84 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Fri, 18 Oct 2019 10:55:09 +0100 Subject: [PATCH 2/3] credentials: Invalid Linux struct ucred means "no information" On Linux, if getsockopt SO_PEERCRED is used on a TCP socket, one might expect it to fail with an appropriate error like ENOTSUP or EPROTONOSUPPORT. However, it appears that in fact it succeeds, but yields a credentials structure with pid 0, uid -1 and gid -1. These are not real process, user and group IDs that can be allocated to a real process (pid 0 needs to be reserved to give kill(0) its documented special semantics, and similarly uid and gid -1 need to be reserved for setresuid() and setresgid()) so it is not meaningful to signal them to high-level API users. An API user with Linux-specific knowledge can still inspect these fields via g_credentials_get_native() if desired. Similarly, if SO_PASSCRED is used to receive a SCM_CREDENTIALS message on a receiving Unix socket, but the sending socket had not enabled SO_PASSCRED at the time that the message was sent, it is possible for it to succeed but yield a credentials structure with pid 0, uid /proc/sys/kernel/overflowuid and gid /proc/sys/kernel/overflowgid. Even if we were to read those pseudo-files, we cannot distinguish between the overflow IDs and a real process that legitimately has the same IDs (typically they are set to 'nobody' and 'nogroup', which can be used by a real process), so we detect this situation by noticing that pid == 0, and to save syscalls we do not read the overflow IDs from /proc at all. This results in a small API change: g_credentials_is_same_user() now returns FALSE if we compare two credentials structures that are both invalid. This seems like reasonable, conservative behaviour: if we cannot prove that they are the same user, we should assume they are not. (Dropped new translatable string when backporting to `glib-2-62`.) Signed-off-by: Simon McVittie --- gio/gcredentials.c | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/gio/gcredentials.c b/gio/gcredentials.c index 57a39f2a2..ff9b7e0b8 100644 --- a/gio/gcredentials.c +++ b/gio/gcredentials.c @@ -265,6 +265,35 @@ g_credentials_to_string (GCredentials *credentials) /* ---------------------------------------------------------------------------------------------------- */ +#if G_CREDENTIALS_USE_LINUX_UCRED +/* + * Check whether @native contains invalid data. If getsockopt SO_PEERCRED + * is used on a TCP socket, it succeeds but yields a credentials structure + * with pid 0, uid -1 and gid -1. Similarly, if SO_PASSCRED is used on a + * receiving Unix socket when the sending socket did not also enable + * SO_PASSCRED, it can succeed but yield a credentials structure with + * pid 0, uid /proc/sys/kernel/overflowuid and gid + * /proc/sys/kernel/overflowgid. + */ +static gboolean +linux_ucred_check_valid (struct ucred *native, + GError **error) +{ + if (native->pid == 0 + || native->uid == -1 + || native->gid == -1) + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "GCredentials contains invalid data"); + return FALSE; + } + + return TRUE; +} +#endif + /** * g_credentials_is_same_user: * @credentials: A #GCredentials. @@ -294,7 +323,8 @@ g_credentials_is_same_user (GCredentials *credentials, ret = FALSE; #if G_CREDENTIALS_USE_LINUX_UCRED - if (credentials->native.uid == other_credentials->native.uid) + if (linux_ucred_check_valid (&credentials->native, NULL) + && credentials->native.uid == other_credentials->native.uid) ret = TRUE; #elif G_CREDENTIALS_USE_FREEBSD_CMSGCRED if (credentials->native.cmcred_euid == other_credentials->native.cmcred_euid) @@ -453,7 +483,10 @@ g_credentials_get_unix_user (GCredentials *credentials, g_return_val_if_fail (error == NULL || *error == NULL, -1); #if G_CREDENTIALS_USE_LINUX_UCRED - ret = credentials->native.uid; + if (linux_ucred_check_valid (&credentials->native, error)) + ret = credentials->native.uid; + else + ret = -1; #elif G_CREDENTIALS_USE_FREEBSD_CMSGCRED ret = credentials->native.cmcred_euid; #elif G_CREDENTIALS_USE_NETBSD_UNPCBID @@ -499,7 +532,10 @@ g_credentials_get_unix_pid (GCredentials *credentials, g_return_val_if_fail (error == NULL || *error == NULL, -1); #if G_CREDENTIALS_USE_LINUX_UCRED - ret = credentials->native.pid; + if (linux_ucred_check_valid (&credentials->native, error)) + ret = credentials->native.pid; + else + ret = -1; #elif G_CREDENTIALS_USE_FREEBSD_CMSGCRED ret = credentials->native.cmcred_pid; #elif G_CREDENTIALS_USE_NETBSD_UNPCBID -- 2.20.1 From c7618cce3752e1f3681f75d0a26c7e07c15bd6a2 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Mon, 14 Oct 2019 08:47:39 +0100 Subject: [PATCH 3/3] GDBus: prefer getsockopt()-style credentials-passing APIs Closes: https://gitlab.gnome.org/GNOME/glib/issues/1831 --- gio/gcredentialsprivate.h | 18 ++++++++++++++++++ gio/gdbusauth.c | 27 +++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/gio/gcredentialsprivate.h b/gio/gcredentialsprivate.h index 06f0aed19..e9ec09b9f 100644 --- a/gio/gcredentialsprivate.h +++ b/gio/gcredentialsprivate.h @@ -81,6 +81,18 @@ */ #undef G_CREDENTIALS_SPOOFING_SUPPORTED +/* + * G_CREDENTIALS_PREFER_MESSAGE_PASSING: + * + * Defined to 1 if the data structure transferred by the message-passing + * API is strictly more informative than the one transferred by the + * `getsockopt()`-style API, and hence should be preferred, even for + * protocols like D-Bus that are defined in terms of the credentials of + * the (process that opened the) socket, as opposed to the credentials + * of an individual message. + */ +#undef G_CREDENTIALS_PREFER_MESSAGE_PASSING + #ifdef __linux__ #define G_CREDENTIALS_SUPPORTED 1 #define G_CREDENTIALS_USE_LINUX_UCRED 1 @@ -100,6 +112,12 @@ #define G_CREDENTIALS_NATIVE_SIZE (sizeof (struct cmsgcred)) #define G_CREDENTIALS_UNIX_CREDENTIALS_MESSAGE_SUPPORTED 1 #define G_CREDENTIALS_SPOOFING_SUPPORTED 1 +/* GLib doesn't implement it yet, but FreeBSD's getsockopt()-style API + * is getpeereid(), which is not as informative as struct cmsgcred - + * it does not tell us the PID. As a result, libdbus prefers to use + * SCM_CREDS, and if we implement getpeereid() in future, we should + * do the same. */ +#define G_CREDENTIALS_PREFER_MESSAGE_PASSING 1 #elif defined(__NetBSD__) #define G_CREDENTIALS_SUPPORTED 1 diff --git a/gio/gdbusauth.c b/gio/gdbusauth.c index 752ec23fc..14cc5d70e 100644 --- a/gio/gdbusauth.c +++ b/gio/gdbusauth.c @@ -31,6 +31,7 @@ #include "gdbusutils.h" #include "gioenumtypes.h" #include "gcredentials.h" +#include "gcredentialsprivate.h" #include "gdbusprivate.h" #include "giostream.h" #include "gdatainputstream.h" @@ -969,9 +970,31 @@ _g_dbus_auth_run_server (GDBusAuth *auth, g_data_input_stream_set_newline_type (dis, G_DATA_STREAM_NEWLINE_TYPE_CR_LF); - /* first read the NUL-byte */ + /* read the NUL-byte, possibly with credentials attached */ #ifdef G_OS_UNIX - if (G_IS_UNIX_CONNECTION (auth->priv->stream)) +#ifndef G_CREDENTIALS_PREFER_MESSAGE_PASSING + if (G_IS_SOCKET_CONNECTION (auth->priv->stream)) + { + GSocket *sock = g_socket_connection_get_socket (G_SOCKET_CONNECTION (auth->priv->stream)); + + local_error = NULL; + credentials = g_socket_get_credentials (sock, &local_error); + + if (credentials == NULL && !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) + { + g_propagate_error (error, local_error); + goto out; + } + else + { + /* Clear the error indicator, so we can retry with + * g_unix_connection_receive_credentials() if necessary */ + g_clear_error (&local_error); + } + } +#endif + + if (credentials == NULL && G_IS_UNIX_CONNECTION (auth->priv->stream)) { local_error = NULL; credentials = g_unix_connection_receive_credentials (G_UNIX_CONNECTION (auth->priv->stream), -- 2.20.1