diff options
Diffstat (limited to 'bus/apparmor.c')
-rw-r--r-- | bus/apparmor.c | 1128 |
1 files changed, 1128 insertions, 0 deletions
diff --git a/bus/apparmor.c b/bus/apparmor.c new file mode 100644 index 00000000..a1b3621a --- /dev/null +++ b/bus/apparmor.c @@ -0,0 +1,1128 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * apparmor.c AppArmor security checks for D-Bus + * + * Based on selinux.c + * + * Copyright © 2014-2015 Canonical, Ltd. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <config.h> +#include "apparmor.h" + +#ifdef HAVE_APPARMOR + +#include <dbus/dbus-internals.h> +#include <dbus/dbus-string.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/apparmor.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <syslog.h> +#include <unistd.h> + +#ifdef HAVE_LIBAUDIT +#include <cap-ng.h> +#include <libaudit.h> +#endif /* HAVE_LIBAUDIT */ + +#include "connection.h" +#include "utils.h" + +/* Store the value telling us if AppArmor D-Bus mediation is enabled. */ +static dbus_bool_t apparmor_enabled = FALSE; + +typedef enum { + APPARMOR_DISABLED, + APPARMOR_ENABLED, + APPARMOR_REQUIRED +} AppArmorConfigMode; + +/* Store the value of the AppArmor mediation mode in the bus configuration */ +static AppArmorConfigMode apparmor_config_mode = APPARMOR_ENABLED; + +#ifdef HAVE_LIBAUDIT +static int audit_fd = -1; +#endif + +/* The AppArmor context, consisting of a label and a mode. */ +struct BusAppArmorConfinement +{ + int refcount; /* Reference count */ + + char *label; /* AppArmor confinement label */ + const char *mode; /* AppArmor confinement mode (freed by freeing *label) */ +}; + +static BusAppArmorConfinement *bus_con = NULL; + +/** + * Callers of this function give up ownership of the *label and *mode + * pointers. + * + * Additionally, the responsibility of freeing *label and *mode becomes the + * responsibility of the bus_apparmor_confinement_unref() function. However, it + * does not free *mode because libapparmor's aa_getcon(), and libapparmor's + * other related functions, allocate a single buffer for *label and *mode and + * then separate the two char arrays with a NUL char. See the aa_getcon(2) man + * page for more details. + */ +static BusAppArmorConfinement* +bus_apparmor_confinement_new (char *label, + const char *mode) +{ + BusAppArmorConfinement *confinement; + + confinement = dbus_new0 (BusAppArmorConfinement, 1); + if (confinement != NULL) + { + confinement->refcount = 1; + confinement->label = label; + confinement->mode = mode; + } + + return confinement; +} + +void +bus_apparmor_audit_init (void) +{ +#ifdef HAVE_LIBAUDIT + audit_fd = audit_open (); + + if (audit_fd < 0) + { + /* If kernel doesn't support audit, bail out */ + if (errno == EINVAL || errno == EPROTONOSUPPORT || errno == EAFNOSUPPORT) + return; + /* If user bus, bail out */ + if (errno == EPERM && getuid () != 0) + return; + _dbus_warn ("Failed opening connection to the audit subsystem"); + } +#endif /* HAVE_LIBAUDIT */ +} + +/* + * Return TRUE on successful check, FALSE on OOM. + * Set *is_supported to whether AA has D-Bus features. + */ +static dbus_bool_t +_bus_apparmor_detect_aa_dbus_support (dbus_bool_t *is_supported) +{ + int mask_file; + DBusString aa_dbus; + char *aa_securityfs = NULL; + dbus_bool_t retval = FALSE; + + *is_supported = FALSE; + + if (!_dbus_string_init (&aa_dbus)) + return FALSE; + + if (aa_find_mountpoint (&aa_securityfs) != 0) + goto out; + + /* + * John Johansen has confirmed that the mainline kernel will not have + * the apparmorfs/features/dbus/mask file until the mainline kernel + * has AppArmor getpeersec support. + */ + if (!_dbus_string_append (&aa_dbus, aa_securityfs) || + !_dbus_string_append (&aa_dbus, "/features/dbus/mask")) + goto out; + + /* We need to open() the flag file, not just stat() it, because AppArmor + * does not mediate stat() in the apparmorfs. If you have a + * dbus-daemon inside an LXC container, with insufficiently broad + * AppArmor privileges to do its own AppArmor mediation, the desired + * result is that it behaves as if AppArmor was not present; but a stat() + * here would succeed, and result in it trying and failing to do full + * mediation. https://bugs.launchpad.net/ubuntu/+source/dbus/+bug/1238267 */ + mask_file = open (_dbus_string_get_const_data (&aa_dbus), + O_RDONLY | O_CLOEXEC); + if (mask_file != -1) + { + *is_supported = TRUE; + close (mask_file); + } + + retval = TRUE; + +out: + free (aa_securityfs); + _dbus_string_free (&aa_dbus); + + return retval; +} + +static dbus_bool_t +modestr_is_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) + { + DBusString avc; + + capng_get_caps_process (); + if (!capng_have_capability (CAPNG_EFFECTIVE, CAP_AUDIT_WRITE)) + goto syslog; + + if (!_dbus_string_init (&avc)) + goto syslog; + + if (!_dbus_string_append_printf (&avc, + "apparmor=\"%s\" operation=\"dbus_%s\" %s\n", + mstr, op, _dbus_string_get_const_data (data))) + { + _dbus_string_free (&avc); + goto syslog; + } + + /* FIXME: need to change this to show real user */ + audit_log_user_avc_message (audit_fd, AUDIT_USER_AVC, + _dbus_string_get_const_data (&avc), + NULL, NULL, NULL, getuid ()); + _dbus_string_free (&avc); + return; + } + +syslog: +#endif /* HAVE_LIBAUDIT */ + + syslog (LOG_USER | LOG_NOTICE, "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) +{ + return _dbus_string_append (auxdata, " ") && + _dbus_string_append (auxdata, name) && + _dbus_string_append (auxdata, "=") && + _dbus_string_append_uint (auxdata, value); +} + +static dbus_bool_t +_dbus_append_pair_str (DBusString *auxdata, const char *name, const char *value) +{ + return _dbus_string_append (auxdata, " ") && + _dbus_string_append (auxdata, name) && + _dbus_string_append (auxdata, "=\"") && + _dbus_string_append (auxdata, value) && + _dbus_string_append (auxdata, "\""); +} + +static dbus_bool_t +_dbus_append_mask (DBusString *auxdata, uint32_t mask) +{ + const char *mask_str; + + /* Only one permission bit can be set */ + if (mask == AA_DBUS_SEND) + mask_str = "send"; + else if (mask == AA_DBUS_RECEIVE) + mask_str = "receive"; + else if (mask == AA_DBUS_BIND) + mask_str = "bind"; + else + return FALSE; + + return _dbus_append_pair_str (auxdata, "mask", mask_str); +} + +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 dbus_bool_t +query_append (DBusString *query, const char *buffer) +{ + if (!_dbus_string_append_byte (query, '\0')) + return FALSE; + + if (buffer && !_dbus_string_append (query, buffer)) + return FALSE; + + return TRUE; +} + +static dbus_bool_t +build_common_query (DBusString *query, const char *con, const char *bustype) +{ + /** + * libapparmor's aa_query_label() function scribbles over the first + * AA_QUERY_CMD_LABEL_SIZE bytes of the query string with a private value. + */ + return _dbus_string_insert_bytes (query, 0, AA_QUERY_CMD_LABEL_SIZE, 0) && + _dbus_string_append (query, con) && + _dbus_string_append_byte (query, '\0') && + _dbus_string_append_byte (query, AA_CLASS_DBUS) && + _dbus_string_append (query, bustype ? bustype : ""); +} + +static dbus_bool_t +build_service_query (DBusString *query, + const char *con, + const char *bustype, + const char *name) +{ + return build_common_query (query, con, bustype) && + query_append (query, name); +} + +static dbus_bool_t +build_message_query (DBusString *query, + const char *src_con, + const char *bustype, + const char *name, + const char *dst_con, + const char *path, + const char *interface, + const char *member) +{ + return build_common_query (query, src_con, bustype) && + query_append (query, name) && + query_append (query, dst_con) && + query_append (query, path) && + query_append (query, interface) && + query_append (query, member); +} + +static dbus_bool_t +build_eavesdrop_query (DBusString *query, const char *con, const char *bustype) +{ + return build_common_query (query, con, bustype); +} + +static void +set_error_from_query_errno (DBusError *error, int error_number) +{ + dbus_set_error (error, _dbus_error_from_errno (error_number), + "Failed to query AppArmor policy: %s", + _dbus_strerror (error_number)); +} + +static void +set_error_from_denied_message (DBusError *error, + DBusConnection *sender, + DBusConnection *proposed_recipient, + dbus_bool_t requested_reply, + const char *msgtype, + const char *path, + const char *interface, + const char *member, + const char *error_name, + const char *destination) +{ + const char *proposed_recipient_loginfo; + const char *unset = "(unset)"; + + proposed_recipient_loginfo = proposed_recipient ? + bus_connection_get_loginfo (proposed_recipient) : + "bus"; + + dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED, + "An AppArmor policy prevents this sender from sending this " + "message to this recipient; type=\"%s\", " + "sender=\"%s\" (%s) interface=\"%s\" member=\"%s\" " + "error name=\"%s\" requested_reply=\"%d\" " + "destination=\"%s\" (%s)", + msgtype, + bus_connection_get_name (sender), + bus_connection_get_loginfo (sender), + interface ? interface : unset, + member ? member : unset, + error_name ? error_name : unset, + requested_reply, + destination, + proposed_recipient_loginfo); +} +#endif /* HAVE_APPARMOR */ + +/** + * Do early initialization; determine whether AppArmor is enabled. + * Return TRUE on successful check (whether AppArmor is actually + * enabled or not) or FALSE on OOM. + */ +dbus_bool_t +bus_apparmor_pre_init (void) +{ +#ifdef HAVE_APPARMOR + apparmor_enabled = FALSE; + + if (!aa_is_enabled ()) + return TRUE; + + if (!_bus_apparmor_detect_aa_dbus_support (&apparmor_enabled)) + return FALSE; +#endif + + return TRUE; +} + +dbus_bool_t +bus_apparmor_set_mode_from_config (const char *mode, DBusError *error) +{ +#ifdef HAVE_APPARMOR + if (mode != NULL) + { + if (strcmp (mode, "disabled") == 0) + apparmor_config_mode = APPARMOR_DISABLED; + else if (strcmp (mode, "enabled") == 0) + apparmor_config_mode = APPARMOR_ENABLED; + else if (strcmp (mode, "required") == 0) + apparmor_config_mode = APPARMOR_REQUIRED; + else + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Mode attribute on <apparmor> must have value " + "\"required\", \"enabled\" or \"disabled\", " + "not \"%s\"", mode); + return FALSE; + } + } + + return TRUE; +#else + if (mode == NULL || strcmp (mode, "disabled") == 0 || + strcmp (mode, "enabled") == 0) + return TRUE; + + dbus_set_error (error, DBUS_ERROR_FAILED, + "Mode attribute on <apparmor> must have value \"enabled\" or " + "\"disabled\" but cannot be \"%s\" when D-Bus is built " + "without AppArmor support", mode); + return FALSE; +#endif +} + +/** + * Verify that the config mode is compatible with the kernel's AppArmor + * support. If AppArmor mediation will be enabled, determine the bus + * confinement label. + */ +dbus_bool_t +bus_apparmor_full_init (DBusError *error) +{ +#ifdef HAVE_APPARMOR + char *label, *mode; + + if (apparmor_enabled) + { + if (apparmor_config_mode == APPARMOR_DISABLED) + { + apparmor_enabled = FALSE; + return TRUE; + } + + if (bus_con == NULL) + { + if (aa_getcon (&label, &mode) == -1) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Error getting AppArmor context of bus: %s", + _dbus_strerror (errno)); + return FALSE; + } + + bus_con = bus_apparmor_confinement_new (label, mode); + if (bus_con == NULL) + { + BUS_SET_OOM (error); + free (label); + return FALSE; + } + } + } + else + { + if (apparmor_config_mode == APPARMOR_REQUIRED) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "AppArmor mediation required but not present"); + return FALSE; + } + else if (apparmor_config_mode == APPARMOR_ENABLED) + { + return TRUE; + } + } +#endif + + return TRUE; +} + +void +bus_apparmor_shutdown (void) +{ +#ifdef HAVE_APPARMOR + if (!apparmor_enabled) + return; + + _dbus_verbose ("AppArmor shutdown\n"); + + bus_apparmor_confinement_unref (bus_con); + bus_con = NULL; + +#ifdef HAVE_LIBAUDIT + audit_close (audit_fd); +#endif /* HAVE_LIBAUDIT */ + +#endif /* HAVE_APPARMOR */ +} + +dbus_bool_t +bus_apparmor_enabled (void) +{ +#ifdef HAVE_APPARMOR + return apparmor_enabled; +#else + return FALSE; +#endif +} + +void +bus_apparmor_confinement_unref (BusAppArmorConfinement *confinement) +{ +#ifdef HAVE_APPARMOR + if (!apparmor_enabled) + return; + + _dbus_assert (confinement != NULL); + _dbus_assert (confinement->refcount > 0); + + confinement->refcount -= 1; + + if (confinement->refcount == 0) + { + /** + * Do not free confinement->mode, as libapparmor does a single malloc for + * both confinement->label and confinement->mode. + */ + free (confinement->label); + dbus_free (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) +{ +#ifdef HAVE_APPARMOR + BusAppArmorConfinement *confinement; + char *label, *mode; + int fd; + + if (!apparmor_enabled) + return NULL; + + _dbus_assert (connection != NULL); + + if (!dbus_connection_get_socket (connection, &fd)) + { + dbus_set_error (error, DBUS_ERROR_FAILED, + "Failed to get socket file descriptor of connection"); + return NULL; + } + + if (aa_getpeercon (fd, &label, &mode) == -1) + { + if (errno == ENOMEM) + BUS_SET_OOM (error); + else + dbus_set_error (error, _dbus_error_from_errno (errno), + "Failed to get AppArmor confinement information of socket peer: %s", + _dbus_strerror (errno)); + return NULL; + } + + confinement = bus_apparmor_confinement_new (label, mode); + if (confinement == NULL) + { + BUS_SET_OOM (error); + free (label); + return NULL; + } + + return confinement; +#else + 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 bustype name of the bus + * @param service_name the name of the service to acquire + * @param error the reason for failure when FALSE is returned + * @returns TRUE if acquire is permitted + */ +dbus_bool_t +bus_apparmor_allows_acquire_service (DBusConnection *connection, + const char *bustype, + const char *service_name, + DBusError *error) +{ + +#ifdef HAVE_APPARMOR + BusAppArmorConfinement *con = NULL; + DBusString qstr, auxdata; + dbus_bool_t free_auxdata = FALSE; + dbus_bool_t allow = FALSE, audit = TRUE; + unsigned long pid; + int res, serrno = 0; + + if (!apparmor_enabled) + return TRUE; + + _dbus_assert (connection != NULL); + + con = bus_connection_dup_apparmor_confinement (connection); + + if (is_unconfined (con->label, con->mode)) + { + allow = TRUE; + audit = FALSE; + goto out; + } + + if (!_dbus_string_init (&qstr)) + goto oom; + + if (!build_service_query (&qstr, con->label, bustype, service_name)) + { + _dbus_string_free (&qstr); + goto oom; + } + + res = aa_query_label (AA_DBUS_BIND, + _dbus_string_get_data (&qstr), + _dbus_string_get_length (&qstr), + &allow, &audit); + _dbus_string_free (&qstr); + if (res == -1) + { + serrno = errno; + set_error_from_query_errno (error, serrno); + goto audit; + } + + /* Don't fail operations on profiles in complain mode */ + if (modestr_is_complain (con->mode)) + allow = TRUE; + + if (!allow) + 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)", + service_name); + + if (!audit) + goto out; + + audit: + if (!_dbus_string_init (&auxdata)) + goto oom; + free_auxdata = TRUE; + + if (!_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->label && !_dbus_append_pair_str (&auxdata, "label", con->label)) + goto oom; + + log_message (allow, "bind", &auxdata); + + out: + if (con != NULL) + bus_apparmor_confinement_unref (con); + if (free_auxdata) + _dbus_string_free (&auxdata); + return allow; + + oom: + if (error != NULL && !dbus_error_is_set (error)) + BUS_SET_OOM (error); + allow = FALSE; + goto out; + +#else + return TRUE; +#endif /* HAVE_APPARMOR */ +} + +/** + * Check if Apparmor security controls allow the message to be sent to a + * particular connection based on the security context of the sender and + * that of the receiver. The destination connection need not be the + * addressed recipient, it could be an "eavesdropper" + * + * @param sender the sender of the message. + * @param proposed_recipient the connection the message is to be sent to. + * @param requested_reply TRUE if the message is a reply requested by + * proposed_recipient + * @param bustype name of the bus + * @param msgtype message type (DBUS_MESSAGE_TYPE_METHOD_CALL, etc.) + * @param path object path the message should be sent to + * @param interface the type of the object instance + * @param member the member of the object + * @param error_name the name of the error if the message type is error + * @param destination name that the message should be sent to + * @param source name that the message should be sent from + * @param error the reason for failure when FALSE is returned + * @returns TRUE if the message is permitted + */ +dbus_bool_t +bus_apparmor_allows_send (DBusConnection *sender, + DBusConnection *proposed_recipient, + dbus_bool_t requested_reply, + const char *bustype, + int msgtype, + const char *path, + const char *interface, + const char *member, + const char *error_name, + const char *destination, + const char *source, + DBusError *error) +{ +#ifdef HAVE_APPARMOR + BusAppArmorConfinement *src_con = NULL, *dst_con = NULL; + DBusString qstr, auxdata; + dbus_bool_t src_allow = FALSE, dst_allow = FALSE; + dbus_bool_t src_audit = TRUE, dst_audit = TRUE; + dbus_bool_t free_auxdata = FALSE; + unsigned long pid; + int len, res, src_errno = 0, dst_errno = 0; + uint32_t src_perm = AA_DBUS_SEND, dst_perm = AA_DBUS_RECEIVE; + const char *msgtypestr = dbus_message_type_to_string(msgtype); + + if (!apparmor_enabled) + return TRUE; + + _dbus_assert (sender != NULL); + + src_con = bus_connection_dup_apparmor_confinement (sender); + + if (proposed_recipient) + { + dst_con = bus_connection_dup_apparmor_confinement (proposed_recipient); + } + else + { + dst_con = bus_con; + bus_apparmor_confinement_ref (dst_con); + } + + /* map reply messages to initial send and receive permission. That is + * permission to receive a message from X grants permission to reply to X. + * And permission to send a message to Y grants permission to receive a reply + * from Y. Note that this only applies to requested replies. Unrequested + * replies still require a policy query. + */ + if (requested_reply) + { + /* ignore requested reply messages and let dbus reply mapping handle them + * as the send was already allowed + */ + src_allow = TRUE; + dst_allow = TRUE; + goto out; + } + + if (is_unconfined (src_con->label, src_con->mode)) + { + src_allow = TRUE; + src_audit = FALSE; + } + else + { + if (!_dbus_string_init (&qstr)) + goto oom; + + if (!build_message_query (&qstr, src_con->label, bustype, destination, + dst_con->label, path, interface, member)) + { + _dbus_string_free (&qstr); + goto oom; + } + + res = aa_query_label (src_perm, + _dbus_string_get_data (&qstr), + _dbus_string_get_length (&qstr), + &src_allow, &src_audit); + _dbus_string_free (&qstr); + if (res == -1) + { + src_errno = errno; + set_error_from_query_errno (error, src_errno); + goto audit; + } + } + + if (is_unconfined (dst_con->label, dst_con->mode)) + { + dst_allow = TRUE; + dst_audit = FALSE; + } + else + { + if (!_dbus_string_init (&qstr)) + goto oom; + + if (!build_message_query (&qstr, dst_con->label, bustype, source, + src_con->label, path, interface, member)) + { + _dbus_string_free (&qstr); + goto oom; + } + + res = aa_query_label (dst_perm, + _dbus_string_get_data (&qstr), + _dbus_string_get_length (&qstr), + &dst_allow, &dst_audit); + _dbus_string_free (&qstr); + if (res == -1) + { + dst_errno = errno; + set_error_from_query_errno (error, dst_errno); + goto audit; + } + } + + /* Don't fail operations on profiles in complain mode */ + if (modestr_is_complain (src_con->mode)) + src_allow = TRUE; + if (modestr_is_complain (dst_con->mode)) + dst_allow = TRUE; + + if (!src_allow || !dst_allow) + set_error_from_denied_message (error, + sender, + proposed_recipient, + requested_reply, + msgtypestr, + path, + interface, + member, + error_name, + destination); + + /* Don't audit the message if one of the following conditions is true: + * 1) The AppArmor query indicates that auditing should not happen. + * 2) The message is a reply type. Reply message are not audited because + * the AppArmor policy language does not have the notion of a reply + * message. Unrequested replies will be silently discarded if the sender + * does not have permission to send to the receiver or if the receiver + * does not have permission to receive from the sender. + */ + if ((!src_audit && !dst_audit) || + (msgtype == DBUS_MESSAGE_TYPE_METHOD_RETURN || + msgtype == DBUS_MESSAGE_TYPE_ERROR)) + goto out; + + audit: + if (!_dbus_string_init (&auxdata)) + goto oom; + free_auxdata = TRUE; + + if (!_dbus_append_pair_str (&auxdata, "bus", bustype ? bustype : "unknown")) + goto oom; + + if (path && !_dbus_append_pair_str (&auxdata, "path", path)) + goto oom; + + if (interface && !_dbus_append_pair_str (&auxdata, "interface", interface)) + goto oom; + + if (member && !_dbus_append_pair_str (&auxdata, "member", member)) + goto oom; + + if (error_name && !_dbus_append_pair_str (&auxdata, "error_name", error_name)) + goto oom; + + len = _dbus_string_get_length (&auxdata); + + if (src_audit) + { + if (!_dbus_append_mask (&auxdata, src_perm)) + goto oom; + + if (destination && !_dbus_append_pair_str (&auxdata, "name", destination)) + goto oom; + + if (sender && dbus_connection_get_unix_process_id (sender, &pid) && + !_dbus_append_pair_uint (&auxdata, "pid", pid)) + goto oom; + + if (src_con->label && + !_dbus_append_pair_str (&auxdata, "label", src_con->label)) + goto oom; + + if (proposed_recipient && + dbus_connection_get_unix_process_id (proposed_recipient, &pid) && + !_dbus_append_pair_uint (&auxdata, "peer_pid", pid)) + goto oom; + + if (dst_con->label && + !_dbus_append_pair_str (&auxdata, "peer_label", dst_con->label)) + goto oom; + + if (src_errno && !_dbus_append_pair_str (&auxdata, "info", strerror (src_errno))) + goto oom; + + if (dst_errno && + !_dbus_append_pair_str (&auxdata, "peer_info", strerror (dst_errno))) + goto oom; + + log_message (src_allow, msgtypestr, &auxdata); + } + if (dst_audit) + { + _dbus_string_set_length (&auxdata, len); + + if (source && !_dbus_append_pair_str (&auxdata, "name", source)) + goto oom; + + if (!_dbus_append_mask (&auxdata, dst_perm)) + goto oom; + + if (proposed_recipient && + dbus_connection_get_unix_process_id (proposed_recipient, &pid) && + !_dbus_append_pair_uint (&auxdata, "pid", pid)) + goto oom; + + if (dst_con->label && + !_dbus_append_pair_str (&auxdata, "label", dst_con->label)) + goto oom; + + if (sender && dbus_connection_get_unix_process_id (sender, &pid) && + !_dbus_append_pair_uint (&auxdata, "peer_pid", pid)) + goto oom; + + if (src_con->label && + !_dbus_append_pair_str (&auxdata, "peer_label", src_con->label)) + goto oom; + + if (dst_errno && !_dbus_append_pair_str (&auxdata, "info", strerror (dst_errno))) + goto oom; + + if (src_errno && + !_dbus_append_pair_str (&auxdata, "peer_info", strerror (src_errno))) + goto oom; + + log_message (dst_allow, msgtypestr, &auxdata); + } + + out: + if (src_con != NULL) + bus_apparmor_confinement_unref (src_con); + if (dst_con != NULL) + bus_apparmor_confinement_unref (dst_con); + if (free_auxdata) + _dbus_string_free (&auxdata); + + return src_allow && dst_allow; + + oom: + if (error != NULL && !dbus_error_is_set (error)) + BUS_SET_OOM (error); + src_allow = FALSE; + dst_allow = FALSE; + goto out; + +#else + return TRUE; +#endif /* HAVE_APPARMOR */ +} + +/** + * Check if Apparmor security controls allow the connection to eavesdrop on + * other connections. + * + * @param connection the connection attempting to eavesdrop. + * @param bustype name of the bus + * @param error the reason for failure when FALSE is returned + * @returns TRUE if eavesdropping is permitted + */ +dbus_bool_t +bus_apparmor_allows_eavesdropping (DBusConnection *connection, + const char *bustype, + DBusError *error) +{ +#ifdef HAVE_APPARMOR + BusAppArmorConfinement *con = NULL; + DBusString qstr, auxdata; + dbus_bool_t allow = FALSE, audit = TRUE; + dbus_bool_t free_auxdata = FALSE; + unsigned long pid; + int res, serrno = 0; + + if (!apparmor_enabled) + return TRUE; + + con = bus_connection_dup_apparmor_confinement (connection); + + if (is_unconfined (con->label, con->mode)) + { + allow = TRUE; + audit = FALSE; + goto out; + } + + if (!_dbus_string_init (&qstr)) + goto oom; + + if (!build_eavesdrop_query (&qstr, con->label, bustype)) + { + _dbus_string_free (&qstr); + goto oom; + } + + res = aa_query_label (AA_DBUS_EAVESDROP, + _dbus_string_get_data (&qstr), + _dbus_string_get_length (&qstr), + &allow, &audit); + _dbus_string_free (&qstr); + if (res == -1) + { + serrno = errno; + set_error_from_query_errno (error, serrno); + goto audit; + } + + /* Don't fail operations on profiles in complain mode */ + if (modestr_is_complain (con->mode)) + allow = TRUE; + + if (!allow) + dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED, + "Connection \"%s\" is not allowed to eavesdrop due to " + "AppArmor policy", + bus_connection_is_active (connection) ? + bus_connection_get_name (connection) : "(inactive)"); + + if (!audit) + goto out; + + audit: + if (!_dbus_string_init (&auxdata)) + goto oom; + free_auxdata = TRUE; + + if (!_dbus_append_pair_str (&auxdata, "bus", bustype ? bustype : "unknown")) + goto oom; + + if (serrno && !_dbus_append_pair_str (&auxdata, "info", strerror (serrno))) + goto oom; + + if (!_dbus_append_pair_str (&auxdata, "mask", "eavesdrop")) + goto oom; + + if (connection && dbus_connection_get_unix_process_id (connection, &pid) && + !_dbus_append_pair_uint (&auxdata, "pid", pid)) + goto oom; + + if (con->label && !_dbus_append_pair_str (&auxdata, "label", con->label)) + goto oom; + + log_message (allow, "eavesdrop", &auxdata); + + out: + if (con != NULL) + bus_apparmor_confinement_unref (con); + if (free_auxdata) + _dbus_string_free (&auxdata); + + return allow; + + oom: + if (error != NULL && !dbus_error_is_set (error)) + BUS_SET_OOM (error); + allow = FALSE; + goto out; + +#else + return TRUE; +#endif /* HAVE_APPARMOR */ +} |