| /* -*- 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 <libaudit.h> |
| #endif /* HAVE_LIBAUDIT */ |
| |
| #include "activation.h" |
| #include "audit.h" |
| #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; |
| |
| /* 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; |
| } |
| |
| /* |
| * 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; |
| #ifdef HAVE_LIBAUDIT |
| int audit_fd; |
| #endif |
| |
| if (allow) |
| mstr = "ALLOWED"; |
| else |
| mstr = "DENIED"; |
| |
| #ifdef HAVE_LIBAUDIT |
| audit_fd = bus_audit_get_fd (); |
| |
| if (audit_fd >= 0) |
| { |
| DBusString avc; |
| |
| 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; |
| #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; |
| /* the AppArmor API uses pointers to int for pointers to boolean, and |
| * int is not strictly guaranteed to be the same as dbus_bool_t */ |
| int 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, |
| BusActivationEntry *activation_entry, |
| DBusError *error) |
| { |
| #ifdef HAVE_APPARMOR |
| BusAppArmorConfinement *src_con = NULL, *dst_con = NULL; |
| DBusString qstr, auxdata; |
| int src_allow = FALSE, dst_allow = FALSE; |
| int 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); |
| const char *dst_label = NULL; |
| const char *dst_mode = NULL; |
| |
| 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 if (activation_entry != NULL) |
| { |
| dst_label = bus_activation_entry_get_assumed_apparmor_label (activation_entry); |
| } |
| else |
| { |
| dst_con = bus_con; |
| bus_apparmor_confinement_ref (dst_con); |
| } |
| |
| if (dst_con != NULL) |
| { |
| dst_label = dst_con->label; |
| dst_mode = dst_con->mode; |
| } |
| |
| /* 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_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; |
| } |
| } |
| |
| /* When deciding whether we can activate a service, we only check that |
| * we are allowed to send a message to it, not that it is allowed to |
| * receive that message from us. |
| */ |
| if (activation_entry != NULL || is_unconfined (dst_label, dst_mode)) |
| { |
| dst_allow = TRUE; |
| dst_audit = FALSE; |
| } |
| else |
| { |
| if (!_dbus_string_init (&qstr)) |
| goto oom; |
| |
| if (!build_message_query (&qstr, dst_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_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_label && |
| !_dbus_append_pair_str (&auxdata, "peer_label", dst_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_label && |
| !_dbus_append_pair_str (&auxdata, "label", dst_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; |
| int 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 */ |
| } |