4835 lines
129 KiB
Diff
4835 lines
129 KiB
Diff
From: Daniel Mack <daniel@zonque.org>
|
|
Date: Thu, 11 Sep 2014 18:57:24 +0200
|
|
Subject: [PATCH] kdbus: add connection, queue handling and message validation
|
|
code
|
|
|
|
This patch adds code to create and destroy connections, to validate
|
|
incoming messages and to maintain the queue of messages that are
|
|
associated with a connection.
|
|
|
|
Note that connection and queue have a 1:1 relation, the code is only
|
|
split in two parts for cleaner separation and better readability.
|
|
|
|
Signed-off-by: Daniel Mack <daniel@zonque.org>
|
|
Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
|
|
Signed-off-by: Djalal Harouni <tixxdz@opendz.org>
|
|
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
|
---
|
|
ipc/kdbus/connection.c | 2215 ++++++++++++++++++++++++++++++++++++++++++++++++
|
|
ipc/kdbus/connection.h | 257 ++++++
|
|
ipc/kdbus/item.c | 339 ++++++++
|
|
ipc/kdbus/item.h | 64 ++
|
|
ipc/kdbus/message.c | 616 ++++++++++++++
|
|
ipc/kdbus/message.h | 133 +++
|
|
ipc/kdbus/queue.c | 678 +++++++++++++++
|
|
ipc/kdbus/queue.h | 92 ++
|
|
ipc/kdbus/reply.c | 259 ++++++
|
|
ipc/kdbus/reply.h | 68 ++
|
|
ipc/kdbus/util.h | 2 +-
|
|
11 files changed, 4722 insertions(+), 1 deletion(-)
|
|
create mode 100644 ipc/kdbus/connection.c
|
|
create mode 100644 ipc/kdbus/connection.h
|
|
create mode 100644 ipc/kdbus/item.c
|
|
create mode 100644 ipc/kdbus/item.h
|
|
create mode 100644 ipc/kdbus/message.c
|
|
create mode 100644 ipc/kdbus/message.h
|
|
create mode 100644 ipc/kdbus/queue.c
|
|
create mode 100644 ipc/kdbus/queue.h
|
|
create mode 100644 ipc/kdbus/reply.c
|
|
create mode 100644 ipc/kdbus/reply.h
|
|
|
|
diff --git a/ipc/kdbus/connection.c b/ipc/kdbus/connection.c
|
|
new file mode 100644
|
|
index 000000000000..e554f1a71aa1
|
|
--- /dev/null
|
|
+++ b/ipc/kdbus/connection.c
|
|
@@ -0,0 +1,2215 @@
|
|
+/*
|
|
+ * 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 <tixxdz@opendz.org>
|
|
+ *
|
|
+ * 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/audit.h>
|
|
+#include <linux/file.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/fs_struct.h>
|
|
+#include <linux/hashtable.h>
|
|
+#include <linux/idr.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/math64.h>
|
|
+#include <linux/mm.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/path.h>
|
|
+#include <linux/poll.h>
|
|
+#include <linux/sched.h>
|
|
+#include <linux/shmem_fs.h>
|
|
+#include <linux/sizes.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/syscalls.h>
|
|
+#include <linux/uio.h>
|
|
+
|
|
+#include "bus.h"
|
|
+#include "connection.h"
|
|
+#include "endpoint.h"
|
|
+#include "handle.h"
|
|
+#include "match.h"
|
|
+#include "message.h"
|
|
+#include "metadata.h"
|
|
+#include "names.h"
|
|
+#include "domain.h"
|
|
+#include "item.h"
|
|
+#include "notify.h"
|
|
+#include "policy.h"
|
|
+#include "pool.h"
|
|
+#include "reply.h"
|
|
+#include "util.h"
|
|
+#include "queue.h"
|
|
+
|
|
+#define KDBUS_CONN_ACTIVE_BIAS (INT_MIN + 2)
|
|
+#define KDBUS_CONN_ACTIVE_NEW (INT_MIN + 1)
|
|
+
|
|
+static struct kdbus_conn *kdbus_conn_new(struct kdbus_ep *ep, bool privileged,
|
|
+ struct kdbus_cmd_hello *hello,
|
|
+ const char *name,
|
|
+ const struct kdbus_creds *creds,
|
|
+ const struct kdbus_pids *pids,
|
|
+ const char *seclabel,
|
|
+ const char *conn_description)
|
|
+{
|
|
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
|
+ static struct lock_class_key __key;
|
|
+#endif
|
|
+ struct kdbus_pool_slice *slice = NULL;
|
|
+ struct kdbus_bus *bus = ep->bus;
|
|
+ struct kdbus_conn *conn;
|
|
+ u64 attach_flags_send;
|
|
+ u64 attach_flags_recv;
|
|
+ u64 items_size = 0;
|
|
+ bool is_policy_holder;
|
|
+ bool is_activator;
|
|
+ bool is_monitor;
|
|
+ struct kvec kvec;
|
|
+ int ret;
|
|
+
|
|
+ struct {
|
|
+ u64 size;
|
|
+ u64 type;
|
|
+ struct kdbus_bloom_parameter bloom;
|
|
+ } bloom_item;
|
|
+
|
|
+ is_monitor = hello->flags & KDBUS_HELLO_MONITOR;
|
|
+ is_activator = hello->flags & KDBUS_HELLO_ACTIVATOR;
|
|
+ is_policy_holder = hello->flags & KDBUS_HELLO_POLICY_HOLDER;
|
|
+
|
|
+ if (!hello->pool_size || !IS_ALIGNED(hello->pool_size, PAGE_SIZE))
|
|
+ return ERR_PTR(-EINVAL);
|
|
+ if (is_monitor + is_activator + is_policy_holder > 1)
|
|
+ return ERR_PTR(-EINVAL);
|
|
+ if (name && !is_activator && !is_policy_holder)
|
|
+ return ERR_PTR(-EINVAL);
|
|
+ if (!name && (is_activator || is_policy_holder))
|
|
+ return ERR_PTR(-EINVAL);
|
|
+ if (name && !kdbus_name_is_valid(name, true))
|
|
+ return ERR_PTR(-EINVAL);
|
|
+ if (is_monitor && ep->user)
|
|
+ return ERR_PTR(-EOPNOTSUPP);
|
|
+ if (!privileged && (is_activator || is_policy_holder || is_monitor))
|
|
+ return ERR_PTR(-EPERM);
|
|
+ if ((creds || pids || seclabel) && !privileged)
|
|
+ return ERR_PTR(-EPERM);
|
|
+
|
|
+ ret = kdbus_sanitize_attach_flags(hello->attach_flags_send,
|
|
+ &attach_flags_send);
|
|
+ if (ret < 0)
|
|
+ return ERR_PTR(ret);
|
|
+
|
|
+ ret = kdbus_sanitize_attach_flags(hello->attach_flags_recv,
|
|
+ &attach_flags_recv);
|
|
+ if (ret < 0)
|
|
+ return ERR_PTR(ret);
|
|
+
|
|
+ /* The attach flags must always satisfy the bus requirements. */
|
|
+ if (bus->attach_flags_req & ~attach_flags_send)
|
|
+ return ERR_PTR(-ECONNREFUSED);
|
|
+
|
|
+ conn = kzalloc(sizeof(*conn), GFP_KERNEL);
|
|
+ if (!conn)
|
|
+ return ERR_PTR(-ENOMEM);
|
|
+
|
|
+ kref_init(&conn->kref);
|
|
+ atomic_set(&conn->active, KDBUS_CONN_ACTIVE_NEW);
|
|
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
|
+ lockdep_init_map(&conn->dep_map, "s_active", &__key, 0);
|
|
+#endif
|
|
+ mutex_init(&conn->lock);
|
|
+ INIT_LIST_HEAD(&conn->names_list);
|
|
+ INIT_LIST_HEAD(&conn->names_queue_list);
|
|
+ INIT_LIST_HEAD(&conn->reply_list);
|
|
+ atomic_set(&conn->name_count, 0);
|
|
+ atomic_set(&conn->request_count, 0);
|
|
+ atomic_set(&conn->lost_count, 0);
|
|
+ INIT_DELAYED_WORK(&conn->work, kdbus_reply_list_scan_work);
|
|
+ conn->cred = get_current_cred();
|
|
+ init_waitqueue_head(&conn->wait);
|
|
+ kdbus_queue_init(&conn->queue);
|
|
+ conn->privileged = privileged;
|
|
+ conn->ep = kdbus_ep_ref(ep);
|
|
+ conn->id = atomic64_inc_return(&bus->domain->last_id);
|
|
+ conn->flags = hello->flags;
|
|
+ atomic64_set(&conn->attach_flags_send, attach_flags_send);
|
|
+ atomic64_set(&conn->attach_flags_recv, attach_flags_recv);
|
|
+ INIT_LIST_HEAD(&conn->monitor_entry);
|
|
+
|
|
+ if (conn_description) {
|
|
+ conn->description = kstrdup(conn_description, GFP_KERNEL);
|
|
+ if (!conn->description) {
|
|
+ ret = -ENOMEM;
|
|
+ goto exit_unref;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ conn->pool = kdbus_pool_new(conn->description, hello->pool_size);
|
|
+ if (IS_ERR(conn->pool)) {
|
|
+ ret = PTR_ERR(conn->pool);
|
|
+ conn->pool = NULL;
|
|
+ goto exit_unref;
|
|
+ }
|
|
+
|
|
+ conn->match_db = kdbus_match_db_new();
|
|
+ if (IS_ERR(conn->match_db)) {
|
|
+ ret = PTR_ERR(conn->match_db);
|
|
+ conn->match_db = NULL;
|
|
+ goto exit_unref;
|
|
+ }
|
|
+
|
|
+ /* return properties of this connection to the caller */
|
|
+ hello->bus_flags = bus->bus_flags;
|
|
+ hello->id = conn->id;
|
|
+
|
|
+ BUILD_BUG_ON(sizeof(bus->id128) != sizeof(hello->id128));
|
|
+ memcpy(hello->id128, bus->id128, sizeof(hello->id128));
|
|
+
|
|
+ conn->meta = kdbus_meta_proc_new();
|
|
+ if (IS_ERR(conn->meta)) {
|
|
+ ret = PTR_ERR(conn->meta);
|
|
+ conn->meta = NULL;
|
|
+ goto exit_unref;
|
|
+ }
|
|
+
|
|
+ /* privileged processes can impersonate somebody else */
|
|
+ if (creds || pids || seclabel) {
|
|
+ ret = kdbus_meta_proc_fake(conn->meta, creds, pids, seclabel);
|
|
+ if (ret < 0)
|
|
+ goto exit_unref;
|
|
+
|
|
+ conn->faked_meta = true;
|
|
+ } else {
|
|
+ ret = kdbus_meta_proc_collect(conn->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;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Account the connection against the current user (UID), or for
|
|
+ * custom endpoints use the anonymous user assigned to the endpoint.
|
|
+ * Note that limits are always accounted against the real UID, not
|
|
+ * the effective UID (cred->user always points to the accounting of
|
|
+ * cred->uid, not cred->euid).
|
|
+ */
|
|
+ if (ep->user) {
|
|
+ conn->user = kdbus_user_ref(ep->user);
|
|
+ } else {
|
|
+ conn->user = kdbus_user_lookup(ep->bus->domain, current_uid());
|
|
+ if (IS_ERR(conn->user)) {
|
|
+ ret = PTR_ERR(conn->user);
|
|
+ conn->user = NULL;
|
|
+ goto exit_unref;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (atomic_inc_return(&conn->user->connections) > KDBUS_USER_MAX_CONN) {
|
|
+ /* decremented by destructor as conn->user is valid */
|
|
+ ret = -EMFILE;
|
|
+ goto exit_unref;
|
|
+ }
|
|
+
|
|
+ bloom_item.size = sizeof(bloom_item);
|
|
+ bloom_item.type = KDBUS_ITEM_BLOOM_PARAMETER;
|
|
+ bloom_item.bloom = bus->bloom;
|
|
+ kdbus_kvec_set(&kvec, &bloom_item, bloom_item.size, &items_size);
|
|
+
|
|
+ slice = kdbus_pool_slice_alloc(conn->pool, items_size, false);
|
|
+ if (IS_ERR(slice)) {
|
|
+ ret = PTR_ERR(slice);
|
|
+ slice = NULL;
|
|
+ goto exit_unref;
|
|
+ }
|
|
+
|
|
+ ret = kdbus_pool_slice_copy_kvec(slice, 0, &kvec, 1, items_size);
|
|
+ if (ret < 0)
|
|
+ goto exit_unref;
|
|
+
|
|
+ kdbus_pool_slice_publish(slice, &hello->offset, &hello->items_size);
|
|
+ kdbus_pool_slice_release(slice);
|
|
+
|
|
+ return conn;
|
|
+
|
|
+exit_unref:
|
|
+ kdbus_pool_slice_release(slice);
|
|
+ kdbus_conn_unref(conn);
|
|
+ return ERR_PTR(ret);
|
|
+}
|
|
+
|
|
+static void __kdbus_conn_free(struct kref *kref)
|
|
+{
|
|
+ struct kdbus_conn *conn = container_of(kref, struct kdbus_conn, kref);
|
|
+
|
|
+ WARN_ON(kdbus_conn_active(conn));
|
|
+ WARN_ON(delayed_work_pending(&conn->work));
|
|
+ WARN_ON(!list_empty(&conn->queue.msg_list));
|
|
+ WARN_ON(!list_empty(&conn->names_list));
|
|
+ WARN_ON(!list_empty(&conn->names_queue_list));
|
|
+ WARN_ON(!list_empty(&conn->reply_list));
|
|
+
|
|
+ if (conn->user) {
|
|
+ atomic_dec(&conn->user->connections);
|
|
+ kdbus_user_unref(conn->user);
|
|
+ }
|
|
+
|
|
+ kdbus_meta_proc_unref(conn->meta);
|
|
+ kdbus_match_db_free(conn->match_db);
|
|
+ kdbus_pool_free(conn->pool);
|
|
+ kdbus_ep_unref(conn->ep);
|
|
+ put_cred(conn->cred);
|
|
+ kfree(conn->description);
|
|
+ kfree(conn->quota);
|
|
+ kfree(conn);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_ref() - take a connection reference
|
|
+ * @conn: Connection, may be %NULL
|
|
+ *
|
|
+ * Return: the connection itself
|
|
+ */
|
|
+struct kdbus_conn *kdbus_conn_ref(struct kdbus_conn *conn)
|
|
+{
|
|
+ if (conn)
|
|
+ kref_get(&conn->kref);
|
|
+ return conn;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_unref() - drop a connection reference
|
|
+ * @conn: Connection (may be NULL)
|
|
+ *
|
|
+ * When the last reference is dropped, the connection's internal structure
|
|
+ * is freed.
|
|
+ *
|
|
+ * Return: NULL
|
|
+ */
|
|
+struct kdbus_conn *kdbus_conn_unref(struct kdbus_conn *conn)
|
|
+{
|
|
+ if (conn)
|
|
+ kref_put(&conn->kref, __kdbus_conn_free);
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_active() - connection is not disconnected
|
|
+ * @conn: Connection to check
|
|
+ *
|
|
+ * Return true if the connection was not disconnected, yet. Note that a
|
|
+ * connection might be disconnected asynchronously, unless you hold the
|
|
+ * connection lock. If that's not suitable for you, see kdbus_conn_acquire() to
|
|
+ * suppress connection shutdown for a short period.
|
|
+ *
|
|
+ * Return: true if the connection is still active
|
|
+ */
|
|
+bool kdbus_conn_active(const struct kdbus_conn *conn)
|
|
+{
|
|
+ return atomic_read(&conn->active) >= 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_acquire() - acquire an active connection reference
|
|
+ * @conn: Connection
|
|
+ *
|
|
+ * Users can close a connection via KDBUS_BYEBYE (or by destroying the
|
|
+ * endpoint/bus/...) at any time. Whenever this happens, we should deny any
|
|
+ * user-visible action on this connection and signal ECONNRESET instead.
|
|
+ * To avoid testing for connection availability everytime you take the
|
|
+ * connection-lock, you can acquire a connection for short periods.
|
|
+ *
|
|
+ * By calling kdbus_conn_acquire(), you gain an "active reference" to the
|
|
+ * connection. You must also hold a regular reference at any time! As long as
|
|
+ * you hold the active-ref, the connection will not be shut down. However, if
|
|
+ * the connection was shut down, you can never acquire an active-ref again.
|
|
+ *
|
|
+ * kdbus_conn_disconnect() disables the connection and then waits for all active
|
|
+ * references to be dropped. It will also wake up any pending operation.
|
|
+ * However, you must not sleep for an indefinite period while holding an
|
|
+ * active-reference. Otherwise, kdbus_conn_disconnect() might stall. If you need
|
|
+ * to sleep for an indefinite period, either release the reference and try to
|
|
+ * acquire it again after waking up, or make kdbus_conn_disconnect() wake up
|
|
+ * your wait-queue.
|
|
+ *
|
|
+ * Return: 0 on success, negative error code on failure.
|
|
+ */
|
|
+int kdbus_conn_acquire(struct kdbus_conn *conn)
|
|
+{
|
|
+ if (!atomic_inc_unless_negative(&conn->active))
|
|
+ return -ECONNRESET;
|
|
+
|
|
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
|
+ rwsem_acquire_read(&conn->dep_map, 0, 1, _RET_IP_);
|
|
+#endif
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_release() - release an active connection reference
|
|
+ * @conn: Connection
|
|
+ *
|
|
+ * This releases an active reference that has been acquired via
|
|
+ * kdbus_conn_acquire(). If the connection was already disabled and this is the
|
|
+ * last active-ref that is dropped, the disconnect-waiter will be woken up and
|
|
+ * properly close the connection.
|
|
+ */
|
|
+void kdbus_conn_release(struct kdbus_conn *conn)
|
|
+{
|
|
+ int v;
|
|
+
|
|
+ if (!conn)
|
|
+ return;
|
|
+
|
|
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
|
+ rwsem_release(&conn->dep_map, 1, _RET_IP_);
|
|
+#endif
|
|
+
|
|
+ v = atomic_dec_return(&conn->active);
|
|
+ if (v != KDBUS_CONN_ACTIVE_BIAS)
|
|
+ return;
|
|
+
|
|
+ wake_up_all(&conn->wait);
|
|
+}
|
|
+
|
|
+static int kdbus_conn_connect(struct kdbus_conn *conn, const char *name)
|
|
+{
|
|
+ struct kdbus_ep *ep = conn->ep;
|
|
+ struct kdbus_bus *bus = ep->bus;
|
|
+ int ret;
|
|
+
|
|
+ if (WARN_ON(atomic_read(&conn->active) != KDBUS_CONN_ACTIVE_NEW))
|
|
+ return -EALREADY;
|
|
+
|
|
+ /* make sure the ep-node is active while we add our connection */
|
|
+ if (!kdbus_node_acquire(&ep->node))
|
|
+ return -ESHUTDOWN;
|
|
+
|
|
+ /* lock order: domain -> bus -> ep -> names -> conn */
|
|
+ mutex_lock(&ep->lock);
|
|
+ down_write(&bus->conn_rwlock);
|
|
+
|
|
+ /* link into monitor list */
|
|
+ if (kdbus_conn_is_monitor(conn))
|
|
+ list_add_tail(&conn->monitor_entry, &bus->monitors_list);
|
|
+
|
|
+ /* link into bus and endpoint */
|
|
+ list_add_tail(&conn->ep_entry, &ep->conn_list);
|
|
+ hash_add(bus->conn_hash, &conn->hentry, conn->id);
|
|
+
|
|
+ /* enable lookups and acquire active ref */
|
|
+ atomic_set(&conn->active, 1);
|
|
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
|
+ rwsem_acquire_read(&conn->dep_map, 0, 1, _RET_IP_);
|
|
+#endif
|
|
+
|
|
+ up_write(&bus->conn_rwlock);
|
|
+ mutex_unlock(&ep->lock);
|
|
+
|
|
+ kdbus_node_release(&ep->node);
|
|
+
|
|
+ /*
|
|
+ * Notify subscribers about the new active connection, unless it is
|
|
+ * a monitor. Monitors are invisible on the bus, can't be addressed
|
|
+ * directly, and won't cause any notifications.
|
|
+ */
|
|
+ if (!kdbus_conn_is_monitor(conn)) {
|
|
+ ret = kdbus_notify_id_change(conn->ep->bus, KDBUS_ITEM_ID_ADD,
|
|
+ conn->id, conn->flags);
|
|
+ if (ret < 0)
|
|
+ goto exit_disconnect;
|
|
+ }
|
|
+
|
|
+ if (kdbus_conn_is_activator(conn)) {
|
|
+ u64 flags = KDBUS_NAME_ACTIVATOR;
|
|
+
|
|
+ if (WARN_ON(!name)) {
|
|
+ ret = -EINVAL;
|
|
+ goto exit_disconnect;
|
|
+ }
|
|
+
|
|
+ ret = kdbus_name_acquire(bus->name_registry, conn, name,
|
|
+ flags, NULL);
|
|
+ if (ret < 0)
|
|
+ goto exit_disconnect;
|
|
+ }
|
|
+
|
|
+ kdbus_conn_release(conn);
|
|
+ kdbus_notify_flush(bus);
|
|
+ return 0;
|
|
+
|
|
+exit_disconnect:
|
|
+ kdbus_conn_release(conn);
|
|
+ kdbus_conn_disconnect(conn, false);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_disconnect() - disconnect a connection
|
|
+ * @conn: The connection to disconnect
|
|
+ * @ensure_queue_empty: Flag to indicate if the call should fail in
|
|
+ * case the connection's message list is not
|
|
+ * empty
|
|
+ *
|
|
+ * If @ensure_msg_list_empty is true, and the connection has pending messages,
|
|
+ * -EBUSY is returned.
|
|
+ *
|
|
+ * Return: 0 on success, negative errno on failure
|
|
+ */
|
|
+int kdbus_conn_disconnect(struct kdbus_conn *conn, bool ensure_queue_empty)
|
|
+{
|
|
+ struct kdbus_queue_entry *entry, *tmp;
|
|
+ struct kdbus_bus *bus = conn->ep->bus;
|
|
+ struct kdbus_reply *r, *r_tmp;
|
|
+ struct kdbus_conn *c;
|
|
+ int i, v;
|
|
+
|
|
+ mutex_lock(&conn->lock);
|
|
+ v = atomic_read(&conn->active);
|
|
+ if (v == KDBUS_CONN_ACTIVE_NEW) {
|
|
+ /* was never connected */
|
|
+ mutex_unlock(&conn->lock);
|
|
+ return 0;
|
|
+ }
|
|
+ if (v < 0) {
|
|
+ /* already dead */
|
|
+ mutex_unlock(&conn->lock);
|
|
+ return -ECONNRESET;
|
|
+ }
|
|
+ if (ensure_queue_empty && !list_empty(&conn->queue.msg_list)) {
|
|
+ /* still busy */
|
|
+ mutex_unlock(&conn->lock);
|
|
+ return -EBUSY;
|
|
+ }
|
|
+
|
|
+ atomic_add(KDBUS_CONN_ACTIVE_BIAS, &conn->active);
|
|
+ mutex_unlock(&conn->lock);
|
|
+
|
|
+ wake_up_interruptible(&conn->wait);
|
|
+
|
|
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
|
+ rwsem_acquire(&conn->dep_map, 0, 0, _RET_IP_);
|
|
+ if (atomic_read(&conn->active) != KDBUS_CONN_ACTIVE_BIAS)
|
|
+ lock_contended(&conn->dep_map, _RET_IP_);
|
|
+#endif
|
|
+
|
|
+ wait_event(conn->wait,
|
|
+ atomic_read(&conn->active) == KDBUS_CONN_ACTIVE_BIAS);
|
|
+
|
|
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
|
+ lock_acquired(&conn->dep_map, _RET_IP_);
|
|
+ rwsem_release(&conn->dep_map, 1, _RET_IP_);
|
|
+#endif
|
|
+
|
|
+ cancel_delayed_work_sync(&conn->work);
|
|
+ kdbus_policy_remove_owner(&conn->ep->bus->policy_db, conn);
|
|
+
|
|
+ /* lock order: domain -> bus -> ep -> names -> conn */
|
|
+ mutex_lock(&conn->ep->lock);
|
|
+ down_write(&bus->conn_rwlock);
|
|
+
|
|
+ /* remove from bus and endpoint */
|
|
+ hash_del(&conn->hentry);
|
|
+ list_del(&conn->monitor_entry);
|
|
+ list_del(&conn->ep_entry);
|
|
+
|
|
+ up_write(&bus->conn_rwlock);
|
|
+ mutex_unlock(&conn->ep->lock);
|
|
+
|
|
+ /*
|
|
+ * Remove all names associated with this connection; this possibly
|
|
+ * moves queued messages back to the activator connection.
|
|
+ */
|
|
+ kdbus_name_release_all(bus->name_registry, conn);
|
|
+
|
|
+ /* if we die while other connections wait for our reply, notify them */
|
|
+ mutex_lock(&conn->lock);
|
|
+ list_for_each_entry_safe(entry, tmp, &conn->queue.msg_list, entry) {
|
|
+ if (entry->reply)
|
|
+ kdbus_notify_reply_dead(bus,
|
|
+ entry->reply->reply_dst->id,
|
|
+ entry->reply->cookie);
|
|
+ kdbus_queue_entry_free(entry);
|
|
+ }
|
|
+
|
|
+ list_for_each_entry_safe(r, r_tmp, &conn->reply_list, entry)
|
|
+ kdbus_reply_unlink(r);
|
|
+ mutex_unlock(&conn->lock);
|
|
+
|
|
+ /* lock order: domain -> bus -> ep -> names -> conn */
|
|
+ down_read(&bus->conn_rwlock);
|
|
+ hash_for_each(bus->conn_hash, i, c, hentry) {
|
|
+ mutex_lock(&c->lock);
|
|
+ list_for_each_entry_safe(r, r_tmp, &c->reply_list, entry) {
|
|
+ if (r->reply_src == conn) {
|
|
+ if (r->sync) {
|
|
+ kdbus_sync_reply_wakeup(r, -EPIPE);
|
|
+ kdbus_reply_unlink(r);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ /* send a 'connection dead' notification */
|
|
+ kdbus_notify_reply_dead(bus, c->id, r->cookie);
|
|
+ kdbus_reply_unlink(r);
|
|
+ }
|
|
+ }
|
|
+ mutex_unlock(&c->lock);
|
|
+ }
|
|
+ up_read(&bus->conn_rwlock);
|
|
+
|
|
+ if (!kdbus_conn_is_monitor(conn))
|
|
+ kdbus_notify_id_change(bus, KDBUS_ITEM_ID_REMOVE,
|
|
+ conn->id, conn->flags);
|
|
+
|
|
+ kdbus_notify_flush(bus);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_has_name() - check if a connection owns a name
|
|
+ * @conn: Connection
|
|
+ * @name: Well-know name to check for
|
|
+ *
|
|
+ * The caller must hold the registry lock of conn->ep->bus.
|
|
+ *
|
|
+ * Return: true if the name is currently owned by the connection
|
|
+ */
|
|
+bool kdbus_conn_has_name(struct kdbus_conn *conn, const char *name)
|
|
+{
|
|
+ struct kdbus_name_entry *e;
|
|
+
|
|
+ lockdep_assert_held(&conn->ep->bus->name_registry->rwlock);
|
|
+
|
|
+ list_for_each_entry(e, &conn->names_list, conn_entry)
|
|
+ if (strcmp(e->name, name) == 0)
|
|
+ return true;
|
|
+
|
|
+ return false;
|
|
+}
|
|
+
|
|
+struct kdbus_quota {
|
|
+ uint32_t memory;
|
|
+ uint16_t msgs;
|
|
+ uint8_t fds;
|
|
+};
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_quota_inc() - increase quota accounting
|
|
+ * @c: connection owning the quota tracking
|
|
+ * @u: user to account for (or NULL for kernel accounting)
|
|
+ * @memory: size of memory to account for
|
|
+ * @fds: number of FDs to account for
|
|
+ *
|
|
+ * This call manages the quotas on resource @c. That is, it's used if other
|
|
+ * users want to use the resources of connection @c, which so far only concerns
|
|
+ * the receive queue of the destination.
|
|
+ *
|
|
+ * This increases the quota-accounting for user @u by @memory bytes and @fds
|
|
+ * file descriptors. If the user has already reached the quota limits, this call
|
|
+ * will not do any accounting but return a negative error code indicating the
|
|
+ * failure.
|
|
+ *
|
|
+ * Return: 0 on success, negative error code on failure.
|
|
+ */
|
|
+int kdbus_conn_quota_inc(struct kdbus_conn *c, struct kdbus_user *u,
|
|
+ size_t memory, size_t fds)
|
|
+{
|
|
+ struct kdbus_quota *quota;
|
|
+ size_t available, accounted;
|
|
+ unsigned int id;
|
|
+
|
|
+ /*
|
|
+ * Pool Layout:
|
|
+ * 50% of a pool is always owned by the connection. It is reserved for
|
|
+ * kernel queries, handling received messages and other tasks that are
|
|
+ * under control of the pool owner. The other 50% of the pool are used
|
|
+ * as incoming queue.
|
|
+ * As we optionally support user-space based policies, we need fair
|
|
+ * allocation schemes. Furthermore, resource utilization should be
|
|
+ * maximized, so only minimal resources stay reserved. However, we need
|
|
+ * to adapt to a dynamic number of users, as we cannot know how many
|
|
+ * users will talk to a connection. Therefore, the current allocations
|
|
+ * works like this:
|
|
+ * We limit the number of bytes in a destination's pool per sending
|
|
+ * user. The space available for a user is 33% of the unused pool space
|
|
+ * (whereas the space used by the user itself is also treated as
|
|
+ * 'unused'). This way, we favor users coming first, but keep enough
|
|
+ * pool space available for any following users. Given that messages are
|
|
+ * dequeued in FIFO order, this should balance nicely if the number of
|
|
+ * users grows. At the same time, this algorithm guarantees that the
|
|
+ * space available to a connection is reduced dynamically, the more
|
|
+ * concurrent users talk to a connection.
|
|
+ */
|
|
+
|
|
+ /* per user-accounting is expensive, so we keep state small */
|
|
+ BUILD_BUG_ON(sizeof(quota->memory) != 4);
|
|
+ BUILD_BUG_ON(sizeof(quota->msgs) != 2);
|
|
+ BUILD_BUG_ON(sizeof(quota->fds) != 1);
|
|
+ BUILD_BUG_ON(KDBUS_CONN_MAX_MSGS > U16_MAX);
|
|
+ BUILD_BUG_ON(KDBUS_CONN_MAX_FDS_PER_USER > U8_MAX);
|
|
+
|
|
+ id = u ? u->id : KDBUS_USER_KERNEL_ID;
|
|
+ if (id >= c->n_quota) {
|
|
+ unsigned int users;
|
|
+
|
|
+ users = max(KDBUS_ALIGN8(id) + 8, id);
|
|
+ quota = krealloc(c->quota, users * sizeof(*quota),
|
|
+ GFP_KERNEL | __GFP_ZERO);
|
|
+ if (!quota)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ c->n_quota = users;
|
|
+ c->quota = quota;
|
|
+ }
|
|
+
|
|
+ quota = &c->quota[id];
|
|
+ kdbus_pool_accounted(c->pool, &available, &accounted);
|
|
+
|
|
+ /* half the pool is _always_ reserved for the pool owner */
|
|
+ available /= 2;
|
|
+
|
|
+ /*
|
|
+ * Pool owner slices are un-accounted slices; they can claim more
|
|
+ * than 50% of the queue. However, the slice we're dealing with here
|
|
+ * belong to the incoming queue, hence they are 'accounted' slices
|
|
+ * to which the 50%-limit applies.
|
|
+ */
|
|
+ if (available < accounted)
|
|
+ return -ENOBUFS;
|
|
+
|
|
+ /* 1/3 of the remaining space (including your own memory) */
|
|
+ available = (available - accounted + quota->memory) / 3;
|
|
+
|
|
+ if (available < quota->memory ||
|
|
+ available - quota->memory < memory ||
|
|
+ quota->memory + memory > U32_MAX)
|
|
+ return -ENOBUFS;
|
|
+ if (quota->msgs >= KDBUS_CONN_MAX_MSGS)
|
|
+ return -ENOBUFS;
|
|
+ if (quota->fds + fds < quota->fds ||
|
|
+ quota->fds + fds > KDBUS_CONN_MAX_FDS_PER_USER)
|
|
+ return -EMFILE;
|
|
+
|
|
+ quota->memory += memory;
|
|
+ quota->fds += fds;
|
|
+ ++quota->msgs;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_quota_dec() - decrease quota accounting
|
|
+ * @c: connection owning the quota tracking
|
|
+ * @u: user which was accounted for (or NULL for kernel accounting)
|
|
+ * @memory: size of memory which was accounted for
|
|
+ * @fds: number of FDs which were accounted for
|
|
+ *
|
|
+ * This does the reverse of kdbus_conn_quota_inc(). You have to release any
|
|
+ * accounted resources that you called kdbus_conn_quota_inc() for. However, you
|
|
+ * must not call kdbus_conn_quota_dec() if the accounting failed (that is,
|
|
+ * kdbus_conn_quota_inc() failed).
|
|
+ */
|
|
+void kdbus_conn_quota_dec(struct kdbus_conn *c, struct kdbus_user *u,
|
|
+ size_t memory, size_t fds)
|
|
+{
|
|
+ struct kdbus_quota *quota;
|
|
+ unsigned int id;
|
|
+
|
|
+ id = u ? u->id : KDBUS_USER_KERNEL_ID;
|
|
+ if (WARN_ON(id >= c->n_quota))
|
|
+ return;
|
|
+
|
|
+ quota = &c->quota[id];
|
|
+
|
|
+ if (!WARN_ON(quota->msgs == 0))
|
|
+ --quota->msgs;
|
|
+ if (!WARN_ON(quota->memory < memory))
|
|
+ quota->memory -= memory;
|
|
+ if (!WARN_ON(quota->fds < fds))
|
|
+ quota->fds -= fds;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_lost_message() - handle lost messages
|
|
+ * @c: connection that lost a message
|
|
+ *
|
|
+ * kdbus is reliable. That means, we try hard to never lose messages. However,
|
|
+ * memory is limited, so we cannot rely on transmissions to never fail.
|
|
+ * Therefore, we use quota-limits to let callers know if there unicast message
|
|
+ * cannot be transmitted to a peer. This works fine for unicasts, but for
|
|
+ * broadcasts we cannot make the caller handle the transmission failure.
|
|
+ * Instead, we must let the destination know that it couldn't receive a
|
|
+ * broadcast.
|
|
+ * As this is an unlikely scenario, we keep it simple. A single lost-counter
|
|
+ * remembers the number of lost messages since the last call to RECV. The next
|
|
+ * message retrieval will notify the connection that it lost messages since the
|
|
+ * last message retrieval and thus should resync its state.
|
|
+ */
|
|
+void kdbus_conn_lost_message(struct kdbus_conn *c)
|
|
+{
|
|
+ if (atomic_inc_return(&c->lost_count) == 1)
|
|
+ wake_up_interruptible(&c->wait);
|
|
+}
|
|
+
|
|
+/* Callers should take the conn_dst lock */
|
|
+static struct kdbus_queue_entry *
|
|
+kdbus_conn_entry_make(struct kdbus_conn *conn_dst,
|
|
+ const struct kdbus_kmsg *kmsg,
|
|
+ struct kdbus_user *user)
|
|
+{
|
|
+ struct kdbus_queue_entry *entry;
|
|
+
|
|
+ /* The remote connection was disconnected */
|
|
+ if (!kdbus_conn_active(conn_dst))
|
|
+ return ERR_PTR(-ECONNRESET);
|
|
+
|
|
+ /*
|
|
+ * If the connection does not accept file descriptors but the message
|
|
+ * has some attached, refuse it.
|
|
+ *
|
|
+ * If this is a monitor connection, accept the message. In that
|
|
+ * case, all file descriptors will be set to -1 at receive time.
|
|
+ */
|
|
+ if (!kdbus_conn_is_monitor(conn_dst) &&
|
|
+ !(conn_dst->flags & KDBUS_HELLO_ACCEPT_FD) &&
|
|
+ kmsg->res && kmsg->res->fds_count > 0)
|
|
+ return ERR_PTR(-ECOMM);
|
|
+
|
|
+ entry = kdbus_queue_entry_new(conn_dst, kmsg, user);
|
|
+ if (IS_ERR(entry))
|
|
+ return entry;
|
|
+
|
|
+ return entry;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Synchronously responding to a message, allocate a queue entry
|
|
+ * and attach it to the reply tracking object.
|
|
+ * The connection's queue will never get to see it.
|
|
+ */
|
|
+static int kdbus_conn_entry_sync_attach(struct kdbus_conn *conn_dst,
|
|
+ const struct kdbus_kmsg *kmsg,
|
|
+ struct kdbus_reply *reply_wake)
|
|
+{
|
|
+ struct kdbus_queue_entry *entry;
|
|
+ int remote_ret;
|
|
+ int ret = 0;
|
|
+
|
|
+ mutex_lock(&reply_wake->reply_dst->lock);
|
|
+
|
|
+ /*
|
|
+ * If we are still waiting then proceed, allocate a queue
|
|
+ * entry and attach it to the reply object
|
|
+ */
|
|
+ if (reply_wake->waiting) {
|
|
+ entry = kdbus_conn_entry_make(conn_dst, kmsg,
|
|
+ reply_wake->reply_src->user);
|
|
+ if (IS_ERR(entry))
|
|
+ ret = PTR_ERR(entry);
|
|
+ else
|
|
+ /* Attach the entry to the reply object */
|
|
+ reply_wake->queue_entry = entry;
|
|
+ } else {
|
|
+ ret = -ECONNRESET;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Update the reply object and wake up remote peer only
|
|
+ * on appropriate return codes
|
|
+ *
|
|
+ * * -ECOMM: if the replying connection failed with -ECOMM
|
|
+ * then wakeup remote peer with -EREMOTEIO
|
|
+ *
|
|
+ * We do this to differenciate between -ECOMM errors
|
|
+ * from the original sender perspective:
|
|
+ * -ECOMM error during the sync send and
|
|
+ * -ECOMM error during the sync reply, this last
|
|
+ * one is rewritten to -EREMOTEIO
|
|
+ *
|
|
+ * * Wake up on all other return codes.
|
|
+ */
|
|
+ remote_ret = ret;
|
|
+
|
|
+ if (ret == -ECOMM)
|
|
+ remote_ret = -EREMOTEIO;
|
|
+
|
|
+ kdbus_sync_reply_wakeup(reply_wake, remote_ret);
|
|
+ kdbus_reply_unlink(reply_wake);
|
|
+ mutex_unlock(&reply_wake->reply_dst->lock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_entry_insert() - enqueue a message into the receiver's pool
|
|
+ * @conn_src: The sending connection
|
|
+ * @conn_dst: The connection to queue into
|
|
+ * @kmsg: The kmsg to queue
|
|
+ * @reply: The reply tracker to attach to the queue entry
|
|
+ *
|
|
+ * Return: 0 on success. negative error otherwise.
|
|
+ */
|
|
+int kdbus_conn_entry_insert(struct kdbus_conn *conn_src,
|
|
+ struct kdbus_conn *conn_dst,
|
|
+ const struct kdbus_kmsg *kmsg,
|
|
+ struct kdbus_reply *reply)
|
|
+{
|
|
+ struct kdbus_queue_entry *entry;
|
|
+ int ret;
|
|
+
|
|
+ kdbus_conn_lock2(conn_src, conn_dst);
|
|
+
|
|
+ entry = kdbus_conn_entry_make(conn_dst, kmsg,
|
|
+ conn_src ? conn_src->user : NULL);
|
|
+ if (IS_ERR(entry)) {
|
|
+ ret = PTR_ERR(entry);
|
|
+ goto exit_unlock;
|
|
+ }
|
|
+
|
|
+ if (reply) {
|
|
+ kdbus_reply_link(reply);
|
|
+ if (!reply->sync)
|
|
+ schedule_delayed_work(&conn_src->work, 0);
|
|
+ }
|
|
+
|
|
+ kdbus_queue_entry_enqueue(entry, reply);
|
|
+ wake_up_interruptible(&conn_dst->wait);
|
|
+
|
|
+ ret = 0;
|
|
+
|
|
+exit_unlock:
|
|
+ kdbus_conn_unlock2(conn_src, conn_dst);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int kdbus_conn_wait_reply(struct kdbus_conn *conn_src,
|
|
+ struct kdbus_cmd_send *cmd_send,
|
|
+ struct file *ioctl_file,
|
|
+ struct file *cancel_fd,
|
|
+ struct kdbus_reply *reply_wait,
|
|
+ ktime_t expire)
|
|
+{
|
|
+ struct kdbus_queue_entry *entry;
|
|
+ struct poll_wqueues pwq = {};
|
|
+ int ret;
|
|
+
|
|
+ if (WARN_ON(!reply_wait))
|
|
+ return -EIO;
|
|
+
|
|
+ /*
|
|
+ * Block until the reply arrives. reply_wait is left untouched
|
|
+ * by the timeout scans that might be conducted for other,
|
|
+ * asynchronous replies of conn_src.
|
|
+ */
|
|
+
|
|
+ poll_initwait(&pwq);
|
|
+ poll_wait(ioctl_file, &conn_src->wait, &pwq.pt);
|
|
+
|
|
+ for (;;) {
|
|
+ /*
|
|
+ * Any of the following conditions will stop our synchronously
|
|
+ * blocking SEND command:
|
|
+ *
|
|
+ * a) The origin sender closed its connection
|
|
+ * b) The remote peer answered, setting reply_wait->waiting = 0
|
|
+ * c) The cancel FD was written to
|
|
+ * d) A signal was received
|
|
+ * e) The specified timeout was reached, and none of the above
|
|
+ * conditions kicked in.
|
|
+ */
|
|
+
|
|
+ /*
|
|
+ * We have already acquired an active reference when
|
|
+ * entering here, but another thread may call
|
|
+ * KDBUS_CMD_BYEBYE which does not acquire an active
|
|
+ * reference, therefore kdbus_conn_disconnect() will
|
|
+ * not wait for us.
|
|
+ */
|
|
+ if (!kdbus_conn_active(conn_src)) {
|
|
+ ret = -ECONNRESET;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * After the replying peer unset the waiting variable
|
|
+ * it will wake up us.
|
|
+ */
|
|
+ if (!reply_wait->waiting) {
|
|
+ ret = reply_wait->err;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (cancel_fd) {
|
|
+ unsigned int r;
|
|
+
|
|
+ r = cancel_fd->f_op->poll(cancel_fd, &pwq.pt);
|
|
+ if (r & POLLIN) {
|
|
+ ret = -ECANCELED;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (signal_pending(current)) {
|
|
+ ret = -EINTR;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (!poll_schedule_timeout(&pwq, TASK_INTERRUPTIBLE,
|
|
+ &expire, 0)) {
|
|
+ ret = -ETIMEDOUT;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Reset the poll worker func, so the waitqueues are not
|
|
+ * added to the poll table again. We just reuse what we've
|
|
+ * collected earlier for further iterations.
|
|
+ */
|
|
+ init_poll_funcptr(&pwq.pt, NULL);
|
|
+ }
|
|
+
|
|
+ poll_freewait(&pwq);
|
|
+
|
|
+ if (ret == -EINTR) {
|
|
+ /*
|
|
+ * Interrupted system call. Unref the reply object, and pass
|
|
+ * the return value down the chain. Mark the reply as
|
|
+ * interrupted, so the cleanup work can remove it, but do not
|
|
+ * unlink it from the list. Once the syscall restarts, we'll
|
|
+ * pick it up and wait on it again.
|
|
+ */
|
|
+ mutex_lock(&conn_src->lock);
|
|
+ reply_wait->interrupted = true;
|
|
+ schedule_delayed_work(&conn_src->work, 0);
|
|
+ mutex_unlock(&conn_src->lock);
|
|
+
|
|
+ return -ERESTARTSYS;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&conn_src->lock);
|
|
+ reply_wait->waiting = false;
|
|
+ entry = reply_wait->queue_entry;
|
|
+ if (entry) {
|
|
+ ret = kdbus_queue_entry_install(entry,
|
|
+ &cmd_send->reply.return_flags,
|
|
+ true);
|
|
+ kdbus_pool_slice_publish(entry->slice, &cmd_send->reply.offset,
|
|
+ &cmd_send->reply.msg_size);
|
|
+ kdbus_queue_entry_free(entry);
|
|
+ }
|
|
+ kdbus_reply_unlink(reply_wait);
|
|
+ mutex_unlock(&conn_src->lock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int kdbus_pin_dst(struct kdbus_bus *bus,
|
|
+ struct kdbus_kmsg *kmsg,
|
|
+ struct kdbus_name_entry **out_name,
|
|
+ struct kdbus_conn **out_dst)
|
|
+{
|
|
+ struct kdbus_msg_resources *res = kmsg->res;
|
|
+ struct kdbus_name_entry *name = NULL;
|
|
+ struct kdbus_conn *dst = NULL;
|
|
+ struct kdbus_msg *msg = &kmsg->msg;
|
|
+ int ret;
|
|
+
|
|
+ if (WARN_ON(!res))
|
|
+ return -EINVAL;
|
|
+
|
|
+ lockdep_assert_held(&bus->name_registry->rwlock);
|
|
+
|
|
+ if (!res->dst_name) {
|
|
+ dst = kdbus_bus_find_conn_by_id(bus, msg->dst_id);
|
|
+ if (!dst)
|
|
+ return -ENXIO;
|
|
+
|
|
+ if (!kdbus_conn_is_ordinary(dst)) {
|
|
+ ret = -ENXIO;
|
|
+ goto error;
|
|
+ }
|
|
+ } else {
|
|
+ name = kdbus_name_lookup_unlocked(bus->name_registry,
|
|
+ res->dst_name);
|
|
+ if (!name)
|
|
+ return -ESRCH;
|
|
+
|
|
+ /*
|
|
+ * If both a name and a connection ID are given as destination
|
|
+ * of a message, check that the currently owning connection of
|
|
+ * the name matches the specified ID.
|
|
+ * This way, we allow userspace to send the message to a
|
|
+ * specific connection by ID only if the connection currently
|
|
+ * owns the given name.
|
|
+ */
|
|
+ if (msg->dst_id != KDBUS_DST_ID_NAME &&
|
|
+ msg->dst_id != name->conn->id)
|
|
+ return -EREMCHG;
|
|
+
|
|
+ if (!name->conn && name->activator)
|
|
+ dst = kdbus_conn_ref(name->activator);
|
|
+ else
|
|
+ dst = kdbus_conn_ref(name->conn);
|
|
+
|
|
+ if ((msg->flags & KDBUS_MSG_NO_AUTO_START) &&
|
|
+ kdbus_conn_is_activator(dst)) {
|
|
+ ret = -EADDRNOTAVAIL;
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Record the sequence number of the registered name; it will
|
|
+ * be passed on to the queue, in case messages addressed to a
|
|
+ * name need to be moved from or to an activator.
|
|
+ */
|
|
+ kmsg->dst_name_id = name->name_id;
|
|
+ }
|
|
+
|
|
+ *out_name = name;
|
|
+ *out_dst = dst;
|
|
+ return 0;
|
|
+
|
|
+error:
|
|
+ kdbus_conn_unref(dst);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int kdbus_conn_reply(struct kdbus_conn *src, struct kdbus_kmsg *kmsg)
|
|
+{
|
|
+ struct kdbus_name_entry *name = NULL;
|
|
+ struct kdbus_reply *reply, *wake = NULL;
|
|
+ struct kdbus_conn *dst = NULL;
|
|
+ struct kdbus_bus *bus = src->ep->bus;
|
|
+ u64 attach;
|
|
+ int ret;
|
|
+
|
|
+ if (WARN_ON(kmsg->msg.dst_id == KDBUS_DST_ID_BROADCAST) ||
|
|
+ WARN_ON(kmsg->msg.flags & KDBUS_MSG_EXPECT_REPLY) ||
|
|
+ WARN_ON(kmsg->msg.flags & KDBUS_MSG_SIGNAL))
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* name-registry must be locked for lookup *and* collecting data */
|
|
+ down_read(&bus->name_registry->rwlock);
|
|
+
|
|
+ /* find and pin destination */
|
|
+
|
|
+ ret = kdbus_pin_dst(bus, kmsg, &name, &dst);
|
|
+ if (ret < 0)
|
|
+ goto exit;
|
|
+
|
|
+ mutex_lock(&dst->lock);
|
|
+ reply = kdbus_reply_find(src, dst, kmsg->msg.cookie_reply);
|
|
+ if (reply) {
|
|
+ if (reply->sync)
|
|
+ wake = kdbus_reply_ref(reply);
|
|
+ kdbus_reply_unlink(reply);
|
|
+ }
|
|
+ mutex_unlock(&dst->lock);
|
|
+
|
|
+ if (!reply) {
|
|
+ ret = -EPERM;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ /* attach metadata */
|
|
+
|
|
+ attach = kdbus_meta_calc_attach_flags(src, dst);
|
|
+
|
|
+ if (!src->faked_meta) {
|
|
+ ret = kdbus_meta_proc_collect(kmsg->proc_meta, attach);
|
|
+ if (ret < 0)
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ ret = kdbus_meta_conn_collect(kmsg->conn_meta, kmsg, src, attach);
|
|
+ if (ret < 0)
|
|
+ goto exit;
|
|
+
|
|
+ /* send message */
|
|
+
|
|
+ kdbus_bus_eavesdrop(bus, src, kmsg);
|
|
+
|
|
+ if (wake)
|
|
+ ret = kdbus_conn_entry_sync_attach(dst, kmsg, wake);
|
|
+ else
|
|
+ ret = kdbus_conn_entry_insert(src, dst, kmsg, NULL);
|
|
+
|
|
+exit:
|
|
+ up_read(&bus->name_registry->rwlock);
|
|
+ kdbus_reply_unref(wake);
|
|
+ kdbus_conn_unref(dst);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static struct kdbus_reply *kdbus_conn_call(struct kdbus_conn *src,
|
|
+ struct kdbus_kmsg *kmsg,
|
|
+ ktime_t exp)
|
|
+{
|
|
+ struct kdbus_name_entry *name = NULL;
|
|
+ struct kdbus_reply *wait = NULL;
|
|
+ struct kdbus_conn *dst = NULL;
|
|
+ struct kdbus_bus *bus = src->ep->bus;
|
|
+ u64 attach;
|
|
+ int ret;
|
|
+
|
|
+ if (WARN_ON(kmsg->msg.dst_id == KDBUS_DST_ID_BROADCAST) ||
|
|
+ WARN_ON(kmsg->msg.flags & KDBUS_MSG_SIGNAL) ||
|
|
+ WARN_ON(!(kmsg->msg.flags & KDBUS_MSG_EXPECT_REPLY)))
|
|
+ return ERR_PTR(-EINVAL);
|
|
+
|
|
+ /* resume previous wait-context, if available */
|
|
+
|
|
+ mutex_lock(&src->lock);
|
|
+ wait = kdbus_reply_find(NULL, src, kmsg->msg.cookie);
|
|
+ if (wait) {
|
|
+ if (wait->interrupted) {
|
|
+ kdbus_reply_ref(wait);
|
|
+ wait->interrupted = false;
|
|
+ } else {
|
|
+ wait = NULL;
|
|
+ }
|
|
+ }
|
|
+ mutex_unlock(&src->lock);
|
|
+
|
|
+ if (wait)
|
|
+ return wait;
|
|
+
|
|
+ if (ktime_compare(ktime_get(), exp) >= 0)
|
|
+ return ERR_PTR(-ETIMEDOUT);
|
|
+
|
|
+ /* name-registry must be locked for lookup *and* collecting data */
|
|
+ down_read(&bus->name_registry->rwlock);
|
|
+
|
|
+ /* find and pin destination */
|
|
+
|
|
+ ret = kdbus_pin_dst(bus, kmsg, &name, &dst);
|
|
+ if (ret < 0)
|
|
+ goto exit;
|
|
+
|
|
+ if (!kdbus_conn_policy_talk(src, current_cred(), dst)) {
|
|
+ ret = -EPERM;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ wait = kdbus_reply_new(dst, src, &kmsg->msg, name, true);
|
|
+ if (IS_ERR(wait)) {
|
|
+ ret = PTR_ERR(wait);
|
|
+ wait = NULL;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ /* attach metadata */
|
|
+
|
|
+ attach = kdbus_meta_calc_attach_flags(src, dst);
|
|
+
|
|
+ if (!src->faked_meta) {
|
|
+ ret = kdbus_meta_proc_collect(kmsg->proc_meta, attach);
|
|
+ if (ret < 0)
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ ret = kdbus_meta_conn_collect(kmsg->conn_meta, kmsg, src, attach);
|
|
+ if (ret < 0)
|
|
+ goto exit;
|
|
+
|
|
+ /* send message */
|
|
+
|
|
+ kdbus_bus_eavesdrop(bus, src, kmsg);
|
|
+
|
|
+ ret = kdbus_conn_entry_insert(src, dst, kmsg, wait);
|
|
+ if (ret < 0)
|
|
+ goto exit;
|
|
+
|
|
+ ret = 0;
|
|
+
|
|
+exit:
|
|
+ up_read(&bus->name_registry->rwlock);
|
|
+ if (ret < 0) {
|
|
+ kdbus_reply_unref(wait);
|
|
+ wait = ERR_PTR(ret);
|
|
+ }
|
|
+ kdbus_conn_unref(dst);
|
|
+ return wait;
|
|
+}
|
|
+
|
|
+static int kdbus_conn_unicast(struct kdbus_conn *src, struct kdbus_kmsg *kmsg)
|
|
+{
|
|
+ struct kdbus_name_entry *name = NULL;
|
|
+ struct kdbus_reply *wait = NULL;
|
|
+ struct kdbus_conn *dst = NULL;
|
|
+ struct kdbus_bus *bus = src->ep->bus;
|
|
+ bool is_signal = (kmsg->msg.flags & KDBUS_MSG_SIGNAL);
|
|
+ u64 attach;
|
|
+ int ret = 0;
|
|
+
|
|
+ if (WARN_ON(kmsg->msg.dst_id == KDBUS_DST_ID_BROADCAST) ||
|
|
+ WARN_ON(!(kmsg->msg.flags & KDBUS_MSG_EXPECT_REPLY) &&
|
|
+ kmsg->msg.cookie_reply != 0))
|
|
+ return -EINVAL;
|
|
+
|
|
+ /* name-registry must be locked for lookup *and* collecting data */
|
|
+ down_read(&bus->name_registry->rwlock);
|
|
+
|
|
+ /* find and pin destination */
|
|
+
|
|
+ ret = kdbus_pin_dst(bus, kmsg, &name, &dst);
|
|
+ if (ret < 0)
|
|
+ goto exit;
|
|
+
|
|
+ if (is_signal) {
|
|
+ /* like broadcasts we eavesdrop even if the msg is dropped */
|
|
+ kdbus_bus_eavesdrop(bus, src, kmsg);
|
|
+
|
|
+ /* drop silently if peer is not interested or not privileged */
|
|
+ if (!kdbus_match_db_match_kmsg(dst->match_db, src, kmsg) ||
|
|
+ !kdbus_conn_policy_talk(dst, NULL, src))
|
|
+ goto exit;
|
|
+ } else if (!kdbus_conn_policy_talk(src, current_cred(), dst)) {
|
|
+ ret = -EPERM;
|
|
+ goto exit;
|
|
+ } else if (kmsg->msg.flags & KDBUS_MSG_EXPECT_REPLY) {
|
|
+ wait = kdbus_reply_new(dst, src, &kmsg->msg, name, false);
|
|
+ if (IS_ERR(wait)) {
|
|
+ ret = PTR_ERR(wait);
|
|
+ wait = NULL;
|
|
+ goto exit;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* attach metadata */
|
|
+
|
|
+ attach = kdbus_meta_calc_attach_flags(src, dst);
|
|
+
|
|
+ if (!src->faked_meta) {
|
|
+ ret = kdbus_meta_proc_collect(kmsg->proc_meta, attach);
|
|
+ if (ret < 0 && !is_signal)
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ ret = kdbus_meta_conn_collect(kmsg->conn_meta, kmsg, src, attach);
|
|
+ if (ret < 0 && !is_signal)
|
|
+ goto exit;
|
|
+
|
|
+ /* send message */
|
|
+
|
|
+ if (!is_signal)
|
|
+ kdbus_bus_eavesdrop(bus, src, kmsg);
|
|
+
|
|
+ ret = kdbus_conn_entry_insert(src, dst, kmsg, wait);
|
|
+ if (ret < 0 && !is_signal)
|
|
+ goto exit;
|
|
+
|
|
+ /* signals are treated like broadcasts, recv-errors are ignored */
|
|
+ ret = 0;
|
|
+
|
|
+exit:
|
|
+ up_read(&bus->name_registry->rwlock);
|
|
+ kdbus_reply_unref(wait);
|
|
+ kdbus_conn_unref(dst);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_move_messages() - move messages from one connection to another
|
|
+ * @conn_dst: Connection to copy to
|
|
+ * @conn_src: Connection to copy from
|
|
+ * @name_id: Filter for the sequence number of the registered
|
|
+ * name, 0 means no filtering.
|
|
+ *
|
|
+ * Move all messages from one connection to another. This is used when
|
|
+ * an implementer connection is taking over/giving back a well-known name
|
|
+ * from/to an activator connection.
|
|
+ */
|
|
+void kdbus_conn_move_messages(struct kdbus_conn *conn_dst,
|
|
+ struct kdbus_conn *conn_src,
|
|
+ u64 name_id)
|
|
+{
|
|
+ struct kdbus_queue_entry *e, *e_tmp;
|
|
+ struct kdbus_reply *r, *r_tmp;
|
|
+ struct kdbus_bus *bus;
|
|
+ struct kdbus_conn *c;
|
|
+ LIST_HEAD(msg_list);
|
|
+ int i, ret = 0;
|
|
+
|
|
+ if (WARN_ON(conn_src == conn_dst))
|
|
+ return;
|
|
+
|
|
+ bus = conn_src->ep->bus;
|
|
+
|
|
+ /* lock order: domain -> bus -> ep -> names -> conn */
|
|
+ down_read(&bus->conn_rwlock);
|
|
+ hash_for_each(bus->conn_hash, i, c, hentry) {
|
|
+ if (c == conn_src || c == conn_dst)
|
|
+ continue;
|
|
+
|
|
+ mutex_lock(&c->lock);
|
|
+ list_for_each_entry_safe(r, r_tmp, &c->reply_list, entry) {
|
|
+ if (r->reply_src != conn_src)
|
|
+ continue;
|
|
+
|
|
+ /* filter messages for a specific name */
|
|
+ if (name_id > 0 && r->name_id != name_id)
|
|
+ continue;
|
|
+
|
|
+ kdbus_conn_unref(r->reply_src);
|
|
+ r->reply_src = kdbus_conn_ref(conn_dst);
|
|
+ }
|
|
+ mutex_unlock(&c->lock);
|
|
+ }
|
|
+ up_read(&bus->conn_rwlock);
|
|
+
|
|
+ kdbus_conn_lock2(conn_src, conn_dst);
|
|
+ list_for_each_entry_safe(e, e_tmp, &conn_src->queue.msg_list, entry) {
|
|
+ /* filter messages for a specific name */
|
|
+ if (name_id > 0 && e->dst_name_id != name_id)
|
|
+ continue;
|
|
+
|
|
+ if (!(conn_dst->flags & KDBUS_HELLO_ACCEPT_FD) &&
|
|
+ e->msg_res && e->msg_res->fds_count > 0) {
|
|
+ kdbus_conn_lost_message(conn_dst);
|
|
+ kdbus_queue_entry_free(e);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ ret = kdbus_queue_entry_move(e, conn_dst);
|
|
+ if (ret < 0) {
|
|
+ kdbus_conn_lost_message(conn_dst);
|
|
+ kdbus_queue_entry_free(e);
|
|
+ continue;
|
|
+ }
|
|
+ }
|
|
+ kdbus_conn_unlock2(conn_src, conn_dst);
|
|
+
|
|
+ /* wake up poll() */
|
|
+ wake_up_interruptible(&conn_dst->wait);
|
|
+}
|
|
+
|
|
+/* query the policy-database for all names of @whom */
|
|
+static bool kdbus_conn_policy_query_all(struct kdbus_conn *conn,
|
|
+ const struct cred *conn_creds,
|
|
+ struct kdbus_policy_db *db,
|
|
+ struct kdbus_conn *whom,
|
|
+ unsigned int access)
|
|
+{
|
|
+ struct kdbus_name_entry *ne;
|
|
+ bool pass = false;
|
|
+ int res;
|
|
+
|
|
+ lockdep_assert_held(&conn->ep->bus->name_registry->rwlock);
|
|
+
|
|
+ down_read(&db->entries_rwlock);
|
|
+ mutex_lock(&whom->lock);
|
|
+
|
|
+ list_for_each_entry(ne, &whom->names_list, conn_entry) {
|
|
+ res = kdbus_policy_query_unlocked(db, conn_creds ? : conn->cred,
|
|
+ ne->name,
|
|
+ kdbus_strhash(ne->name));
|
|
+ if (res >= (int)access) {
|
|
+ pass = true;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&whom->lock);
|
|
+ up_read(&db->entries_rwlock);
|
|
+
|
|
+ return pass;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_policy_own_name() - verify a connection can own the given name
|
|
+ * @conn: Connection
|
|
+ * @conn_creds: Credentials of @conn to use for policy check
|
|
+ * @name: Name
|
|
+ *
|
|
+ * This verifies that @conn is allowed to acquire the well-known name @name.
|
|
+ *
|
|
+ * Return: true if allowed, false if not.
|
|
+ */
|
|
+bool kdbus_conn_policy_own_name(struct kdbus_conn *conn,
|
|
+ const struct cred *conn_creds,
|
|
+ const char *name)
|
|
+{
|
|
+ unsigned int hash = kdbus_strhash(name);
|
|
+ int res;
|
|
+
|
|
+ if (!conn_creds)
|
|
+ conn_creds = conn->cred;
|
|
+
|
|
+ if (conn->ep->user) {
|
|
+ res = kdbus_policy_query(&conn->ep->policy_db, conn_creds,
|
|
+ name, hash);
|
|
+ if (res < KDBUS_POLICY_OWN)
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (conn->privileged)
|
|
+ return true;
|
|
+
|
|
+ res = kdbus_policy_query(&conn->ep->bus->policy_db, conn_creds,
|
|
+ name, hash);
|
|
+ return res >= KDBUS_POLICY_OWN;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_policy_talk() - verify a connection can talk to a given peer
|
|
+ * @conn: Connection that tries to talk
|
|
+ * @conn_creds: Credentials of @conn to use for policy check
|
|
+ * @to: Connection that is talked to
|
|
+ *
|
|
+ * This verifies that @conn is allowed to talk to @to.
|
|
+ *
|
|
+ * Return: true if allowed, false if not.
|
|
+ */
|
|
+bool kdbus_conn_policy_talk(struct kdbus_conn *conn,
|
|
+ const struct cred *conn_creds,
|
|
+ struct kdbus_conn *to)
|
|
+{
|
|
+ if (!conn_creds)
|
|
+ conn_creds = conn->cred;
|
|
+
|
|
+ if (conn->ep->user &&
|
|
+ !kdbus_conn_policy_query_all(conn, conn_creds, &conn->ep->policy_db,
|
|
+ to, KDBUS_POLICY_TALK))
|
|
+ return false;
|
|
+
|
|
+ if (conn->privileged)
|
|
+ return true;
|
|
+ if (uid_eq(conn_creds->euid, to->cred->uid))
|
|
+ return true;
|
|
+
|
|
+ return kdbus_conn_policy_query_all(conn, conn_creds,
|
|
+ &conn->ep->bus->policy_db, to,
|
|
+ KDBUS_POLICY_TALK);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_policy_see_name_unlocked() - verify a connection can see a given
|
|
+ * name
|
|
+ * @conn: Connection
|
|
+ * @conn_creds: Credentials of @conn to use for policy check
|
|
+ * @name: Name
|
|
+ *
|
|
+ * This verifies that @conn is allowed to see the well-known name @name. Caller
|
|
+ * must hold policy-lock.
|
|
+ *
|
|
+ * Return: true if allowed, false if not.
|
|
+ */
|
|
+bool kdbus_conn_policy_see_name_unlocked(struct kdbus_conn *conn,
|
|
+ const struct cred *conn_creds,
|
|
+ const char *name)
|
|
+{
|
|
+ int res;
|
|
+
|
|
+ /*
|
|
+ * By default, all names are visible on a bus. SEE policies can only be
|
|
+ * installed on custom endpoints, where by default no name is visible.
|
|
+ */
|
|
+ if (!conn->ep->user)
|
|
+ return true;
|
|
+
|
|
+ res = kdbus_policy_query_unlocked(&conn->ep->policy_db,
|
|
+ conn_creds ? : conn->cred,
|
|
+ name, kdbus_strhash(name));
|
|
+ return res >= KDBUS_POLICY_SEE;
|
|
+}
|
|
+
|
|
+static bool kdbus_conn_policy_see_name(struct kdbus_conn *conn,
|
|
+ const struct cred *conn_creds,
|
|
+ const char *name)
|
|
+{
|
|
+ bool res;
|
|
+
|
|
+ down_read(&conn->ep->policy_db.entries_rwlock);
|
|
+ res = kdbus_conn_policy_see_name_unlocked(conn, conn_creds, name);
|
|
+ up_read(&conn->ep->policy_db.entries_rwlock);
|
|
+
|
|
+ return res;
|
|
+}
|
|
+
|
|
+static bool kdbus_conn_policy_see(struct kdbus_conn *conn,
|
|
+ const struct cred *conn_creds,
|
|
+ struct kdbus_conn *whom)
|
|
+{
|
|
+ /*
|
|
+ * By default, all names are visible on a bus, so a connection can
|
|
+ * always see other connections. SEE policies can only be installed on
|
|
+ * custom endpoints, where by default no name is visible and we hide
|
|
+ * peers from each other, unless you see at least _one_ name of the
|
|
+ * peer.
|
|
+ */
|
|
+ return !conn->ep->user ||
|
|
+ kdbus_conn_policy_query_all(conn, conn_creds,
|
|
+ &conn->ep->policy_db, whom,
|
|
+ KDBUS_POLICY_SEE);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_policy_see_notification() - verify a connection is allowed to
|
|
+ * receive a given kernel notification
|
|
+ * @conn: Connection
|
|
+ * @conn_creds: Credentials of @conn to use for policy check
|
|
+ * @kmsg: The message carrying the notification
|
|
+ *
|
|
+ * This checks whether @conn is allowed to see the kernel notification @kmsg.
|
|
+ *
|
|
+ * Return: true if allowed, false if not.
|
|
+ */
|
|
+bool kdbus_conn_policy_see_notification(struct kdbus_conn *conn,
|
|
+ const struct cred *conn_creds,
|
|
+ const struct kdbus_kmsg *kmsg)
|
|
+{
|
|
+ if (WARN_ON(kmsg->msg.src_id != KDBUS_SRC_ID_KERNEL))
|
|
+ return false;
|
|
+
|
|
+ /*
|
|
+ * Depending on the notification type, broadcasted kernel notifications
|
|
+ * have to be filtered:
|
|
+ *
|
|
+ * KDBUS_ITEM_NAME_{ADD,REMOVE,CHANGE}: This notification is forwarded
|
|
+ * to a peer if, and only if, that peer can see the name this
|
|
+ * notification is for.
|
|
+ *
|
|
+ * KDBUS_ITEM_ID_{ADD,REMOVE}: As new peers cannot have names, and all
|
|
+ * names are dropped before a peer is removed, those notifications
|
|
+ * cannot be seen on custom endpoints. Thus, we only pass them
|
|
+ * through on default endpoints.
|
|
+ */
|
|
+
|
|
+ switch (kmsg->notify_type) {
|
|
+ case KDBUS_ITEM_NAME_ADD:
|
|
+ case KDBUS_ITEM_NAME_REMOVE:
|
|
+ case KDBUS_ITEM_NAME_CHANGE:
|
|
+ return kdbus_conn_policy_see_name(conn, conn_creds,
|
|
+ kmsg->notify_name);
|
|
+
|
|
+ case KDBUS_ITEM_ID_ADD:
|
|
+ case KDBUS_ITEM_ID_REMOVE:
|
|
+ return !conn->ep->user;
|
|
+
|
|
+ default:
|
|
+ WARN(1, "Invalid type for notification broadcast: %llu\n",
|
|
+ (unsigned long long)kmsg->notify_type);
|
|
+ return false;
|
|
+ }
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_cmd_hello() - handle KDBUS_CMD_HELLO
|
|
+ * @ep: Endpoint to operate on
|
|
+ * @privileged: Whether the caller is privileged
|
|
+ * @argp: Command payload
|
|
+ *
|
|
+ * Return: Newly created connection on success, ERR_PTR on failure.
|
|
+ */
|
|
+struct kdbus_conn *kdbus_cmd_hello(struct kdbus_ep *ep, bool privileged,
|
|
+ void __user *argp)
|
|
+{
|
|
+ struct kdbus_cmd_hello *cmd;
|
|
+ struct kdbus_conn *c = NULL;
|
|
+ const char *item_name;
|
|
+ int ret;
|
|
+
|
|
+ struct kdbus_arg argv[] = {
|
|
+ { .type = KDBUS_ITEM_NEGOTIATE },
|
|
+ { .type = KDBUS_ITEM_NAME },
|
|
+ { .type = KDBUS_ITEM_CREDS },
|
|
+ { .type = KDBUS_ITEM_PIDS },
|
|
+ { .type = KDBUS_ITEM_SECLABEL },
|
|
+ { .type = KDBUS_ITEM_CONN_DESCRIPTION },
|
|
+ { .type = KDBUS_ITEM_POLICY_ACCESS, .multiple = true },
|
|
+ };
|
|
+ struct kdbus_args args = {
|
|
+ .allowed_flags = KDBUS_FLAG_NEGOTIATE |
|
|
+ KDBUS_HELLO_ACCEPT_FD |
|
|
+ KDBUS_HELLO_ACTIVATOR |
|
|
+ KDBUS_HELLO_POLICY_HOLDER |
|
|
+ KDBUS_HELLO_MONITOR,
|
|
+ .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;
|
|
+
|
|
+ item_name = argv[1].item ? argv[1].item->str : NULL;
|
|
+
|
|
+ c = kdbus_conn_new(ep, privileged, cmd, item_name,
|
|
+ argv[2].item ? &argv[2].item->creds : NULL,
|
|
+ argv[3].item ? &argv[3].item->pids : NULL,
|
|
+ argv[4].item ? argv[4].item->str : NULL,
|
|
+ argv[5].item ? argv[5].item->str : NULL);
|
|
+ if (IS_ERR(c)) {
|
|
+ ret = PTR_ERR(c);
|
|
+ c = NULL;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ ret = kdbus_conn_connect(c, item_name);
|
|
+ if (ret < 0)
|
|
+ goto exit;
|
|
+
|
|
+ if (kdbus_conn_is_activator(c) || kdbus_conn_is_policy_holder(c)) {
|
|
+ ret = kdbus_conn_acquire(c);
|
|
+ if (ret < 0)
|
|
+ goto exit;
|
|
+
|
|
+ ret = kdbus_policy_set(&c->ep->bus->policy_db, args.items,
|
|
+ args.items_size, 1,
|
|
+ kdbus_conn_is_policy_holder(c), c);
|
|
+ kdbus_conn_release(c);
|
|
+ if (ret < 0)
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ if (copy_to_user(argp, cmd, sizeof(*cmd)))
|
|
+ ret = -EFAULT;
|
|
+
|
|
+exit:
|
|
+ ret = kdbus_args_clear(&args, ret);
|
|
+ if (ret < 0) {
|
|
+ if (c) {
|
|
+ kdbus_conn_disconnect(c, false);
|
|
+ kdbus_conn_unref(c);
|
|
+ }
|
|
+ return ERR_PTR(ret);
|
|
+ }
|
|
+ return c;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_cmd_byebye_unlocked() - handle KDBUS_CMD_BYEBYE
|
|
+ * @conn: connection to operate on
|
|
+ * @argp: command payload
|
|
+ *
|
|
+ * The caller must not hold any active reference to @conn or this will deadlock.
|
|
+ *
|
|
+ * Return: 0 on success, negative error code on failure.
|
|
+ */
|
|
+int kdbus_cmd_byebye_unlocked(struct kdbus_conn *conn, void __user *argp)
|
|
+{
|
|
+ struct kdbus_cmd *cmd;
|
|
+ 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),
|
|
+ };
|
|
+
|
|
+ if (!kdbus_conn_is_ordinary(conn))
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
+ ret = kdbus_args_parse(&args, argp, &cmd);
|
|
+ if (ret != 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = kdbus_conn_disconnect(conn, true);
|
|
+ return kdbus_args_clear(&args, ret);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_cmd_conn_info() - handle KDBUS_CMD_CONN_INFO
|
|
+ * @conn: connection to operate on
|
|
+ * @argp: command payload
|
|
+ *
|
|
+ * Return: 0 on success, negative error code on failure.
|
|
+ */
|
|
+int kdbus_cmd_conn_info(struct kdbus_conn *conn, void __user *argp)
|
|
+{
|
|
+ struct kdbus_meta_conn *conn_meta = NULL;
|
|
+ struct kdbus_pool_slice *slice = NULL;
|
|
+ struct kdbus_name_entry *entry = NULL;
|
|
+ struct kdbus_conn *owner_conn = NULL;
|
|
+ struct kdbus_info info = {};
|
|
+ struct kdbus_cmd_info *cmd;
|
|
+ struct kdbus_bus *bus = conn->ep->bus;
|
|
+ struct kvec kvec;
|
|
+ size_t meta_size;
|
|
+ const char *name;
|
|
+ u64 attach_flags;
|
|
+ int ret;
|
|
+
|
|
+ struct kdbus_arg argv[] = {
|
|
+ { .type = KDBUS_ITEM_NEGOTIATE },
|
|
+ { .type = KDBUS_ITEM_NAME },
|
|
+ };
|
|
+ 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;
|
|
+
|
|
+ /* registry must be held throughout lookup *and* collecting data */
|
|
+ down_read(&bus->name_registry->rwlock);
|
|
+
|
|
+ ret = kdbus_sanitize_attach_flags(cmd->attach_flags, &attach_flags);
|
|
+ if (ret < 0)
|
|
+ goto exit;
|
|
+
|
|
+ name = argv[1].item ? argv[1].item->str : NULL;
|
|
+
|
|
+ if (name) {
|
|
+ entry = kdbus_name_lookup_unlocked(bus->name_registry, name);
|
|
+ if (!entry || !entry->conn ||
|
|
+ !kdbus_conn_policy_see_name(conn, current_cred(), name) ||
|
|
+ (cmd->id != 0 && entry->conn->id != cmd->id)) {
|
|
+ /* pretend a name doesn't exist if you cannot see it */
|
|
+ ret = -ESRCH;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ owner_conn = kdbus_conn_ref(entry->conn);
|
|
+ } else if (cmd->id > 0) {
|
|
+ owner_conn = kdbus_bus_find_conn_by_id(bus, cmd->id);
|
|
+ if (!owner_conn || !kdbus_conn_policy_see(conn, current_cred(),
|
|
+ owner_conn)) {
|
|
+ /* pretend an id doesn't exist if you cannot see it */
|
|
+ ret = -ENXIO;
|
|
+ goto exit;
|
|
+ }
|
|
+ } else {
|
|
+ ret = -EINVAL;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ info.id = owner_conn->id;
|
|
+ info.flags = owner_conn->flags;
|
|
+ kdbus_kvec_set(&kvec, &info, sizeof(info), &info.size);
|
|
+
|
|
+ attach_flags &= atomic64_read(&owner_conn->attach_flags_send);
|
|
+
|
|
+ conn_meta = kdbus_meta_conn_new();
|
|
+ if (IS_ERR(conn_meta)) {
|
|
+ ret = PTR_ERR(conn_meta);
|
|
+ conn_meta = NULL;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ ret = kdbus_meta_conn_collect(conn_meta, NULL, owner_conn,
|
|
+ attach_flags);
|
|
+ if (ret < 0)
|
|
+ goto exit;
|
|
+
|
|
+ ret = kdbus_meta_export_prepare(owner_conn->meta, conn_meta,
|
|
+ &attach_flags, &meta_size);
|
|
+ if (ret < 0)
|
|
+ goto exit;
|
|
+
|
|
+ slice = kdbus_pool_slice_alloc(conn->pool,
|
|
+ info.size + meta_size, false);
|
|
+ if (IS_ERR(slice)) {
|
|
+ ret = PTR_ERR(slice);
|
|
+ slice = NULL;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ ret = kdbus_meta_export(owner_conn->meta, conn_meta, attach_flags,
|
|
+ slice, sizeof(info), &meta_size);
|
|
+ if (ret < 0)
|
|
+ goto exit;
|
|
+
|
|
+ info.size += meta_size;
|
|
+
|
|
+ ret = kdbus_pool_slice_copy_kvec(slice, 0, &kvec, 1, sizeof(info));
|
|
+ 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;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ ret = 0;
|
|
+
|
|
+exit:
|
|
+ up_read(&bus->name_registry->rwlock);
|
|
+ kdbus_pool_slice_release(slice);
|
|
+ kdbus_meta_conn_unref(conn_meta);
|
|
+ kdbus_conn_unref(owner_conn);
|
|
+ return kdbus_args_clear(&args, ret);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_cmd_update() - handle KDBUS_CMD_UPDATE
|
|
+ * @conn: connection to operate on
|
|
+ * @argp: command payload
|
|
+ *
|
|
+ * Return: 0 on success, negative error code on failure.
|
|
+ */
|
|
+int kdbus_cmd_update(struct kdbus_conn *conn, void __user *argp)
|
|
+{
|
|
+ struct kdbus_bus *bus = conn->ep->bus;
|
|
+ struct kdbus_item *item_policy;
|
|
+ u64 *item_attach_send = NULL;
|
|
+ u64 *item_attach_recv = NULL;
|
|
+ struct kdbus_cmd *cmd;
|
|
+ u64 attach_send;
|
|
+ u64 attach_recv;
|
|
+ int ret;
|
|
+
|
|
+ struct kdbus_arg argv[] = {
|
|
+ { .type = KDBUS_ITEM_NEGOTIATE },
|
|
+ { .type = KDBUS_ITEM_ATTACH_FLAGS_SEND },
|
|
+ { .type = KDBUS_ITEM_ATTACH_FLAGS_RECV },
|
|
+ { .type = KDBUS_ITEM_NAME, .multiple = true },
|
|
+ { .type = KDBUS_ITEM_POLICY_ACCESS, .multiple = true },
|
|
+ };
|
|
+ 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;
|
|
+
|
|
+ item_attach_send = argv[1].item ? &argv[1].item->data64[0] : NULL;
|
|
+ item_attach_recv = argv[2].item ? &argv[2].item->data64[0] : NULL;
|
|
+ item_policy = argv[3].item ? : argv[4].item;
|
|
+
|
|
+ if (item_attach_send) {
|
|
+ if (!kdbus_conn_is_ordinary(conn) &&
|
|
+ !kdbus_conn_is_monitor(conn)) {
|
|
+ ret = -EOPNOTSUPP;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ ret = kdbus_sanitize_attach_flags(*item_attach_send,
|
|
+ &attach_send);
|
|
+ if (ret < 0)
|
|
+ goto exit;
|
|
+
|
|
+ if (bus->attach_flags_req & ~attach_send) {
|
|
+ ret = -EINVAL;
|
|
+ goto exit;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (item_attach_recv) {
|
|
+ if (!kdbus_conn_is_ordinary(conn) &&
|
|
+ !kdbus_conn_is_monitor(conn) &&
|
|
+ !kdbus_conn_is_activator(conn)) {
|
|
+ ret = -EOPNOTSUPP;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ ret = kdbus_sanitize_attach_flags(*item_attach_recv,
|
|
+ &attach_recv);
|
|
+ if (ret < 0)
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ if (item_policy && !kdbus_conn_is_policy_holder(conn)) {
|
|
+ ret = -EOPNOTSUPP;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ /* now that we verified the input, update the connection */
|
|
+
|
|
+ if (item_policy) {
|
|
+ ret = kdbus_policy_set(&conn->ep->bus->policy_db, cmd->items,
|
|
+ KDBUS_ITEMS_SIZE(cmd, items),
|
|
+ 1, true, conn);
|
|
+ if (ret < 0)
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ if (item_attach_send)
|
|
+ atomic64_set(&conn->attach_flags_send, attach_send);
|
|
+
|
|
+ if (item_attach_recv)
|
|
+ atomic64_set(&conn->attach_flags_recv, attach_recv);
|
|
+
|
|
+exit:
|
|
+ return kdbus_args_clear(&args, ret);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_cmd_send() - handle KDBUS_CMD_SEND
|
|
+ * @conn: connection to operate on
|
|
+ * @f: file this command was called on
|
|
+ * @argp: command payload
|
|
+ *
|
|
+ * Return: 0 on success, negative error code on failure.
|
|
+ */
|
|
+int kdbus_cmd_send(struct kdbus_conn *conn, struct file *f, void __user *argp)
|
|
+{
|
|
+ struct kdbus_cmd_send *cmd;
|
|
+ struct kdbus_kmsg *kmsg = NULL;
|
|
+ struct file *cancel_fd = NULL;
|
|
+ int ret;
|
|
+
|
|
+ struct kdbus_arg argv[] = {
|
|
+ { .type = KDBUS_ITEM_NEGOTIATE },
|
|
+ { .type = KDBUS_ITEM_CANCEL_FD },
|
|
+ };
|
|
+ struct kdbus_args args = {
|
|
+ .allowed_flags = KDBUS_FLAG_NEGOTIATE |
|
|
+ KDBUS_SEND_SYNC_REPLY,
|
|
+ .argv = argv,
|
|
+ .argc = ARRAY_SIZE(argv),
|
|
+ };
|
|
+
|
|
+ if (!kdbus_conn_is_ordinary(conn))
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
+ ret = kdbus_args_parse(&args, argp, &cmd);
|
|
+ if (ret != 0)
|
|
+ return ret;
|
|
+
|
|
+ cmd->reply.return_flags = 0;
|
|
+ kdbus_pool_publish_empty(conn->pool, &cmd->reply.offset,
|
|
+ &cmd->reply.msg_size);
|
|
+
|
|
+ if (argv[1].item) {
|
|
+ cancel_fd = fget(argv[1].item->fds[0]);
|
|
+ if (IS_ERR(cancel_fd)) {
|
|
+ ret = PTR_ERR(cancel_fd);
|
|
+ cancel_fd = NULL;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ if (!cancel_fd->f_op->poll) {
|
|
+ ret = -EINVAL;
|
|
+ goto exit;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ kmsg = kdbus_kmsg_new_from_cmd(conn, cmd);
|
|
+ if (IS_ERR(kmsg)) {
|
|
+ ret = PTR_ERR(kmsg);
|
|
+ kmsg = NULL;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ if (kmsg->msg.dst_id == KDBUS_DST_ID_BROADCAST) {
|
|
+ down_read(&conn->ep->bus->name_registry->rwlock);
|
|
+ kdbus_bus_broadcast(conn->ep->bus, conn, kmsg);
|
|
+ up_read(&conn->ep->bus->name_registry->rwlock);
|
|
+ } else if (cmd->flags & KDBUS_SEND_SYNC_REPLY) {
|
|
+ struct kdbus_reply *r;
|
|
+ ktime_t exp;
|
|
+
|
|
+ exp = ns_to_ktime(kmsg->msg.timeout_ns);
|
|
+ r = kdbus_conn_call(conn, kmsg, exp);
|
|
+ if (IS_ERR(r)) {
|
|
+ ret = PTR_ERR(r);
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ ret = kdbus_conn_wait_reply(conn, cmd, f, cancel_fd, r, exp);
|
|
+ kdbus_reply_unref(r);
|
|
+ if (ret < 0)
|
|
+ goto exit;
|
|
+ } else if ((kmsg->msg.flags & KDBUS_MSG_EXPECT_REPLY) ||
|
|
+ kmsg->msg.cookie_reply == 0) {
|
|
+ ret = kdbus_conn_unicast(conn, kmsg);
|
|
+ if (ret < 0)
|
|
+ goto exit;
|
|
+ } else {
|
|
+ ret = kdbus_conn_reply(conn, kmsg);
|
|
+ if (ret < 0)
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ if (kdbus_member_set_user(&cmd->reply, argp, typeof(*cmd), reply))
|
|
+ ret = -EFAULT;
|
|
+
|
|
+exit:
|
|
+ if (cancel_fd)
|
|
+ fput(cancel_fd);
|
|
+ kdbus_kmsg_free(kmsg);
|
|
+ return kdbus_args_clear(&args, ret);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_cmd_recv() - handle KDBUS_CMD_RECV
|
|
+ * @conn: connection to operate on
|
|
+ * @argp: command payload
|
|
+ *
|
|
+ * Return: 0 on success, negative error code on failure.
|
|
+ */
|
|
+int kdbus_cmd_recv(struct kdbus_conn *conn, void __user *argp)
|
|
+{
|
|
+ struct kdbus_queue_entry *entry;
|
|
+ struct kdbus_cmd_recv *cmd;
|
|
+ int ret;
|
|
+
|
|
+ struct kdbus_arg argv[] = {
|
|
+ { .type = KDBUS_ITEM_NEGOTIATE },
|
|
+ };
|
|
+ struct kdbus_args args = {
|
|
+ .allowed_flags = KDBUS_FLAG_NEGOTIATE |
|
|
+ KDBUS_RECV_PEEK |
|
|
+ KDBUS_RECV_DROP |
|
|
+ KDBUS_RECV_USE_PRIORITY,
|
|
+ .argv = argv,
|
|
+ .argc = ARRAY_SIZE(argv),
|
|
+ };
|
|
+
|
|
+ if (!kdbus_conn_is_ordinary(conn) &&
|
|
+ !kdbus_conn_is_monitor(conn) &&
|
|
+ !kdbus_conn_is_activator(conn))
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
+ ret = kdbus_args_parse(&args, argp, &cmd);
|
|
+ if (ret != 0)
|
|
+ return ret;
|
|
+
|
|
+ cmd->dropped_msgs = 0;
|
|
+ cmd->msg.return_flags = 0;
|
|
+ kdbus_pool_publish_empty(conn->pool, &cmd->msg.offset,
|
|
+ &cmd->msg.msg_size);
|
|
+
|
|
+ /* DROP+priority is not realiably, so prevent it */
|
|
+ if ((cmd->flags & KDBUS_RECV_DROP) &&
|
|
+ (cmd->flags & KDBUS_RECV_USE_PRIORITY)) {
|
|
+ ret = -EINVAL;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&conn->lock);
|
|
+
|
|
+ entry = kdbus_queue_peek(&conn->queue, cmd->priority,
|
|
+ cmd->flags & KDBUS_RECV_USE_PRIORITY);
|
|
+ if (!entry) {
|
|
+ mutex_unlock(&conn->lock);
|
|
+ ret = -EAGAIN;
|
|
+ } else if (cmd->flags & KDBUS_RECV_DROP) {
|
|
+ struct kdbus_reply *reply = kdbus_reply_ref(entry->reply);
|
|
+
|
|
+ kdbus_queue_entry_free(entry);
|
|
+
|
|
+ mutex_unlock(&conn->lock);
|
|
+
|
|
+ if (reply) {
|
|
+ mutex_lock(&reply->reply_dst->lock);
|
|
+ if (!list_empty(&reply->entry)) {
|
|
+ kdbus_reply_unlink(reply);
|
|
+ if (reply->sync)
|
|
+ kdbus_sync_reply_wakeup(reply, -EPIPE);
|
|
+ else
|
|
+ kdbus_notify_reply_dead(conn->ep->bus,
|
|
+ reply->reply_dst->id,
|
|
+ reply->cookie);
|
|
+ }
|
|
+ mutex_unlock(&reply->reply_dst->lock);
|
|
+ kdbus_notify_flush(conn->ep->bus);
|
|
+ }
|
|
+
|
|
+ kdbus_reply_unref(reply);
|
|
+ } else {
|
|
+ bool install_fds;
|
|
+
|
|
+ /*
|
|
+ * PEEK just returns the location of the next message. Do not
|
|
+ * install FDs nor memfds nor anything else. The only
|
|
+ * information of interest should be the message header and
|
|
+ * metadata. Any FD numbers in the payload is undefined for
|
|
+ * PEEK'ed messages.
|
|
+ * Also make sure to never install fds into a connection that
|
|
+ * has refused to receive any. Ordinary connections will not get
|
|
+ * messages with FDs queued (the receiver will get -ECOMM), but
|
|
+ * eavesdroppers might.
|
|
+ */
|
|
+ install_fds = (conn->flags & KDBUS_HELLO_ACCEPT_FD) &&
|
|
+ !(cmd->flags & KDBUS_RECV_PEEK);
|
|
+
|
|
+ ret = kdbus_queue_entry_install(entry,
|
|
+ &cmd->msg.return_flags,
|
|
+ install_fds);
|
|
+ if (ret < 0) {
|
|
+ mutex_unlock(&conn->lock);
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ kdbus_pool_slice_publish(entry->slice, &cmd->msg.offset,
|
|
+ &cmd->msg.msg_size);
|
|
+
|
|
+ if (!(cmd->flags & KDBUS_RECV_PEEK))
|
|
+ kdbus_queue_entry_free(entry);
|
|
+
|
|
+ mutex_unlock(&conn->lock);
|
|
+ }
|
|
+
|
|
+ cmd->dropped_msgs = atomic_xchg(&conn->lost_count, 0);
|
|
+ if (cmd->dropped_msgs > 0)
|
|
+ cmd->return_flags |= KDBUS_RECV_RETURN_DROPPED_MSGS;
|
|
+
|
|
+ if (kdbus_member_set_user(&cmd->msg, argp, typeof(*cmd), msg) ||
|
|
+ kdbus_member_set_user(&cmd->dropped_msgs, argp, typeof(*cmd),
|
|
+ dropped_msgs))
|
|
+ ret = -EFAULT;
|
|
+
|
|
+exit:
|
|
+ return kdbus_args_clear(&args, ret);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_cmd_free() - handle KDBUS_CMD_FREE
|
|
+ * @conn: connection to operate on
|
|
+ * @argp: command payload
|
|
+ *
|
|
+ * Return: 0 on success, negative error code on failure.
|
|
+ */
|
|
+int kdbus_cmd_free(struct kdbus_conn *conn, void __user *argp)
|
|
+{
|
|
+ struct kdbus_cmd_free *cmd;
|
|
+ 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),
|
|
+ };
|
|
+
|
|
+ if (!kdbus_conn_is_ordinary(conn) &&
|
|
+ !kdbus_conn_is_monitor(conn) &&
|
|
+ !kdbus_conn_is_activator(conn))
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
+ ret = kdbus_args_parse(&args, argp, &cmd);
|
|
+ if (ret != 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = kdbus_pool_release_offset(conn->pool, cmd->offset);
|
|
+
|
|
+ return kdbus_args_clear(&args, ret);
|
|
+}
|
|
diff --git a/ipc/kdbus/connection.h b/ipc/kdbus/connection.h
|
|
new file mode 100644
|
|
index 000000000000..d1ffe909cb31
|
|
--- /dev/null
|
|
+++ b/ipc/kdbus/connection.h
|
|
@@ -0,0 +1,257 @@
|
|
+/*
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#ifndef __KDBUS_CONNECTION_H
|
|
+#define __KDBUS_CONNECTION_H
|
|
+
|
|
+#include <linux/atomic.h>
|
|
+#include <linux/kref.h>
|
|
+#include <linux/lockdep.h>
|
|
+#include <linux/path.h>
|
|
+
|
|
+#include "limits.h"
|
|
+#include "metadata.h"
|
|
+#include "pool.h"
|
|
+#include "queue.h"
|
|
+#include "util.h"
|
|
+
|
|
+#define KDBUS_HELLO_SPECIAL_CONN (KDBUS_HELLO_ACTIVATOR | \
|
|
+ KDBUS_HELLO_POLICY_HOLDER | \
|
|
+ KDBUS_HELLO_MONITOR)
|
|
+
|
|
+struct kdbus_quota;
|
|
+struct kdbus_kmsg;
|
|
+
|
|
+/**
|
|
+ * struct kdbus_conn - connection to a bus
|
|
+ * @kref: Reference count
|
|
+ * @active: Active references to the connection
|
|
+ * @id: Connection ID
|
|
+ * @flags: KDBUS_HELLO_* flags
|
|
+ * @attach_flags_send: KDBUS_ATTACH_* flags for sending
|
|
+ * @attach_flags_recv: KDBUS_ATTACH_* flags for receiving
|
|
+ * @description: Human-readable connection description, used for
|
|
+ * debugging. This field is only set when the
|
|
+ * connection is created.
|
|
+ * @ep: The endpoint this connection belongs to
|
|
+ * @lock: Connection data lock
|
|
+ * @hentry: Entry in ID <-> connection map
|
|
+ * @ep_entry: Entry in endpoint
|
|
+ * @monitor_entry: Entry in monitor, if the connection is a monitor
|
|
+ * @reply_list: List of connections this connection should
|
|
+ * reply to
|
|
+ * @work: Delayed work to handle timeouts
|
|
+ * activator for
|
|
+ * @match_db: Subscription filter to broadcast messages
|
|
+ * @meta: Active connection creator's metadata/credentials,
|
|
+ * either from the handle or from HELLO
|
|
+ * @pool: The user's buffer to receive messages
|
|
+ * @user: Owner of the connection
|
|
+ * @cred: The credentials of the connection at creation time
|
|
+ * @name_count: Number of owned well-known names
|
|
+ * @request_count: Number of pending requests issued by this
|
|
+ * connection that are waiting for replies from
|
|
+ * other peers
|
|
+ * @lost_count: Number of lost broadcast messages
|
|
+ * @wait: Wake up this endpoint
|
|
+ * @queue: The message queue associated with this connection
|
|
+ * @quota: Array of per-user quota indexed by user->id
|
|
+ * @n_quota: Number of elements in quota array
|
|
+ * @activator_of: Well-known name entry this connection acts as an
|
|
+ * @names_list: List of well-known names
|
|
+ * @names_queue_list: Well-known names this connection waits for
|
|
+ * @privileged: Whether this connection is privileged on the bus
|
|
+ * @faked_meta: Whether the metadata was faked on HELLO
|
|
+ */
|
|
+struct kdbus_conn {
|
|
+ struct kref kref;
|
|
+ atomic_t active;
|
|
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
|
+ struct lockdep_map dep_map;
|
|
+#endif
|
|
+ u64 id;
|
|
+ u64 flags;
|
|
+ atomic64_t attach_flags_send;
|
|
+ atomic64_t attach_flags_recv;
|
|
+ const char *description;
|
|
+ struct kdbus_ep *ep;
|
|
+ struct mutex lock;
|
|
+ struct hlist_node hentry;
|
|
+ struct list_head ep_entry;
|
|
+ struct list_head monitor_entry;
|
|
+ struct list_head reply_list;
|
|
+ struct delayed_work work;
|
|
+ struct kdbus_match_db *match_db;
|
|
+ struct kdbus_meta_proc *meta;
|
|
+ struct kdbus_pool *pool;
|
|
+ struct kdbus_user *user;
|
|
+ const struct cred *cred;
|
|
+ atomic_t name_count;
|
|
+ atomic_t request_count;
|
|
+ atomic_t lost_count;
|
|
+ wait_queue_head_t wait;
|
|
+ struct kdbus_queue queue;
|
|
+
|
|
+ struct kdbus_quota *quota;
|
|
+ unsigned int n_quota;
|
|
+
|
|
+ /* protected by registry->rwlock */
|
|
+ struct kdbus_name_entry *activator_of;
|
|
+ struct list_head names_list;
|
|
+ struct list_head names_queue_list;
|
|
+
|
|
+ bool privileged:1;
|
|
+ bool faked_meta:1;
|
|
+};
|
|
+
|
|
+struct kdbus_conn *kdbus_conn_ref(struct kdbus_conn *conn);
|
|
+struct kdbus_conn *kdbus_conn_unref(struct kdbus_conn *conn);
|
|
+bool kdbus_conn_active(const struct kdbus_conn *conn);
|
|
+int kdbus_conn_acquire(struct kdbus_conn *conn);
|
|
+void kdbus_conn_release(struct kdbus_conn *conn);
|
|
+int kdbus_conn_disconnect(struct kdbus_conn *conn, bool ensure_queue_empty);
|
|
+bool kdbus_conn_has_name(struct kdbus_conn *conn, const char *name);
|
|
+int kdbus_conn_quota_inc(struct kdbus_conn *c, struct kdbus_user *u,
|
|
+ size_t memory, size_t fds);
|
|
+void kdbus_conn_quota_dec(struct kdbus_conn *c, struct kdbus_user *u,
|
|
+ size_t memory, size_t fds);
|
|
+void kdbus_conn_lost_message(struct kdbus_conn *c);
|
|
+int kdbus_conn_entry_insert(struct kdbus_conn *conn_src,
|
|
+ struct kdbus_conn *conn_dst,
|
|
+ const struct kdbus_kmsg *kmsg,
|
|
+ struct kdbus_reply *reply);
|
|
+void kdbus_conn_move_messages(struct kdbus_conn *conn_dst,
|
|
+ struct kdbus_conn *conn_src,
|
|
+ u64 name_id);
|
|
+
|
|
+/* policy */
|
|
+bool kdbus_conn_policy_own_name(struct kdbus_conn *conn,
|
|
+ const struct cred *conn_creds,
|
|
+ const char *name);
|
|
+bool kdbus_conn_policy_talk(struct kdbus_conn *conn,
|
|
+ const struct cred *conn_creds,
|
|
+ struct kdbus_conn *to);
|
|
+bool kdbus_conn_policy_see_name_unlocked(struct kdbus_conn *conn,
|
|
+ const struct cred *curr_creds,
|
|
+ const char *name);
|
|
+bool kdbus_conn_policy_see_notification(struct kdbus_conn *conn,
|
|
+ const struct cred *curr_creds,
|
|
+ const struct kdbus_kmsg *kmsg);
|
|
+
|
|
+/* command dispatcher */
|
|
+struct kdbus_conn *kdbus_cmd_hello(struct kdbus_ep *ep, bool privileged,
|
|
+ void __user *argp);
|
|
+int kdbus_cmd_byebye_unlocked(struct kdbus_conn *conn, void __user *argp);
|
|
+int kdbus_cmd_conn_info(struct kdbus_conn *conn, void __user *argp);
|
|
+int kdbus_cmd_update(struct kdbus_conn *conn, void __user *argp);
|
|
+int kdbus_cmd_send(struct kdbus_conn *conn, struct file *f, void __user *argp);
|
|
+int kdbus_cmd_recv(struct kdbus_conn *conn, void __user *argp);
|
|
+int kdbus_cmd_free(struct kdbus_conn *conn, void __user *argp);
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_is_ordinary() - Check if connection is ordinary
|
|
+ * @conn: The connection to check
|
|
+ *
|
|
+ * Return: Non-zero if the connection is an ordinary connection
|
|
+ */
|
|
+static inline int kdbus_conn_is_ordinary(const struct kdbus_conn *conn)
|
|
+{
|
|
+ return !(conn->flags & KDBUS_HELLO_SPECIAL_CONN);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_is_activator() - Check if connection is an activator
|
|
+ * @conn: The connection to check
|
|
+ *
|
|
+ * Return: Non-zero if the connection is an activator
|
|
+ */
|
|
+static inline int kdbus_conn_is_activator(const struct kdbus_conn *conn)
|
|
+{
|
|
+ return conn->flags & KDBUS_HELLO_ACTIVATOR;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_is_policy_holder() - Check if connection is a policy holder
|
|
+ * @conn: The connection to check
|
|
+ *
|
|
+ * Return: Non-zero if the connection is a policy holder
|
|
+ */
|
|
+static inline int kdbus_conn_is_policy_holder(const struct kdbus_conn *conn)
|
|
+{
|
|
+ return conn->flags & KDBUS_HELLO_POLICY_HOLDER;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_is_monitor() - Check if connection is a monitor
|
|
+ * @conn: The connection to check
|
|
+ *
|
|
+ * Return: Non-zero if the connection is a monitor
|
|
+ */
|
|
+static inline int kdbus_conn_is_monitor(const struct kdbus_conn *conn)
|
|
+{
|
|
+ return conn->flags & KDBUS_HELLO_MONITOR;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_lock2() - Lock two connections
|
|
+ * @a: connection A to lock or NULL
|
|
+ * @b: connection B to lock or NULL
|
|
+ *
|
|
+ * Lock two connections at once. As we need to have a stable locking order, we
|
|
+ * always lock the connection with lower memory address first.
|
|
+ */
|
|
+static inline void kdbus_conn_lock2(struct kdbus_conn *a, struct kdbus_conn *b)
|
|
+{
|
|
+ if (a < b) {
|
|
+ if (a)
|
|
+ mutex_lock(&a->lock);
|
|
+ if (b && b != a)
|
|
+ mutex_lock_nested(&b->lock, !!a);
|
|
+ } else {
|
|
+ if (b)
|
|
+ mutex_lock(&b->lock);
|
|
+ if (a && a != b)
|
|
+ mutex_lock_nested(&a->lock, !!b);
|
|
+ }
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_unlock2() - Unlock two connections
|
|
+ * @a: connection A to unlock or NULL
|
|
+ * @b: connection B to unlock or NULL
|
|
+ *
|
|
+ * Unlock two connections at once. See kdbus_conn_lock2().
|
|
+ */
|
|
+static inline void kdbus_conn_unlock2(struct kdbus_conn *a,
|
|
+ struct kdbus_conn *b)
|
|
+{
|
|
+ if (a)
|
|
+ mutex_unlock(&a->lock);
|
|
+ if (b && b != a)
|
|
+ mutex_unlock(&b->lock);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_conn_assert_active() - lockdep assert on active lock
|
|
+ * @conn: connection that shall be active
|
|
+ *
|
|
+ * This verifies via lockdep that the caller holds an active reference to the
|
|
+ * given connection.
|
|
+ */
|
|
+static inline void kdbus_conn_assert_active(struct kdbus_conn *conn)
|
|
+{
|
|
+ lockdep_assert_held(conn);
|
|
+}
|
|
+
|
|
+#endif
|
|
diff --git a/ipc/kdbus/item.c b/ipc/kdbus/item.c
|
|
new file mode 100644
|
|
index 000000000000..745ad5495096
|
|
--- /dev/null
|
|
+++ b/ipc/kdbus/item.c
|
|
@@ -0,0 +1,339 @@
|
|
+/*
|
|
+ * 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 <tixxdz@opendz.org>
|
|
+ *
|
|
+ * 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/ctype.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/string.h>
|
|
+
|
|
+#include "item.h"
|
|
+#include "limits.h"
|
|
+#include "util.h"
|
|
+
|
|
+/*
|
|
+ * This verifies the string at position @str with size @size is properly
|
|
+ * zero-terminated and does not contain a 0-byte but at the end.
|
|
+ */
|
|
+static bool kdbus_str_valid(const char *str, size_t size)
|
|
+{
|
|
+ return size > 0 && memchr(str, '\0', size) == str + size - 1;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_item_validate_name() - validate an item containing a name
|
|
+ * @item: Item to validate
|
|
+ *
|
|
+ * Return: zero on success or an negative error code on failure
|
|
+ */
|
|
+int kdbus_item_validate_name(const struct kdbus_item *item)
|
|
+{
|
|
+ const char *name = item->str;
|
|
+ unsigned int i;
|
|
+ size_t len;
|
|
+
|
|
+ if (item->size < KDBUS_ITEM_HEADER_SIZE + 2)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (item->size > KDBUS_ITEM_HEADER_SIZE +
|
|
+ KDBUS_SYSNAME_MAX_LEN + 1)
|
|
+ return -ENAMETOOLONG;
|
|
+
|
|
+ if (!kdbus_str_valid(name, KDBUS_ITEM_PAYLOAD_SIZE(item)))
|
|
+ return -EINVAL;
|
|
+
|
|
+ len = strlen(name);
|
|
+ if (len == 0)
|
|
+ return -EINVAL;
|
|
+
|
|
+ for (i = 0; i < len; i++) {
|
|
+ if (isalpha(name[i]))
|
|
+ continue;
|
|
+ if (isdigit(name[i]))
|
|
+ continue;
|
|
+ if (name[i] == '_')
|
|
+ continue;
|
|
+ if (i > 0 && i + 1 < len && (name[i] == '-' || name[i] == '.'))
|
|
+ continue;
|
|
+
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_item_validate() - validate a single item
|
|
+ * @item: item to validate
|
|
+ *
|
|
+ * Return: 0 if item is valid, negative error code if not.
|
|
+ */
|
|
+int kdbus_item_validate(const struct kdbus_item *item)
|
|
+{
|
|
+ size_t payload_size = KDBUS_ITEM_PAYLOAD_SIZE(item);
|
|
+ size_t l;
|
|
+ int ret;
|
|
+
|
|
+ BUILD_BUG_ON(KDBUS_ITEM_HEADER_SIZE !=
|
|
+ sizeof(struct kdbus_item_header));
|
|
+
|
|
+ if (item->size < KDBUS_ITEM_HEADER_SIZE)
|
|
+ return -EINVAL;
|
|
+
|
|
+ switch (item->type) {
|
|
+ case KDBUS_ITEM_NEGOTIATE:
|
|
+ if (payload_size % sizeof(u64) != 0)
|
|
+ return -EINVAL;
|
|
+ break;
|
|
+
|
|
+ case KDBUS_ITEM_PAYLOAD_VEC:
|
|
+ if (payload_size != sizeof(struct kdbus_vec))
|
|
+ return -EINVAL;
|
|
+ if (item->vec.size == 0 || item->vec.size > SIZE_MAX)
|
|
+ return -EINVAL;
|
|
+ break;
|
|
+
|
|
+ case KDBUS_ITEM_PAYLOAD_OFF:
|
|
+ if (payload_size != sizeof(struct kdbus_vec))
|
|
+ return -EINVAL;
|
|
+ if (item->vec.size == 0 || item->vec.size > SIZE_MAX)
|
|
+ return -EINVAL;
|
|
+ break;
|
|
+
|
|
+ case KDBUS_ITEM_PAYLOAD_MEMFD:
|
|
+ if (payload_size != sizeof(struct kdbus_memfd))
|
|
+ return -EINVAL;
|
|
+ if (item->memfd.size == 0 || item->memfd.size > SIZE_MAX)
|
|
+ return -EINVAL;
|
|
+ if (item->memfd.fd < 0)
|
|
+ return -EBADF;
|
|
+ break;
|
|
+
|
|
+ case KDBUS_ITEM_FDS:
|
|
+ if (payload_size % sizeof(int) != 0)
|
|
+ return -EINVAL;
|
|
+ break;
|
|
+
|
|
+ case KDBUS_ITEM_CANCEL_FD:
|
|
+ if (payload_size != sizeof(int))
|
|
+ return -EINVAL;
|
|
+ break;
|
|
+
|
|
+ case KDBUS_ITEM_BLOOM_PARAMETER:
|
|
+ if (payload_size != sizeof(struct kdbus_bloom_parameter))
|
|
+ return -EINVAL;
|
|
+ break;
|
|
+
|
|
+ case KDBUS_ITEM_BLOOM_FILTER:
|
|
+ /* followed by the bloom-mask, depends on the bloom-size */
|
|
+ if (payload_size < sizeof(struct kdbus_bloom_filter))
|
|
+ return -EINVAL;
|
|
+ break;
|
|
+
|
|
+ case KDBUS_ITEM_BLOOM_MASK:
|
|
+ /* size depends on bloom-size of bus */
|
|
+ break;
|
|
+
|
|
+ case KDBUS_ITEM_CONN_DESCRIPTION:
|
|
+ case KDBUS_ITEM_MAKE_NAME:
|
|
+ ret = kdbus_item_validate_name(item);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ break;
|
|
+
|
|
+ case KDBUS_ITEM_ATTACH_FLAGS_SEND:
|
|
+ case KDBUS_ITEM_ATTACH_FLAGS_RECV:
|
|
+ case KDBUS_ITEM_ID:
|
|
+ if (payload_size != sizeof(u64))
|
|
+ return -EINVAL;
|
|
+ break;
|
|
+
|
|
+ case KDBUS_ITEM_TIMESTAMP:
|
|
+ if (payload_size != sizeof(struct kdbus_timestamp))
|
|
+ return -EINVAL;
|
|
+ break;
|
|
+
|
|
+ case KDBUS_ITEM_CREDS:
|
|
+ if (payload_size != sizeof(struct kdbus_creds))
|
|
+ return -EINVAL;
|
|
+ break;
|
|
+
|
|
+ case KDBUS_ITEM_AUXGROUPS:
|
|
+ if (payload_size % sizeof(u32) != 0)
|
|
+ return -EINVAL;
|
|
+ break;
|
|
+
|
|
+ case KDBUS_ITEM_NAME:
|
|
+ case KDBUS_ITEM_DST_NAME:
|
|
+ case KDBUS_ITEM_PID_COMM:
|
|
+ case KDBUS_ITEM_TID_COMM:
|
|
+ case KDBUS_ITEM_EXE:
|
|
+ case KDBUS_ITEM_CMDLINE:
|
|
+ case KDBUS_ITEM_CGROUP:
|
|
+ case KDBUS_ITEM_SECLABEL:
|
|
+ if (!kdbus_str_valid(item->str, payload_size))
|
|
+ return -EINVAL;
|
|
+ break;
|
|
+
|
|
+ case KDBUS_ITEM_CAPS:
|
|
+ if (payload_size < sizeof(u32))
|
|
+ return -EINVAL;
|
|
+ if (payload_size < sizeof(u32) +
|
|
+ 4 * CAP_TO_INDEX(item->caps.last_cap) * sizeof(u32))
|
|
+ return -EINVAL;
|
|
+ break;
|
|
+
|
|
+ case KDBUS_ITEM_AUDIT:
|
|
+ if (payload_size != sizeof(struct kdbus_audit))
|
|
+ return -EINVAL;
|
|
+ break;
|
|
+
|
|
+ case KDBUS_ITEM_POLICY_ACCESS:
|
|
+ if (payload_size != sizeof(struct kdbus_policy_access))
|
|
+ return -EINVAL;
|
|
+ break;
|
|
+
|
|
+ case KDBUS_ITEM_NAME_ADD:
|
|
+ case KDBUS_ITEM_NAME_REMOVE:
|
|
+ case KDBUS_ITEM_NAME_CHANGE:
|
|
+ if (payload_size < sizeof(struct kdbus_notify_name_change))
|
|
+ return -EINVAL;
|
|
+ l = payload_size - offsetof(struct kdbus_notify_name_change,
|
|
+ name);
|
|
+ if (l > 0 && !kdbus_str_valid(item->name_change.name, l))
|
|
+ return -EINVAL;
|
|
+ break;
|
|
+
|
|
+ case KDBUS_ITEM_ID_ADD:
|
|
+ case KDBUS_ITEM_ID_REMOVE:
|
|
+ if (payload_size != sizeof(struct kdbus_notify_id_change))
|
|
+ return -EINVAL;
|
|
+ break;
|
|
+
|
|
+ case KDBUS_ITEM_REPLY_TIMEOUT:
|
|
+ case KDBUS_ITEM_REPLY_DEAD:
|
|
+ if (payload_size != 0)
|
|
+ return -EINVAL;
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_items_validate() - validate items passed by user-space
|
|
+ * @items: items to validate
|
|
+ * @items_size: number of items
|
|
+ *
|
|
+ * This verifies that the passed items pointer is consistent and valid.
|
|
+ * Furthermore, each item is checked for:
|
|
+ * - valid "size" value
|
|
+ * - payload is of expected type
|
|
+ * - payload is fully included in the item
|
|
+ * - string payloads are zero-terminated
|
|
+ *
|
|
+ * Return: 0 on success, negative error code on failure.
|
|
+ */
|
|
+int kdbus_items_validate(const struct kdbus_item *items, size_t items_size)
|
|
+{
|
|
+ const struct kdbus_item *item;
|
|
+ int ret;
|
|
+
|
|
+ KDBUS_ITEMS_FOREACH(item, items, items_size) {
|
|
+ if (!KDBUS_ITEM_VALID(item, items, items_size))
|
|
+ return -EINVAL;
|
|
+
|
|
+ ret = kdbus_item_validate(item);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ if (!KDBUS_ITEMS_END(item, items, items_size))
|
|
+ return -EINVAL;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct kdbus_item *kdbus_items_get(const struct kdbus_item *items,
|
|
+ size_t items_size,
|
|
+ unsigned int item_type)
|
|
+{
|
|
+ const struct kdbus_item *iter, *found = NULL;
|
|
+
|
|
+ KDBUS_ITEMS_FOREACH(iter, items, items_size) {
|
|
+ if (iter->type == item_type) {
|
|
+ if (found)
|
|
+ return ERR_PTR(-EEXIST);
|
|
+ found = iter;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return (struct kdbus_item *)found ? : ERR_PTR(-EBADMSG);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_items_get_str() - get string from a list of items
|
|
+ * @items: The items to walk
|
|
+ * @items_size: The size of all items
|
|
+ * @item_type: The item type to look for
|
|
+ *
|
|
+ * This function walks a list of items and searches for items of type
|
|
+ * @item_type. If it finds exactly one such item, @str_ret will be set to
|
|
+ * the .str member of the item.
|
|
+ *
|
|
+ * Return: the string, if the item was found exactly once, ERR_PTR(-EEXIST)
|
|
+ * if the item was found more than once, and ERR_PTR(-EBADMSG) if there was
|
|
+ * no item of the given type.
|
|
+ */
|
|
+const char *kdbus_items_get_str(const struct kdbus_item *items,
|
|
+ size_t items_size,
|
|
+ unsigned int item_type)
|
|
+{
|
|
+ const struct kdbus_item *item;
|
|
+
|
|
+ item = kdbus_items_get(items, items_size, item_type);
|
|
+ return IS_ERR(item) ? ERR_CAST(item) : item->str;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_item_set() - Set item content
|
|
+ * @item: The item to modify
|
|
+ * @type: The item type to set (KDBUS_ITEM_*)
|
|
+ * @data: Data to copy to item->data, may be %NULL
|
|
+ * @len: Number of bytes in @data
|
|
+ *
|
|
+ * This sets type, size and data fields of an item. If @data is NULL, the data
|
|
+ * memory is cleared.
|
|
+ *
|
|
+ * Note that you must align your @data memory to 8 bytes. Trailing padding (in
|
|
+ * case @len is not 8byte aligned) is cleared by this call.
|
|
+ *
|
|
+ * Returns: Pointer to the following item.
|
|
+ */
|
|
+struct kdbus_item *kdbus_item_set(struct kdbus_item *item, u64 type,
|
|
+ const void *data, size_t len)
|
|
+{
|
|
+ item->type = type;
|
|
+ item->size = KDBUS_ITEM_HEADER_SIZE + len;
|
|
+
|
|
+ if (data) {
|
|
+ memcpy(item->data, data, len);
|
|
+ memset(item->data + len, 0, KDBUS_ALIGN8(len) - len);
|
|
+ } else {
|
|
+ memset(item->data, 0, KDBUS_ALIGN8(len));
|
|
+ }
|
|
+
|
|
+ return KDBUS_ITEM_NEXT(item);
|
|
+}
|
|
diff --git a/ipc/kdbus/item.h b/ipc/kdbus/item.h
|
|
new file mode 100644
|
|
index 000000000000..eeefd8beac3b
|
|
--- /dev/null
|
|
+++ b/ipc/kdbus/item.h
|
|
@@ -0,0 +1,64 @@
|
|
+/*
|
|
+ * 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 <tixxdz@opendz.org>
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#ifndef __KDBUS_ITEM_H
|
|
+#define __KDBUS_ITEM_H
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <uapi/linux/kdbus.h>
|
|
+
|
|
+#include "util.h"
|
|
+
|
|
+/* generic access and iterators over a stream of items */
|
|
+#define KDBUS_ITEM_NEXT(_i) (typeof(_i))(((u8 *)_i) + KDBUS_ALIGN8((_i)->size))
|
|
+#define KDBUS_ITEMS_SIZE(_h, _is) ((_h)->size - offsetof(typeof(*_h), _is))
|
|
+#define KDBUS_ITEM_HEADER_SIZE offsetof(struct kdbus_item, data)
|
|
+#define KDBUS_ITEM_SIZE(_s) KDBUS_ALIGN8(KDBUS_ITEM_HEADER_SIZE + (_s))
|
|
+#define KDBUS_ITEM_PAYLOAD_SIZE(_i) ((_i)->size - KDBUS_ITEM_HEADER_SIZE)
|
|
+
|
|
+#define KDBUS_ITEMS_FOREACH(_i, _is, _s) \
|
|
+ for (_i = _is; \
|
|
+ ((u8 *)(_i) < (u8 *)(_is) + (_s)) && \
|
|
+ ((u8 *)(_i) >= (u8 *)(_is)); \
|
|
+ _i = KDBUS_ITEM_NEXT(_i))
|
|
+
|
|
+#define KDBUS_ITEM_VALID(_i, _is, _s) \
|
|
+ ((_i)->size >= KDBUS_ITEM_HEADER_SIZE && \
|
|
+ (u8 *)(_i) + (_i)->size > (u8 *)(_i) && \
|
|
+ (u8 *)(_i) + (_i)->size <= (u8 *)(_is) + (_s) && \
|
|
+ (u8 *)(_i) >= (u8 *)(_is))
|
|
+
|
|
+#define KDBUS_ITEMS_END(_i, _is, _s) \
|
|
+ ((u8 *)_i == ((u8 *)(_is) + KDBUS_ALIGN8(_s)))
|
|
+
|
|
+/**
|
|
+ * struct kdbus_item_header - Describes the fix part of an item
|
|
+ * @size: The total size of the item
|
|
+ * @type: The item type, one of KDBUS_ITEM_*
|
|
+ */
|
|
+struct kdbus_item_header {
|
|
+ u64 size;
|
|
+ u64 type;
|
|
+};
|
|
+
|
|
+int kdbus_item_validate_name(const struct kdbus_item *item);
|
|
+int kdbus_item_validate(const struct kdbus_item *item);
|
|
+int kdbus_items_validate(const struct kdbus_item *items, size_t items_size);
|
|
+const char *kdbus_items_get_str(const struct kdbus_item *items,
|
|
+ size_t items_size,
|
|
+ unsigned int item_type);
|
|
+struct kdbus_item *kdbus_item_set(struct kdbus_item *item, u64 type,
|
|
+ const void *data, size_t len);
|
|
+
|
|
+#endif
|
|
diff --git a/ipc/kdbus/message.c b/ipc/kdbus/message.c
|
|
new file mode 100644
|
|
index 000000000000..80960756a329
|
|
--- /dev/null
|
|
+++ b/ipc/kdbus/message.c
|
|
@@ -0,0 +1,616 @@
|
|
+/*
|
|
+ * 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 <tixxdz@opendz.org>
|
|
+ *
|
|
+ * 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/capability.h>
|
|
+#include <linux/cgroup.h>
|
|
+#include <linux/cred.h>
|
|
+#include <linux/file.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/sched.h>
|
|
+#include <linux/shmem_fs.h>
|
|
+#include <linux/sizes.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/uaccess.h>
|
|
+#include <net/sock.h>
|
|
+
|
|
+#include "bus.h"
|
|
+#include "connection.h"
|
|
+#include "domain.h"
|
|
+#include "endpoint.h"
|
|
+#include "handle.h"
|
|
+#include "item.h"
|
|
+#include "match.h"
|
|
+#include "message.h"
|
|
+#include "names.h"
|
|
+#include "policy.h"
|
|
+
|
|
+#define KDBUS_KMSG_HEADER_SIZE offsetof(struct kdbus_kmsg, msg)
|
|
+
|
|
+static struct kdbus_msg_resources *kdbus_msg_resources_new(void)
|
|
+{
|
|
+ struct kdbus_msg_resources *r;
|
|
+
|
|
+ r = kzalloc(sizeof(*r), GFP_KERNEL);
|
|
+ if (!r)
|
|
+ return ERR_PTR(-ENOMEM);
|
|
+
|
|
+ kref_init(&r->kref);
|
|
+
|
|
+ return r;
|
|
+}
|
|
+
|
|
+static void __kdbus_msg_resources_free(struct kref *kref)
|
|
+{
|
|
+ struct kdbus_msg_resources *r =
|
|
+ container_of(kref, struct kdbus_msg_resources, kref);
|
|
+ size_t i;
|
|
+
|
|
+ for (i = 0; i < r->data_count; ++i) {
|
|
+ switch (r->data[i].type) {
|
|
+ case KDBUS_MSG_DATA_VEC:
|
|
+ /* nothing to do */
|
|
+ break;
|
|
+ case KDBUS_MSG_DATA_MEMFD:
|
|
+ if (r->data[i].memfd.file)
|
|
+ fput(r->data[i].memfd.file);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < r->fds_count; i++)
|
|
+ if (r->fds[i])
|
|
+ fput(r->fds[i]);
|
|
+
|
|
+ kfree(r->dst_name);
|
|
+ kfree(r->data);
|
|
+ kfree(r->fds);
|
|
+ kfree(r);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_msg_resources_ref() - Acquire reference to msg resources
|
|
+ * @r: resources to acquire ref to
|
|
+ *
|
|
+ * Return: The acquired resource
|
|
+ */
|
|
+struct kdbus_msg_resources *
|
|
+kdbus_msg_resources_ref(struct kdbus_msg_resources *r)
|
|
+{
|
|
+ if (r)
|
|
+ kref_get(&r->kref);
|
|
+ return r;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_msg_resources_unref() - Drop reference to msg resources
|
|
+ * @r: resources to drop reference of
|
|
+ *
|
|
+ * Return: NULL
|
|
+ */
|
|
+struct kdbus_msg_resources *
|
|
+kdbus_msg_resources_unref(struct kdbus_msg_resources *r)
|
|
+{
|
|
+ if (r)
|
|
+ kref_put(&r->kref, __kdbus_msg_resources_free);
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_kmsg_free() - free allocated message
|
|
+ * @kmsg: Message
|
|
+ */
|
|
+void kdbus_kmsg_free(struct kdbus_kmsg *kmsg)
|
|
+{
|
|
+ if (!kmsg)
|
|
+ return;
|
|
+
|
|
+ kdbus_msg_resources_unref(kmsg->res);
|
|
+ kdbus_meta_conn_unref(kmsg->conn_meta);
|
|
+ kdbus_meta_proc_unref(kmsg->proc_meta);
|
|
+ kfree(kmsg->iov);
|
|
+ kfree(kmsg);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_kmsg_new() - allocate message
|
|
+ * @bus: Bus this message is allocated on
|
|
+ * @extra_size: Additional size to reserve for data
|
|
+ *
|
|
+ * Return: new kdbus_kmsg on success, ERR_PTR on failure.
|
|
+ */
|
|
+struct kdbus_kmsg *kdbus_kmsg_new(struct kdbus_bus *bus, size_t extra_size)
|
|
+{
|
|
+ struct kdbus_kmsg *m;
|
|
+ size_t size;
|
|
+ int ret;
|
|
+
|
|
+ size = sizeof(struct kdbus_kmsg) + KDBUS_ITEM_SIZE(extra_size);
|
|
+ m = kzalloc(size, GFP_KERNEL);
|
|
+ if (!m)
|
|
+ return ERR_PTR(-ENOMEM);
|
|
+
|
|
+ m->seq = atomic64_inc_return(&bus->domain->last_id);
|
|
+ m->msg.size = size - KDBUS_KMSG_HEADER_SIZE;
|
|
+ m->msg.items[0].size = KDBUS_ITEM_SIZE(extra_size);
|
|
+
|
|
+ m->proc_meta = kdbus_meta_proc_new();
|
|
+ if (IS_ERR(m->proc_meta)) {
|
|
+ ret = PTR_ERR(m->proc_meta);
|
|
+ m->proc_meta = NULL;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ m->conn_meta = kdbus_meta_conn_new();
|
|
+ if (IS_ERR(m->conn_meta)) {
|
|
+ ret = PTR_ERR(m->conn_meta);
|
|
+ m->conn_meta = NULL;
|
|
+ goto exit;
|
|
+ }
|
|
+
|
|
+ return m;
|
|
+
|
|
+exit:
|
|
+ kdbus_kmsg_free(m);
|
|
+ return ERR_PTR(ret);
|
|
+}
|
|
+
|
|
+static int kdbus_handle_check_file(struct file *file)
|
|
+{
|
|
+ struct inode *inode = file_inode(file);
|
|
+ struct socket *sock;
|
|
+
|
|
+ /*
|
|
+ * Don't allow file descriptors in the transport that themselves allow
|
|
+ * file descriptor queueing. This will eventually be allowed once both
|
|
+ * unix domain sockets and kdbus share a generic garbage collector.
|
|
+ */
|
|
+
|
|
+ if (file->f_op == &kdbus_handle_ops)
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
+ if (!S_ISSOCK(inode->i_mode))
|
|
+ return 0;
|
|
+
|
|
+ if (file->f_mode & FMODE_PATH)
|
|
+ return 0;
|
|
+
|
|
+ sock = SOCKET_I(inode);
|
|
+ if (sock->sk && sock->ops && sock->ops->family == PF_UNIX)
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const char * const zeros = "\0\0\0\0\0\0\0";
|
|
+
|
|
+/*
|
|
+ * kdbus_msg_scan_items() - validate incoming data and prepare parsing
|
|
+ * @kmsg: Message
|
|
+ * @bus: Bus the message is sent over
|
|
+ *
|
|
+ * Return: 0 on success, negative errno on failure.
|
|
+ *
|
|
+ * Files references in MEMFD or FDS items are pinned.
|
|
+ *
|
|
+ * On errors, the caller should drop any taken reference with
|
|
+ * kdbus_kmsg_free()
|
|
+ */
|
|
+static int kdbus_msg_scan_items(struct kdbus_kmsg *kmsg,
|
|
+ struct kdbus_bus *bus)
|
|
+{
|
|
+ struct kdbus_msg_resources *res = kmsg->res;
|
|
+ const struct kdbus_msg *msg = &kmsg->msg;
|
|
+ const struct kdbus_item *item;
|
|
+ size_t n, n_vecs, n_memfds;
|
|
+ bool has_bloom = false;
|
|
+ bool has_name = false;
|
|
+ bool has_fds = false;
|
|
+ bool is_broadcast;
|
|
+ bool is_signal;
|
|
+ u64 vec_size;
|
|
+
|
|
+ is_broadcast = (msg->dst_id == KDBUS_DST_ID_BROADCAST);
|
|
+ is_signal = !!(msg->flags & KDBUS_MSG_SIGNAL);
|
|
+
|
|
+ /* count data payloads */
|
|
+ n_vecs = 0;
|
|
+ n_memfds = 0;
|
|
+ KDBUS_ITEMS_FOREACH(item, msg->items, KDBUS_ITEMS_SIZE(msg, items)) {
|
|
+ switch (item->type) {
|
|
+ case KDBUS_ITEM_PAYLOAD_VEC:
|
|
+ ++n_vecs;
|
|
+ break;
|
|
+ case KDBUS_ITEM_PAYLOAD_MEMFD:
|
|
+ ++n_memfds;
|
|
+ if (item->memfd.size % 8)
|
|
+ ++n_vecs;
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ n = n_vecs + n_memfds;
|
|
+ if (n > 0) {
|
|
+ res->data = kcalloc(n, sizeof(*res->data), GFP_KERNEL);
|
|
+ if (!res->data)
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ if (n_vecs > 0) {
|
|
+ kmsg->iov = kcalloc(n_vecs, sizeof(*kmsg->iov), GFP_KERNEL);
|
|
+ if (!kmsg->iov)
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ /* import data payloads */
|
|
+ n = 0;
|
|
+ vec_size = 0;
|
|
+ KDBUS_ITEMS_FOREACH(item, msg->items, KDBUS_ITEMS_SIZE(msg, items)) {
|
|
+ size_t payload_size = KDBUS_ITEM_PAYLOAD_SIZE(item);
|
|
+ struct iovec *iov = kmsg->iov + kmsg->iov_count;
|
|
+
|
|
+ if (++n > KDBUS_MSG_MAX_ITEMS)
|
|
+ return -E2BIG;
|
|
+
|
|
+ switch (item->type) {
|
|
+ case KDBUS_ITEM_PAYLOAD_VEC: {
|
|
+ struct kdbus_msg_data *d = res->data + res->data_count;
|
|
+ void __force __user *ptr = KDBUS_PTR(item->vec.address);
|
|
+ size_t size = item->vec.size;
|
|
+
|
|
+ if (vec_size + size < vec_size)
|
|
+ return -EMSGSIZE;
|
|
+ if (vec_size + size > KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE)
|
|
+ return -EMSGSIZE;
|
|
+
|
|
+ d->type = KDBUS_MSG_DATA_VEC;
|
|
+ d->size = size;
|
|
+
|
|
+ if (ptr) {
|
|
+ if (unlikely(!access_ok(VERIFY_READ, ptr,
|
|
+ size)))
|
|
+ return -EFAULT;
|
|
+
|
|
+ d->vec.off = kmsg->pool_size;
|
|
+ iov->iov_base = ptr;
|
|
+ iov->iov_len = size;
|
|
+ } else {
|
|
+ d->vec.off = ~0ULL;
|
|
+ iov->iov_base = (char __user *)zeros;
|
|
+ iov->iov_len = size % 8;
|
|
+ }
|
|
+
|
|
+ if (kmsg->pool_size + iov->iov_len < kmsg->pool_size)
|
|
+ return -EMSGSIZE;
|
|
+
|
|
+ kmsg->pool_size += iov->iov_len;
|
|
+ ++kmsg->iov_count;
|
|
+ ++res->vec_count;
|
|
+ ++res->data_count;
|
|
+ vec_size += size;
|
|
+
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ case KDBUS_ITEM_PAYLOAD_MEMFD: {
|
|
+ struct kdbus_msg_data *d = res->data + res->data_count;
|
|
+ u64 start = item->memfd.start;
|
|
+ u64 size = item->memfd.size;
|
|
+ size_t pad = size % 8;
|
|
+ int seals, mask;
|
|
+ struct file *f;
|
|
+
|
|
+ if (kmsg->pool_size + size % 8 < kmsg->pool_size)
|
|
+ return -EMSGSIZE;
|
|
+ if (start + size < start)
|
|
+ return -EMSGSIZE;
|
|
+
|
|
+ if (item->memfd.fd < 0)
|
|
+ return -EBADF;
|
|
+
|
|
+ if (res->memfd_count >= KDBUS_MSG_MAX_MEMFD_ITEMS)
|
|
+ return -E2BIG;
|
|
+
|
|
+ f = fget(item->memfd.fd);
|
|
+ if (!f)
|
|
+ return -EBADF;
|
|
+
|
|
+ if (pad) {
|
|
+ iov->iov_base = (char __user *)zeros;
|
|
+ iov->iov_len = pad;
|
|
+
|
|
+ kmsg->pool_size += pad;
|
|
+ ++kmsg->iov_count;
|
|
+ }
|
|
+
|
|
+ ++res->data_count;
|
|
+ ++res->memfd_count;
|
|
+
|
|
+ d->type = KDBUS_MSG_DATA_MEMFD;
|
|
+ d->size = size;
|
|
+ d->memfd.start = start;
|
|
+ d->memfd.file = f;
|
|
+
|
|
+ /*
|
|
+ * We only accept a sealed memfd file whose content
|
|
+ * cannot be altered by the sender or anybody else
|
|
+ * while it is shared or in-flight. Other files need
|
|
+ * to be passed with KDBUS_MSG_FDS.
|
|
+ */
|
|
+ seals = shmem_get_seals(f);
|
|
+ if (seals < 0)
|
|
+ return -EMEDIUMTYPE;
|
|
+
|
|
+ mask = F_SEAL_SHRINK | F_SEAL_GROW |
|
|
+ F_SEAL_WRITE | F_SEAL_SEAL;
|
|
+ if ((seals & mask) != mask)
|
|
+ return -ETXTBSY;
|
|
+
|
|
+ if (start + size > (u64)i_size_read(file_inode(f)))
|
|
+ return -EBADF;
|
|
+
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ case KDBUS_ITEM_FDS: {
|
|
+ unsigned int i;
|
|
+ unsigned int fds_count = payload_size / sizeof(int);
|
|
+
|
|
+ /* do not allow multiple fd arrays */
|
|
+ if (has_fds)
|
|
+ return -EEXIST;
|
|
+ has_fds = true;
|
|
+
|
|
+ /* Do not allow to broadcast file descriptors */
|
|
+ if (is_broadcast)
|
|
+ return -ENOTUNIQ;
|
|
+
|
|
+ if (fds_count > KDBUS_CONN_MAX_FDS_PER_USER)
|
|
+ return -EMFILE;
|
|
+
|
|
+ res->fds = kcalloc(fds_count, sizeof(struct file *),
|
|
+ GFP_KERNEL);
|
|
+ if (!res->fds)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ for (i = 0; i < fds_count; i++) {
|
|
+ int fd = item->fds[i];
|
|
+ int ret;
|
|
+
|
|
+ /*
|
|
+ * Verify the fd and increment the usage count.
|
|
+ * Use fget_raw() to allow passing O_PATH fds.
|
|
+ */
|
|
+ if (fd < 0)
|
|
+ return -EBADF;
|
|
+
|
|
+ res->fds[i] = fget_raw(fd);
|
|
+ if (!res->fds[i])
|
|
+ return -EBADF;
|
|
+
|
|
+ res->fds_count++;
|
|
+
|
|
+ ret = kdbus_handle_check_file(res->fds[i]);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ case KDBUS_ITEM_BLOOM_FILTER: {
|
|
+ u64 bloom_size;
|
|
+
|
|
+ /* do not allow multiple bloom filters */
|
|
+ if (has_bloom)
|
|
+ return -EEXIST;
|
|
+ has_bloom = true;
|
|
+
|
|
+ bloom_size = payload_size -
|
|
+ offsetof(struct kdbus_bloom_filter, data);
|
|
+
|
|
+ /*
|
|
+ * Allow only bloom filter sizes of a multiple of 64bit.
|
|
+ */
|
|
+ if (!KDBUS_IS_ALIGNED8(bloom_size))
|
|
+ return -EFAULT;
|
|
+
|
|
+ /* do not allow mismatching bloom filter sizes */
|
|
+ if (bloom_size != bus->bloom.size)
|
|
+ return -EDOM;
|
|
+
|
|
+ kmsg->bloom_filter = &item->bloom_filter;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ case KDBUS_ITEM_DST_NAME:
|
|
+ /* do not allow multiple names */
|
|
+ if (has_name)
|
|
+ return -EEXIST;
|
|
+ has_name = true;
|
|
+
|
|
+ if (!kdbus_name_is_valid(item->str, false))
|
|
+ return -EINVAL;
|
|
+
|
|
+ res->dst_name = kstrdup(item->str, GFP_KERNEL);
|
|
+ if (!res->dst_name)
|
|
+ return -ENOMEM;
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* name is needed if no ID is given */
|
|
+ if (msg->dst_id == KDBUS_DST_ID_NAME && !has_name)
|
|
+ return -EDESTADDRREQ;
|
|
+
|
|
+ if (is_broadcast) {
|
|
+ /* Broadcasts can't take names */
|
|
+ if (has_name)
|
|
+ return -EBADMSG;
|
|
+
|
|
+ /* All broadcasts have to be signals */
|
|
+ if (!is_signal)
|
|
+ return -EBADMSG;
|
|
+
|
|
+ /* Timeouts are not allowed for broadcasts */
|
|
+ if (msg->timeout_ns > 0)
|
|
+ return -ENOTUNIQ;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Signal messages require a bloom filter, and bloom filters are
|
|
+ * only valid with signals.
|
|
+ */
|
|
+ if (is_signal ^ has_bloom)
|
|
+ return -EBADMSG;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_kmsg_new_from_cmd() - create kernel message from send payload
|
|
+ * @conn: Connection
|
|
+ * @cmd_send: Payload of KDBUS_CMD_SEND
|
|
+ *
|
|
+ * Return: a new kdbus_kmsg on success, ERR_PTR on failure.
|
|
+ */
|
|
+struct kdbus_kmsg *kdbus_kmsg_new_from_cmd(struct kdbus_conn *conn,
|
|
+ struct kdbus_cmd_send *cmd_send)
|
|
+{
|
|
+ struct kdbus_kmsg *m;
|
|
+ u64 size;
|
|
+ int ret;
|
|
+
|
|
+ ret = kdbus_copy_from_user(&size, KDBUS_PTR(cmd_send->msg_address),
|
|
+ sizeof(size));
|
|
+ if (ret < 0)
|
|
+ return ERR_PTR(ret);
|
|
+
|
|
+ if (size < sizeof(struct kdbus_msg) || size > KDBUS_MSG_MAX_SIZE)
|
|
+ return ERR_PTR(-EINVAL);
|
|
+
|
|
+ m = kmalloc(size + KDBUS_KMSG_HEADER_SIZE, GFP_KERNEL);
|
|
+ if (!m)
|
|
+ return ERR_PTR(-ENOMEM);
|
|
+
|
|
+ memset(m, 0, KDBUS_KMSG_HEADER_SIZE);
|
|
+ m->seq = atomic64_inc_return(&conn->ep->bus->domain->last_id);
|
|
+
|
|
+ m->proc_meta = kdbus_meta_proc_new();
|
|
+ if (IS_ERR(m->proc_meta)) {
|
|
+ ret = PTR_ERR(m->proc_meta);
|
|
+ m->proc_meta = NULL;
|
|
+ goto exit_free;
|
|
+ }
|
|
+
|
|
+ m->conn_meta = kdbus_meta_conn_new();
|
|
+ if (IS_ERR(m->conn_meta)) {
|
|
+ ret = PTR_ERR(m->conn_meta);
|
|
+ m->conn_meta = NULL;
|
|
+ goto exit_free;
|
|
+ }
|
|
+
|
|
+ if (copy_from_user(&m->msg, KDBUS_PTR(cmd_send->msg_address), size)) {
|
|
+ ret = -EFAULT;
|
|
+ goto exit_free;
|
|
+ }
|
|
+
|
|
+ if (m->msg.size != size) {
|
|
+ ret = -EINVAL;
|
|
+ goto exit_free;
|
|
+ }
|
|
+
|
|
+ if (m->msg.flags & ~(KDBUS_MSG_EXPECT_REPLY |
|
|
+ KDBUS_MSG_NO_AUTO_START |
|
|
+ KDBUS_MSG_SIGNAL)) {
|
|
+ ret = -EINVAL;
|
|
+ goto exit_free;
|
|
+ }
|
|
+
|
|
+ ret = kdbus_items_validate(m->msg.items,
|
|
+ KDBUS_ITEMS_SIZE(&m->msg, items));
|
|
+ if (ret < 0)
|
|
+ goto exit_free;
|
|
+
|
|
+ m->res = kdbus_msg_resources_new();
|
|
+ if (IS_ERR(m->res)) {
|
|
+ ret = PTR_ERR(m->res);
|
|
+ m->res = NULL;
|
|
+ goto exit_free;
|
|
+ }
|
|
+
|
|
+ /* do not accept kernel-generated messages */
|
|
+ if (m->msg.payload_type == KDBUS_PAYLOAD_KERNEL) {
|
|
+ ret = -EINVAL;
|
|
+ goto exit_free;
|
|
+ }
|
|
+
|
|
+ if (m->msg.flags & KDBUS_MSG_EXPECT_REPLY) {
|
|
+ /* requests for replies need timeout and cookie */
|
|
+ if (m->msg.timeout_ns == 0 || m->msg.cookie == 0) {
|
|
+ ret = -EINVAL;
|
|
+ goto exit_free;
|
|
+ }
|
|
+
|
|
+ /* replies may not be expected for broadcasts */
|
|
+ if (m->msg.dst_id == KDBUS_DST_ID_BROADCAST) {
|
|
+ ret = -ENOTUNIQ;
|
|
+ goto exit_free;
|
|
+ }
|
|
+
|
|
+ /* replies may not be expected for signals */
|
|
+ if (m->msg.flags & KDBUS_MSG_SIGNAL) {
|
|
+ ret = -EINVAL;
|
|
+ goto exit_free;
|
|
+ }
|
|
+ } else {
|
|
+ /*
|
|
+ * KDBUS_SEND_SYNC_REPLY is only valid together with
|
|
+ * KDBUS_MSG_EXPECT_REPLY
|
|
+ */
|
|
+ if (cmd_send->flags & KDBUS_SEND_SYNC_REPLY) {
|
|
+ ret = -EINVAL;
|
|
+ goto exit_free;
|
|
+ }
|
|
+
|
|
+ /* replies cannot be signals */
|
|
+ if (m->msg.cookie_reply && (m->msg.flags & KDBUS_MSG_SIGNAL)) {
|
|
+ ret = -EINVAL;
|
|
+ goto exit_free;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ ret = kdbus_msg_scan_items(m, conn->ep->bus);
|
|
+ if (ret < 0)
|
|
+ goto exit_free;
|
|
+
|
|
+ /* patch-in the source of this message */
|
|
+ if (m->msg.src_id > 0 && m->msg.src_id != conn->id) {
|
|
+ ret = -EINVAL;
|
|
+ goto exit_free;
|
|
+ }
|
|
+ m->msg.src_id = conn->id;
|
|
+
|
|
+ return m;
|
|
+
|
|
+exit_free:
|
|
+ kdbus_kmsg_free(m);
|
|
+ return ERR_PTR(ret);
|
|
+}
|
|
diff --git a/ipc/kdbus/message.h b/ipc/kdbus/message.h
|
|
new file mode 100644
|
|
index 000000000000..af4775850235
|
|
--- /dev/null
|
|
+++ b/ipc/kdbus/message.h
|
|
@@ -0,0 +1,133 @@
|
|
+/*
|
|
+ * 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
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#ifndef __KDBUS_MESSAGE_H
|
|
+#define __KDBUS_MESSAGE_H
|
|
+
|
|
+#include "util.h"
|
|
+#include "metadata.h"
|
|
+
|
|
+/**
|
|
+ * enum kdbus_msg_data_type - Type of kdbus_msg_data payloads
|
|
+ * @KDBUS_MSG_DATA_VEC: Data vector provided by user-space
|
|
+ * @KDBUS_MSG_DATA_MEMFD: Memfd payload
|
|
+ */
|
|
+enum kdbus_msg_data_type {
|
|
+ KDBUS_MSG_DATA_VEC,
|
|
+ KDBUS_MSG_DATA_MEMFD,
|
|
+};
|
|
+
|
|
+/**
|
|
+ * struct kdbus_msg_data - Data payload as stored by messages
|
|
+ * @type: Type of payload (KDBUS_MSG_DATA_*)
|
|
+ * @size: Size of the described payload
|
|
+ * @off: The offset, relative to the vec slice
|
|
+ * @start: Offset inside the memfd
|
|
+ * @file: Backing file referenced by the memfd
|
|
+ */
|
|
+struct kdbus_msg_data {
|
|
+ unsigned int type;
|
|
+ u64 size;
|
|
+
|
|
+ union {
|
|
+ struct {
|
|
+ u64 off;
|
|
+ } vec;
|
|
+ struct {
|
|
+ u64 start;
|
|
+ struct file *file;
|
|
+ } memfd;
|
|
+ };
|
|
+};
|
|
+
|
|
+/**
|
|
+ * struct kdbus_kmsg_resources - resources of a message
|
|
+ * @kref: Reference counter
|
|
+ * @dst_name: Short-cut to msg for faster lookup
|
|
+ * @fds: Array of file descriptors to pass
|
|
+ * @fds_count: Number of file descriptors to pass
|
|
+ * @data: Array of data payloads
|
|
+ * @vec_count: Number of VEC entries
|
|
+ * @memfd_count: Number of MEMFD entries in @data
|
|
+ * @data_count: Sum of @vec_count + @memfd_count
|
|
+ */
|
|
+struct kdbus_msg_resources {
|
|
+ struct kref kref;
|
|
+ const char *dst_name;
|
|
+
|
|
+ struct file **fds;
|
|
+ unsigned int fds_count;
|
|
+
|
|
+ struct kdbus_msg_data *data;
|
|
+ size_t vec_count;
|
|
+ size_t memfd_count;
|
|
+ size_t data_count;
|
|
+};
|
|
+
|
|
+struct kdbus_msg_resources *
|
|
+kdbus_msg_resources_ref(struct kdbus_msg_resources *r);
|
|
+struct kdbus_msg_resources *
|
|
+kdbus_msg_resources_unref(struct kdbus_msg_resources *r);
|
|
+
|
|
+/**
|
|
+ * struct kdbus_kmsg - internal message handling data
|
|
+ * @seq: Domain-global message sequence number
|
|
+ * @notify_type: Short-cut for faster lookup
|
|
+ * @notify_old_id: Short-cut for faster lookup
|
|
+ * @notify_new_id: Short-cut for faster lookup
|
|
+ * @notify_name: Short-cut for faster lookup
|
|
+ * @dst_name_id: Short-cut to msg for faster lookup
|
|
+ * @bloom_filter: Bloom filter to match message properties
|
|
+ * @bloom_generation: Generation of bloom element set
|
|
+ * @notify_entry: List of kernel-generated notifications
|
|
+ * @iov: Array of iovec, describing the payload to copy
|
|
+ * @iov_count: Number of array members in @iov
|
|
+ * @pool_size: Overall size of inlined data referenced by @iov
|
|
+ * @proc_meta: Appended SCM-like metadata of the sending process
|
|
+ * @conn_meta: Appended SCM-like metadata of the sending connection
|
|
+ * @res: Message resources
|
|
+ * @msg: Message from or to userspace
|
|
+ */
|
|
+struct kdbus_kmsg {
|
|
+ u64 seq;
|
|
+ u64 notify_type;
|
|
+ u64 notify_old_id;
|
|
+ u64 notify_new_id;
|
|
+ const char *notify_name;
|
|
+
|
|
+ u64 dst_name_id;
|
|
+ const struct kdbus_bloom_filter *bloom_filter;
|
|
+ u64 bloom_generation;
|
|
+ struct list_head notify_entry;
|
|
+
|
|
+ struct iovec *iov;
|
|
+ size_t iov_count;
|
|
+ u64 pool_size;
|
|
+
|
|
+ struct kdbus_meta_proc *proc_meta;
|
|
+ struct kdbus_meta_conn *conn_meta;
|
|
+ struct kdbus_msg_resources *res;
|
|
+
|
|
+ /* variable size, must be the last member */
|
|
+ struct kdbus_msg msg;
|
|
+};
|
|
+
|
|
+struct kdbus_bus;
|
|
+struct kdbus_conn;
|
|
+
|
|
+struct kdbus_kmsg *kdbus_kmsg_new(struct kdbus_bus *bus, size_t extra_size);
|
|
+struct kdbus_kmsg *kdbus_kmsg_new_from_cmd(struct kdbus_conn *conn,
|
|
+ struct kdbus_cmd_send *cmd_send);
|
|
+void kdbus_kmsg_free(struct kdbus_kmsg *kmsg);
|
|
+
|
|
+#endif
|
|
diff --git a/ipc/kdbus/queue.c b/ipc/kdbus/queue.c
|
|
new file mode 100644
|
|
index 000000000000..a449464a3975
|
|
--- /dev/null
|
|
+++ b/ipc/kdbus/queue.c
|
|
@@ -0,0 +1,678 @@
|
|
+/*
|
|
+ * 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 <tixxdz@opendz.org>
|
|
+ *
|
|
+ * 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/audit.h>
|
|
+#include <linux/file.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/hashtable.h>
|
|
+#include <linux/idr.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/math64.h>
|
|
+#include <linux/mm.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/poll.h>
|
|
+#include <linux/sched.h>
|
|
+#include <linux/sizes.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/syscalls.h>
|
|
+#include <linux/uio.h>
|
|
+
|
|
+#include "util.h"
|
|
+#include "domain.h"
|
|
+#include "connection.h"
|
|
+#include "item.h"
|
|
+#include "message.h"
|
|
+#include "metadata.h"
|
|
+#include "queue.h"
|
|
+#include "reply.h"
|
|
+
|
|
+/**
|
|
+ * kdbus_queue_init() - initialize data structure related to a queue
|
|
+ * @queue: The queue to initialize
|
|
+ */
|
|
+void kdbus_queue_init(struct kdbus_queue *queue)
|
|
+{
|
|
+ INIT_LIST_HEAD(&queue->msg_list);
|
|
+ queue->msg_prio_queue = RB_ROOT;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_queue_peek() - Retrieves an entry from a queue
|
|
+ * @queue: The queue
|
|
+ * @priority: The minimum priority of the entry to peek
|
|
+ * @use_priority: Boolean flag whether or not to peek by priority
|
|
+ *
|
|
+ * Look for a entry in a queue, either by priority, or the oldest one (FIFO).
|
|
+ * The entry is not freed, put off the queue's lists or anything else.
|
|
+ *
|
|
+ * Return: the peeked queue entry on success, NULL if no suitable msg is found
|
|
+ */
|
|
+struct kdbus_queue_entry *kdbus_queue_peek(struct kdbus_queue *queue,
|
|
+ s64 priority, bool use_priority)
|
|
+{
|
|
+ struct kdbus_queue_entry *e;
|
|
+
|
|
+ if (list_empty(&queue->msg_list))
|
|
+ return NULL;
|
|
+
|
|
+ if (use_priority) {
|
|
+ /* get next entry with highest priority */
|
|
+ e = rb_entry(queue->msg_prio_highest,
|
|
+ struct kdbus_queue_entry, prio_node);
|
|
+
|
|
+ /* no entry with the requested priority */
|
|
+ if (e->priority > priority)
|
|
+ return NULL;
|
|
+ } else {
|
|
+ /* ignore the priority, return the next entry in the entry */
|
|
+ e = list_first_entry(&queue->msg_list,
|
|
+ struct kdbus_queue_entry, entry);
|
|
+ }
|
|
+
|
|
+ return e;
|
|
+}
|
|
+
|
|
+static void kdbus_queue_entry_link(struct kdbus_queue_entry *entry)
|
|
+{
|
|
+ struct kdbus_queue *queue = &entry->conn->queue;
|
|
+ struct rb_node **n, *pn = NULL;
|
|
+ bool highest = true;
|
|
+
|
|
+ lockdep_assert_held(&entry->conn->lock);
|
|
+ if (WARN_ON(!list_empty(&entry->entry)))
|
|
+ return;
|
|
+
|
|
+ /* sort into priority entry tree */
|
|
+ n = &queue->msg_prio_queue.rb_node;
|
|
+ while (*n) {
|
|
+ struct kdbus_queue_entry *e;
|
|
+
|
|
+ pn = *n;
|
|
+ e = rb_entry(pn, struct kdbus_queue_entry, prio_node);
|
|
+
|
|
+ /* existing node for this priority, add to its list */
|
|
+ if (likely(entry->priority == e->priority)) {
|
|
+ list_add_tail(&entry->prio_entry, &e->prio_entry);
|
|
+ goto prio_done;
|
|
+ }
|
|
+
|
|
+ if (entry->priority < e->priority) {
|
|
+ n = &pn->rb_left;
|
|
+ } else {
|
|
+ n = &pn->rb_right;
|
|
+ highest = false;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* cache highest-priority entry */
|
|
+ if (highest)
|
|
+ queue->msg_prio_highest = &entry->prio_node;
|
|
+
|
|
+ /* new node for this priority */
|
|
+ rb_link_node(&entry->prio_node, pn, n);
|
|
+ rb_insert_color(&entry->prio_node, &queue->msg_prio_queue);
|
|
+ INIT_LIST_HEAD(&entry->prio_entry);
|
|
+
|
|
+prio_done:
|
|
+ /* add to unsorted fifo list */
|
|
+ list_add_tail(&entry->entry, &queue->msg_list);
|
|
+}
|
|
+
|
|
+static void kdbus_queue_entry_unlink(struct kdbus_queue_entry *entry)
|
|
+{
|
|
+ struct kdbus_queue *queue = &entry->conn->queue;
|
|
+
|
|
+ lockdep_assert_held(&entry->conn->lock);
|
|
+ if (list_empty(&entry->entry))
|
|
+ return;
|
|
+
|
|
+ list_del_init(&entry->entry);
|
|
+
|
|
+ if (list_empty(&entry->prio_entry)) {
|
|
+ /*
|
|
+ * Single entry for this priority, update cached
|
|
+ * highest-priority entry, remove the tree node.
|
|
+ */
|
|
+ if (queue->msg_prio_highest == &entry->prio_node)
|
|
+ queue->msg_prio_highest = rb_next(&entry->prio_node);
|
|
+
|
|
+ rb_erase(&entry->prio_node, &queue->msg_prio_queue);
|
|
+ } else {
|
|
+ struct kdbus_queue_entry *q;
|
|
+
|
|
+ /*
|
|
+ * Multiple entries for this priority entry, get next one in
|
|
+ * the list. Update cached highest-priority entry, store the
|
|
+ * new one as the tree node.
|
|
+ */
|
|
+ q = list_first_entry(&entry->prio_entry,
|
|
+ struct kdbus_queue_entry, prio_entry);
|
|
+ list_del(&entry->prio_entry);
|
|
+
|
|
+ if (queue->msg_prio_highest == &entry->prio_node)
|
|
+ queue->msg_prio_highest = &q->prio_node;
|
|
+
|
|
+ rb_replace_node(&entry->prio_node, &q->prio_node,
|
|
+ &queue->msg_prio_queue);
|
|
+ }
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_queue_entry_new() - allocate a queue entry
|
|
+ * @conn_dst: destination connection
|
|
+ * @kmsg: kmsg object the queue entry should track
|
|
+ * @user: user to account message on (or NULL for kernel messages)
|
|
+ *
|
|
+ * Allocates a queue entry based on a given kmsg and allocate space for
|
|
+ * the message payload and the requested metadata in the connection's pool.
|
|
+ * The entry is not actually added to the queue's lists at this point.
|
|
+ *
|
|
+ * Return: the allocated entry on success, or an ERR_PTR on failures.
|
|
+ */
|
|
+struct kdbus_queue_entry *kdbus_queue_entry_new(struct kdbus_conn *conn_dst,
|
|
+ const struct kdbus_kmsg *kmsg,
|
|
+ struct kdbus_user *user)
|
|
+{
|
|
+ struct kdbus_msg_resources *res = kmsg->res;
|
|
+ const struct kdbus_msg *msg = &kmsg->msg;
|
|
+ struct kdbus_queue_entry *entry;
|
|
+ size_t memfd_cnt = 0;
|
|
+ struct kvec kvec[2];
|
|
+ size_t meta_size;
|
|
+ size_t msg_size;
|
|
+ u64 payload_off;
|
|
+ u64 size = 0;
|
|
+ int ret = 0;
|
|
+
|
|
+ entry = kzalloc(sizeof(*entry), GFP_KERNEL);
|
|
+ if (!entry)
|
|
+ return ERR_PTR(-ENOMEM);
|
|
+
|
|
+ INIT_LIST_HEAD(&entry->entry);
|
|
+ entry->priority = msg->priority;
|
|
+ entry->dst_name_id = kmsg->dst_name_id;
|
|
+ entry->msg_res = kdbus_msg_resources_ref(res);
|
|
+ entry->proc_meta = kdbus_meta_proc_ref(kmsg->proc_meta);
|
|
+ entry->conn_meta = kdbus_meta_conn_ref(kmsg->conn_meta);
|
|
+ entry->conn = kdbus_conn_ref(conn_dst);
|
|
+
|
|
+ if (kmsg->msg.src_id == KDBUS_SRC_ID_KERNEL)
|
|
+ msg_size = msg->size;
|
|
+ else
|
|
+ msg_size = offsetof(struct kdbus_msg, items);
|
|
+
|
|
+ /* sum up the size of the needed slice */
|
|
+ size = msg_size;
|
|
+
|
|
+ if (res) {
|
|
+ size += res->vec_count *
|
|
+ KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec));
|
|
+
|
|
+ if (res->memfd_count) {
|
|
+ entry->memfd_offset =
|
|
+ kcalloc(res->memfd_count, sizeof(size_t),
|
|
+ GFP_KERNEL);
|
|
+ if (!entry->memfd_offset) {
|
|
+ ret = -ENOMEM;
|
|
+ goto exit_free_entry;
|
|
+ }
|
|
+
|
|
+ size += res->memfd_count *
|
|
+ KDBUS_ITEM_SIZE(sizeof(struct kdbus_memfd));
|
|
+ }
|
|
+
|
|
+ if (res->fds_count)
|
|
+ size += KDBUS_ITEM_SIZE(sizeof(int) * res->fds_count);
|
|
+
|
|
+ if (res->dst_name)
|
|
+ size += KDBUS_ITEM_SIZE(strlen(res->dst_name) + 1);
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Remember the offset of the metadata part, so we can override
|
|
+ * this part later during kdbus_queue_entry_install().
|
|
+ */
|
|
+ entry->meta_offset = size;
|
|
+
|
|
+ if (entry->proc_meta || entry->conn_meta) {
|
|
+ entry->attach_flags =
|
|
+ atomic64_read(&conn_dst->attach_flags_recv);
|
|
+
|
|
+ ret = kdbus_meta_export_prepare(entry->proc_meta,
|
|
+ entry->conn_meta,
|
|
+ &entry->attach_flags,
|
|
+ &meta_size);
|
|
+ if (ret < 0)
|
|
+ goto exit_free_entry;
|
|
+
|
|
+ size += meta_size;
|
|
+ }
|
|
+
|
|
+ payload_off = size;
|
|
+ size += kmsg->pool_size;
|
|
+ size = KDBUS_ALIGN8(size);
|
|
+
|
|
+ ret = kdbus_conn_quota_inc(conn_dst, user, size,
|
|
+ res ? res->fds_count : 0);
|
|
+ if (ret < 0)
|
|
+ goto exit_free_entry;
|
|
+
|
|
+ entry->slice = kdbus_pool_slice_alloc(conn_dst->pool, size, true);
|
|
+ if (IS_ERR(entry->slice)) {
|
|
+ ret = PTR_ERR(entry->slice);
|
|
+ entry->slice = NULL;
|
|
+ kdbus_conn_quota_dec(conn_dst, user, size,
|
|
+ res ? res->fds_count : 0);
|
|
+ goto exit_free_entry;
|
|
+ }
|
|
+
|
|
+ /* we accounted for exactly 'size' bytes, make sure it didn't grow */
|
|
+ WARN_ON(kdbus_pool_slice_size(entry->slice) != size);
|
|
+ entry->user = kdbus_user_ref(user);
|
|
+
|
|
+ /* copy message header */
|
|
+ kvec[0].iov_base = (char *)msg;
|
|
+ kvec[0].iov_len = msg_size;
|
|
+
|
|
+ ret = kdbus_pool_slice_copy_kvec(entry->slice, 0, kvec, 1, msg_size);
|
|
+ if (ret < 0)
|
|
+ goto exit_free_entry;
|
|
+
|
|
+ /* 'size' will now track the write position */
|
|
+ size = msg_size;
|
|
+
|
|
+ /* create message payload items */
|
|
+ if (res) {
|
|
+ size_t dst_name_len = 0;
|
|
+ unsigned int i;
|
|
+ size_t sz = 0;
|
|
+
|
|
+ if (res->dst_name) {
|
|
+ dst_name_len = strlen(res->dst_name) + 1;
|
|
+ sz += KDBUS_ITEM_SIZE(dst_name_len);
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < res->data_count; ++i) {
|
|
+ struct kdbus_vec v;
|
|
+ struct kdbus_memfd m;
|
|
+
|
|
+ switch (res->data[i].type) {
|
|
+ case KDBUS_MSG_DATA_VEC:
|
|
+ sz += KDBUS_ITEM_SIZE(sizeof(v));
|
|
+ break;
|
|
+
|
|
+ case KDBUS_MSG_DATA_MEMFD:
|
|
+ sz += KDBUS_ITEM_SIZE(sizeof(m));
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (sz) {
|
|
+ struct kdbus_item *items, *item;
|
|
+
|
|
+ items = kmalloc(sz, GFP_KERNEL);
|
|
+ if (!items) {
|
|
+ ret = -ENOMEM;
|
|
+ goto exit_free_entry;
|
|
+ }
|
|
+
|
|
+ item = items;
|
|
+
|
|
+ if (res->dst_name)
|
|
+ item = kdbus_item_set(item, KDBUS_ITEM_DST_NAME,
|
|
+ res->dst_name,
|
|
+ dst_name_len);
|
|
+
|
|
+ for (i = 0; i < res->data_count; ++i) {
|
|
+ struct kdbus_msg_data *d = res->data + i;
|
|
+ struct kdbus_memfd m = {};
|
|
+ struct kdbus_vec v = {};
|
|
+
|
|
+ switch (d->type) {
|
|
+ case KDBUS_MSG_DATA_VEC:
|
|
+ v.size = d->size;
|
|
+ v.offset = d->vec.off;
|
|
+ if (v.offset != ~0ULL)
|
|
+ v.offset += payload_off;
|
|
+
|
|
+ item = kdbus_item_set(item,
|
|
+ KDBUS_ITEM_PAYLOAD_OFF,
|
|
+ &v, sizeof(v));
|
|
+ break;
|
|
+
|
|
+ case KDBUS_MSG_DATA_MEMFD:
|
|
+ /*
|
|
+ * Remember the location of memfds, so
|
|
+ * we can override the content from
|
|
+ * kdbus_queue_entry_install().
|
|
+ */
|
|
+ entry->memfd_offset[memfd_cnt++] =
|
|
+ msg_size +
|
|
+ (char *)item - (char *)items +
|
|
+ offsetof(struct kdbus_item,
|
|
+ memfd);
|
|
+
|
|
+ item = kdbus_item_set(item,
|
|
+ KDBUS_ITEM_PAYLOAD_MEMFD,
|
|
+ &m, sizeof(m));
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ kvec[0].iov_base = items;
|
|
+ kvec[0].iov_len = sz;
|
|
+
|
|
+ ret = kdbus_pool_slice_copy_kvec(entry->slice, size,
|
|
+ kvec, 1, sz);
|
|
+ kfree(items);
|
|
+
|
|
+ if (ret < 0)
|
|
+ goto exit_free_entry;
|
|
+
|
|
+ size += sz;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Remember the location of the FD part, so we can override the
|
|
+ * content in kdbus_queue_entry_install().
|
|
+ */
|
|
+ if (res->fds_count) {
|
|
+ entry->fds_offset = size;
|
|
+ size += KDBUS_ITEM_SIZE(sizeof(int) * res->fds_count);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* finally, copy over the actual message payload */
|
|
+ if (kmsg->iov_count) {
|
|
+ ret = kdbus_pool_slice_copy_iovec(entry->slice, payload_off,
|
|
+ kmsg->iov,
|
|
+ kmsg->iov_count,
|
|
+ kmsg->pool_size);
|
|
+ if (ret < 0)
|
|
+ goto exit_free_entry;
|
|
+ }
|
|
+
|
|
+ return entry;
|
|
+
|
|
+exit_free_entry:
|
|
+ kdbus_queue_entry_free(entry);
|
|
+ return ERR_PTR(ret);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_queue_entry_free() - free resources of an entry
|
|
+ * @entry: The entry to free
|
|
+ *
|
|
+ * Removes resources allocated by a queue entry, along with the entry itself.
|
|
+ * Note that the entry's slice is not freed at this point.
|
|
+ */
|
|
+void kdbus_queue_entry_free(struct kdbus_queue_entry *entry)
|
|
+{
|
|
+ if (!entry)
|
|
+ return;
|
|
+
|
|
+ lockdep_assert_held(&entry->conn->lock);
|
|
+
|
|
+ kdbus_queue_entry_unlink(entry);
|
|
+ kdbus_reply_unref(entry->reply);
|
|
+
|
|
+ if (entry->slice) {
|
|
+ kdbus_conn_quota_dec(entry->conn, entry->user,
|
|
+ kdbus_pool_slice_size(entry->slice),
|
|
+ entry->msg_res ?
|
|
+ entry->msg_res->fds_count : 0);
|
|
+ kdbus_pool_slice_release(entry->slice);
|
|
+ kdbus_user_unref(entry->user);
|
|
+ }
|
|
+
|
|
+ kdbus_msg_resources_unref(entry->msg_res);
|
|
+ kdbus_meta_conn_unref(entry->conn_meta);
|
|
+ kdbus_meta_proc_unref(entry->proc_meta);
|
|
+ kdbus_conn_unref(entry->conn);
|
|
+ kfree(entry->memfd_offset);
|
|
+ kfree(entry);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_queue_entry_install() - install message components into the
|
|
+ * receiver's process
|
|
+ * @entry: The queue entry to install
|
|
+ * @return_flags: Pointer to store the return flags for userspace
|
|
+ * @install_fds: Whether or not to install associated file descriptors
|
|
+ *
|
|
+ * This function will create a slice to transport the message header, the
|
|
+ * metadata items and other items for information stored in @entry, and
|
|
+ * store it as entry->slice.
|
|
+ *
|
|
+ * If @install_fds is %true, file descriptors will as well be installed.
|
|
+ * This function must always be called from the task context of the receiver.
|
|
+ *
|
|
+ * Return: 0 on success.
|
|
+ */
|
|
+int kdbus_queue_entry_install(struct kdbus_queue_entry *entry,
|
|
+ u64 *return_flags, bool install_fds)
|
|
+{
|
|
+ u64 msg_size = entry->meta_offset;
|
|
+ struct kdbus_conn *conn_dst = entry->conn;
|
|
+ struct kdbus_msg_resources *res;
|
|
+ bool incomplete_fds = false;
|
|
+ struct kvec kvec[2];
|
|
+ size_t memfds = 0;
|
|
+ int i, ret;
|
|
+
|
|
+ lockdep_assert_held(&conn_dst->lock);
|
|
+
|
|
+ if (entry->proc_meta || entry->conn_meta) {
|
|
+ size_t meta_size;
|
|
+
|
|
+ ret = kdbus_meta_export(entry->proc_meta,
|
|
+ entry->conn_meta,
|
|
+ entry->attach_flags,
|
|
+ entry->slice,
|
|
+ entry->meta_offset,
|
|
+ &meta_size);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ msg_size += meta_size;
|
|
+ }
|
|
+
|
|
+ /* Update message size at offset 0 */
|
|
+ kvec[0].iov_base = &msg_size;
|
|
+ kvec[0].iov_len = sizeof(msg_size);
|
|
+
|
|
+ ret = kdbus_pool_slice_copy_kvec(entry->slice, 0, kvec, 1,
|
|
+ sizeof(msg_size));
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ res = entry->msg_res;
|
|
+
|
|
+ if (!res)
|
|
+ return 0;
|
|
+
|
|
+ if (res->fds_count) {
|
|
+ struct kdbus_item_header hdr;
|
|
+ size_t off;
|
|
+ int *fds;
|
|
+
|
|
+ fds = kmalloc_array(res->fds_count, sizeof(int), GFP_KERNEL);
|
|
+ if (!fds)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ for (i = 0; i < res->fds_count; i++) {
|
|
+ if (install_fds) {
|
|
+ fds[i] = get_unused_fd_flags(O_CLOEXEC);
|
|
+ if (fds[i] >= 0)
|
|
+ fd_install(fds[i],
|
|
+ get_file(res->fds[i]));
|
|
+ else
|
|
+ incomplete_fds = true;
|
|
+ } else {
|
|
+ fds[i] = -1;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ off = entry->fds_offset;
|
|
+
|
|
+ hdr.type = KDBUS_ITEM_FDS;
|
|
+ hdr.size = KDBUS_ITEM_HEADER_SIZE +
|
|
+ sizeof(int) * res->fds_count;
|
|
+
|
|
+ kvec[0].iov_base = &hdr;
|
|
+ kvec[0].iov_len = sizeof(hdr);
|
|
+
|
|
+ kvec[1].iov_base = fds;
|
|
+ kvec[1].iov_len = sizeof(int) * res->fds_count;
|
|
+
|
|
+ ret = kdbus_pool_slice_copy_kvec(entry->slice, off,
|
|
+ kvec, 2, hdr.size);
|
|
+ kfree(fds);
|
|
+
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < res->data_count; ++i) {
|
|
+ struct kdbus_msg_data *d = res->data + i;
|
|
+ struct kdbus_memfd m;
|
|
+
|
|
+ if (d->type != KDBUS_MSG_DATA_MEMFD)
|
|
+ continue;
|
|
+
|
|
+ m.start = d->memfd.start;
|
|
+ m.size = d->size;
|
|
+ m.fd = -1;
|
|
+
|
|
+ if (install_fds) {
|
|
+ m.fd = get_unused_fd_flags(O_CLOEXEC);
|
|
+ if (m.fd < 0) {
|
|
+ m.fd = -1;
|
|
+ incomplete_fds = true;
|
|
+ } else {
|
|
+ fd_install(m.fd,
|
|
+ get_file(d->memfd.file));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ kvec[0].iov_base = &m;
|
|
+ kvec[0].iov_len = sizeof(m);
|
|
+
|
|
+ ret = kdbus_pool_slice_copy_kvec(entry->slice,
|
|
+ entry->memfd_offset[memfds++],
|
|
+ kvec, 1, sizeof(m));
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ if (incomplete_fds)
|
|
+ *return_flags |= KDBUS_RECV_RETURN_INCOMPLETE_FDS;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_queue_entry_enqueue() - enqueue an entry
|
|
+ * @entry: entry to enqueue
|
|
+ * @reply: reply to link to this entry (or NULL if none)
|
|
+ *
|
|
+ * This enqueues an unqueued entry into the message queue of the linked
|
|
+ * connection. It also binds a reply object to the entry so we can remember it
|
|
+ * when the message is moved.
|
|
+ *
|
|
+ * Once this call returns (and the connection lock is released), this entry can
|
|
+ * be dequeued by the target connection. Note that the entry will not be removed
|
|
+ * from the queue until it is destroyed.
|
|
+ */
|
|
+void kdbus_queue_entry_enqueue(struct kdbus_queue_entry *entry,
|
|
+ struct kdbus_reply *reply)
|
|
+{
|
|
+ lockdep_assert_held(&entry->conn->lock);
|
|
+
|
|
+ if (WARN_ON(entry->reply) || WARN_ON(!list_empty(&entry->entry)))
|
|
+ return;
|
|
+
|
|
+ entry->reply = kdbus_reply_ref(reply);
|
|
+ kdbus_queue_entry_link(entry);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_queue_entry_move() - move queue entry
|
|
+ * @e: queue entry to move
|
|
+ * @dst: destination connection to queue the entry on
|
|
+ *
|
|
+ * This moves a queue entry onto a different connection. It allocates a new
|
|
+ * slice on the target connection and copies the message over. If the copy
|
|
+ * succeeded, we move the entry from @src to @dst.
|
|
+ *
|
|
+ * On failure, the entry is left untouched.
|
|
+ *
|
|
+ * The queue entry must be queued right now, and after the call succeeds it will
|
|
+ * be queued on the destination, but no longer on the source.
|
|
+ *
|
|
+ * The caller must hold the connection lock of the source *and* destination.
|
|
+ *
|
|
+ * Return: 0 on success, negative error code on failure.
|
|
+ */
|
|
+int kdbus_queue_entry_move(struct kdbus_queue_entry *e,
|
|
+ struct kdbus_conn *dst)
|
|
+{
|
|
+ struct kdbus_pool_slice *slice = NULL;
|
|
+ struct kdbus_conn *src = e->conn;
|
|
+ size_t size, fds;
|
|
+ int ret;
|
|
+
|
|
+ lockdep_assert_held(&src->lock);
|
|
+ lockdep_assert_held(&dst->lock);
|
|
+
|
|
+ if (WARN_ON(IS_ERR(e->user)) || WARN_ON(list_empty(&e->entry)))
|
|
+ return -EINVAL;
|
|
+ if (src == dst)
|
|
+ return 0;
|
|
+
|
|
+ size = kdbus_pool_slice_size(e->slice);
|
|
+ fds = e->msg_res ? e->msg_res->fds_count : 0;
|
|
+
|
|
+ ret = kdbus_conn_quota_inc(dst, e->user, size, fds);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ slice = kdbus_pool_slice_alloc(dst->pool, size, true);
|
|
+ if (IS_ERR(slice)) {
|
|
+ ret = PTR_ERR(slice);
|
|
+ slice = NULL;
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ ret = kdbus_pool_slice_copy(slice, e->slice);
|
|
+ if (ret < 0)
|
|
+ goto error;
|
|
+
|
|
+ kdbus_queue_entry_unlink(e);
|
|
+ kdbus_conn_quota_dec(src, e->user, size, fds);
|
|
+ kdbus_pool_slice_release(e->slice);
|
|
+ kdbus_conn_unref(e->conn);
|
|
+
|
|
+ e->slice = slice;
|
|
+ e->conn = kdbus_conn_ref(dst);
|
|
+ kdbus_queue_entry_link(e);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+error:
|
|
+ kdbus_pool_slice_release(slice);
|
|
+ kdbus_conn_quota_dec(dst, e->user, size, fds);
|
|
+ return ret;
|
|
+}
|
|
diff --git a/ipc/kdbus/queue.h b/ipc/kdbus/queue.h
|
|
new file mode 100644
|
|
index 000000000000..7f2db96fe308
|
|
--- /dev/null
|
|
+++ b/ipc/kdbus/queue.h
|
|
@@ -0,0 +1,92 @@
|
|
+/*
|
|
+ * 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 <tixxdz@opendz.org>
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#ifndef __KDBUS_QUEUE_H
|
|
+#define __KDBUS_QUEUE_H
|
|
+
|
|
+struct kdbus_user;
|
|
+
|
|
+/**
|
|
+ * struct kdbus_queue - a connection's message queue
|
|
+ * @msg_list: List head for kdbus_queue_entry objects
|
|
+ * @msg_prio_queue: RB tree root for messages, sorted by priority
|
|
+ * @msg_prio_highest: Link to the RB node referencing the message with the
|
|
+ * highest priority in the tree.
|
|
+ */
|
|
+struct kdbus_queue {
|
|
+ struct list_head msg_list;
|
|
+ struct rb_root msg_prio_queue;
|
|
+ struct rb_node *msg_prio_highest;
|
|
+};
|
|
+
|
|
+/**
|
|
+ * struct kdbus_queue_entry - messages waiting to be read
|
|
+ * @entry: Entry in the connection's list
|
|
+ * @prio_node: Entry in the priority queue tree
|
|
+ * @prio_entry: Queue tree node entry in the list of one priority
|
|
+ * @slice: Slice in the receiver's pool for the message
|
|
+ * @attach_flags: Attach flags used during slice allocation
|
|
+ * @meta_offset: Offset of first metadata item in slice
|
|
+ * @fds_offset: Offset of FD item in slice
|
|
+ * @memfd_offset: Array of slice-offsets for all memfd items
|
|
+ * @priority: Message priority
|
|
+ * @dst_name_id: The sequence number of the name this message is
|
|
+ * addressed to, 0 for messages sent to an ID
|
|
+ * @msg_res: Message resources
|
|
+ * @proc_meta: Process metadata, captured at message arrival
|
|
+ * @conn_meta: Connection metadata, captured at message arrival
|
|
+ * @reply: The reply block if a reply to this message is expected
|
|
+ * @user: User used for accounting
|
|
+ */
|
|
+struct kdbus_queue_entry {
|
|
+ struct list_head entry;
|
|
+ struct rb_node prio_node;
|
|
+ struct list_head prio_entry;
|
|
+
|
|
+ struct kdbus_pool_slice *slice;
|
|
+
|
|
+ u64 attach_flags;
|
|
+ size_t meta_offset;
|
|
+ size_t fds_offset;
|
|
+ size_t *memfd_offset;
|
|
+
|
|
+ s64 priority;
|
|
+ u64 dst_name_id;
|
|
+
|
|
+ struct kdbus_msg_resources *msg_res;
|
|
+ struct kdbus_meta_proc *proc_meta;
|
|
+ struct kdbus_meta_conn *conn_meta;
|
|
+ struct kdbus_reply *reply;
|
|
+ struct kdbus_conn *conn;
|
|
+ struct kdbus_user *user;
|
|
+};
|
|
+
|
|
+struct kdbus_kmsg;
|
|
+
|
|
+void kdbus_queue_init(struct kdbus_queue *queue);
|
|
+struct kdbus_queue_entry *kdbus_queue_peek(struct kdbus_queue *queue,
|
|
+ s64 priority, bool use_priority);
|
|
+
|
|
+struct kdbus_queue_entry *kdbus_queue_entry_new(struct kdbus_conn *conn_dst,
|
|
+ const struct kdbus_kmsg *kmsg,
|
|
+ struct kdbus_user *user);
|
|
+void kdbus_queue_entry_free(struct kdbus_queue_entry *entry);
|
|
+int kdbus_queue_entry_install(struct kdbus_queue_entry *entry,
|
|
+ u64 *return_flags, bool install_fds);
|
|
+void kdbus_queue_entry_enqueue(struct kdbus_queue_entry *entry,
|
|
+ struct kdbus_reply *reply);
|
|
+int kdbus_queue_entry_move(struct kdbus_queue_entry *entry,
|
|
+ struct kdbus_conn *dst);
|
|
+
|
|
+#endif /* __KDBUS_QUEUE_H */
|
|
diff --git a/ipc/kdbus/reply.c b/ipc/kdbus/reply.c
|
|
new file mode 100644
|
|
index 000000000000..6b3bd81bbb4d
|
|
--- /dev/null
|
|
+++ b/ipc/kdbus/reply.c
|
|
@@ -0,0 +1,259 @@
|
|
+#include <linux/init.h>
|
|
+#include <linux/mm.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/uio.h>
|
|
+
|
|
+#include "bus.h"
|
|
+#include "connection.h"
|
|
+#include "endpoint.h"
|
|
+#include "message.h"
|
|
+#include "metadata.h"
|
|
+#include "names.h"
|
|
+#include "domain.h"
|
|
+#include "item.h"
|
|
+#include "notify.h"
|
|
+#include "policy.h"
|
|
+#include "reply.h"
|
|
+#include "util.h"
|
|
+
|
|
+/**
|
|
+ * kdbus_reply_new() - Allocate and set up a new kdbus_reply object
|
|
+ * @reply_src: The connection a reply is expected from
|
|
+ * @reply_dst: The connection this reply object belongs to
|
|
+ * @msg: Message associated with the reply
|
|
+ * @name_entry: Name entry used to send the message
|
|
+ * @sync: Whether or not to make this reply synchronous
|
|
+ *
|
|
+ * Allocate and fill a new kdbus_reply object.
|
|
+ *
|
|
+ * Return: New kdbus_conn object on success, ERR_PTR on error.
|
|
+ */
|
|
+struct kdbus_reply *kdbus_reply_new(struct kdbus_conn *reply_src,
|
|
+ struct kdbus_conn *reply_dst,
|
|
+ const struct kdbus_msg *msg,
|
|
+ struct kdbus_name_entry *name_entry,
|
|
+ bool sync)
|
|
+{
|
|
+ struct kdbus_reply *r;
|
|
+ int ret = 0;
|
|
+
|
|
+ if (atomic_inc_return(&reply_dst->request_count) >
|
|
+ KDBUS_CONN_MAX_REQUESTS_PENDING) {
|
|
+ ret = -EMLINK;
|
|
+ goto exit_dec_request_count;
|
|
+ }
|
|
+
|
|
+ r = kzalloc(sizeof(*r), GFP_KERNEL);
|
|
+ if (!r) {
|
|
+ ret = -ENOMEM;
|
|
+ goto exit_dec_request_count;
|
|
+ }
|
|
+
|
|
+ kref_init(&r->kref);
|
|
+ INIT_LIST_HEAD(&r->entry);
|
|
+ r->reply_src = kdbus_conn_ref(reply_src);
|
|
+ r->reply_dst = kdbus_conn_ref(reply_dst);
|
|
+ r->cookie = msg->cookie;
|
|
+ r->name_id = name_entry ? name_entry->name_id : 0;
|
|
+ r->deadline_ns = msg->timeout_ns;
|
|
+
|
|
+ if (sync) {
|
|
+ r->sync = true;
|
|
+ r->waiting = true;
|
|
+ }
|
|
+
|
|
+exit_dec_request_count:
|
|
+ if (ret < 0) {
|
|
+ atomic_dec(&reply_dst->request_count);
|
|
+ return ERR_PTR(ret);
|
|
+ }
|
|
+
|
|
+ return r;
|
|
+}
|
|
+
|
|
+static void __kdbus_reply_free(struct kref *kref)
|
|
+{
|
|
+ struct kdbus_reply *reply =
|
|
+ container_of(kref, struct kdbus_reply, kref);
|
|
+
|
|
+ atomic_dec(&reply->reply_dst->request_count);
|
|
+ kdbus_conn_unref(reply->reply_src);
|
|
+ kdbus_conn_unref(reply->reply_dst);
|
|
+ kfree(reply);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_reply_ref() - Increase reference on kdbus_reply
|
|
+ * @r: The reply, may be %NULL
|
|
+ *
|
|
+ * Return: The reply object with an extra reference
|
|
+ */
|
|
+struct kdbus_reply *kdbus_reply_ref(struct kdbus_reply *r)
|
|
+{
|
|
+ if (r)
|
|
+ kref_get(&r->kref);
|
|
+ return r;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_reply_unref() - Decrease reference on kdbus_reply
|
|
+ * @r: The reply, may be %NULL
|
|
+ *
|
|
+ * Return: NULL
|
|
+ */
|
|
+struct kdbus_reply *kdbus_reply_unref(struct kdbus_reply *r)
|
|
+{
|
|
+ if (r)
|
|
+ kref_put(&r->kref, __kdbus_reply_free);
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_reply_link() - Link reply object into target connection
|
|
+ * @r: Reply to link
|
|
+ */
|
|
+void kdbus_reply_link(struct kdbus_reply *r)
|
|
+{
|
|
+ if (WARN_ON(!list_empty(&r->entry)))
|
|
+ return;
|
|
+
|
|
+ list_add(&r->entry, &r->reply_dst->reply_list);
|
|
+ kdbus_reply_ref(r);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_reply_unlink() - Unlink reply object from target connection
|
|
+ * @r: Reply to unlink
|
|
+ */
|
|
+void kdbus_reply_unlink(struct kdbus_reply *r)
|
|
+{
|
|
+ if (!list_empty(&r->entry)) {
|
|
+ list_del_init(&r->entry);
|
|
+ kdbus_reply_unref(r);
|
|
+ }
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_sync_reply_wakeup() - Wake a synchronously blocking reply
|
|
+ * @reply: The reply object
|
|
+ * @err: Error code to set on the remote side
|
|
+ *
|
|
+ * Remove the synchronous reply object from its connection reply_list, and
|
|
+ * wake up remote peer (method origin) with the appropriate synchronous reply
|
|
+ * code.
|
|
+ */
|
|
+void kdbus_sync_reply_wakeup(struct kdbus_reply *reply, int err)
|
|
+{
|
|
+ if (WARN_ON(!reply->sync))
|
|
+ return;
|
|
+
|
|
+ reply->waiting = false;
|
|
+ reply->err = err;
|
|
+ wake_up_interruptible(&reply->reply_dst->wait);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_reply_find() - Find the corresponding reply object
|
|
+ * @replying: The replying connection or NULL
|
|
+ * @reply_dst: The connection the reply will be sent to
|
|
+ * (method origin)
|
|
+ * @cookie: The cookie of the requesting message
|
|
+ *
|
|
+ * Lookup a reply object that should be sent as a reply by
|
|
+ * @replying to @reply_dst with the given cookie.
|
|
+ *
|
|
+ * Callers must take the @reply_dst lock.
|
|
+ *
|
|
+ * Return: the corresponding reply object or NULL if not found
|
|
+ */
|
|
+struct kdbus_reply *kdbus_reply_find(struct kdbus_conn *replying,
|
|
+ struct kdbus_conn *reply_dst,
|
|
+ u64 cookie)
|
|
+{
|
|
+ struct kdbus_reply *r, *reply = NULL;
|
|
+
|
|
+ list_for_each_entry(r, &reply_dst->reply_list, entry) {
|
|
+ if (r->cookie == cookie &&
|
|
+ (!replying || r->reply_src == replying)) {
|
|
+ reply = r;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return reply;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * kdbus_reply_list_scan_work() - Worker callback to scan the replies of a
|
|
+ * connection for exceeded timeouts
|
|
+ * @work: Work struct of the connection to scan
|
|
+ *
|
|
+ * Walk the list of replies stored with a connection and look for entries
|
|
+ * that have exceeded their timeout. If such an entry is found, a timeout
|
|
+ * notification is sent to the waiting peer, and the reply is removed from
|
|
+ * the list.
|
|
+ *
|
|
+ * The work is rescheduled to the nearest timeout found during the list
|
|
+ * iteration.
|
|
+ */
|
|
+void kdbus_reply_list_scan_work(struct work_struct *work)
|
|
+{
|
|
+ struct kdbus_conn *conn =
|
|
+ container_of(work, struct kdbus_conn, work.work);
|
|
+ struct kdbus_reply *reply, *reply_tmp;
|
|
+ u64 deadline = ~0ULL;
|
|
+ struct timespec64 ts;
|
|
+ u64 now;
|
|
+
|
|
+ ktime_get_ts64(&ts);
|
|
+ now = timespec64_to_ns(&ts);
|
|
+
|
|
+ mutex_lock(&conn->lock);
|
|
+ if (!kdbus_conn_active(conn)) {
|
|
+ mutex_unlock(&conn->lock);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ list_for_each_entry_safe(reply, reply_tmp, &conn->reply_list, entry) {
|
|
+ /*
|
|
+ * If the reply block is waiting for synchronous I/O,
|
|
+ * the timeout is handled by wait_event_*_timeout(),
|
|
+ * so we don't have to care for it here.
|
|
+ */
|
|
+ if (reply->sync && !reply->interrupted)
|
|
+ continue;
|
|
+
|
|
+ WARN_ON(reply->reply_dst != conn);
|
|
+
|
|
+ if (reply->deadline_ns > now) {
|
|
+ /* remember next timeout */
|
|
+ if (deadline > reply->deadline_ns)
|
|
+ deadline = reply->deadline_ns;
|
|
+
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * A zero deadline means the connection died, was
|
|
+ * cleaned up already and the notification was sent.
|
|
+ * Don't send notifications for reply trackers that were
|
|
+ * left in an interrupted syscall state.
|
|
+ */
|
|
+ if (reply->deadline_ns != 0 && !reply->interrupted)
|
|
+ kdbus_notify_reply_timeout(conn->ep->bus, conn->id,
|
|
+ reply->cookie);
|
|
+
|
|
+ kdbus_reply_unlink(reply);
|
|
+ }
|
|
+
|
|
+ /* rearm delayed work with next timeout */
|
|
+ if (deadline != ~0ULL)
|
|
+ schedule_delayed_work(&conn->work,
|
|
+ nsecs_to_jiffies(deadline - now));
|
|
+
|
|
+ mutex_unlock(&conn->lock);
|
|
+
|
|
+ kdbus_notify_flush(conn->ep->bus);
|
|
+}
|
|
diff --git a/ipc/kdbus/reply.h b/ipc/kdbus/reply.h
|
|
new file mode 100644
|
|
index 000000000000..68d52321a917
|
|
--- /dev/null
|
|
+++ b/ipc/kdbus/reply.h
|
|
@@ -0,0 +1,68 @@
|
|
+/*
|
|
+ * 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 <tixxdz@opendz.org>
|
|
+ *
|
|
+ * 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.
|
|
+ */
|
|
+
|
|
+#ifndef __KDBUS_REPLY_H
|
|
+#define __KDBUS_REPLY_H
|
|
+
|
|
+/**
|
|
+ * struct kdbus_reply - an entry of kdbus_conn's list of replies
|
|
+ * @kref: Ref-count of this object
|
|
+ * @entry: The entry of the connection's reply_list
|
|
+ * @reply_src: The connection the reply will be sent from
|
|
+ * @reply_dst: The connection the reply will be sent to
|
|
+ * @queue_entry: The queue entry item that is prepared by the replying
|
|
+ * connection
|
|
+ * @deadline_ns: The deadline of the reply, in nanoseconds
|
|
+ * @cookie: The cookie of the requesting message
|
|
+ * @name_id: ID of the well-known name the original msg was sent to
|
|
+ * @sync: The reply block is waiting for synchronous I/O
|
|
+ * @waiting: The condition to synchronously wait for
|
|
+ * @interrupted: The sync reply was left in an interrupted state
|
|
+ * @err: The error code for the synchronous reply
|
|
+ */
|
|
+struct kdbus_reply {
|
|
+ struct kref kref;
|
|
+ struct list_head entry;
|
|
+ struct kdbus_conn *reply_src;
|
|
+ struct kdbus_conn *reply_dst;
|
|
+ struct kdbus_queue_entry *queue_entry;
|
|
+ u64 deadline_ns;
|
|
+ u64 cookie;
|
|
+ u64 name_id;
|
|
+ bool sync:1;
|
|
+ bool waiting:1;
|
|
+ bool interrupted:1;
|
|
+ int err;
|
|
+};
|
|
+
|
|
+struct kdbus_reply *kdbus_reply_new(struct kdbus_conn *reply_src,
|
|
+ struct kdbus_conn *reply_dst,
|
|
+ const struct kdbus_msg *msg,
|
|
+ struct kdbus_name_entry *name_entry,
|
|
+ bool sync);
|
|
+
|
|
+struct kdbus_reply *kdbus_reply_ref(struct kdbus_reply *r);
|
|
+struct kdbus_reply *kdbus_reply_unref(struct kdbus_reply *r);
|
|
+
|
|
+void kdbus_reply_link(struct kdbus_reply *r);
|
|
+void kdbus_reply_unlink(struct kdbus_reply *r);
|
|
+
|
|
+struct kdbus_reply *kdbus_reply_find(struct kdbus_conn *replying,
|
|
+ struct kdbus_conn *reply_dst,
|
|
+ u64 cookie);
|
|
+
|
|
+void kdbus_sync_reply_wakeup(struct kdbus_reply *reply, int err);
|
|
+void kdbus_reply_list_scan_work(struct work_struct *work);
|
|
+
|
|
+#endif /* __KDBUS_REPLY_H */
|
|
diff --git a/ipc/kdbus/util.h b/ipc/kdbus/util.h
|
|
index 9caadb337912..740b19880985 100644
|
|
--- a/ipc/kdbus/util.h
|
|
+++ b/ipc/kdbus/util.h
|
|
@@ -18,7 +18,7 @@
|
|
#include <linux/dcache.h>
|
|
#include <linux/ioctl.h>
|
|
|
|
-#include "kdbus.h"
|
|
+#include <uapi/linux/kdbus.h>
|
|
|
|
/* all exported addresses are 64 bit */
|
|
#define KDBUS_PTR(addr) ((void __user *)(uintptr_t)(addr))
|