1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
|
From: Simon McVittie <smcv@collabora.com>
Date: Fri, 18 Oct 2019 10:55:09 +0100
Subject: 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.
[Philip Withnall: Dropped new translatable string when backporting to
`glib-2-62`.]
Signed-off-by: Simon McVittie <smcv@collabora.com>
Origin: upstream, 2.62.3, commit:5f9318af8f19756685c1b79cf8b76f3e66614d84
---
gio/gcredentials.c | 42 +++++++++++++++++++++++++++++++++++++++---
1 file changed, 39 insertions(+), 3 deletions(-)
diff --git a/gio/gcredentials.c b/gio/gcredentials.c
index cc0cd82..866c671 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
|