summaryrefslogtreecommitdiff
path: root/ipc/kdbus/connection.c
diff options
context:
space:
mode:
Diffstat (limited to 'ipc/kdbus/connection.c')
-rw-r--r--ipc/kdbus/connection.c2227
1 files changed, 0 insertions, 2227 deletions
diff --git a/ipc/kdbus/connection.c b/ipc/kdbus/connection.c
deleted file mode 100644
index ef63d6533..000000000
--- a/ipc/kdbus/connection.c
+++ /dev/null
@@ -1,2227 +0,0 @@
-/*
- * 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,
- struct file *file,
- 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;
- bool privileged;
- bool owner;
- struct kvec kvec;
- int ret;
-
- struct {
- u64 size;
- u64 type;
- struct kdbus_bloom_parameter bloom;
- } bloom_item;
-
- privileged = kdbus_ep_is_privileged(ep, file);
- owner = kdbus_ep_is_owner(ep, file);
-
- 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 (!owner && (is_activator || is_policy_holder || is_monitor))
- return ERR_PTR(-EPERM);
- if (!owner && (creds || pids || seclabel))
- 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);
-
- 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->reply_list);
- 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_cred(file->f_cred);
- conn->pid = get_pid(task_pid(current));
- get_fs_root(current->fs, &conn->root_path);
- init_waitqueue_head(&conn->wait);
- kdbus_queue_init(&conn->queue);
- conn->privileged = privileged;
- conn->owner = owner;
- 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));
-
- /* privileged processes can impersonate somebody else */
- if (creds || pids || seclabel) {
- conn->meta_fake = kdbus_meta_fake_new();
- if (IS_ERR(conn->meta_fake)) {
- ret = PTR_ERR(conn->meta_fake);
- conn->meta_fake = NULL;
- goto exit_unref;
- }
-
- ret = kdbus_meta_fake_collect(conn->meta_fake,
- creds, pids, seclabel);
- if (ret < 0)
- goto exit_unref;
- } else {
- conn->meta_proc = kdbus_meta_proc_new();
- if (IS_ERR(conn->meta_proc)) {
- ret = PTR_ERR(conn->meta_proc);
- conn->meta_proc = NULL;
- goto exit_unref;
- }
-
- ret = kdbus_meta_proc_collect(conn->meta_proc,
- 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).
- * In case the caller is privileged, we allow changing the accounting
- * to the faked user.
- */
- if (ep->user) {
- conn->user = kdbus_user_ref(ep->user);
- } else {
- kuid_t uid;
-
- if (conn->meta_fake && uid_valid(conn->meta_fake->uid) &&
- conn->privileged)
- uid = conn->meta_fake->uid;
- else
- uid = conn->cred->uid;
-
- conn->user = kdbus_user_lookup(ep->bus->domain, 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->reply_list));
-
- if (conn->user) {
- atomic_dec(&conn->user->connections);
- kdbus_user_unref(conn->user);
- }
-
- kdbus_meta_fake_free(conn->meta_fake);
- kdbus_meta_proc_unref(conn->meta_proc);
- kdbus_match_db_free(conn->match_db);
- kdbus_pool_free(conn->pool);
- kdbus_ep_unref(conn->ep);
- path_put(&conn->root_path);
- put_pid(conn->pid);
- 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(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)
- continue;
-
- if (r->sync)
- kdbus_sync_reply_wakeup(r, -EPIPE);
- else
- /* 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_owner *owner;
-
- lockdep_assert_held(&conn->ep->bus->name_registry->rwlock);
-
- list_for_each_entry(owner, &conn->names_list, conn_entry)
- if (!(owner->flags & KDBUS_NAME_IN_QUEUE) &&
- !strcmp(name, owner->name->name))
- return true;
-
- return false;
-}
-
-struct kdbus_quota {
- u32 memory;
- u16 msgs;
- u8 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 allocation
- * 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 slices 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 their 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_src,
- struct kdbus_conn *conn_dst,
- struct kdbus_staging *staging)
-{
- /* 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) &&
- staging->gaps && staging->gaps->n_fds > 0)
- return ERR_PTR(-ECOMM);
-
- return kdbus_queue_entry_new(conn_src, conn_dst, staging);
-}
-
-/*
- * 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,
- struct kdbus_staging *staging,
- struct kdbus_reply *reply_wake)
-{
- struct kdbus_queue_entry *entry;
- int remote_ret, 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(reply_wake->reply_src, conn_dst,
- staging);
- 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
- * @staging: Message to send
- * @reply: The reply tracker to attach to the queue entry
- * @name: Destination name this msg is sent to, or NULL
- *
- * Return: 0 on success. negative error otherwise.
- */
-int kdbus_conn_entry_insert(struct kdbus_conn *conn_src,
- struct kdbus_conn *conn_dst,
- struct kdbus_staging *staging,
- struct kdbus_reply *reply,
- const struct kdbus_name_entry *name)
-{
- struct kdbus_queue_entry *entry;
- int ret;
-
- kdbus_conn_lock2(conn_src, conn_dst);
-
- entry = kdbus_conn_entry_make(conn_src, conn_dst, staging);
- 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);
- }
-
- /*
- * Record the sequence number of the registered name; it will
- * be remembered by the queue, in case messages addressed to a
- * name need to be moved from or to an activator.
- */
- if (name)
- entry->dst_name_id = name->name_id;
-
- 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_staging *staging,
- struct kdbus_name_entry **out_name,
- struct kdbus_conn **out_dst)
-{
- const struct kdbus_msg *msg = staging->msg;
- struct kdbus_name_owner *owner = NULL;
- struct kdbus_name_entry *name = NULL;
- struct kdbus_conn *dst = NULL;
- int ret;
-
- lockdep_assert_held(&bus->name_registry->rwlock);
-
- if (!staging->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,
- staging->dst_name);
- if (name)
- owner = kdbus_name_get_owner(name);
- if (!owner)
- 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 != owner->conn->id)
- return -EREMCHG;
-
- if ((msg->flags & KDBUS_MSG_NO_AUTO_START) &&
- kdbus_conn_is_activator(owner->conn))
- return -EADDRNOTAVAIL;
-
- dst = kdbus_conn_ref(owner->conn);
- }
-
- *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_staging *staging)
-{
- const struct kdbus_msg *msg = staging->msg;
- struct kdbus_name_entry *name = NULL;
- struct kdbus_reply *reply, *wake = NULL;
- struct kdbus_conn *dst = NULL;
- struct kdbus_bus *bus = src->ep->bus;
- int ret;
-
- if (WARN_ON(msg->dst_id == KDBUS_DST_ID_BROADCAST) ||
- WARN_ON(msg->flags & KDBUS_MSG_EXPECT_REPLY) ||
- WARN_ON(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, staging, &name, &dst);
- if (ret < 0)
- goto exit;
-
- mutex_lock(&dst->lock);
- reply = kdbus_reply_find(src, dst, msg->cookie_reply);
- if (reply) {
- if (reply->sync)
- wake = kdbus_reply_ref(reply);
- kdbus_reply_unlink(reply);
- }
- mutex_unlock(&dst->lock);
-
- if (!reply) {
- ret = -EBADSLT;
- goto exit;
- }
-
- /* send message */
-
- kdbus_bus_eavesdrop(bus, src, staging);
-
- if (wake)
- ret = kdbus_conn_entry_sync_attach(dst, staging, wake);
- else
- ret = kdbus_conn_entry_insert(src, dst, staging, NULL, name);
-
-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_staging *staging,
- ktime_t exp)
-{
- const struct kdbus_msg *msg = staging->msg;
- struct kdbus_name_entry *name = NULL;
- struct kdbus_reply *wait = NULL;
- struct kdbus_conn *dst = NULL;
- struct kdbus_bus *bus = src->ep->bus;
- int ret;
-
- if (WARN_ON(msg->dst_id == KDBUS_DST_ID_BROADCAST) ||
- WARN_ON(msg->flags & KDBUS_MSG_SIGNAL) ||
- WARN_ON(!(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, 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, staging, &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, msg, name, true);
- if (IS_ERR(wait)) {
- ret = PTR_ERR(wait);
- wait = NULL;
- goto exit;
- }
-
- /* send message */
-
- kdbus_bus_eavesdrop(bus, src, staging);
-
- ret = kdbus_conn_entry_insert(src, dst, staging, wait, name);
- 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_staging *staging)
-{
- const struct kdbus_msg *msg = staging->msg;
- 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 = (msg->flags & KDBUS_MSG_SIGNAL);
- int ret = 0;
-
- if (WARN_ON(msg->dst_id == KDBUS_DST_ID_BROADCAST) ||
- WARN_ON(!(msg->flags & KDBUS_MSG_EXPECT_REPLY) &&
- 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, staging, &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, staging);
-
- /* drop silently if peer is not interested or not privileged */
- if (!kdbus_match_db_match_msg(dst->match_db, src, staging) ||
- !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 (msg->flags & KDBUS_MSG_EXPECT_REPLY) {
- wait = kdbus_reply_new(dst, src, msg, name, false);
- if (IS_ERR(wait)) {
- ret = PTR_ERR(wait);
- wait = NULL;
- goto exit;
- }
- }
-
- /* send message */
-
- if (!is_signal)
- kdbus_bus_eavesdrop(bus, src, staging);
-
- ret = kdbus_conn_entry_insert(src, dst, staging, wait, name);
- 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->gaps && e->gaps->n_fds > 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_owner *owner;
- 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(owner, &whom->names_list, conn_entry) {
- if (owner->flags & KDBUS_NAME_IN_QUEUE)
- continue;
-
- res = kdbus_policy_query_unlocked(db,
- conn_creds ? : conn->cred,
- owner->name->name,
- kdbus_strhash(owner->name->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->owner)
- 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->owner)
- 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
- * @msg: Notification message
- *
- * This checks whether @conn is allowed to see the kernel notification.
- *
- * 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_msg *msg)
-{
- /*
- * 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}: Notifications for ID changes are
- * broadcast to everyone, to allow tracking peers.
- */
-
- switch (msg->items[0].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,
- msg->items[0].name_change.name);
-
- case KDBUS_ITEM_ID_ADD:
- case KDBUS_ITEM_ID_REMOVE:
- return true;
-
- default:
- WARN(1, "Invalid type for notification broadcast: %llu\n",
- (unsigned long long)msg->items[0].type);
- return false;
- }
-}
-
-/**
- * kdbus_cmd_hello() - handle KDBUS_CMD_HELLO
- * @ep: Endpoint to operate on
- * @file: File this connection is opened on
- * @argp: Command payload
- *
- * Return: NULL or newly created connection on success, ERR_PTR on failure.
- */
-struct kdbus_conn *kdbus_cmd_hello(struct kdbus_ep *ep, struct file *file,
- 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, file, 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_name_owner *owner = NULL;
- struct kdbus_conn *owner_conn = NULL;
- struct kdbus_item *meta_items = NULL;
- struct kdbus_info info = {};
- struct kdbus_cmd_info *cmd;
- struct kdbus_bus *bus = conn->ep->bus;
- struct kvec kvec[3];
- size_t meta_size, cnt = 0;
- const char *name;
- u64 attach_flags, size = 0;
- 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)
- owner = kdbus_name_get_owner(entry);
- if (!owner ||
- !kdbus_conn_policy_see_name(conn, current_cred(), name) ||
- (cmd->id != 0 && owner->conn->id != cmd->id)) {
- /* pretend a name doesn't exist if you cannot see it */
- ret = -ESRCH;
- goto exit;
- }
-
- owner_conn = kdbus_conn_ref(owner->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;
- }
-
- 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, owner_conn, 0, attach_flags);
- if (ret < 0)
- goto exit;
-
- ret = kdbus_meta_emit(owner_conn->meta_proc, owner_conn->meta_fake,
- conn_meta, conn, attach_flags,
- &meta_items, &meta_size);
- if (ret < 0)
- goto exit;
-
- info.id = owner_conn->id;
- info.flags = owner_conn->flags;
-
- kdbus_kvec_set(&kvec[cnt++], &info, sizeof(info), &size);
- if (meta_size > 0) {
- kdbus_kvec_set(&kvec[cnt++], meta_items, meta_size, &size);
- cnt += !!kdbus_kvec_pad(&kvec[cnt], &size);
- }
-
- info.size = size;
-
- slice = kdbus_pool_slice_alloc(conn->pool, size, false);
- if (IS_ERR(slice)) {
- ret = PTR_ERR(slice);
- slice = NULL;
- goto exit;
- }
-
- ret = kdbus_pool_slice_copy_kvec(slice, 0, kvec, cnt, size);
- if (ret < 0)
- goto exit;
-
- kdbus_pool_slice_publish(slice, &cmd->offset, &cmd->info_size);
-
- if (kdbus_member_set_user(&cmd->offset, argp, typeof(*cmd), offset) ||
- kdbus_member_set_user(&cmd->info_size, argp,
- typeof(*cmd), info_size)) {
- ret = -EFAULT;
- goto exit;
- }
-
- ret = 0;
-
-exit:
- up_read(&bus->name_registry->rwlock);
- kdbus_pool_slice_release(slice);
- kfree(meta_items);
- 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_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 (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_staging *staging = NULL;
- struct kdbus_msg *msg = NULL;
- struct file *cancel_fd = NULL;
- int ret, ret2;
-
- /* command arguments */
- 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),
- };
-
- /* message arguments */
- struct kdbus_arg msg_argv[] = {
- { .type = KDBUS_ITEM_NEGOTIATE },
- { .type = KDBUS_ITEM_PAYLOAD_VEC, .multiple = true },
- { .type = KDBUS_ITEM_PAYLOAD_MEMFD, .multiple = true },
- { .type = KDBUS_ITEM_FDS },
- { .type = KDBUS_ITEM_BLOOM_FILTER },
- { .type = KDBUS_ITEM_DST_NAME },
- };
- struct kdbus_args msg_args = {
- .allowed_flags = KDBUS_FLAG_NEGOTIATE |
- KDBUS_MSG_EXPECT_REPLY |
- KDBUS_MSG_NO_AUTO_START |
- KDBUS_MSG_SIGNAL,
- .argv = msg_argv,
- .argc = ARRAY_SIZE(msg_argv),
- };
-
- if (!kdbus_conn_is_ordinary(conn))
- return -EOPNOTSUPP;
-
- /* make sure to parse both, @cmd and @msg on negotiation */
-
- ret = kdbus_args_parse(&args, argp, &cmd);
- if (ret < 0)
- goto exit;
- else if (ret > 0 && !cmd->msg_address) /* negotiation without msg */
- goto exit;
-
- ret2 = kdbus_args_parse_msg(&msg_args, KDBUS_PTR(cmd->msg_address),
- &msg);
- if (ret2 < 0) { /* cannot parse message */
- ret = ret2;
- goto exit;
- } else if (ret2 > 0 && !ret) { /* msg-negot implies cmd-negot */
- ret = -EINVAL;
- goto exit;
- } else if (ret > 0) { /* negotiation */
- goto exit;
- }
-
- /* here we parsed both, @cmd and @msg, and neither wants negotiation */
-
- 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 (!cancel_fd) {
- ret = -EBADF;
- goto exit;
- }
-
- if (!cancel_fd->f_op->poll) {
- ret = -EINVAL;
- goto exit;
- }
- }
-
- /* patch-in the source of this message */
- if (msg->src_id > 0 && msg->src_id != conn->id) {
- ret = -EINVAL;
- goto exit;
- }
- msg->src_id = conn->id;
-
- staging = kdbus_staging_new_user(conn->ep->bus, cmd, msg);
- if (IS_ERR(staging)) {
- ret = PTR_ERR(staging);
- staging = NULL;
- goto exit;
- }
-
- if (msg->dst_id == KDBUS_DST_ID_BROADCAST) {
- down_read(&conn->ep->bus->name_registry->rwlock);
- kdbus_bus_broadcast(conn->ep->bus, conn, staging);
- 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(msg->timeout_ns);
- r = kdbus_conn_call(conn, staging, 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 ((msg->flags & KDBUS_MSG_EXPECT_REPLY) ||
- msg->cookie_reply == 0) {
- ret = kdbus_conn_unicast(conn, staging);
- if (ret < 0)
- goto exit;
- } else {
- ret = kdbus_conn_reply(conn, staging);
- 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_staging_free(staging);
- ret = kdbus_args_clear(&msg_args, ret);
- 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);
-}