From d39ee67c6794e1566277d05f8d85662353e230c1 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 13 Feb 2014 12:55:52 -0600 Subject: [PATCH 08/10] Mediation of processes that acquire well-known names When an AppArmor confined process wants to acquire a well-known name, a check is performed to see if the action should be allowed. The check is based on the connection's label, the bus type, and the name being requested. An example AppArmor rule that would allow the name "com.example.ExampleName" to be acquired on the system bus would be: dbus bind bus=system name=com.example.ExampleName, To let a process acquire any name on any bus, the rule would be: dbus bind, Signed-off-by: John Johansen [tyhicks: Use BusAppArmorConfinement, bug fixes, cleanup, commit msg] Signed-off-by: Tyler Hicks --- bus/apparmor.c | 263 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ bus/apparmor.h | 8 ++ bus/connection.c | 13 +++ bus/connection.h | 1 + bus/services.c | 23 +++++ 5 files changed, 308 insertions(+) diff --git a/bus/apparmor.c b/bus/apparmor.c index 414047c..fe6b3b9 100644 --- a/bus/apparmor.c +++ b/bus/apparmor.c @@ -33,6 +33,7 @@ #endif /* HAVE_ERRNO_H */ #include +#include #include #include #include @@ -50,6 +51,7 @@ #include #endif /* HAVE_LIBAUDIT */ +#include "connection.h" #include "utils.h" /* Store the value telling us if AppArmor D-Bus mediation is enabled. */ @@ -133,6 +135,151 @@ aa_supports_dbus (void) close (mask_file); return TRUE; } + +static dbus_bool_t +modestr_to_complain (const char *mode) +{ + if (mode && strcmp (mode, "complain") == 0) + return TRUE; + return FALSE; +} + +static void +log_message (dbus_bool_t allow, const char *op, DBusString *data) +{ + const char *mstr; + + if (allow) + mstr = "ALLOWED"; + else + mstr = "DENIED"; + +#ifdef HAVE_LIBAUDIT + if (audit_fd >= 0) + { + capng_get_caps_process (); + if (capng_have_capability (CAPNG_EFFECTIVE, CAP_AUDIT_WRITE)) + { + char buf[PATH_MAX*2]; + + /* FIXME: need to change this to show real user */ + snprintf (buf, sizeof (buf), "apparmor=\"%s\" operation=\"dbus_%s\" %s\n", + mstr, op, _dbus_string_get_const_data (data)); + audit_log_user_avc_message (audit_fd, AUDIT_USER_AVC, buf, NULL, NULL, + NULL, getuid ()); + return; + } + } +#endif /* HAVE_LIBAUDIT */ + + syslog (LOG_USER | LOG_INFO, "apparmor=\"%s\" operation=\"dbus_%s\" %s\n", + mstr, op,_dbus_string_get_const_data (data)); +} + +static dbus_bool_t +_dbus_append_pair_uint (DBusString *auxdata, const char *name, + unsigned long value) +{ + if (!_dbus_string_append (auxdata, " ")) + return FALSE; + if (!_dbus_string_append (auxdata, name)) + return FALSE; + if (!_dbus_string_append (auxdata, "=")) + return FALSE; + if (!_dbus_string_append_uint (auxdata, value)) + return FALSE; + + return TRUE; +} + +static dbus_bool_t +_dbus_append_pair_str (DBusString *auxdata, const char *name, const char *value) +{ + if (!_dbus_string_append (auxdata, " ")) + return FALSE; + if (!_dbus_string_append (auxdata, name)) + return FALSE; + if (!_dbus_string_append (auxdata, "=\"")) + return FALSE; + if (!_dbus_string_append (auxdata, value)) + return FALSE; + if (!_dbus_string_append (auxdata, "\"")) + return FALSE; + + return TRUE; +} + +static dbus_bool_t +_dbus_append_mask (DBusString *auxdata, uint32_t mask) +{ + if (mask & AA_DBUS_SEND) + return _dbus_append_pair_str (auxdata, "mask", "send"); + else if (mask & AA_DBUS_RECEIVE) + return _dbus_append_pair_str (auxdata, "mask", "receive"); + else if (mask & AA_DBUS_BIND) + return _dbus_append_pair_str (auxdata, "mask", "bind"); + + return FALSE; +} + +static dbus_bool_t +is_unconfined (const char *con, const char *mode) +{ + /* treat con == NULL as confined as it is going to result in a denial */ + if ((!mode && con && strcmp (con, "unconfined") == 0) || + strcmp (mode, "unconfined") == 0) + { + return TRUE; + } + + return FALSE; +} + +static ssize_t (build_query) (char **qstr, const char *con, int count, ...) +{ + va_list ap; + int size, con_size, i; + char *buffer, *to; + + con_size = strlen (con); + size = con_size + 1; + va_start (ap, count); + for (i = 0; i < count; i++) + { + char *s = va_arg (ap, char *); + if (s) + size += strlen (s); + } + va_end (ap); + + buffer = malloc (size + count + 1 + AA_QUERY_CMD_LABEL_SIZE); + if (!buffer) + return -1; + + to = buffer + AA_QUERY_CMD_LABEL_SIZE; + strcpy (to, con); + to += con_size; + *(to)++ = '\0'; + *(to)++ = AA_CLASS_DBUS; + + va_start (ap, count); + for (i = 0; i < count; to++, i++) + { + char *arg = va_arg (ap, char *); + if (!arg) + arg = ""; + to = stpcpy (to, arg); + } + va_end (ap); + *qstr = buffer; + + /* don't include trailing \0 in size */ + return size + count + AA_QUERY_CMD_LABEL_SIZE; +} + +#define build_query(T, C, X...) \ + (build_query) (T, C, __macroarg_counter (X), X) + #endif /* HAVE_APPARMOR */ /** @@ -288,6 +435,20 @@ bus_apparmor_confinement_unref (BusAppArmorConfinement *confinement) #endif } +void +bus_apparmor_confinement_ref (BusAppArmorConfinement *confinement) +{ +#ifdef HAVE_APPARMOR + if (!apparmor_enabled) + return; + + _dbus_assert (confinement != NULL); + _dbus_assert (confinement->refcount > 0); + + confinement->refcount += 1; +#endif /* HAVE_APPARMOR */ +} + BusAppArmorConfinement* bus_apparmor_init_connection_confinement (DBusConnection *connection, DBusError *error) @@ -333,3 +494,105 @@ bus_apparmor_init_connection_confinement (DBusConnection *connection, return NULL; #endif /* HAVE_APPARMOR */ } + +/** + * Returns true if the given connection can acquire a service, + * using the tasks security context + * + * @param connection connection that wants to own the service + * @param service_sid the SID of the service from the table + * @returns #TRUE if acquire is permitted. + */ +dbus_bool_t +bus_apparmor_allows_acquire_service (DBusConnection *connection, + BusSELinuxID *service_sid, + const char *bustype, + const char *service_name, + DBusError *error) +{ + +#ifdef HAVE_APPARMOR + BusAppArmorConfinement *con = NULL; + DBusString auxdata; + dbus_bool_t string_alloced = FALSE; + dbus_bool_t allow = FALSE, audit = TRUE; + unsigned long pid; + int res, serrno = 0; + char *qstr = NULL; + ssize_t qsize; + + if (!apparmor_enabled) + return TRUE; + + _dbus_assert (connection != NULL); + + con = bus_connection_get_apparmor_confinement (connection); + + if (is_unconfined (con->context, con->mode)) + { + allow = TRUE; + audit = FALSE; + goto out; + } + + qsize = build_query (&qstr, con->context, bustype, service_name); + if (qsize == -1) + goto oom; + + res = aa_query_label (AA_DBUS_BIND, qstr, qsize, &allow, &audit); + free (qstr); + if (res == -1) + { + serrno = errno; + goto audit; + } + + /* Don't fail operations on profiles in complain mode */ + if (modestr_to_complain (con->mode)) + allow = TRUE; + + if (!audit) + goto out; + + audit: + if (!_dbus_string_init (&auxdata)) + goto oom; + string_alloced = TRUE; + + if (bustype && !_dbus_append_pair_str (&auxdata, "bus", bustype ? bustype : "unknown")) + goto oom; + + if (!_dbus_append_pair_str (&auxdata, "name", service_name)) + goto oom; + + if (serrno && !_dbus_append_pair_str (&auxdata, "info", strerror (serrno))) + goto oom; + + if (!_dbus_append_mask (&auxdata, AA_DBUS_BIND)) + goto oom; + + if (connection && dbus_connection_get_unix_process_id (connection, &pid) && + !_dbus_append_pair_uint (&auxdata, "pid", pid)) + goto oom; + + if (con->context && !_dbus_append_pair_str (&auxdata, "profile", con->context)) + goto oom; + + log_message (allow, "bind", &auxdata); + + out: + if (con != NULL) + bus_apparmor_confinement_unref (con); + if (string_alloced) + _dbus_string_free (&auxdata); + return allow; + + oom: + BUS_SET_OOM (error); + allow = FALSE; + goto out; + +#else + return TRUE; +#endif /* HAVE_APPARMOR */ +} diff --git a/bus/apparmor.h b/bus/apparmor.h index 8c359d8..dcae382 100644 --- a/bus/apparmor.h +++ b/bus/apparmor.h @@ -38,8 +38,16 @@ void bus_apparmor_shutdown (void); dbus_bool_t bus_apparmor_enabled (void); void bus_apparmor_confinement_unref (BusAppArmorConfinement *confinement); +void bus_apparmor_confinement_ref (BusAppArmorConfinement *confinement); BusAppArmorConfinement* bus_apparmor_init_connection_confinement (DBusConnection *connection, DBusError *error); +dbus_bool_t +bus_apparmor_allows_acquire_service (DBusConnection *connection, + BusSELinuxID *service_sid, + const char *bustype, + const char *service_name, + DBusError *error); + #endif /* BUS_APPARMOR_H */ diff --git a/bus/connection.c b/bus/connection.c index d15cb56..a8a0fc9 100644 --- a/bus/connection.c +++ b/bus/connection.c @@ -1129,6 +1129,19 @@ bus_connection_get_selinux_id (DBusConnection *connection) return d->selinux_id; } +BusAppArmorConfinement* +bus_connection_get_apparmor_confinement (DBusConnection *connection) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + + _dbus_assert (d != NULL); + + bus_apparmor_confinement_ref (d->apparmor_confinement); + return d->apparmor_confinement; +} + /** * Checks whether the connection is registered with the message bus. * diff --git a/bus/connection.h b/bus/connection.h index 9f4f9ae..de5625a 100644 --- a/bus/connection.h +++ b/bus/connection.h @@ -54,6 +54,7 @@ BusActivation* bus_connection_get_activation (DBusConnection BusMatchmaker* bus_connection_get_matchmaker (DBusConnection *connection); const char * bus_connection_get_loginfo (DBusConnection *connection); BusSELinuxID* bus_connection_get_selinux_id (DBusConnection *connection); +BusAppArmorConfinement* bus_connection_get_apparmor_confinement (DBusConnection *connection); dbus_bool_t bus_connections_check_limits (BusConnections *connections, DBusConnection *requesting_completion, DBusError *error); diff --git a/bus/services.c b/bus/services.c index 01a720e..604b501 100644 --- a/bus/services.c +++ b/bus/services.c @@ -36,6 +36,7 @@ #include "policy.h" #include "bus.h" #include "selinux.h" +#include "apparmor.h" struct BusService { @@ -458,6 +459,28 @@ bus_registry_acquire_service (BusRegistry *registry, _dbus_string_get_const_data (service_name)); goto out; } + + if (!bus_apparmor_allows_acquire_service (connection, sid, + (registry->context ? + bus_context_get_type(registry->context) : NULL), + _dbus_string_get_const_data (service_name), error)) + { + + if (dbus_error_is_set (error) && + dbus_error_has_name (error, DBUS_ERROR_NO_MEMORY)) + { + goto out; + } + + dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED, + "Connection \"%s\" is not allowed to own the service \"%s\" due " + "to AppArmor policy", + bus_connection_is_active (connection) ? + bus_connection_get_name (connection) : + "(inactive)", + _dbus_string_get_const_data (service_name)); + goto out; + } if (!bus_client_policy_check_can_own (policy, service_name)) { -- 1.9.rc1