From 92e9e237f138658eccde559778684d9ef63a9145 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Tue, 14 Dec 2021 19:18:24 +0100 Subject: [PATCH] util/apparmor: add apparmor support Add full AppArmor support using libapparmor. This requires af_unix mediation support in the kernel (which has not yet landed upstream). Signed-off-by: Sebastian Reichel --- meson.build | 9 + meson_options.txt | 1 + src/broker/main.c | 8 + src/bus/bus.c | 17 +- src/bus/bus.h | 3 +- src/bus/driver.c | 51 +++- src/bus/peer.c | 49 ++- src/bus/policy.c | 56 +++- src/bus/policy.h | 11 +- src/launch/config.c | 11 +- src/launch/config.h | 9 + src/launch/launcher.c | 56 ++-- src/launch/policy.c | 12 +- src/launch/policy.h | 2 + src/meson.build | 14 +- src/util/apparmor-fallback.c | 87 ++++++ src/util/apparmor.c | 558 ++++++++++++++++++++++++++++++++++- src/util/apparmor.h | 32 ++ 18 files changed, 935 insertions(+), 51 deletions(-) create mode 100644 src/util/apparmor-fallback.c diff --git a/meson.build b/meson.build index c9468458..8781cd95 100644 --- a/meson.build +++ b/meson.build @@ -50,6 +50,15 @@ if use_audit dep_libcapng = dependency('libcap-ng', version: '>=0.6') endif +# +# Config: apparmor +# + +use_apparmor = get_option('apparmor') +if use_apparmor + dep_libapparmor = dependency('libapparmor', version: '>=3.0') +endif + # # Config: docs # diff --git a/meson_options.txt b/meson_options.txt index 02f3a429..15fc72d2 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,3 +1,4 @@ +option('apparmor', type: 'boolean', value: false, description: 'AppArmor support') option('audit', type: 'boolean', value: false, description: 'Audit support') option('docs', type: 'boolean', value: false, description: 'Build documentation') option('launcher', type: 'boolean', value: true, description: 'Build compatibility launcher') diff --git a/src/broker/main.c b/src/broker/main.c index 292b2bb1..2e7dddc0 100644 --- a/src/broker/main.c +++ b/src/broker/main.c @@ -11,6 +11,7 @@ #include #include "broker/broker.h" #include "broker/main.h" +#include "util/apparmor.h" #include "util/audit.h" #include "util/error.h" #include "util/selinux.h" @@ -286,9 +287,16 @@ int main(int argc, char **argv) { goto exit; } + r = bus_apparmor_init_global(); + if (r) { + r = error_fold(r); + goto exit; + } + r = run(); exit: + bus_apparmor_deinit_global(); bus_selinux_deinit_global(); util_audit_deinit_global(); diff --git a/src/bus/bus.c b/src/bus/bus.c index 7392ebcb..04ec23ae 100644 --- a/src/bus/bus.c +++ b/src/bus/bus.c @@ -224,6 +224,9 @@ void bus_log_append_policy_send(Bus *bus, int policy_type, uint64_t sender_id, u case BUS_LOG_POLICY_TYPE_SELINUX: log_appendf(bus->log, "DBUS_BROKER_POLICY_TYPE=selinux\n"); break; + case BUS_LOG_POLICY_TYPE_APPARMOR: + log_appendf(bus->log, "DBUS_BROKER_POLICY_TYPE=apparmor\n"); + break; default: c_assert(0); abort(); @@ -233,8 +236,18 @@ void bus_log_append_policy_send(Bus *bus, int policy_type, uint64_t sender_id, u bus_log_append_transaction(bus, sender_id, receiver_id, sender_names, receiver_names, sender_label, receiver_label, message); } -void bus_log_append_policy_receive(Bus *bus, uint64_t receiver_id, uint64_t sender_id, NameSet *sender_names, NameSet *receiver_names, Message *message) { - log_appendf(bus->log, "DBUS_BROKER_POLICY_TYPE=internal\n"); +void bus_log_append_policy_receive(Bus *bus, int policy_type, uint64_t receiver_id, uint64_t sender_id, NameSet *sender_names, NameSet *receiver_names, Message *message) { + switch (policy_type) { + case BUS_LOG_POLICY_TYPE_INTERNAL: + log_appendf(bus->log, "DBUS_BROKER_POLICY_TYPE=internal\n"); + break; + case BUS_LOG_POLICY_TYPE_APPARMOR: + log_appendf(bus->log, "DBUS_BROKER_POLICY_TYPE=apparmor\n"); + break; + default: + c_assert(0); + abort(); + } log_appendf(bus->log, "DBUS_BROKER_TRANSMIT_ACTION=receive\n"); bus_log_append_transaction(bus, sender_id, receiver_id, sender_names, receiver_names, NULL, NULL, message); } diff --git a/src/bus/bus.h b/src/bus/bus.h index 07b7369b..ad8081f1 100644 --- a/src/bus/bus.h +++ b/src/bus/bus.h @@ -24,6 +24,7 @@ enum { enum { BUS_LOG_POLICY_TYPE_INTERNAL, BUS_LOG_POLICY_TYPE_SELINUX, + BUS_LOG_POLICY_TYPE_APPARMOR, }; typedef struct Bus Bus; @@ -81,4 +82,4 @@ void bus_log_append_receiver(Bus *bus, uint64_t receiver_id, NameSet *receiver_n void bus_log_append_sender(Bus *bus, uint64_t sender_id, NameSet *sender_names, const char *sender_label); void bus_log_append_transaction(Bus *bus, uint64_t sender_id, uint64_t receiver_id, NameSet *sender_names, NameSet *receiver_names, const char *sender_label, const char *receiver_label, Message *message); void bus_log_append_policy_send(Bus *bus, int policy_type, uint64_t sender_id, uint64_t receiver_id, NameSet *sender_names, NameSet *receiver_names, const char *sender_label, const char *receiver_label, Message *message); -void bus_log_append_policy_receive(Bus *bus, uint64_t sender_id, uint64_t receiver_id, NameSet *sender_names, NameSet *receievr_names, Message *message); +void bus_log_append_policy_receive(Bus *bus, int policy_type, uint64_t sender_id, uint64_t receiver_id, NameSet *sender_names, NameSet *receievr_names, Message *message); diff --git a/src/bus/driver.c b/src/bus/driver.c index b9774d60..bf6b76d3 100644 --- a/src/bus/driver.c +++ b/src/bus/driver.c @@ -19,6 +19,7 @@ #include "dbus/protocol.h" #include "dbus/socket.h" #include "util/error.h" +#include "util/apparmor.h" #include "util/selinux.h" #include "util/string.h" @@ -651,6 +652,9 @@ static int driver_notify_name_owner_changed(Bus *bus, MatchRegistry *matches, co r = policy_snapshot_check_receive(receiver->policy, NULL, + receiver->seclabel, + NULL, + 0, metadata.fields.interface, metadata.fields.member, metadata.fields.path, @@ -1824,6 +1828,12 @@ static int driver_method_become_monitor(Peer *peer, const char *path, CDVar *in_ if (!peer_is_privileged(peer)) return DRIVER_E_PEER_NOT_PRIVILEGED; + r = bus_apparmor_check_eavesdrop(peer->policy->apparmor, peer->policy->seclabel); + if (r == APPARMOR_E_DENIED) + return DRIVER_E_PEER_NOT_PRIVILEGED; + if (r) + return r; + /* first create all the match objects before modifying the peer */ match_owner_init(&owned_matches); @@ -2331,6 +2341,17 @@ static int driver_dispatch_method(Peer *peer, const DriverMethod *methods, uint3 return DRIVER_E_UNEXPECTED_METHOD; } +static int driver_map_denied_error(int err) { + switch (err) { + case POLICY_E_APPARMOR_ACCESS_DENIED: + return BUS_LOG_POLICY_TYPE_APPARMOR; + case POLICY_E_SELINUX_ACCESS_DENIED: + return BUS_LOG_POLICY_TYPE_SELINUX; + default: + return BUS_LOG_POLICY_TYPE_INTERNAL; + } +} + static int driver_dispatch_interface(Peer *peer, uint32_t serial, const char *interface, const char *member, const char *path, const char *signature, Message *message) { static const DriverInterface interfaces[] = { { "org.freedesktop.DBus", driver_methods }, @@ -2346,14 +2367,18 @@ static int driver_dispatch_interface(Peer *peer, uint32_t serial, const char *in /* ignore */ return 0; - r = policy_snapshot_check_send(peer->policy, NULL, NULL, interface, member, path, message->header->type, false, message->metadata.fields.unix_fds); + r = policy_snapshot_check_send(peer->policy, peer->policy->seclabel, NULL, NULL, 0, interface, member, path, message->header->type, false, message->metadata.fields.unix_fds); if (r) { - if (r == POLICY_E_ACCESS_DENIED || r == POLICY_E_SELINUX_ACCESS_DENIED) { + switch (r) { + case POLICY_E_ACCESS_DENIED: + case POLICY_E_APPARMOR_ACCESS_DENIED: + case POLICY_E_SELINUX_ACCESS_DENIED: + { NameSet names = NAME_SET_INIT_FROM_OWNER(&peer->owned_names); log_append_here(peer->bus->log, LOG_WARNING, 0, NULL); bus_log_append_policy_send(peer->bus, - (r == POLICY_E_ACCESS_DENIED ? BUS_LOG_POLICY_TYPE_INTERNAL : BUS_LOG_POLICY_TYPE_SELINUX), + driver_map_denied_error(r), peer->id, ADDRESS_ID_INVALID, &names, NULL, peer->policy->seclabel, peer->bus->seclabel, message); r = log_commitf(peer->bus->log, "A security policy denied :1.%llu to send method call %s:%s.%s to org.freedesktop.DBus.", peer->id, path, interface, member); @@ -2361,9 +2386,10 @@ static int driver_dispatch_interface(Peer *peer, uint32_t serial, const char *in return error_fold(r); return DRIVER_E_SEND_DENIED; + } + default: + return error_fold(r); } - - return error_fold(r); } if (interface) { @@ -2504,8 +2530,10 @@ static int driver_forward_broadcast(Peer *sender, Message *message) { c_list_unlink(&match_owner->destinations_link); r = policy_snapshot_check_send(sender->policy, + sender->seclabel, receiver->seclabel, &receiver_names, + receiver->id, message->metadata.fields.interface, message->metadata.fields.member, message->metadata.fields.path, @@ -2513,14 +2541,21 @@ static int driver_forward_broadcast(Peer *sender, Message *message) { true, message->metadata.fields.unix_fds); if (r) { - if (r == POLICY_E_ACCESS_DENIED || r == POLICY_E_SELINUX_ACCESS_DENIED) + switch (r) { + case POLICY_E_ACCESS_DENIED: + case POLICY_E_APPARMOR_ACCESS_DENIED: + case POLICY_E_SELINUX_ACCESS_DENIED: continue; - - return error_fold(r); + default: + return error_fold(r); + } } r = policy_snapshot_check_receive(receiver->policy, + sender->seclabel, + receiver->seclabel, &sender_names, + sender->id, message->metadata.fields.interface, message->metadata.fields.member, message->metadata.fields.path, diff --git a/src/bus/peer.c b/src/bus/peer.c index 6152ee86..ac075280 100644 --- a/src/bus/peer.c +++ b/src/bus/peer.c @@ -410,10 +410,14 @@ int peer_request_name(Peer *peer, const char *name, uint32_t flags, NameChange * r = policy_snapshot_check_own(peer->policy, name); if (r) { - if (r == POLICY_E_ACCESS_DENIED || r == POLICY_E_SELINUX_ACCESS_DENIED) + switch (r) { + case POLICY_E_ACCESS_DENIED: + case POLICY_E_APPARMOR_ACCESS_DENIED: + case POLICY_E_SELINUX_ACCESS_DENIED: return PEER_E_NAME_REFUSED; - - return error_fold(r); + default: + return error_fold(r); + } } r = name_registry_request_name(&peer->bus->names, @@ -720,6 +724,17 @@ void peer_flush_matches(Peer *peer) { } } +static int peer_map_denied_error(int err) { + switch (err) { + case POLICY_E_APPARMOR_ACCESS_DENIED: + return BUS_LOG_POLICY_TYPE_APPARMOR; + case POLICY_E_SELINUX_ACCESS_DENIED: + return BUS_LOG_POLICY_TYPE_SELINUX; + default: + return BUS_LOG_POLICY_TYPE_INTERNAL; + } +} + int peer_queue_unicast(PolicySnapshot *sender_policy, NameSet *sender_names, ReplyOwner *sender_replies, User *sender_user, uint64_t sender_id, Peer *receiver, Message *message) { _c_cleanup_(reply_slot_freep) ReplySlot *slot = NULL; NameSet receiver_names = NAME_SET_INIT_FROM_OWNER(&receiver->owned_names); @@ -740,7 +755,10 @@ int peer_queue_unicast(PolicySnapshot *sender_policy, NameSet *sender_names, Rep } r = policy_snapshot_check_receive(receiver->policy, + sender_policy->seclabel, + receiver->seclabel, sender_names, + sender_id, message->metadata.fields.interface, message->metadata.fields.member, message->metadata.fields.path, @@ -748,9 +766,13 @@ int peer_queue_unicast(PolicySnapshot *sender_policy, NameSet *sender_names, Rep false, message->metadata.fields.unix_fds); if (r) { - if (r == POLICY_E_ACCESS_DENIED) { + switch (r) { + case POLICY_E_ACCESS_DENIED: + case POLICY_E_APPARMOR_ACCESS_DENIED: log_append_here(receiver->bus->log, LOG_WARNING, 0, NULL); - bus_log_append_policy_receive(receiver->bus, receiver->id, sender_id, sender_names, &receiver_names, message); + bus_log_append_policy_receive(receiver->bus, + peer_map_denied_error(r), + receiver->id, sender_id, sender_names, &receiver_names, message); r = log_commitf(receiver->bus->log, "A security policy denied %s to receive %s %s:%s.%s from :1.%llu.", message->metadata.fields.destination, message->header->type == DBUS_MESSAGE_TYPE_METHOD_CALL ? "method call" : "signal", @@ -760,14 +782,16 @@ int peer_queue_unicast(PolicySnapshot *sender_policy, NameSet *sender_names, Rep return error_fold(r); return PEER_E_RECEIVE_DENIED; + default: + return error_fold(r); } - - return error_fold(r); } r = policy_snapshot_check_send(sender_policy, + sender_policy->seclabel, receiver->seclabel, &receiver_names, + receiver->id, message->metadata.fields.interface, message->metadata.fields.member, message->metadata.fields.path, @@ -775,10 +799,13 @@ int peer_queue_unicast(PolicySnapshot *sender_policy, NameSet *sender_names, Rep false, message->metadata.fields.unix_fds); if (r) { - if (r == POLICY_E_ACCESS_DENIED || r == POLICY_E_SELINUX_ACCESS_DENIED) { + switch (r) { + case POLICY_E_ACCESS_DENIED: + case POLICY_E_APPARMOR_ACCESS_DENIED: + case POLICY_E_SELINUX_ACCESS_DENIED: log_append_here(receiver->bus->log, LOG_WARNING, 0, NULL); bus_log_append_policy_send(receiver->bus, - (r == POLICY_E_ACCESS_DENIED ? BUS_LOG_POLICY_TYPE_INTERNAL : BUS_LOG_POLICY_TYPE_SELINUX), + peer_map_denied_error(r), sender_id, receiver->id, sender_names, &receiver_names, sender_policy->seclabel, receiver->policy->seclabel, message); r = log_commitf(receiver->bus->log, "A security policy denied :1.%llu to send %s %s:%s.%s to %s.", @@ -790,9 +817,9 @@ int peer_queue_unicast(PolicySnapshot *sender_policy, NameSet *sender_names, Rep return error_fold(r); return PEER_E_SEND_DENIED; + default: + return error_fold(r); } - - return error_fold(r); } r = connection_queue(&receiver->connection, sender_user, message); diff --git a/src/bus/policy.c b/src/bus/policy.c index 23000866..d71deb79 100644 --- a/src/bus/policy.c +++ b/src/bus/policy.c @@ -10,6 +10,7 @@ #include "bus/name.h" #include "bus/policy.h" #include "dbus/protocol.h" +#include "util/apparmor.h" #include "util/common.h" #include "util/error.h" #include "util/selinux.h" @@ -413,6 +414,10 @@ int policy_registry_new(PolicyRegistry **registryp, const char *fallback_seclabe *registry = (PolicyRegistry)POLICY_REGISTRY_NULL; + r = bus_apparmor_registry_new(®istry->apparmor, fallback_seclabel); + if (r) + return error_fold(r); + r = bus_selinux_registry_new(®istry->selinux, fallback_seclabel); if (r) return error_fold(r); @@ -443,6 +448,7 @@ PolicyRegistry *policy_registry_free(PolicyRegistry *registry) { policy_registry_node_free(node); policy_batch_unref(registry->default_batch); + bus_apparmor_registry_unref(registry->apparmor); bus_selinux_registry_unref(registry->selinux); free(registry); @@ -619,6 +625,7 @@ static int policy_registry_import_batch(PolicyRegistry *registry, */ int policy_registry_import(PolicyRegistry *registry, CDVar *v) { PolicyRegistryNode *node; + const char *bustype; bool apparmor; int r; @@ -688,11 +695,13 @@ int policy_registry_import(PolicyRegistry *registry, CDVar *v) { return error_fold(r); } - c_dvar_read(v, "]b)>", &apparmor); + c_dvar_read(v, "]bs)>", &apparmor, &bustype); - if (apparmor) - /* XXX: AppArmor is currently not supported. */ - return POLICY_E_INVALID; + if (apparmor) { + r = bus_apparmor_set_bus_type(registry->apparmor, bustype); + if (r) + return error_trace(r); + } r = c_dvar_get_poison(v); if (r) @@ -729,6 +738,7 @@ int policy_snapshot_new(PolicySnapshot **snapshotp, *snapshot = (PolicySnapshot)POLICY_SNAPSHOT_NULL; + snapshot->apparmor = bus_apparmor_registry_ref(registry->apparmor); snapshot->selinux = bus_selinux_registry_ref(registry->selinux); snapshot->seclabel = strdup(seclabel); @@ -776,6 +786,7 @@ PolicySnapshot *policy_snapshot_free(PolicySnapshot *snapshot) { while (snapshot->n_batches-- > 0) policy_batch_unref(snapshot->batches[snapshot->n_batches]); free(snapshot->seclabel); + bus_apparmor_registry_unref(snapshot->apparmor); bus_selinux_registry_unref(snapshot->selinux); free(snapshot); @@ -795,6 +806,7 @@ int policy_snapshot_dup(PolicySnapshot *snapshot, PolicySnapshot **newp) { *new = (PolicySnapshot)POLICY_SNAPSHOT_NULL; + new->apparmor = bus_apparmor_registry_ref(snapshot->apparmor); new->selinux = bus_selinux_registry_ref(snapshot->selinux); new->seclabel = strdup(snapshot->seclabel); @@ -834,6 +846,14 @@ int policy_snapshot_check_own(PolicySnapshot *snapshot, const char *name_str) { size_t i; int v, r; + r = bus_apparmor_check_own(snapshot->apparmor, snapshot->seclabel, name_str); + if (r) { + if (r == APPARMOR_E_DENIED) + return POLICY_E_APPARMOR_ACCESS_DENIED; + + return error_fold(r); + } + r = bus_selinux_check_own(snapshot->selinux, snapshot->seclabel, name_str); if (r) { if (r == SELINUX_E_DENIED) @@ -1033,8 +1053,10 @@ static void policy_snapshot_check_xmit(PolicyBatch *batch, * policy_snapshot_check_send() - XXX */ int policy_snapshot_check_send(PolicySnapshot *snapshot, - const char *subject_seclabel, + const char *sender_seclabel, + const char *receiver_seclabel, NameSet *subject, + uint64_t subject_id, const char *interface, const char *method, const char *path, @@ -1045,7 +1067,16 @@ int policy_snapshot_check_send(PolicySnapshot *snapshot, size_t i; int r; - r = bus_selinux_check_send(snapshot->selinux, snapshot->seclabel, subject_seclabel); + r = bus_apparmor_check_xmit(snapshot->apparmor, true, sender_seclabel, receiver_seclabel, + subject, subject_id, path, interface, method); + if (r) { + if (r == APPARMOR_E_DENIED) + return POLICY_E_APPARMOR_ACCESS_DENIED; + + return error_fold(r); + } + + r = bus_selinux_check_send(snapshot->selinux, snapshot->seclabel, receiver_seclabel); if (r) { if (r == SELINUX_E_DENIED) return POLICY_E_SELINUX_ACCESS_DENIED; @@ -1072,7 +1103,10 @@ int policy_snapshot_check_send(PolicySnapshot *snapshot, * policy_snapshot_check_receive() - XXX */ int policy_snapshot_check_receive(PolicySnapshot *snapshot, + const char *sender_seclabel, + const char *receiver_seclabel, NameSet *subject, + uint64_t subject_id, const char *interface, const char *method, const char *path, @@ -1081,6 +1115,16 @@ int policy_snapshot_check_receive(PolicySnapshot *snapshot, size_t n_fds) { PolicyVerdict verdict = POLICY_VERDICT_INIT; size_t i; + int r; + + r = bus_apparmor_check_xmit(snapshot->apparmor, false, sender_seclabel, receiver_seclabel, + subject, subject_id, path, interface, method); + if (r) { + if (r == APPARMOR_E_DENIED) + return POLICY_E_APPARMOR_ACCESS_DENIED; + + return error_fold(r); + } for (i = 0; i < snapshot->n_batches; ++i) policy_snapshot_check_xmit(snapshot->batches[i], diff --git a/src/bus/policy.h b/src/bus/policy.h index f9fd144a..b136a3eb 100644 --- a/src/bus/policy.h +++ b/src/bus/policy.h @@ -12,6 +12,7 @@ #include "dbus/protocol.h" #include "util/ref.h" +typedef struct BusAppArmorRegistry BusAppArmorRegistry; typedef struct BusSELinuxRegistry BusSELinuxRegistry; typedef struct NameSet NameSet; typedef struct PolicyBatch PolicyBatch; @@ -29,6 +30,7 @@ enum { POLICY_E_INVALID, POLICY_E_ACCESS_DENIED, POLICY_E_SELINUX_ACCESS_DENIED, + POLICY_E_APPARMOR_ACCESS_DENIED, }; struct PolicyVerdict { @@ -111,6 +113,7 @@ struct PolicyRegistryNode { } struct PolicyRegistry { + BusAppArmorRegistry *apparmor; BusSELinuxRegistry *selinux; PolicyBatch *default_batch; CRBTree uid_range_tree; @@ -125,6 +128,7 @@ struct PolicyRegistry { } struct PolicySnapshot { + BusAppArmorRegistry *apparmor; BusSELinuxRegistry *selinux; char *seclabel; size_t n_batches; @@ -162,8 +166,10 @@ int policy_snapshot_dup(PolicySnapshot *snapshot, PolicySnapshot **newp); int policy_snapshot_check_connect(PolicySnapshot *snapshot); int policy_snapshot_check_own(PolicySnapshot *snapshot, const char *name); int policy_snapshot_check_send(PolicySnapshot *snapshot, - const char *subject_context, + const char *sender_context, + const char *receiver_context, NameSet *subject, + uint64_t subject_id, const char *interface, const char *method, const char *path, @@ -171,7 +177,10 @@ int policy_snapshot_check_send(PolicySnapshot *snapshot, bool broadcast, size_t n_fds); int policy_snapshot_check_receive(PolicySnapshot *snapshot, + const char *sender_seclabel, + const char *receiver_seclabel, NameSet *subject, + uint64_t subject_id, const char *interface, const char *method, const char *path, diff --git a/src/launch/config.c b/src/launch/config.c index 490d7b7d..d6c65fd5 100644 --- a/src/launch/config.c +++ b/src/launch/config.c @@ -1020,6 +1020,16 @@ static void config_parser_end_fn(void *userdata, const XML_Char *name) { * that all mandatory data was given. */ switch (state->current->type) { + case CONFIG_NODE_TYPE: + if (!strcmp(state->current->cdata, "system")) + state->current->bus_type.type = CONFIG_BUS_TYPE_SYSTEM; + else if (!strcmp(state->current->cdata, "session")) + state->current->bus_type.type = CONFIG_BUS_TYPE_SESSION; + else + CONFIG_ERR(state, "Unknown bus type", ": %s", state->current->cdata); + + break; + case CONFIG_NODE_USER: state->current->user.valid = false; r = nss_cache_get_uid(state->nss, @@ -1143,7 +1153,6 @@ static void config_parser_end_fn(void *userdata, const XML_Char *name) { break; - case CONFIG_NODE_TYPE: case CONFIG_NODE_LISTEN: case CONFIG_NODE_PIDFILE: case CONFIG_NODE_SERVICEHELPER: diff --git a/src/launch/config.h b/src/launch/config.h index 3fe037dc..eb7d228a 100644 --- a/src/launch/config.h +++ b/src/launch/config.h @@ -99,6 +99,11 @@ enum { CONFIG_APPARMOR_REQUIRED, }; +enum { + CONFIG_BUS_TYPE_SYSTEM, + CONFIG_BUS_TYPE_SESSION, +}; + struct ConfigPath { unsigned long n_refs; ConfigPath *parent; @@ -126,6 +131,10 @@ struct ConfigNode { unsigned int type; union { + struct { + uint32_t type; + } bus_type; + struct { uint32_t uid; uint32_t gid; diff --git a/src/launch/launcher.c b/src/launch/launcher.c index 5bf5cf59..893bea5b 100644 --- a/src/launch/launcher.c +++ b/src/launch/launcher.c @@ -1197,18 +1197,25 @@ static int launcher_reload_config(Launcher *launcher) { switch (policy.apparmor_mode) { case CONFIG_APPARMOR_ENABLED: { - bool enabled; - - /* XXX: See comments in launcher_run() */ + bool enabled, supported; r = bus_apparmor_is_enabled(&enabled); if (r) return error_fold(r); - if (enabled) - fprintf(stderr, "AppArmor enabled, but not supported. Ignoring.\n"); + r = bus_apparmor_dbus_supported(&supported); + if (r) + return error_fold(r); + + if (enabled && !supported) { + fprintf(stderr, "Kernel is missing AppArmor DBus support.\n"); + policy.apparmor_mode = CONFIG_APPARMOR_DISABLED; + } else if (enabled) { + policy.apparmor_mode = CONFIG_APPARMOR_ENABLED; + } else { + policy.apparmor_mode = CONFIG_APPARMOR_DISABLED; + } - policy.apparmor_mode = CONFIG_APPARMOR_DISABLED; break; } case CONFIG_APPARMOR_REQUIRED: @@ -1359,22 +1366,35 @@ int launcher_run(Launcher *launcher) { if (r) return error_fold(r); - if (enabled && !supported) - fprintf(stderr, "Kernel is missing AppArmor DBus support. Ignoring.\n"); - else if (enabled) - fprintf(stderr, "AppArmor enabled, but not supported. Ignoring.\n"); + if (enabled && !supported) { + fprintf(stderr, "Kernel is missing AppArmor DBus support.\n"); + policy.apparmor_mode = CONFIG_APPARMOR_DISABLED; + } else if (enabled) { + policy.apparmor_mode = CONFIG_APPARMOR_ENABLED; + } else { + policy.apparmor_mode = CONFIG_APPARMOR_DISABLED; + } - /* XXX: once the broker supports AppArmor, set this to DISABLED if and only if - * it is disabled in the kernel. */ - policy.apparmor_mode = CONFIG_APPARMOR_DISABLED; break; } - case CONFIG_APPARMOR_REQUIRED: - fprintf(stderr, "AppArmor required, but not supported. Exiting.\n"); + case CONFIG_APPARMOR_REQUIRED: { + bool enabled, supported; - /* XXX: once the broker supports AppArmor, set this to enabled if and only - * if it is enabled in the kernel, and exit the launcher otherwise. */ - return 0; + r = bus_apparmor_is_enabled(&enabled); + if (r) + return error_fold(r); + + r = bus_apparmor_dbus_supported(&supported); + if (r) + return error_fold(r); + + if (!enabled || !supported) { + fprintf(stderr, "AppArmor required, but not supported. Exiting.\n"); + return 0; + } + + policy.apparmor_mode = CONFIG_APPARMOR_ENABLED; + } } c_assert(launcher->fd_listen >= 0); diff --git a/src/launch/policy.c b/src/launch/policy.c index 981c945c..e7afbcfe 100644 --- a/src/launch/policy.c +++ b/src/launch/policy.c @@ -638,6 +638,11 @@ int policy_import(Policy *policy, ConfigRoot *root) { return error_trace(r); c_list_for_each_entry(i_cnode, &root->node_list, root_link) { + if (i_cnode->type == CONFIG_NODE_TYPE) { + policy->bus_type = i_cnode->bus_type.type; + continue; + } + if (i_cnode->type == CONFIG_NODE_ASSOCIATE) { r = policy_import_selinux(policy, i_cnode); if (r) @@ -927,7 +932,8 @@ static int policy_export_xmit(Policy *policy, CList *list1, CList *list2, sd_bus "a(u(" POLICY_T_BATCH "))" \ "a(buu(" POLICY_T_BATCH "))" \ "a(ss)" \ - "b" + "b" \ + "s" static int policy_export_console(Policy *policy, sd_bus_message *m, PolicyEntries *entries, uint32_t uid_start, uint32_t n_uid) { int r; @@ -1149,6 +1155,10 @@ int policy_export(Policy *policy, sd_bus_message *m, uint32_t *at_console_uids, if (r < 0) return error_origin(r); + r = sd_bus_message_append(m, "s", (policy->bus_type == CONFIG_BUS_TYPE_SESSION ? "session" : "system")); + if (r < 0) + return error_origin(r); + r = sd_bus_message_close_container(m); if (r < 0) return error_origin(r); diff --git a/src/launch/policy.h b/src/launch/policy.h index 99faef55..10caf648 100644 --- a/src/launch/policy.h +++ b/src/launch/policy.h @@ -103,6 +103,7 @@ struct Policy { CList selinux_list; unsigned int apparmor_mode; + unsigned int bus_type; }; #define POLICY_INIT(_x) { \ @@ -114,6 +115,7 @@ struct Policy { .gid_tree = C_RBTREE_INIT, \ .selinux_list = C_LIST_INIT((_x).selinux_list), \ .apparmor_mode = CONFIG_APPARMOR_ENABLED, \ + .bus_type = CONFIG_BUS_TYPE_SYSTEM, \ } /* records */ diff --git a/src/meson.build b/src/meson.build index c363cbcd..aa08f83f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -32,7 +32,6 @@ sources_bus = [ 'dbus/queue.c', 'dbus/sasl.c', 'dbus/socket.c', - 'util/apparmor.c', 'util/error.c', 'util/dirwatch.c', 'util/dispatch.c', @@ -55,6 +54,19 @@ deps_bus = [ dep_math, ] +if use_apparmor + sources_bus += [ + 'util/apparmor.c', + ] + deps_bus += [ + dep_libapparmor, + ] +else + sources_bus += [ + 'util/apparmor-fallback.c', + ] +endif + if use_audit sources_bus += [ 'util/audit.c', diff --git a/src/util/apparmor-fallback.c b/src/util/apparmor-fallback.c new file mode 100644 index 00000000..af3730a9 --- /dev/null +++ b/src/util/apparmor-fallback.c @@ -0,0 +1,87 @@ +#include +#include +#include +#include "util/apparmor.h" +#include "util/error.h" + +/** + * bus_apparmor_is_enabled() - checks if AppArmor is currently enabled + * @enabled: return argument telling if AppArmor is enabled + * + * If the AppArmor module is not loaded, or AppArmor is disabled in the + * kernel, set @enabledp to 'false', otherwise set it to 'true'. + * + * Returns: 0 if check succeeded, or negative error code on failure. + */ +int bus_apparmor_is_enabled(bool *enabledp) { + *enabledp = false; + return 0; +} + +/** + * bus_apparmor_dbus_supported() - checks if Kernel has AppArmor support for DBus + * @supported: return argument telling if AppArmor DBus is supported + * + * If the AppArmor module is not loaded, or AppArmor does not support DBus, + * set @supportedp to 'false', otherwise set it to 'true'. + * + * Returns: 0 if check succeeded, or negative error code on failure. + */ +int bus_apparmor_dbus_supported(bool *supportedp) { + *supportedp = false; + return 0; +} + +/** + * bus_apparmor_full_init() - initialize AppArmor support + * + * This initializes AppArmor support. + * + * Returns: 0 if it succeeded, or negative error code on failure. + */ +int bus_apparmor_init_global(void) { + return -EINVAL; +} + +void bus_apparmor_deinit_global(void) { +} + +void bus_apparmor_confinement_ref(struct BusAppArmorConfinement *confinement) { + return; +} + +void bus_apparmor_confinement_unref(struct BusAppArmorConfinement *confinement) { + return; +} + +int bus_apparmor_registry_new(struct BusAppArmorRegistry **registryp, const char *fallback_context) { + return -EINVAL; +} + +BusAppArmorRegistry *bus_apparmor_registry_ref(BusAppArmorRegistry *registry) { + return NULL; +} + +BusAppArmorRegistry *bus_apparmor_registry_unref(BusAppArmorRegistry *registry) { + return NULL; +} + +int bus_apparmor_set_bus_type(BusAppArmorRegistry *registry, const char *bustype) { + return 0; +} + +int bus_apparmor_check_own(struct BusAppArmorRegistry *registry, const char *owner_context, + const char *name) { + return -EINVAL; +} + +int bus_apparmor_check_xmit(BusAppArmorRegistry *registry, bool check_send, + const char *sender_context, const char *receiver_context, + NameSet *subject, uint64_t subject_id, + const char *path, const char *interface, const char *method) { + return -EINVAL; +} + +int bus_apparmor_check_eavesdrop(BusAppArmorRegistry *registry, const char *context) { + return -EINVAL; +} diff --git a/src/util/apparmor.c b/src/util/apparmor.c index 9e4eaec4..05a09cc5 100644 --- a/src/util/apparmor.c +++ b/src/util/apparmor.c @@ -10,11 +10,78 @@ * applicable policy. */ +#include #include +#include #include #include +#include #include "util/apparmor.h" +#include "util/audit.h" #include "util/error.h" +#include "util/ref.h" +#include "bus/name.h" + +struct BusAppArmorRegistry { + _Atomic unsigned long n_refs; + const char *fallback_context; + const char *bustype; +}; + +/* 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 struct 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 struct BusAppArmorConfinement* bus_apparmor_confinement_new(char *label, const char *mode) { + struct BusAppArmorConfinement *confinement; + + confinement = malloc(sizeof(*confinement)); + if (confinement) { + confinement->refcount = 1; + confinement->label = label; + confinement->mode = mode; + } + + return confinement; +} + +void bus_apparmor_confinement_ref(struct BusAppArmorConfinement *confinement) { + if (confinement && confinement->refcount > 0) { + confinement->refcount += 1; + } +} + +void bus_apparmor_confinement_unref(struct BusAppArmorConfinement *confinement) { + if (confinement && 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); + free(confinement); + } + } +} /** * bus_apparmor_is_enabled() - checks if AppArmor is currently enabled @@ -46,6 +113,7 @@ int bus_apparmor_is_enabled(bool *enabledp) { enabled = false; break; default: + fprintf(stderr, "Invalid apparmor enable state.\n"); return error_origin(-EIO); } } else if (errno == ENOENT) { @@ -76,8 +144,10 @@ int bus_apparmor_dbus_supported(bool *supportedp) { if (f) { errno = 0; if (!fgets(buffer, sizeof(buffer), f)) { - if (ferror(f) && errno != EINVAL) + if (ferror(f) && errno != EINVAL) { + fprintf(stderr, "Invalid apparmor feature mask.\n"); return errno ? error_origin(-errno) : error_origin(-ENOTRECOVERABLE); + } } if (strstr(buffer, "acquire") && strstr(buffer, "send") && strstr(buffer, "receive")) @@ -93,3 +163,489 @@ int bus_apparmor_dbus_supported(bool *supportedp) { *supportedp = supported; return 0; } + +static bool is_apparmor_enabled(void) { + int ret; + bool enabled; + + ret = bus_apparmor_dbus_supported(&enabled); + if (ret || !enabled) + return false; + + ret = bus_apparmor_is_enabled(&enabled); + if (ret || !enabled) + return false; + + return true; +} + +static int bus_apparmor_log(const char *fmt, ...) { + _c_cleanup_(c_freep) char *message = NULL; + va_list ap; + int r; + + va_start(ap, fmt); + r = vasprintf(&message, fmt, ap); + va_end(ap); + if (r < 0) + return r; + + /* XXX: we don't have access to any context, so can't find + * the right UID to use, follow dbus-daemon(1) and use our + * own. */ + r = util_audit_log(UTIL_AUDIT_TYPE_AVC, message, getuid()); + if (r) + return error_fold(r); + + return 0; +} + +/** + * bus_apparmor_init_global() - initialize AppArmor support + * + * This initializes AppArmor support. + * + * Returns: 0 if it succeeded, or negative error code on failure. + */ +int bus_apparmor_init_global(void) { + char *label, *mode; + + if (is_apparmor_enabled()) + return 0; + + if (!bus_con) { + if (aa_getcon(&label, &mode) == -1) { + fprintf(stderr, "Error getting AppArmor context of bus\n"); + return error_origin(-errno); + } + + bus_con = bus_apparmor_confinement_new(label, mode); + if (!bus_con) { + free(label); + return error_origin(-ENOMEM); + } + } + + return 0; +} + +void bus_apparmor_deinit_global(void) { + if (bus_con) { + bus_apparmor_confinement_unref(bus_con); + bus_con = NULL; + } +} + +/** + * bus_apparmor_registry_new() - create a new AppArmor registry + * @registryp: pointer to the new registry + * @fallback_context: fallback security context for queries against this registry + * + * Return: 0 on success, or a negative error code on failure. + */ +int bus_apparmor_registry_new(struct BusAppArmorRegistry **registryp, const char *fallback_context) { + _c_cleanup_(bus_apparmor_registry_unrefp) BusAppArmorRegistry *registry = NULL; + size_t n_fallback_context = strlen(fallback_context) + 1; + + registry = malloc(sizeof(*registry) + n_fallback_context); + if (!registry) + return error_origin(-ENOMEM); + + registry->n_refs = REF_INIT; + registry->fallback_context = (const char *)(registry + 1); + memcpy((char *)registry->fallback_context, fallback_context, n_fallback_context); + + *registryp = registry; + registry = NULL; + return 0; +} + +static void bus_apparmor_registry_free(_Atomic unsigned long *n_refs, void *userdata) { + BusAppArmorRegistry *registry = c_container_of(n_refs, BusAppArmorRegistry, n_refs); + + free(registry); +} + +BusAppArmorRegistry *bus_apparmor_registry_ref(BusAppArmorRegistry *registry) { + if (registry) + ref_inc(®istry->n_refs); + + return registry; +} + +BusAppArmorRegistry *bus_apparmor_registry_unref(BusAppArmorRegistry *registry) { + if (registry) + ref_dec(®istry->n_refs, bus_apparmor_registry_free, NULL); + + return NULL; +} + +int bus_apparmor_set_bus_type(BusAppArmorRegistry *registry, const char* bustype) { + registry->bustype = strdup(bustype); + if (!registry->bustype) + return -ENOMEM; + return 0; +} + +static bool is_unconfined(const char *context) { + return !strcmp(context, "unconfined"); +} + +static bool is_complain(const char *mode) { + if (mode == NULL) return false; + return !strcmp(mode, "complain"); +} + +static const char* allowstr(bool allow) { + return allow ? "ALLOWED" : "DENIED"; +} + +static int build_service_query(char **query, const char *security_label, + const char *bustype, const char *name) { + char *qstr; + int i = 0, len; + + len = AA_QUERY_CMD_LABEL_SIZE; + len += strlen(security_label) + 1; + len += 1; /* AA_CLASS_DBUS */ + len += strlen(bustype) + 1; + len += strlen(name) + 1; + qstr = malloc(len); + if (!qstr) + return -ENOMEM; + + i += AA_QUERY_CMD_LABEL_SIZE; + strcpy(qstr+i, security_label); + i += strlen(security_label) + 1; + qstr[i++] = AA_CLASS_DBUS; + strcpy(qstr+i, bustype); + i += strlen(bustype) + 1; + strcpy(qstr+i, name); + + *query = qstr; + return len-1; +} + +static int build_message_query_name(char **query, const char *security_label, + const char *bustype, const char *name, + const char *receiver_context, + const char *path, const char *interface, const char *method) { + char *qstr; + int i = 0, len; + + len = AA_QUERY_CMD_LABEL_SIZE; + len += strlen(security_label) + 1; + len += 1; /* AA_CLASS_DBUS */ + len += strlen(bustype) + 1; + len += strlen(receiver_context) + 1; + len += strlen(name) + 1; + len += strlen(path) + 1; + len += strlen(interface) + 1; + len += strlen(method) + 1; + + qstr = malloc(len); + if (!qstr) + return -ENOMEM; + + i += AA_QUERY_CMD_LABEL_SIZE; + strcpy(qstr+i, security_label); + i += strlen(security_label) + 1; + qstr[i++] = AA_CLASS_DBUS; + strcpy(qstr+i, bustype); + i += strlen(bustype) + 1; + strcpy(qstr+i, receiver_context); + i += strlen(receiver_context) + 1; + strcpy(qstr+i, name); + i += strlen(name) + 1; + strcpy(qstr+i, path); + i += strlen(path) + 1; + strcpy(qstr+i, interface); + i += strlen(interface) + 1; + strcpy(qstr+i, method); + + *query = qstr; + return len-1; +} + +static int build_eavesdrop_query(char **query, const char *security_label, const char *bustype) { + char *qstr; + int i = 0, len; + + len = AA_QUERY_CMD_LABEL_SIZE; + len += strlen(security_label) + 1; + len += 1; /* AA_CLASS_DBUS */ + len += strlen(bustype) + 1; + qstr = malloc(len); + if (!qstr) + return -ENOMEM; + + i += AA_QUERY_CMD_LABEL_SIZE; + strcpy(qstr+i, security_label); + i += strlen(security_label) + 1; + qstr[i++] = AA_CLASS_DBUS; + strcpy(qstr+i, bustype); + + *query = qstr; + return len-1; +} + +static int apparmor_message_query_name(bool check_send, const char *security_label, + const char *bustype, const char *receiver_context, + const char *name, const char *path, const char *interface, + const char *method, int *allow, int *audit) { + _c_cleanup_(c_freep) char *qstr = NULL; + int r; + + r = build_message_query_name(&qstr, security_label, bustype, receiver_context, + name, path, interface, method); + if (r < 0) + return error_origin(r); + + r = aa_query_label(check_send ? AA_DBUS_SEND : AA_DBUS_RECEIVE, qstr, r, allow, audit); + if (r < 0) + return error_origin(-errno); + + return r; +} + +static int apparmor_message_query(bool check_send, const char *security_label, const char *bustype, + const char *receiver_context, NameSet *nameset, + uint64_t subject_id, const char *path, const char *interface, + const char *method, int *allow, int *audit) { + NameOwnership *ownership; + int i, r, audit_tmp = 0; + + if (!nameset) { + r = apparmor_message_query_name(check_send, security_label, bustype, + receiver_context, "org.freedesktop.DBus", + path, interface, method, allow, audit); + } else if (nameset->type == NAME_SET_TYPE_OWNER) { + if (c_rbtree_is_empty(&nameset->owner->ownership_tree)) { + struct Address addr; + address_init_from_id(&addr, subject_id); + r = apparmor_message_query_name(check_send, security_label, bustype, + receiver_context, address_to_string(&addr), + path, interface, method, allow, audit); + } else { + /* + * A set of owned names is given. In this case, we iterate all + * of them and match against each. Note that this matches even + * on non-primary name owners. + */ + c_rbtree_for_each_entry(ownership, &nameset->owner->ownership_tree, owner_node) { + r = apparmor_message_query_name(check_send, security_label, + bustype, receiver_context, + ownership->name->name, path, + interface, method, allow, &audit_tmp); + if (r < 0) + return r; + if (!allow) + return r; + if (audit_tmp) + *audit = 1; + } + } + } else if (nameset->type == NAME_SET_TYPE_SNAPSHOT) { + /* + * An ownership-snapshot is given. Again, we simply iterate the + * names and match each. Note that the snapshot must contain + * queued names as well, since the policy matches on it. + */ + for (i = 0; i < nameset->snapshot->n_names; ++i) { + r = apparmor_message_query_name(check_send, security_label, bustype, + receiver_context, + nameset->snapshot->names[i]->name, + path, interface, method, allow, &audit_tmp); + if (r < 0) + return r; + if (!allow) + return r; + if (audit_tmp) + *audit = 1; + } + } else if (nameset->type != NAME_SET_TYPE_EMPTY) { + c_assert(0); + r = -EINVAL; + } + + return r; +} + +/** + * bus_apparmor_check_own() - check if the given transaction is allowed + * @registry: AppArmor registry to operate on + * @context: security context requesting the name + * @name: name to be owned + * + * Check if the given owner context is allowed to own the given name. + * + * The contexts are pinned when peers connect, and as such could in principle + * become invalid in case a new policy is loaded that does not know the + * old labels. In this case we treat this as if the ownership request was + * denied. + * + * Return: 0 if the ownership is allowed, APPARMOR_E_DENIED if it is not, + * or a negative error code on failure. + */ +int bus_apparmor_check_own(struct BusAppArmorRegistry *registry, + const char *owner_context, + const char *name) { + _c_cleanup_(c_freep) char *condup = strdup(owner_context); + char *security_label, *security_mode; + _c_cleanup_(c_freep) char *qstr = NULL; + int r; + /* the AppArmor API uses pointers to int for pointers to boolean */ + int allow = false, audit = true; + + if (!is_apparmor_enabled()) + return 0; + + if (!condup) + return -ENOMEM; + + security_label = aa_splitcon(condup, &security_mode); + + r = build_service_query(&qstr, security_label, registry->bustype, name); + if (r < 0) + return error_origin(r); + + r = aa_query_label(AA_DBUS_BIND, qstr, r, &allow, &audit); + if (r < 0) + return error_origin(-errno); + + if (audit) + bus_apparmor_log("apparmor=\"%s\" operation=\"dbus_bind\" bus=\"%s\" name=\"%s\"", allowstr(allow), registry->bustype, name); + + if (is_complain(security_mode)) + allow = 1; + + if (!allow) + return APPARMOR_E_DENIED; + + return 0; +} + +/** + * bus_apparmor_check_xmit() - check if the given transaction is allowed + * @registry: AppArmor registry to operate on + * @check_send: true if sending should be checked, false is receiving should be checked + * @sender_context: security context of the sender + * @receiver_context: security context of the receiver, or NULL + * @subject: List of names + * @subject_id: Unique ID of the subject + * @path: Dbus object path + * @interface: DBus method interface + * @method: DBus method that is being called + * + * Check if the given sender context is allowed to send/receive a message + * to the given receiver context. If the any context is given as NULL, + * the per-registry fallback context is used instead. + * + * The contexts are pinned when peers connect, and as such could in principle + * become invalid in case a new policy is loaded that does not know the + * old labels. In this case we treat this as if the transaction was + * denied. + * + * In case multiple names are available all are being checked and the function + * will deny access if any of them is denied by AppArmor. + * + * Return: 0 if the transaction is allowed, APPARMOR_E_DENIED if it is not, + * or a negative error code on failure. + */ +int bus_apparmor_check_xmit(BusAppArmorRegistry *registry, + bool check_send, + const char *sender_context, + const char *receiver_context, + NameSet *subject, + uint64_t subject_id, + const char *path, + const char *interface, + const char *method) { + _c_cleanup_(c_freep) char *sender_context_dup = strdup(sender_context ?: registry->fallback_context); + _c_cleanup_(c_freep) char *receiver_context_dup = strdup(receiver_context ?: registry->fallback_context); + char *sender_security_label, *sender_security_mode; + char *receiver_security_label, *receiver_security_mode; + const char *direction = check_send ? "send" : "receive"; + int r; + /* the AppArmor API uses pointers to int for pointers to boolean */ + int allow = false, audit = true; + + if (!is_apparmor_enabled()) + return 0; + + if (!sender_context_dup || !receiver_context_dup) + return -ENOMEM; + + sender_security_label = aa_splitcon(sender_context_dup, &sender_security_mode); + receiver_security_label = aa_splitcon(receiver_context_dup, &receiver_security_mode); + + if (is_unconfined(sender_security_label) && is_unconfined(receiver_security_label)) + return 0; + + r = apparmor_message_query(check_send, + check_send ? sender_security_label : receiver_security_label, + registry->bustype, + check_send ? receiver_security_label : sender_security_label, + subject, subject_id, path, interface, method, &allow, &audit); + if (r < 0) + return error_origin(r); + + if (audit) + bus_apparmor_log("apparmor=\"%s\" operation=\"dbus_%s\" bus=\"%s\" path=\"%s\" interface=\"%s\" method=\"%s\"", allowstr(allow), direction, registry->bustype, path, interface, method); + + if (is_complain(check_send ? sender_security_mode : receiver_security_mode)) + allow = 1; + + if (!allow) + return APPARMOR_E_DENIED; + + return 0; +} + +/** + * bus_apparmor_check_eavesdrop() - check if the given context may eavesdrop + * @registry: AppArmor registry to operate on + * @context: security context that wants to eavesdrop + * + * Check if the given sender context is allowed to do eavesdropping. + * + * Return: 0 if the transaction is allowed, APPARMOR_E_DENIED if it is not, + * or a negative error code on failure. + */ +int bus_apparmor_check_eavesdrop(BusAppArmorRegistry *registry, + const char *context) +{ + _c_cleanup_(c_freep) char *condup = strdup(context); + char *security_label, *security_mode; + _c_cleanup_(c_freep) char *qstr = NULL; + int r; + /* the AppArmor API uses pointers to int for pointers to boolean */ + int allow = false, audit = true; + + if (!is_apparmor_enabled() || is_unconfined(context)) + return 0; + + if (!condup) + return -ENOMEM; + + security_label = aa_splitcon(condup, &security_mode); + + r = build_eavesdrop_query(&qstr, security_label, registry->bustype); + if (r < 0) + return error_origin(r); + + r = aa_query_label(AA_DBUS_EAVESDROP, qstr, r, &allow, &audit); + if (r < 0) + return error_origin(-errno); + + if (audit) + bus_apparmor_log("apparmor=\"%s\" operation=\"dbus_eavesdrop\" bus=\"%s\" label=\"%s\"", allowstr(allow), registry->bustype, context); + + if (is_complain(security_mode)) + allow = 1; + + if (!allow) + return APPARMOR_E_DENIED; + + return 0; +} diff --git a/src/util/apparmor.h b/src/util/apparmor.h index a44aa4ef..3b09743f 100644 --- a/src/util/apparmor.h +++ b/src/util/apparmor.h @@ -6,5 +6,37 @@ #include +typedef struct NameSet NameSet; + +enum { + _APPARMOR_E_SUCCESS, + + APPARMOR_E_DENIED, +}; + +typedef struct BusAppArmorRegistry BusAppArmorRegistry; + +struct BusAppArmorConfinement; + int bus_apparmor_is_enabled(bool *enabledp); int bus_apparmor_dbus_supported(bool *supportedp); + +int bus_apparmor_init_global(void); +void bus_apparmor_deinit_global(void); + +void bus_apparmor_confinement_ref(struct BusAppArmorConfinement *confinement); +void bus_apparmor_confinement_unref(struct BusAppArmorConfinement *confinement); + +int bus_apparmor_registry_new(struct BusAppArmorRegistry **registryp, const char *fallback_context); +BusAppArmorRegistry *bus_apparmor_registry_ref(BusAppArmorRegistry *registry); +BusAppArmorRegistry *bus_apparmor_registry_unref(BusAppArmorRegistry *registry); +C_DEFINE_CLEANUP(BusAppArmorRegistry *, bus_apparmor_registry_unref); +int bus_apparmor_set_bus_type(BusAppArmorRegistry *registry, const char *bustype); + +int bus_apparmor_check_own(struct BusAppArmorRegistry *registry, const char *context, + const char *name); +int bus_apparmor_check_xmit(BusAppArmorRegistry *registry, bool check_send, + const char *sender_context, const char *receiver_context, + NameSet *subject, uint64_t subject_id, + const char *path, const char *interface, const char *method); +int bus_apparmor_check_eavesdrop(BusAppArmorRegistry *registry, const char *context);