From bdb81b4f8b15893d35c82c39b582064348eb5489 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 13 Feb 2014 13:07:32 -0600 Subject: [PATCH 09/10] Mediation of processes sending and receiving messages When an AppArmor confined process wants to send or receive a message, a check is performed to see if the action should be allowed. When a message is going through dbus-daemon, there are two checks performed at once. One for the sending process and one for the receiving process. The checks are based on the process's label, the bus type, destination, path, interface, and member, as well as the peer's label and/or destination name. This allows for the traditional connection-based enforcement, as well as any fine-grained filtering desired by the system administrator. It is important to note that error and method_return messages are allowed to cut down on the amount of rules needed. If a process was allowed to send a message, it can receive error and method_return messages. An example AppArmor rule that would allow a process to call the Hello method of the session bus itself would be: dbus send bus=session path=/org/freedesktop/DBus interface=org.freedesktop.DBus member=Hello peer=(name=org.freedesktop.DBus), To receive any message on the system bus from a process confined by the "confined-client" AppArmor profile: dbus receive bus=system peer=(label=confined-client), Signed-off-by: John Johansen [tyhicks: Use BusAppArmorConfinement, bug fixes, cleanup, commit msg] Signed-off-by: Tyler Hicks --- bus/apparmor.c | 232 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ bus/apparmor.h | 12 +++ bus/bus.c | 29 +++++++- 3 files changed, 272 insertions(+), 1 deletion(-) diff --git a/bus/apparmor.c b/bus/apparmor.c index fe6b3b9..8047ea7 100644 --- a/bus/apparmor.c +++ b/bus/apparmor.c @@ -596,3 +596,235 @@ bus_apparmor_allows_acquire_service (DBusConnection *connection, 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. + * @returns whether to allow the send + */ +dbus_bool_t +bus_apparmor_allows_send (DBusConnection *sender, + DBusConnection *proposed_recipient, + const char *bustype, + const char *msgtype, + const char *path, + const char *interface, + const char *method, + const char *error_name, + const char *destination, + const char *source, + DBusError *error) +{ +#ifdef HAVE_APPARMOR + BusAppArmorConfinement *scon = NULL, *tcon = NULL; + DBusString auxdata; + dbus_bool_t sallow = FALSE, tallow = FALSE, saudit = TRUE, taudit = TRUE; + dbus_bool_t string_alloced = FALSE; + unsigned long pid; + int len, res, serrno = 0, terrno = 0; + char *qstr = NULL; + ssize_t qsize; + uint32_t sperm = AA_DBUS_SEND, tperm = AA_DBUS_RECEIVE; + + if (!apparmor_enabled) + return TRUE; + + _dbus_assert (sender != NULL); + + scon = bus_connection_get_apparmor_confinement (sender); + + if (proposed_recipient) + tcon = bus_connection_get_apparmor_confinement (proposed_recipient); + else + { + tcon = bus_con; + bus_apparmor_confinement_ref (tcon); + } + + /* map reply messages: "error" and "method_return" to initial send and + * receive. 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 + */ + if (strcmp (msgtype, "error") == 0 || strcmp (msgtype, "method_return") == 0) + { + /* ignore reply messages and let dbus reply mapping handle them + * as the send was already allowed + */ + sallow = TRUE; + tallow = TRUE; + goto out; + } + + /* do we want to include the msgtype in the encoding ??? what of tpid + * so we can do conditional object owner perms + */ + if (is_unconfined (scon->context, scon->mode)) + { + sallow = TRUE; + saudit = FALSE; + } + else + { + qsize = build_query (&qstr, scon->context, bustype, destination, + tcon->context, path, interface, method); + if (qsize == -1) + goto oom; + + res = aa_query_label (sperm, qstr, qsize, &sallow, &saudit); + free (qstr); + if (res == -1) + { + serrno = errno; + goto audit; + } + } + + if (is_unconfined (tcon->context, tcon->mode)) + { + tallow = TRUE; + taudit = FALSE; + } + else + { + qsize = build_query (&qstr, tcon->context, bustype, source, + scon->context, path, interface, method); + if (qsize == -1) + goto oom; + + res = aa_query_label (tperm, qstr, qsize, &tallow, &taudit); + free (qstr); + if (res == -1) + { + terrno = errno; + goto audit; + } + } + + /* Don't fail operations on profiles in complain mode */ + if (modestr_to_complain (scon->mode)) + sallow = TRUE; + if (modestr_to_complain (tcon->mode)) + tallow = TRUE; + + if (!saudit && !taudit) + 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 (path && !_dbus_append_pair_str (&auxdata, "path", path)) + goto oom; + + if (interface && !_dbus_append_pair_str (&auxdata, "interface", interface)) + goto oom; + + if (method && !_dbus_append_pair_str (&auxdata, "member", method)) + goto oom; + + if (error_name && !_dbus_append_pair_str (&auxdata, "error_name", error_name)) + goto oom; + + len = _dbus_string_get_length (&auxdata); + + if (saudit) + { + if (!_dbus_append_mask (&auxdata, sperm)) + 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 (scon->context && + !_dbus_append_pair_str (&auxdata, "profile", scon->context)) + 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 (tcon->context && + !_dbus_append_pair_str (&auxdata, "peer_profile", tcon->context)) + goto oom; + + if (serrno && !_dbus_append_pair_str (&auxdata, "info", strerror (serrno))) + goto oom; + + if (terrno && + !_dbus_append_pair_str (&auxdata, "peer_info", strerror (terrno))) + goto oom; + + log_message (sallow, msgtype, &auxdata); + } + if (taudit) + { + _dbus_string_set_length (&auxdata, len); + + if (source && !_dbus_append_pair_str (&auxdata, "name", source)) + goto oom; + + if (!_dbus_append_mask (&auxdata, tperm)) + goto oom; + + if (proposed_recipient && + dbus_connection_get_unix_process_id (proposed_recipient, &pid) && + !_dbus_append_pair_uint (&auxdata, "pid", pid)) + goto oom; + + if (tcon->context && + !_dbus_append_pair_str (&auxdata, "profile", tcon->context)) + goto oom; + + if (sender && dbus_connection_get_unix_process_id (sender, &pid) && + !_dbus_append_pair_uint (&auxdata, "peer_pid", pid)) + goto oom; + + if (scon->context && + !_dbus_append_pair_str (&auxdata, "peer_profile", scon->context)) + goto oom; + + if (terrno && !_dbus_append_pair_str (&auxdata, "info", strerror (terrno))) + goto oom; + + if (serrno && + !_dbus_append_pair_str (&auxdata, "peer_info", strerror (serrno))) + goto oom; + + log_message (tallow, msgtype, &auxdata); + } + + out: + if (scon != NULL) + bus_apparmor_confinement_unref (scon); + if (tcon != NULL) + bus_apparmor_confinement_unref (tcon); + if (string_alloced) + _dbus_string_free (&auxdata); + + return sallow && tallow; + + oom: + BUS_SET_OOM (error); + sallow = FALSE; + tallow = FALSE; + goto out; + +#else + return TRUE; +#endif /* HAVE_APPARMOR */ +} diff --git a/bus/apparmor.h b/bus/apparmor.h index dcae382..0618afd 100644 --- a/bus/apparmor.h +++ b/bus/apparmor.h @@ -49,5 +49,17 @@ bus_apparmor_allows_acquire_service (DBusConnection *connection, const char *bustype, const char *service_name, DBusError *error); +dbus_bool_t +bus_apparmor_allows_send (DBusConnection *sender, + DBusConnection *proposed_recipient, + const char *bustype, + const char *msgtype, + const char *path, + const char *interface, + const char *member, + const char *error_name, + const char *destination, + const char *source, + DBusError *error); #endif /* BUS_APPARMOR_H */ diff --git a/bus/bus.c b/bus/bus.c index 9c26249..bd8da56 100644 --- a/bus/bus.c +++ b/bus/bus.c @@ -1438,7 +1438,7 @@ bus_context_check_security_policy (BusContext *context, DBusMessage *message, DBusError *error) { - const char *dest; + const char *src, *dest; BusClientPolicy *sender_policy; BusClientPolicy *recipient_policy; dbus_int32_t toggles; @@ -1447,6 +1447,7 @@ bus_context_check_security_policy (BusContext *context, dbus_bool_t requested_reply; type = dbus_message_get_type (message); + src = dbus_message_get_sender (message); dest = dbus_message_get_destination (message); /* dispatch.c was supposed to ensure these invariants */ @@ -1502,6 +1503,32 @@ bus_context_check_security_policy (BusContext *context, return FALSE; } + /* next verify AppArmor access controls. If allowed then + * go on with the standard checks. + */ + if (!bus_apparmor_allows_send (sender, proposed_recipient, + bus_context_get_type(context), + dbus_message_type_to_string (dbus_message_get_type (message)), + dbus_message_get_path (message), + dbus_message_get_interface (message), + dbus_message_get_member (message), + dbus_message_get_error_name (message), + dest ? dest : DBUS_SERVICE_DBUS, + src ? src : DBUS_SERVICE_DBUS, + error)) + { + if (error != NULL && !dbus_error_is_set (error)) + { + /* apparmor should have written to audit log or syslog */ + complain_about_message (context, DBUS_ERROR_ACCESS_DENIED, + "An AppArmor policy prevents this sender from sending this " + "message to this recipient", + 0, message, sender, proposed_recipient, FALSE, FALSE, error); + _dbus_verbose ("AppArmor security check denying send to service\n"); + } + + return FALSE; + } if (bus_connection_is_active (sender)) { -- 1.9.rc1