diff options
Diffstat (limited to 'ipc/kdbus/handle.c')
-rw-r--r-- | ipc/kdbus/handle.c | 709 |
1 files changed, 709 insertions, 0 deletions
diff --git a/ipc/kdbus/handle.c b/ipc/kdbus/handle.c new file mode 100644 index 000000000..e0e06b0e1 --- /dev/null +++ b/ipc/kdbus/handle.c @@ -0,0 +1,709 @@ +/* + * 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/file.h> +#include <linux/fs.h> +#include <linux/idr.h> +#include <linux/init.h> +#include <linux/kdev_t.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/poll.h> +#include <linux/rwsem.h> +#include <linux/sched.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/syscalls.h> + +#include "bus.h" +#include "connection.h" +#include "endpoint.h" +#include "fs.h" +#include "handle.h" +#include "item.h" +#include "match.h" +#include "message.h" +#include "names.h" +#include "domain.h" +#include "policy.h" + +static int kdbus_args_verify(struct kdbus_args *args) +{ + struct kdbus_item *item; + size_t i; + int ret; + + KDBUS_ITEMS_FOREACH(item, args->items, args->items_size) { + struct kdbus_arg *arg = NULL; + + if (!KDBUS_ITEM_VALID(item, args->items, args->items_size)) + return -EINVAL; + + for (i = 0; i < args->argc; ++i) + if (args->argv[i].type == item->type) + break; + if (i >= args->argc) + return -EINVAL; + + arg = &args->argv[i]; + + ret = kdbus_item_validate(item); + if (ret < 0) + return ret; + + if (arg->item && !arg->multiple) + return -EINVAL; + + arg->item = item; + } + + if (!KDBUS_ITEMS_END(item, args->items, args->items_size)) + return -EINVAL; + + return 0; +} + +static int kdbus_args_negotiate(struct kdbus_args *args) +{ + struct kdbus_item __user *user; + struct kdbus_item *negotiation; + size_t i, j, num; + + /* + * If KDBUS_FLAG_NEGOTIATE is set, we overwrite the flags field with + * the set of supported flags. Furthermore, if an KDBUS_ITEM_NEGOTIATE + * item is passed, we iterate its payload (array of u64, each set to an + * item type) and clear all unsupported item-types to 0. + * The caller might do this recursively, if other flags or objects are + * embedded in the payload itself. + */ + + if (args->cmd->flags & KDBUS_FLAG_NEGOTIATE) { + if (put_user(args->allowed_flags & ~KDBUS_FLAG_NEGOTIATE, + &args->user->flags)) + return -EFAULT; + } + + if (args->argc < 1 || args->argv[0].type != KDBUS_ITEM_NEGOTIATE || + !args->argv[0].item) + return 0; + + negotiation = args->argv[0].item; + user = (struct kdbus_item __user *) + ((u8 __user *)args->user + + ((u8 *)negotiation - (u8 *)args->cmd)); + num = KDBUS_ITEM_PAYLOAD_SIZE(negotiation) / sizeof(u64); + + for (i = 0; i < num; ++i) { + for (j = 0; j < args->argc; ++j) + if (negotiation->data64[i] == args->argv[j].type) + break; + + if (j < args->argc) + continue; + + /* this item is not supported, clear it out */ + negotiation->data64[i] = 0; + if (put_user(negotiation->data64[i], &user->data64[i])) + return -EFAULT; + } + + return 0; +} + +/** + * __kdbus_args_parse() - parse payload of kdbus command + * @args: object to parse data into + * @is_cmd: whether this is a command or msg payload + * @argp: user-space location of command payload to parse + * @type_size: overall size of command payload to parse + * @items_offset: offset of items array in command payload + * @out: output variable to store pointer to copied payload + * + * This parses the ioctl payload at user-space location @argp into @args. @args + * must be pre-initialized by the caller to reflect the supported flags and + * items of this command. This parser will then copy the command payload into + * kernel-space, verify correctness and consistency and cache pointers to parsed + * items and other data in @args. + * + * If this function succeeded, you must call kdbus_args_clear() to release + * allocated resources before destroying @args. + * + * This can also be used to import kdbus_msg objects. In that case, @is_cmd must + * be set to 'false' and the 'return_flags' field will not be touched (as it + * doesn't exist on kdbus_msg). + * + * Return: On failure a negative error code is returned. Otherwise, 1 is + * returned if negotiation was requested, 0 if not. + */ +int __kdbus_args_parse(struct kdbus_args *args, bool is_cmd, void __user *argp, + size_t type_size, size_t items_offset, void **out) +{ + u64 user_size; + int ret, i; + + ret = kdbus_copy_from_user(&user_size, argp, sizeof(user_size)); + if (ret < 0) + return ret; + + if (user_size < type_size) + return -EINVAL; + if (user_size > KDBUS_CMD_MAX_SIZE) + return -EMSGSIZE; + + if (user_size <= sizeof(args->cmd_buf)) { + if (copy_from_user(args->cmd_buf, argp, user_size)) + return -EFAULT; + args->cmd = (void*)args->cmd_buf; + } else { + args->cmd = memdup_user(argp, user_size); + if (IS_ERR(args->cmd)) + return PTR_ERR(args->cmd); + } + + if (args->cmd->size != user_size) { + ret = -EINVAL; + goto error; + } + + if (is_cmd) + args->cmd->return_flags = 0; + args->user = argp; + args->items = (void *)((u8 *)args->cmd + items_offset); + args->items_size = args->cmd->size - items_offset; + args->is_cmd = is_cmd; + + if (args->cmd->flags & ~args->allowed_flags) { + ret = -EINVAL; + goto error; + } + + ret = kdbus_args_verify(args); + if (ret < 0) + goto error; + + ret = kdbus_args_negotiate(args); + if (ret < 0) + goto error; + + /* mandatory items must be given (but not on negotiation) */ + if (!(args->cmd->flags & KDBUS_FLAG_NEGOTIATE)) { + for (i = 0; i < args->argc; ++i) + if (args->argv[i].mandatory && !args->argv[i].item) { + ret = -EINVAL; + goto error; + } + } + + *out = args->cmd; + return !!(args->cmd->flags & KDBUS_FLAG_NEGOTIATE); + +error: + return kdbus_args_clear(args, ret); +} + +/** + * kdbus_args_clear() - release allocated command resources + * @args: object to release resources of + * @ret: return value of this command + * + * This frees all allocated resources on @args and copies the command result + * flags into user-space. @ret is usually returned unchanged by this function, + * so it can be used in the final 'return' statement of the command handler. + * + * Return: -EFAULT if return values cannot be copied into user-space, otherwise + * @ret is returned unchanged. + */ +int kdbus_args_clear(struct kdbus_args *args, int ret) +{ + if (!args) + return ret; + + if (!IS_ERR_OR_NULL(args->cmd)) { + if (args->is_cmd && put_user(args->cmd->return_flags, + &args->user->return_flags)) + ret = -EFAULT; + if (args->cmd != (void*)args->cmd_buf) + kfree(args->cmd); + args->cmd = NULL; + } + + return ret; +} + +/** + * enum kdbus_handle_type - type an handle can be of + * @KDBUS_HANDLE_NONE: no type set, yet + * @KDBUS_HANDLE_BUS_OWNER: bus owner + * @KDBUS_HANDLE_EP_OWNER: endpoint owner + * @KDBUS_HANDLE_CONNECTED: endpoint connection after HELLO + */ +enum kdbus_handle_type { + KDBUS_HANDLE_NONE, + KDBUS_HANDLE_BUS_OWNER, + KDBUS_HANDLE_EP_OWNER, + KDBUS_HANDLE_CONNECTED, +}; + +/** + * struct kdbus_handle - handle to the kdbus system + * @lock: handle lock + * @type: type of this handle (KDBUS_HANDLE_*) + * @bus_owner: bus this handle owns + * @ep_owner: endpoint this handle owns + * @conn: connection this handle owns + * @privileged: Flag to mark a handle as privileged + */ +struct kdbus_handle { + struct mutex lock; + + enum kdbus_handle_type type; + union { + struct kdbus_bus *bus_owner; + struct kdbus_ep *ep_owner; + struct kdbus_conn *conn; + }; + + bool privileged:1; +}; + +static int kdbus_handle_open(struct inode *inode, struct file *file) +{ + struct kdbus_handle *handle; + struct kdbus_node *node; + int ret; + + node = kdbus_node_from_inode(inode); + if (!kdbus_node_acquire(node)) + return -ESHUTDOWN; + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (!handle) { + ret = -ENOMEM; + goto exit; + } + + mutex_init(&handle->lock); + handle->type = KDBUS_HANDLE_NONE; + + if (node->type == KDBUS_NODE_ENDPOINT) { + struct kdbus_ep *ep = kdbus_ep_from_node(node); + struct kdbus_bus *bus = ep->bus; + + /* + * A connection is privileged if it is opened on an endpoint + * without custom policy and either: + * * the user has CAP_IPC_OWNER in the domain user namespace + * or + * * the callers euid matches the uid of the bus creator + */ + if (!ep->user && + (ns_capable(bus->domain->user_namespace, CAP_IPC_OWNER) || + uid_eq(file->f_cred->euid, bus->node.uid))) + handle->privileged = true; + } + + file->private_data = handle; + ret = 0; + +exit: + kdbus_node_release(node); + return ret; +} + +static int kdbus_handle_release(struct inode *inode, struct file *file) +{ + struct kdbus_handle *handle = file->private_data; + + switch (handle->type) { + case KDBUS_HANDLE_BUS_OWNER: + if (handle->bus_owner) { + kdbus_node_deactivate(&handle->bus_owner->node); + kdbus_bus_unref(handle->bus_owner); + } + break; + case KDBUS_HANDLE_EP_OWNER: + if (handle->ep_owner) { + kdbus_node_deactivate(&handle->ep_owner->node); + kdbus_ep_unref(handle->ep_owner); + } + break; + case KDBUS_HANDLE_CONNECTED: + kdbus_conn_disconnect(handle->conn, false); + kdbus_conn_unref(handle->conn); + break; + case KDBUS_HANDLE_NONE: + /* nothing to clean up */ + break; + } + + kfree(handle); + + return 0; +} + +static long kdbus_handle_ioctl_control(struct file *file, unsigned int cmd, + void __user *argp) +{ + struct kdbus_handle *handle = file->private_data; + struct kdbus_node *node = file_inode(file)->i_private; + struct kdbus_domain *domain; + int ret = 0; + + if (!kdbus_node_acquire(node)) + return -ESHUTDOWN; + + /* + * The parent of control-nodes is always a domain, make sure to pin it + * so the parent is actually valid. + */ + domain = kdbus_domain_from_node(node->parent); + if (!kdbus_node_acquire(&domain->node)) { + kdbus_node_release(node); + return -ESHUTDOWN; + } + + switch (cmd) { + case KDBUS_CMD_BUS_MAKE: { + struct kdbus_bus *bus; + + bus = kdbus_cmd_bus_make(domain, argp); + if (IS_ERR_OR_NULL(bus)) { + ret = PTR_ERR_OR_ZERO(bus); + break; + } + + handle->bus_owner = bus; + ret = KDBUS_HANDLE_BUS_OWNER; + break; + } + + default: + ret = -EBADFD; + break; + } + + kdbus_node_release(&domain->node); + kdbus_node_release(node); + return ret; +} + +static long kdbus_handle_ioctl_ep(struct file *file, unsigned int cmd, + void __user *buf) +{ + struct kdbus_handle *handle = file->private_data; + struct kdbus_node *node = file_inode(file)->i_private; + struct kdbus_ep *ep, *file_ep = kdbus_ep_from_node(node); + struct kdbus_conn *conn; + int ret = 0; + + if (!kdbus_node_acquire(node)) + return -ESHUTDOWN; + + switch (cmd) { + case KDBUS_CMD_ENDPOINT_MAKE: + /* creating custom endpoints is a privileged operation */ + if (!handle->privileged) { + ret = -EPERM; + break; + } + + ep = kdbus_cmd_ep_make(file_ep->bus, buf); + if (IS_ERR_OR_NULL(ep)) { + ret = PTR_ERR_OR_ZERO(ep); + break; + } + + handle->ep_owner = ep; + ret = KDBUS_HANDLE_EP_OWNER; + break; + + case KDBUS_CMD_HELLO: + conn = kdbus_cmd_hello(file_ep, handle->privileged, buf); + if (IS_ERR_OR_NULL(conn)) { + ret = PTR_ERR_OR_ZERO(conn); + break; + } + + handle->conn = conn; + ret = KDBUS_HANDLE_CONNECTED; + break; + + default: + ret = -EBADFD; + break; + } + + kdbus_node_release(node); + return ret; +} + +static long kdbus_handle_ioctl_ep_owner(struct file *file, unsigned int command, + void __user *buf) +{ + struct kdbus_handle *handle = file->private_data; + struct kdbus_ep *ep = handle->ep_owner; + int ret; + + if (!kdbus_node_acquire(&ep->node)) + return -ESHUTDOWN; + + switch (command) { + case KDBUS_CMD_ENDPOINT_UPDATE: + ret = kdbus_cmd_ep_update(ep, buf); + break; + default: + ret = -EBADFD; + break; + } + + kdbus_node_release(&ep->node); + return ret; +} + +static long kdbus_handle_ioctl_connected(struct file *file, + unsigned int command, void __user *buf) +{ + struct kdbus_handle *handle = file->private_data; + struct kdbus_conn *conn = handle->conn; + struct kdbus_conn *release_conn = NULL; + int ret; + + release_conn = conn; + ret = kdbus_conn_acquire(release_conn); + if (ret < 0) + return ret; + + switch (command) { + case KDBUS_CMD_BYEBYE: + /* + * BYEBYE is special; we must not acquire a connection when + * calling into kdbus_conn_disconnect() or we will deadlock, + * because kdbus_conn_disconnect() will wait for all acquired + * references to be dropped. + */ + kdbus_conn_release(release_conn); + release_conn = NULL; + ret = kdbus_cmd_byebye_unlocked(conn, buf); + break; + case KDBUS_CMD_NAME_ACQUIRE: + ret = kdbus_cmd_name_acquire(conn, buf); + break; + case KDBUS_CMD_NAME_RELEASE: + ret = kdbus_cmd_name_release(conn, buf); + break; + case KDBUS_CMD_LIST: + ret = kdbus_cmd_list(conn, buf); + break; + case KDBUS_CMD_CONN_INFO: + ret = kdbus_cmd_conn_info(conn, buf); + break; + case KDBUS_CMD_BUS_CREATOR_INFO: + ret = kdbus_cmd_bus_creator_info(conn, buf); + break; + case KDBUS_CMD_UPDATE: + ret = kdbus_cmd_update(conn, buf); + break; + case KDBUS_CMD_MATCH_ADD: + ret = kdbus_cmd_match_add(conn, buf); + break; + case KDBUS_CMD_MATCH_REMOVE: + ret = kdbus_cmd_match_remove(conn, buf); + break; + case KDBUS_CMD_SEND: + ret = kdbus_cmd_send(conn, file, buf); + break; + case KDBUS_CMD_RECV: + ret = kdbus_cmd_recv(conn, buf); + break; + case KDBUS_CMD_FREE: + ret = kdbus_cmd_free(conn, buf); + break; + default: + ret = -EBADFD; + break; + } + + kdbus_conn_release(release_conn); + return ret; +} + +static long kdbus_handle_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct kdbus_handle *handle = file->private_data; + struct kdbus_node *node = kdbus_node_from_inode(file_inode(file)); + void __user *argp = (void __user *)arg; + long ret = -EBADFD; + + switch (cmd) { + case KDBUS_CMD_BUS_MAKE: + case KDBUS_CMD_ENDPOINT_MAKE: + case KDBUS_CMD_HELLO: + mutex_lock(&handle->lock); + if (handle->type == KDBUS_HANDLE_NONE) { + if (node->type == KDBUS_NODE_CONTROL) + ret = kdbus_handle_ioctl_control(file, cmd, + argp); + else if (node->type == KDBUS_NODE_ENDPOINT) + ret = kdbus_handle_ioctl_ep(file, cmd, argp); + + if (ret > 0) { + /* + * The data given via open() is not sufficient + * to setup a kdbus handle. Hence, we require + * the user to perform a setup ioctl. This setup + * can only be performed once and defines the + * type of the handle. The different setup + * ioctls are locked against each other so they + * cannot race. Once the handle type is set, + * the type-dependent ioctls are enabled. To + * improve performance, we don't lock those via + * handle->lock. Instead, we issue a + * write-barrier before performing the + * type-change, which pairs with smp_rmb() in + * all handlers that access the type field. This + * guarantees the handle is fully setup, if + * handle->type is set. If handle->type is + * unset, you must not make any assumptions + * without taking handle->lock. + * Note that handle->type is only set once. It + * will never change afterwards. + */ + smp_wmb(); + handle->type = ret; + } + } + mutex_unlock(&handle->lock); + break; + + case KDBUS_CMD_ENDPOINT_UPDATE: + case KDBUS_CMD_BYEBYE: + case KDBUS_CMD_NAME_ACQUIRE: + case KDBUS_CMD_NAME_RELEASE: + case KDBUS_CMD_LIST: + case KDBUS_CMD_CONN_INFO: + case KDBUS_CMD_BUS_CREATOR_INFO: + case KDBUS_CMD_UPDATE: + case KDBUS_CMD_MATCH_ADD: + case KDBUS_CMD_MATCH_REMOVE: + case KDBUS_CMD_SEND: + case KDBUS_CMD_RECV: + case KDBUS_CMD_FREE: { + enum kdbus_handle_type type; + + /* + * This read-barrier pairs with smp_wmb() of the handle setup. + * it guarantees the handle is fully written, in case the + * type has been set. It allows us to access the handle without + * taking handle->lock, given the guarantee that the type is + * only ever set once, and stays constant afterwards. + * Furthermore, the handle object itself is not modified in any + * way after the type is set. That is, the type-field is the + * last field that is written on any handle. If it has not been + * set, we must not access the handle here. + */ + type = handle->type; + smp_rmb(); + + if (type == KDBUS_HANDLE_EP_OWNER) + ret = kdbus_handle_ioctl_ep_owner(file, cmd, argp); + else if (type == KDBUS_HANDLE_CONNECTED) + ret = kdbus_handle_ioctl_connected(file, cmd, argp); + + break; + } + default: + ret = -ENOTTY; + break; + } + + return ret < 0 ? ret : 0; +} + +static unsigned int kdbus_handle_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct kdbus_handle *handle = file->private_data; + enum kdbus_handle_type type; + unsigned int mask = POLLOUT | POLLWRNORM; + + /* + * This pairs with smp_wmb() during handle setup. It guarantees that + * _iff_ the handle type is set, handle->conn is valid. Furthermore, + * _iff_ the type is set, the handle object is constant and never + * changed again. If it's not set, we must not access the handle but + * bail out. We also must assume no setup has taken place, yet. + */ + type = handle->type; + smp_rmb(); + + /* Only a connected endpoint can read/write data */ + if (type != KDBUS_HANDLE_CONNECTED) + return POLLERR | POLLHUP; + + poll_wait(file, &handle->conn->wait, wait); + + /* + * Verify the connection hasn't been deactivated _after_ adding the + * wait-queue. This guarantees, that if the connection is deactivated + * after we checked it, the waitqueue is signaled and we're called + * again. + */ + if (!kdbus_conn_active(handle->conn)) + return POLLERR | POLLHUP; + + if (!list_empty(&handle->conn->queue.msg_list) || + atomic_read(&handle->conn->lost_count) > 0) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +static int kdbus_handle_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct kdbus_handle *handle = file->private_data; + enum kdbus_handle_type type; + int ret = -EBADFD; + + /* + * This pairs with smp_wmb() during handle setup. It guarantees that + * _iff_ the handle type is set, handle->conn is valid. Furthermore, + * _iff_ the type is set, the handle object is constant and never + * changed again. If it's not set, we must not access the handle but + * bail out. We also must assume no setup has taken place, yet. + */ + type = handle->type; + smp_rmb(); + + /* Only connected handles have a pool we can map */ + if (type == KDBUS_HANDLE_CONNECTED) + ret = kdbus_pool_mmap(handle->conn->pool, vma); + + return ret; +} + +const struct file_operations kdbus_handle_ops = { + .owner = THIS_MODULE, + .open = kdbus_handle_open, + .release = kdbus_handle_release, + .poll = kdbus_handle_poll, + .llseek = noop_llseek, + .unlocked_ioctl = kdbus_handle_ioctl, + .mmap = kdbus_handle_mmap, +#ifdef CONFIG_COMPAT + .compat_ioctl = kdbus_handle_ioctl, +#endif +}; |