From 9da49d4eb6982c659fec988231baef8cd1b05be2 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Wed, 11 Feb 2015 13:19:15 +0000 Subject: [PATCH 3/8] Add LSM-agnostic support for LinuxSecurityLabel credential Bug: https://bugs.freedesktop.org/show_bug.cgi?id=89041 Change-Id: I70512843d1a7661c87461b1b6d86fbfbda934ad5 Reviewed-by: Philip Withnall Acked-by: Stephen Smalley (for SELinux) Acked-by: John Johansen (for AppArmor) Acked-by: Casey Schaufler (for Smack) Tested-by: Tyler Hicks --- bus/driver.c | 19 ++++++++ dbus/dbus-auth.c | 11 +++-- dbus/dbus-connection-internal.h | 3 ++ dbus/dbus-connection.c | 26 ++++++++++ dbus/dbus-credentials.c | 68 ++++++++++++++++++++++++++ dbus/dbus-credentials.h | 4 ++ dbus/dbus-sysdeps-unix.c | 105 ++++++++++++++++++++++++++++++++++++++++ dbus/dbus-transport.c | 27 +++++++++++ dbus/dbus-transport.h | 3 ++ 9 files changed, 262 insertions(+), 4 deletions(-) diff --git a/bus/driver.c b/bus/driver.c index 888c7ca..11706f8 100644 --- a/bus/driver.c +++ b/bus/driver.c @@ -34,6 +34,7 @@ #include "utils.h" #include +#include #include #include #include @@ -1567,6 +1568,7 @@ bus_driver_handle_get_connection_credentials (DBusConnection *connection, DBusMessageIter reply_iter; DBusMessageIter array_iter; unsigned long ulong_val; + char *s; const char *service; _DBUS_ASSERT_ERROR_IS_CLEAR (error); @@ -1601,6 +1603,23 @@ bus_driver_handle_get_connection_credentials (DBusConnection *connection, goto oom; } + if (_dbus_connection_get_linux_security_label (conn, &s)) + { + if (s == NULL) + goto oom; + + /* use the GVariant bytestring convention for strings of unknown + * encoding: include the \0 in the payload, for zero-copy reading */ + if (!_dbus_asv_add_byte_array (&array_iter, "LinuxSecurityLabel", + s, strlen (s) + 1)) + { + dbus_free (s); + goto oom; + } + + dbus_free (s); + } + if (!_dbus_asv_close (&reply_iter, &array_iter)) goto oom; diff --git a/dbus/dbus-auth.c b/dbus/dbus-auth.c index 6a07665..aee877d 100644 --- a/dbus/dbus-auth.c +++ b/dbus/dbus-auth.c @@ -1102,20 +1102,23 @@ handle_server_data_external_mech (DBusAuth *auth, auth->desired_identity)) return FALSE; - /* also copy process ID from the socket credentials + /* also copy misc process info from the socket credentials */ if (!_dbus_credentials_add_credential (auth->authorized_identity, DBUS_CREDENTIAL_UNIX_PROCESS_ID, auth->credentials)) return FALSE; - /* also copy audit data from the socket credentials - */ if (!_dbus_credentials_add_credential (auth->authorized_identity, DBUS_CREDENTIAL_ADT_AUDIT_DATA_ID, auth->credentials)) return FALSE; - + + if (!_dbus_credentials_add_credential (auth->authorized_identity, + DBUS_CREDENTIAL_LINUX_SECURITY_LABEL, + auth->credentials)) + return FALSE; + if (!send_ok (auth)) return FALSE; diff --git a/dbus/dbus-connection-internal.h b/dbus/dbus-connection-internal.h index 2897404..64ef336 100644 --- a/dbus/dbus-connection-internal.h +++ b/dbus/dbus-connection-internal.h @@ -107,6 +107,9 @@ void _dbus_connection_set_pending_fds_function (DBusConnectio DBusPendingFdsChangeFunction callback, void *data); +dbus_bool_t _dbus_connection_get_linux_security_label (DBusConnection *connection, + char **label_p); + /* if DBUS_ENABLE_STATS */ void _dbus_connection_get_stats (DBusConnection *connection, dbus_uint32_t *in_messages, diff --git a/dbus/dbus-connection.c b/dbus/dbus-connection.c index b574207..8952b75 100644 --- a/dbus/dbus-connection.c +++ b/dbus/dbus-connection.c @@ -5322,6 +5322,32 @@ dbus_connection_set_unix_user_function (DBusConnection *connection, (* old_free_function) (old_data); } +/* Same calling convention as dbus_connection_get_windows_user */ +dbus_bool_t +_dbus_connection_get_linux_security_label (DBusConnection *connection, + char **label_p) +{ + dbus_bool_t result; + + _dbus_assert (connection != NULL); + _dbus_assert (label_p != NULL); + + CONNECTION_LOCK (connection); + + if (!_dbus_transport_try_to_authenticate (connection->transport)) + result = FALSE; + else + result = _dbus_transport_get_linux_security_label (connection->transport, + label_p); +#ifndef __linux__ + _dbus_assert (!result); +#endif + + CONNECTION_UNLOCK (connection); + + return result; +} + /** * Gets the Windows user SID of the connection if known. Returns * #TRUE if the ID is filled in. Always returns #FALSE on non-Windows diff --git a/dbus/dbus-credentials.c b/dbus/dbus-credentials.c index 7325125..151bb00 100644 --- a/dbus/dbus-credentials.c +++ b/dbus/dbus-credentials.c @@ -50,6 +50,7 @@ struct DBusCredentials { dbus_uid_t unix_uid; dbus_pid_t pid; char *windows_sid; + char *linux_security_label; void *adt_audit_data; dbus_int32_t adt_audit_data_size; }; @@ -79,6 +80,7 @@ _dbus_credentials_new (void) creds->unix_uid = DBUS_UID_UNSET; creds->pid = DBUS_PID_UNSET; creds->windows_sid = NULL; + creds->linux_security_label = NULL; creds->adt_audit_data = NULL; creds->adt_audit_data_size = 0; @@ -133,6 +135,7 @@ _dbus_credentials_unref (DBusCredentials *credentials) if (credentials->refcount == 0) { dbus_free (credentials->windows_sid); + dbus_free (credentials->linux_security_label); dbus_free (credentials->adt_audit_data); dbus_free (credentials); } @@ -193,6 +196,30 @@ _dbus_credentials_add_windows_sid (DBusCredentials *credentials, } /** + * Add a Linux security label, as used by LSMs such as SELinux, Smack and + * AppArmor, to the credentials. + * + * @param credentials the object + * @param label the label + * @returns #FALSE if no memory + */ +dbus_bool_t +_dbus_credentials_add_linux_security_label (DBusCredentials *credentials, + const char *label) +{ + char *copy; + + copy = _dbus_strdup (label); + if (copy == NULL) + return FALSE; + + dbus_free (credentials->linux_security_label); + credentials->linux_security_label = copy; + + return TRUE; +} + +/** * Add ADT audit data to the credentials. * * @param credentials the object @@ -236,6 +263,8 @@ _dbus_credentials_include (DBusCredentials *credentials, return credentials->unix_uid != DBUS_UID_UNSET; case DBUS_CREDENTIAL_WINDOWS_SID: return credentials->windows_sid != NULL; + case DBUS_CREDENTIAL_LINUX_SECURITY_LABEL: + return credentials->linux_security_label != NULL; case DBUS_CREDENTIAL_ADT_AUDIT_DATA_ID: return credentials->adt_audit_data != NULL; } @@ -284,6 +313,19 @@ _dbus_credentials_get_windows_sid (DBusCredentials *credentials) } /** + * Gets the Linux security label (as used by LSMs) from the credentials, + * or #NULL if the credentials object doesn't contain a security label. + * + * @param credentials the object + * @returns the security label + */ +const char * +_dbus_credentials_get_linux_security_label (DBusCredentials *credentials) +{ + return credentials->linux_security_label; +} + +/** * Gets the ADT audit data in the credentials, or #NULL if * the credentials object doesn't contain ADT audit data. * @@ -329,6 +371,10 @@ _dbus_credentials_are_superset (DBusCredentials *credentials, (possible_subset->windows_sid == NULL || (credentials->windows_sid && strcmp (possible_subset->windows_sid, credentials->windows_sid) == 0)) && + (possible_subset->linux_security_label == NULL || + (credentials->linux_security_label != NULL && + strcmp (possible_subset->linux_security_label, + credentials->linux_security_label) == 0)) && (possible_subset->adt_audit_data == NULL || (credentials->adt_audit_data && memcmp (possible_subset->adt_audit_data, credentials->adt_audit_data, @@ -348,6 +394,7 @@ _dbus_credentials_are_empty (DBusCredentials *credentials) credentials->pid == DBUS_PID_UNSET && credentials->unix_uid == DBUS_UID_UNSET && credentials->windows_sid == NULL && + credentials->linux_security_label == NULL && credentials->adt_audit_data == NULL; } @@ -388,6 +435,9 @@ _dbus_credentials_add_credentials (DBusCredentials *credentials, DBUS_CREDENTIAL_ADT_AUDIT_DATA_ID, other_credentials) && _dbus_credentials_add_credential (credentials, + DBUS_CREDENTIAL_LINUX_SECURITY_LABEL, + other_credentials) && + _dbus_credentials_add_credential (credentials, DBUS_CREDENTIAL_WINDOWS_SID, other_credentials); } @@ -427,6 +477,13 @@ _dbus_credentials_add_credential (DBusCredentials *credentials, if (!_dbus_credentials_add_windows_sid (credentials, other_credentials->windows_sid)) return FALSE; } + else if (which == DBUS_CREDENTIAL_LINUX_SECURITY_LABEL && + other_credentials->linux_security_label != NULL) + { + if (!_dbus_credentials_add_linux_security_label (credentials, + other_credentials->linux_security_label)) + return FALSE; + } else if (which == DBUS_CREDENTIAL_ADT_AUDIT_DATA_ID && other_credentials->adt_audit_data != NULL) { @@ -449,6 +506,8 @@ _dbus_credentials_clear (DBusCredentials *credentials) credentials->unix_uid = DBUS_UID_UNSET; dbus_free (credentials->windows_sid); credentials->windows_sid = NULL; + dbus_free (credentials->linux_security_label); + credentials->linux_security_label = NULL; dbus_free (credentials->adt_audit_data); credentials->adt_audit_data = NULL; credentials->adt_audit_data_size = 0; @@ -540,6 +599,15 @@ _dbus_credentials_to_string_append (DBusCredentials *credentials, else join = FALSE; + if (credentials->linux_security_label != NULL) + { + if (!_dbus_string_append_printf (string, "%slsm='%s'", + join ? " " : "", + credentials->linux_security_label)) + goto oom; + join = TRUE; + } + return TRUE; oom: return FALSE; diff --git a/dbus/dbus-credentials.h b/dbus/dbus-credentials.h index abcc4bb..ab74eac 100644 --- a/dbus/dbus-credentials.h +++ b/dbus/dbus-credentials.h @@ -34,6 +34,7 @@ typedef enum { DBUS_CREDENTIAL_UNIX_PROCESS_ID, DBUS_CREDENTIAL_UNIX_USER_ID, DBUS_CREDENTIAL_ADT_AUDIT_DATA_ID, + DBUS_CREDENTIAL_LINUX_SECURITY_LABEL, DBUS_CREDENTIAL_WINDOWS_SID } DBusCredentialType; @@ -47,6 +48,8 @@ dbus_bool_t _dbus_credentials_add_unix_uid (DBusCredentials dbus_uid_t uid); dbus_bool_t _dbus_credentials_add_windows_sid (DBusCredentials *credentials, const char *windows_sid); +dbus_bool_t _dbus_credentials_add_linux_security_label (DBusCredentials *credentials, + const char *label); dbus_bool_t _dbus_credentials_add_adt_audit_data (DBusCredentials *credentials, void *audit_data, dbus_int32_t size); @@ -55,6 +58,7 @@ dbus_bool_t _dbus_credentials_include (DBusCredentials dbus_pid_t _dbus_credentials_get_pid (DBusCredentials *credentials); dbus_uid_t _dbus_credentials_get_unix_uid (DBusCredentials *credentials); const char* _dbus_credentials_get_windows_sid (DBusCredentials *credentials); +const char * _dbus_credentials_get_linux_security_label (DBusCredentials *credentials); void * _dbus_credentials_get_adt_audit_data (DBusCredentials *credentials); dbus_int32_t _dbus_credentials_get_adt_audit_data_size (DBusCredentials *credentials); dbus_bool_t _dbus_credentials_are_superset (DBusCredentials *credentials, diff --git a/dbus/dbus-sysdeps-unix.c b/dbus/dbus-sysdeps-unix.c index fe891ab..61af423 100644 --- a/dbus/dbus-sysdeps-unix.c +++ b/dbus/dbus-sysdeps-unix.c @@ -1639,6 +1639,105 @@ write_credentials_byte (int server_fd, } } +/* return FALSE on OOM, TRUE otherwise, even if no credentials were found */ +static dbus_bool_t +add_linux_security_label_to_credentials (int client_fd, + DBusCredentials *credentials) +{ +#if defined(__linux__) && defined(SO_PEERSEC) + DBusString buf; + socklen_t len = 1024; + dbus_bool_t oom = FALSE; + + if (!_dbus_string_init_preallocated (&buf, len) || + !_dbus_string_set_length (&buf, len)) + return FALSE; + + while (getsockopt (client_fd, SOL_SOCKET, SO_PEERSEC, + _dbus_string_get_data (&buf), &len) < 0) + { + int e = errno; + + _dbus_verbose ("getsockopt failed with %s, len now %lu\n", + _dbus_strerror (e), (unsigned long) len); + + if (e != ERANGE || len <= _dbus_string_get_length (&buf)) + { + _dbus_verbose ("Failed to getsockopt(SO_PEERSEC): %s\n", + _dbus_strerror (e)); + goto out; + } + + /* If not enough space, len is updated to be enough. + * Try again with a large enough buffer. */ + if (!_dbus_string_set_length (&buf, len)) + { + oom = TRUE; + goto out; + } + + _dbus_verbose ("will try again with %lu\n", (unsigned long) len); + } + + if (len <= 0) + { + _dbus_verbose ("getsockopt(SO_PEERSEC) yielded <= 0 bytes: %lu\n", + (unsigned long) len); + goto out; + } + + if (len > _dbus_string_get_length (&buf)) + { + _dbus_verbose ("%lu > %d", (unsigned long) len, + _dbus_string_get_length (&buf)); + _dbus_assert_not_reached ("getsockopt(SO_PEERSEC) overflowed"); + } + + if (_dbus_string_get_byte (&buf, len - 1) == 0) + { + /* the kernel included the trailing \0 in its count, + * but DBusString always has an extra \0 after the data anyway */ + _dbus_verbose ("subtracting trailing \\0\n"); + len--; + } + + if (!_dbus_string_set_length (&buf, len)) + { + _dbus_assert_not_reached ("shortening string should not lead to OOM"); + oom = TRUE; + goto out; + } + + if (strlen (_dbus_string_get_const_data (&buf)) != len) + { + /* LSM people on the linux-security-module@ mailing list say this + * should never happen: the label should be a bytestring with + * an optional trailing \0 */ + _dbus_verbose ("security label from kernel had an embedded \\0, " + "ignoring it\n"); + goto out; + } + + _dbus_verbose ("getsockopt(SO_PEERSEC): %lu bytes excluding \\0: %s\n", + (unsigned long) len, + _dbus_string_get_const_data (&buf)); + + if (!_dbus_credentials_add_linux_security_label (credentials, + _dbus_string_get_const_data (&buf))) + { + oom = TRUE; + goto out; + } + +out: + _dbus_string_free (&buf); + return !oom; +#else + /* no error */ + return TRUE; +#endif +} + /** * Reads a single byte which must be nul (an error occurs otherwise), * and reads unix credentials if available. Clears the credentials @@ -1922,6 +2021,12 @@ _dbus_read_credentials_socket (int client_fd, } } + if (!add_linux_security_label_to_credentials (client_fd, credentials)) + { + _DBUS_SET_OOM (error); + return FALSE; + } + return TRUE; } diff --git a/dbus/dbus-transport.c b/dbus/dbus-transport.c index e9dcc56..a43e7bb 100644 --- a/dbus/dbus-transport.c +++ b/dbus/dbus-transport.c @@ -1425,6 +1425,33 @@ _dbus_transport_set_unix_user_function (DBusTransport *transport, transport->free_unix_user_data = free_data_function; } +dbus_bool_t +_dbus_transport_get_linux_security_label (DBusTransport *transport, + char **label_p) +{ + DBusCredentials *auth_identity; + + *label_p = NULL; + + if (!transport->authenticated) + return FALSE; + + auth_identity = _dbus_auth_get_identity (transport->auth); + + if (_dbus_credentials_include (auth_identity, + DBUS_CREDENTIAL_LINUX_SECURITY_LABEL)) + { + /* If no memory, we are supposed to return TRUE and set NULL */ + *label_p = _dbus_strdup (_dbus_credentials_get_linux_security_label (auth_identity)); + + return TRUE; + } + else + { + return FALSE; + } +} + /** * See dbus_connection_get_windows_user(). * diff --git a/dbus/dbus-transport.h b/dbus/dbus-transport.h index 39c74c4..843f231 100644 --- a/dbus/dbus-transport.h +++ b/dbus/dbus-transport.h @@ -87,6 +87,9 @@ void _dbus_transport_set_unix_user_function (DBusTransport DBusFreeFunction *old_free_data_function); dbus_bool_t _dbus_transport_get_windows_user (DBusTransport *transport, char **windows_sid_p); +dbus_bool_t _dbus_transport_get_linux_security_label (DBusTransport *transport, + char **label_p); + void _dbus_transport_set_windows_user_function (DBusTransport *transport, DBusAllowWindowsUserFunction function, void *data, -- 2.1.4