summaryrefslogtreecommitdiff
path: root/ipc/kdbus/bus.c
diff options
context:
space:
mode:
authorAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-08-05 17:04:01 -0300
committerAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-08-05 17:04:01 -0300
commit57f0f512b273f60d52568b8c6b77e17f5636edc0 (patch)
tree5e910f0e82173f4ef4f51111366a3f1299037a7b /ipc/kdbus/bus.c
Initial import
Diffstat (limited to 'ipc/kdbus/bus.c')
-rw-r--r--ipc/kdbus/bus.c514
1 files changed, 514 insertions, 0 deletions
diff --git a/ipc/kdbus/bus.c b/ipc/kdbus/bus.c
new file mode 100644
index 000000000..a67f825bd
--- /dev/null
+++ b/ipc/kdbus/bus.c
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2013-2015 Kay Sievers
+ * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+ * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org>
+ * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com>
+ * Copyright (C) 2013-2015 Linux Foundation
+ * Copyright (C) 2014-2015 Djalal Harouni
+ *
+ * kdbus is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version.
+ */
+
+#include <linux/fs.h>
+#include <linux/hashtable.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/random.h>
+#include <linux/sched.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/uio.h>
+
+#include "bus.h"
+#include "notify.h"
+#include "connection.h"
+#include "domain.h"
+#include "endpoint.h"
+#include "handle.h"
+#include "item.h"
+#include "match.h"
+#include "message.h"
+#include "metadata.h"
+#include "names.h"
+#include "policy.h"
+#include "util.h"
+
+static void kdbus_bus_free(struct kdbus_node *node)
+{
+ struct kdbus_bus *bus = container_of(node, struct kdbus_bus, node);
+
+ WARN_ON(!list_empty(&bus->monitors_list));
+ WARN_ON(!hash_empty(bus->conn_hash));
+
+ kdbus_notify_free(bus);
+
+ kdbus_user_unref(bus->creator);
+ kdbus_name_registry_free(bus->name_registry);
+ kdbus_domain_unref(bus->domain);
+ kdbus_policy_db_clear(&bus->policy_db);
+ kdbus_meta_proc_unref(bus->creator_meta);
+ kfree(bus);
+}
+
+static void kdbus_bus_release(struct kdbus_node *node, bool was_active)
+{
+ struct kdbus_bus *bus = container_of(node, struct kdbus_bus, node);
+
+ if (was_active)
+ atomic_dec(&bus->creator->buses);
+}
+
+static struct kdbus_bus *kdbus_bus_new(struct kdbus_domain *domain,
+ const char *name,
+ struct kdbus_bloom_parameter *bloom,
+ const u64 *pattach_owner,
+ u64 flags, kuid_t uid, kgid_t gid)
+{
+ struct kdbus_bus *b;
+ u64 attach_owner;
+ int ret;
+
+ if (bloom->size < 8 || bloom->size > KDBUS_BUS_BLOOM_MAX_SIZE ||
+ !KDBUS_IS_ALIGNED8(bloom->size) || bloom->n_hash < 1)
+ return ERR_PTR(-EINVAL);
+
+ ret = kdbus_sanitize_attach_flags(pattach_owner ? *pattach_owner : 0,
+ &attach_owner);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ ret = kdbus_verify_uid_prefix(name, domain->user_namespace, uid);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ b = kzalloc(sizeof(*b), GFP_KERNEL);
+ if (!b)
+ return ERR_PTR(-ENOMEM);
+
+ kdbus_node_init(&b->node, KDBUS_NODE_BUS);
+
+ b->node.free_cb = kdbus_bus_free;
+ b->node.release_cb = kdbus_bus_release;
+ b->node.uid = uid;
+ b->node.gid = gid;
+ b->node.mode = S_IRUSR | S_IXUSR;
+
+ if (flags & (KDBUS_MAKE_ACCESS_GROUP | KDBUS_MAKE_ACCESS_WORLD))
+ b->node.mode |= S_IRGRP | S_IXGRP;
+ if (flags & KDBUS_MAKE_ACCESS_WORLD)
+ b->node.mode |= S_IROTH | S_IXOTH;
+
+ b->id = atomic64_inc_return(&domain->last_id);
+ b->bus_flags = flags;
+ b->attach_flags_owner = attach_owner;
+ generate_random_uuid(b->id128);
+ b->bloom = *bloom;
+ b->domain = kdbus_domain_ref(domain);
+
+ kdbus_policy_db_init(&b->policy_db);
+
+ init_rwsem(&b->conn_rwlock);
+ hash_init(b->conn_hash);
+ INIT_LIST_HEAD(&b->monitors_list);
+
+ INIT_LIST_HEAD(&b->notify_list);
+ spin_lock_init(&b->notify_lock);
+ mutex_init(&b->notify_flush_lock);
+
+ ret = kdbus_node_link(&b->node, &domain->node, name);
+ if (ret < 0)
+ goto exit_unref;
+
+ /* cache the metadata/credentials of the creator */
+ b->creator_meta = kdbus_meta_proc_new();
+ if (IS_ERR(b->creator_meta)) {
+ ret = PTR_ERR(b->creator_meta);
+ b->creator_meta = NULL;
+ goto exit_unref;
+ }
+
+ ret = kdbus_meta_proc_collect(b->creator_meta,
+ KDBUS_ATTACH_CREDS |
+ KDBUS_ATTACH_PIDS |
+ KDBUS_ATTACH_AUXGROUPS |
+ KDBUS_ATTACH_TID_COMM |
+ KDBUS_ATTACH_PID_COMM |
+ KDBUS_ATTACH_EXE |
+ KDBUS_ATTACH_CMDLINE |
+ KDBUS_ATTACH_CGROUP |
+ KDBUS_ATTACH_CAPS |
+ KDBUS_ATTACH_SECLABEL |
+ KDBUS_ATTACH_AUDIT);
+ if (ret < 0)
+ goto exit_unref;
+
+ b->name_registry = kdbus_name_registry_new();
+ if (IS_ERR(b->name_registry)) {
+ ret = PTR_ERR(b->name_registry);
+ b->name_registry = NULL;
+ goto exit_unref;
+ }
+
+ /*
+ * Bus-limits of the creator are accounted on its real UID, just like
+ * all other per-user limits.
+ */
+ b->creator = kdbus_user_lookup(domain, current_uid());
+ if (IS_ERR(b->creator)) {
+ ret = PTR_ERR(b->creator);
+ b->creator = NULL;
+ goto exit_unref;
+ }
+
+ return b;
+
+exit_unref:
+ kdbus_node_deactivate(&b->node);
+ kdbus_node_unref(&b->node);
+ return ERR_PTR(ret);
+}
+
+/**
+ * kdbus_bus_ref() - increase the reference counter of a kdbus_bus
+ * @bus: The bus to reference
+ *
+ * Every user of a bus, except for its creator, must add a reference to the
+ * kdbus_bus using this function.
+ *
+ * Return: the bus itself
+ */
+struct kdbus_bus *kdbus_bus_ref(struct kdbus_bus *bus)
+{
+ if (bus)
+ kdbus_node_ref(&bus->node);
+ return bus;
+}
+
+/**
+ * kdbus_bus_unref() - decrease the reference counter of a kdbus_bus
+ * @bus: The bus to unref
+ *
+ * Release a reference. If the reference count drops to 0, the bus will be
+ * freed.
+ *
+ * Return: NULL
+ */
+struct kdbus_bus *kdbus_bus_unref(struct kdbus_bus *bus)
+{
+ if (bus)
+ kdbus_node_unref(&bus->node);
+ return NULL;
+}
+
+/**
+ * kdbus_bus_find_conn_by_id() - find a connection with a given id
+ * @bus: The bus to look for the connection
+ * @id: The 64-bit connection id
+ *
+ * Looks up a connection with a given id. The returned connection
+ * is ref'ed, and needs to be unref'ed by the user. Returns NULL if
+ * the connection can't be found.
+ */
+struct kdbus_conn *kdbus_bus_find_conn_by_id(struct kdbus_bus *bus, u64 id)
+{
+ struct kdbus_conn *conn, *found = NULL;
+
+ down_read(&bus->conn_rwlock);
+ hash_for_each_possible(bus->conn_hash, conn, hentry, id)
+ if (conn->id == id) {
+ found = kdbus_conn_ref(conn);
+ break;
+ }
+ up_read(&bus->conn_rwlock);
+
+ return found;
+}
+
+/**
+ * kdbus_bus_broadcast() - send a message to all subscribed connections
+ * @bus: The bus the connections are connected to
+ * @conn_src: The source connection, may be %NULL for kernel notifications
+ * @staging: Staging object containing the message to send
+ *
+ * Send message to all connections that are currently active on the bus.
+ * Connections must still have matches installed in order to let the message
+ * pass.
+ *
+ * The caller must hold the name-registry lock of @bus.
+ */
+void kdbus_bus_broadcast(struct kdbus_bus *bus,
+ struct kdbus_conn *conn_src,
+ struct kdbus_staging *staging)
+{
+ struct kdbus_conn *conn_dst;
+ unsigned int i;
+ int ret;
+
+ lockdep_assert_held(&bus->name_registry->rwlock);
+
+ /*
+ * Make sure broadcast are queued on monitors before we send it out to
+ * anyone else. Otherwise, connections might react to broadcasts before
+ * the monitor gets the broadcast queued. In the worst case, the
+ * monitor sees a reaction to the broadcast before the broadcast itself.
+ * We don't give ordering guarantees across connections (and monitors
+ * can re-construct order via sequence numbers), but we should at least
+ * try to avoid re-ordering for monitors.
+ */
+ kdbus_bus_eavesdrop(bus, conn_src, staging);
+
+ down_read(&bus->conn_rwlock);
+ hash_for_each(bus->conn_hash, i, conn_dst, hentry) {
+ if (!kdbus_conn_is_ordinary(conn_dst))
+ continue;
+
+ /*
+ * Check if there is a match for the kmsg object in
+ * the destination connection match db
+ */
+ if (!kdbus_match_db_match_msg(conn_dst->match_db, conn_src,
+ staging))
+ continue;
+
+ if (conn_src) {
+ /*
+ * Anyone can send broadcasts, as they have no
+ * destination. But a receiver needs TALK access to
+ * the sender in order to receive broadcasts.
+ */
+ if (!kdbus_conn_policy_talk(conn_dst, NULL, conn_src))
+ continue;
+ } else {
+ /*
+ * Check if there is a policy db that prevents the
+ * destination connection from receiving this kernel
+ * notification
+ */
+ if (!kdbus_conn_policy_see_notification(conn_dst, NULL,
+ staging->msg))
+ continue;
+ }
+
+ ret = kdbus_conn_entry_insert(conn_src, conn_dst, staging,
+ NULL, NULL);
+ if (ret < 0)
+ kdbus_conn_lost_message(conn_dst);
+ }
+ up_read(&bus->conn_rwlock);
+}
+
+/**
+ * kdbus_bus_eavesdrop() - send a message to all subscribed monitors
+ * @bus: The bus the monitors are connected to
+ * @conn_src: The source connection, may be %NULL for kernel notifications
+ * @staging: Staging object containing the message to send
+ *
+ * Send message to all monitors that are currently active on the bus. Monitors
+ * must still have matches installed in order to let the message pass.
+ *
+ * The caller must hold the name-registry lock of @bus.
+ */
+void kdbus_bus_eavesdrop(struct kdbus_bus *bus,
+ struct kdbus_conn *conn_src,
+ struct kdbus_staging *staging)
+{
+ struct kdbus_conn *conn_dst;
+ int ret;
+
+ /*
+ * Monitor connections get all messages; ignore possible errors
+ * when sending messages to monitor connections.
+ */
+
+ lockdep_assert_held(&bus->name_registry->rwlock);
+
+ down_read(&bus->conn_rwlock);
+ list_for_each_entry(conn_dst, &bus->monitors_list, monitor_entry) {
+ ret = kdbus_conn_entry_insert(conn_src, conn_dst, staging,
+ NULL, NULL);
+ if (ret < 0)
+ kdbus_conn_lost_message(conn_dst);
+ }
+ up_read(&bus->conn_rwlock);
+}
+
+/**
+ * kdbus_cmd_bus_make() - handle KDBUS_CMD_BUS_MAKE
+ * @domain: domain to operate on
+ * @argp: command payload
+ *
+ * Return: NULL or newly created bus on success, ERR_PTR on failure.
+ */
+struct kdbus_bus *kdbus_cmd_bus_make(struct kdbus_domain *domain,
+ void __user *argp)
+{
+ struct kdbus_bus *bus = NULL;
+ struct kdbus_cmd *cmd;
+ struct kdbus_ep *ep = NULL;
+ int ret;
+
+ struct kdbus_arg argv[] = {
+ { .type = KDBUS_ITEM_NEGOTIATE },
+ { .type = KDBUS_ITEM_MAKE_NAME, .mandatory = true },
+ { .type = KDBUS_ITEM_BLOOM_PARAMETER, .mandatory = true },
+ { .type = KDBUS_ITEM_ATTACH_FLAGS_SEND },
+ };
+ struct kdbus_args args = {
+ .allowed_flags = KDBUS_FLAG_NEGOTIATE |
+ KDBUS_MAKE_ACCESS_GROUP |
+ KDBUS_MAKE_ACCESS_WORLD,
+ .argv = argv,
+ .argc = ARRAY_SIZE(argv),
+ };
+
+ ret = kdbus_args_parse(&args, argp, &cmd);
+ if (ret < 0)
+ return ERR_PTR(ret);
+ if (ret > 0)
+ return NULL;
+
+ bus = kdbus_bus_new(domain,
+ argv[1].item->str, &argv[2].item->bloom_parameter,
+ argv[3].item ? argv[3].item->data64 : NULL,
+ cmd->flags, current_euid(), current_egid());
+ if (IS_ERR(bus)) {
+ ret = PTR_ERR(bus);
+ bus = NULL;
+ goto exit;
+ }
+
+ if (atomic_inc_return(&bus->creator->buses) > KDBUS_USER_MAX_BUSES) {
+ atomic_dec(&bus->creator->buses);
+ ret = -EMFILE;
+ goto exit;
+ }
+
+ if (!kdbus_node_activate(&bus->node)) {
+ atomic_dec(&bus->creator->buses);
+ ret = -ESHUTDOWN;
+ goto exit;
+ }
+
+ ep = kdbus_ep_new(bus, "bus", cmd->flags, bus->node.uid, bus->node.gid,
+ false);
+ if (IS_ERR(ep)) {
+ ret = PTR_ERR(ep);
+ ep = NULL;
+ goto exit;
+ }
+
+ if (!kdbus_node_activate(&ep->node)) {
+ ret = -ESHUTDOWN;
+ goto exit;
+ }
+
+ /*
+ * Drop our own reference, effectively causing the endpoint to be
+ * deactivated and released when the parent bus is.
+ */
+ ep = kdbus_ep_unref(ep);
+
+exit:
+ ret = kdbus_args_clear(&args, ret);
+ if (ret < 0) {
+ if (ep) {
+ kdbus_node_deactivate(&ep->node);
+ kdbus_ep_unref(ep);
+ }
+ if (bus) {
+ kdbus_node_deactivate(&bus->node);
+ kdbus_bus_unref(bus);
+ }
+ return ERR_PTR(ret);
+ }
+ return bus;
+}
+
+/**
+ * kdbus_cmd_bus_creator_info() - handle KDBUS_CMD_BUS_CREATOR_INFO
+ * @conn: connection to operate on
+ * @argp: command payload
+ *
+ * Return: >=0 on success, negative error code on failure.
+ */
+int kdbus_cmd_bus_creator_info(struct kdbus_conn *conn, void __user *argp)
+{
+ struct kdbus_cmd_info *cmd;
+ struct kdbus_bus *bus = conn->ep->bus;
+ struct kdbus_pool_slice *slice = NULL;
+ struct kdbus_item *meta_items = NULL;
+ struct kdbus_item_header item_hdr;
+ struct kdbus_info info = {};
+ size_t meta_size, name_len, cnt = 0;
+ struct kvec kvec[6];
+ u64 attach_flags, size = 0;
+ int ret;
+
+ struct kdbus_arg argv[] = {
+ { .type = KDBUS_ITEM_NEGOTIATE },
+ };
+ struct kdbus_args args = {
+ .allowed_flags = KDBUS_FLAG_NEGOTIATE,
+ .argv = argv,
+ .argc = ARRAY_SIZE(argv),
+ };
+
+ ret = kdbus_args_parse(&args, argp, &cmd);
+ if (ret != 0)
+ return ret;
+
+ ret = kdbus_sanitize_attach_flags(cmd->attach_flags, &attach_flags);
+ if (ret < 0)
+ goto exit;
+
+ attach_flags &= bus->attach_flags_owner;
+
+ ret = kdbus_meta_emit(bus->creator_meta, NULL, NULL, conn,
+ attach_flags, &meta_items, &meta_size);
+ if (ret < 0)
+ goto exit;
+
+ name_len = strlen(bus->node.name) + 1;
+ info.id = bus->id;
+ info.flags = bus->bus_flags;
+ item_hdr.type = KDBUS_ITEM_MAKE_NAME;
+ item_hdr.size = KDBUS_ITEM_HEADER_SIZE + name_len;
+
+ kdbus_kvec_set(&kvec[cnt++], &info, sizeof(info), &size);
+ kdbus_kvec_set(&kvec[cnt++], &item_hdr, sizeof(item_hdr), &size);
+ kdbus_kvec_set(&kvec[cnt++], bus->node.name, name_len, &size);
+ cnt += !!kdbus_kvec_pad(&kvec[cnt], &size);
+ if (meta_size > 0) {
+ kdbus_kvec_set(&kvec[cnt++], meta_items, meta_size, &size);
+ cnt += !!kdbus_kvec_pad(&kvec[cnt], &size);
+ }
+
+ info.size = size;
+
+ slice = kdbus_pool_slice_alloc(conn->pool, size, false);
+ if (IS_ERR(slice)) {
+ ret = PTR_ERR(slice);
+ slice = NULL;
+ goto exit;
+ }
+
+ ret = kdbus_pool_slice_copy_kvec(slice, 0, kvec, cnt, size);
+ if (ret < 0)
+ goto exit;
+
+ kdbus_pool_slice_publish(slice, &cmd->offset, &cmd->info_size);
+
+ if (kdbus_member_set_user(&cmd->offset, argp, typeof(*cmd), offset) ||
+ kdbus_member_set_user(&cmd->info_size, argp,
+ typeof(*cmd), info_size))
+ ret = -EFAULT;
+
+exit:
+ kdbus_pool_slice_release(slice);
+ kfree(meta_items);
+ return kdbus_args_clear(&args, ret);
+}