summaryrefslogtreecommitdiff
path: root/ipc/kdbus/connection.c
diff options
context:
space:
mode:
authorDaniel Mack <daniel@zonque.org>2014-09-11 18:57:24 +0200
committerSeung-Woo Kim <sw0312.kim@samsung.com>2016-12-14 13:48:29 +0900
commit75e9a0a84d2a484c83c206737d08e0e4709d7c52 (patch)
treef012246a6c1315a65b0dd3196836330236f817e9 /ipc/kdbus/connection.c
parent3570baba84bcafe5d17eae24d02baefc45d97a2c (diff)
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> Signed-off-by: Paul Osmialowski <p.osmialowsk@samsung.com>
Diffstat (limited to 'ipc/kdbus/connection.c')
-rw-r--r--ipc/kdbus/connection.c2215
1 files changed, 2215 insertions, 0 deletions
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);
+}