blob: a449464a39755eb5d7b2e8d9c785fab5f260ea2d [file] [log] [blame]
/*
* Copyright (C) 2013-2015 Kay Sievers
* Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org>
* Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org>
* Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com>
* Copyright (C) 2013-2015 Linux Foundation
* Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org>
*
* kdbus is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*/
#include <linux/audit.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/hashtable.h>
#include <linux/idr.h>
#include <linux/init.h>
#include <linux/math64.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/syscalls.h>
#include <linux/uio.h>
#include "util.h"
#include "domain.h"
#include "connection.h"
#include "item.h"
#include "message.h"
#include "metadata.h"
#include "queue.h"
#include "reply.h"
/**
* kdbus_queue_init() - initialize data structure related to a queue
* @queue: The queue to initialize
*/
void kdbus_queue_init(struct kdbus_queue *queue)
{
INIT_LIST_HEAD(&queue->msg_list);
queue->msg_prio_queue = RB_ROOT;
}
/**
* kdbus_queue_peek() - Retrieves an entry from a queue
* @queue: The queue
* @priority: The minimum priority of the entry to peek
* @use_priority: Boolean flag whether or not to peek by priority
*
* Look for a entry in a queue, either by priority, or the oldest one (FIFO).
* The entry is not freed, put off the queue's lists or anything else.
*
* Return: the peeked queue entry on success, NULL if no suitable msg is found
*/
struct kdbus_queue_entry *kdbus_queue_peek(struct kdbus_queue *queue,
s64 priority, bool use_priority)
{
struct kdbus_queue_entry *e;
if (list_empty(&queue->msg_list))
return NULL;
if (use_priority) {
/* get next entry with highest priority */
e = rb_entry(queue->msg_prio_highest,
struct kdbus_queue_entry, prio_node);
/* no entry with the requested priority */
if (e->priority > priority)
return NULL;
} else {
/* ignore the priority, return the next entry in the entry */
e = list_first_entry(&queue->msg_list,
struct kdbus_queue_entry, entry);
}
return e;
}
static void kdbus_queue_entry_link(struct kdbus_queue_entry *entry)
{
struct kdbus_queue *queue = &entry->conn->queue;
struct rb_node **n, *pn = NULL;
bool highest = true;
lockdep_assert_held(&entry->conn->lock);
if (WARN_ON(!list_empty(&entry->entry)))
return;
/* sort into priority entry tree */
n = &queue->msg_prio_queue.rb_node;
while (*n) {
struct kdbus_queue_entry *e;
pn = *n;
e = rb_entry(pn, struct kdbus_queue_entry, prio_node);
/* existing node for this priority, add to its list */
if (likely(entry->priority == e->priority)) {
list_add_tail(&entry->prio_entry, &e->prio_entry);
goto prio_done;
}
if (entry->priority < e->priority) {
n = &pn->rb_left;
} else {
n = &pn->rb_right;
highest = false;
}
}
/* cache highest-priority entry */
if (highest)
queue->msg_prio_highest = &entry->prio_node;
/* new node for this priority */
rb_link_node(&entry->prio_node, pn, n);
rb_insert_color(&entry->prio_node, &queue->msg_prio_queue);
INIT_LIST_HEAD(&entry->prio_entry);
prio_done:
/* add to unsorted fifo list */
list_add_tail(&entry->entry, &queue->msg_list);
}
static void kdbus_queue_entry_unlink(struct kdbus_queue_entry *entry)
{
struct kdbus_queue *queue = &entry->conn->queue;
lockdep_assert_held(&entry->conn->lock);
if (list_empty(&entry->entry))
return;
list_del_init(&entry->entry);
if (list_empty(&entry->prio_entry)) {
/*
* Single entry for this priority, update cached
* highest-priority entry, remove the tree node.
*/
if (queue->msg_prio_highest == &entry->prio_node)
queue->msg_prio_highest = rb_next(&entry->prio_node);
rb_erase(&entry->prio_node, &queue->msg_prio_queue);
} else {
struct kdbus_queue_entry *q;
/*
* Multiple entries for this priority entry, get next one in
* the list. Update cached highest-priority entry, store the
* new one as the tree node.
*/
q = list_first_entry(&entry->prio_entry,
struct kdbus_queue_entry, prio_entry);
list_del(&entry->prio_entry);
if (queue->msg_prio_highest == &entry->prio_node)
queue->msg_prio_highest = &q->prio_node;
rb_replace_node(&entry->prio_node, &q->prio_node,
&queue->msg_prio_queue);
}
}
/**
* kdbus_queue_entry_new() - allocate a queue entry
* @conn_dst: destination connection
* @kmsg: kmsg object the queue entry should track
* @user: user to account message on (or NULL for kernel messages)
*
* Allocates a queue entry based on a given kmsg and allocate space for
* the message payload and the requested metadata in the connection's pool.
* The entry is not actually added to the queue's lists at this point.
*
* Return: the allocated entry on success, or an ERR_PTR on failures.
*/
struct kdbus_queue_entry *kdbus_queue_entry_new(struct kdbus_conn *conn_dst,
const struct kdbus_kmsg *kmsg,
struct kdbus_user *user)
{
struct kdbus_msg_resources *res = kmsg->res;
const struct kdbus_msg *msg = &kmsg->msg;
struct kdbus_queue_entry *entry;
size_t memfd_cnt = 0;
struct kvec kvec[2];
size_t meta_size;
size_t msg_size;
u64 payload_off;
u64 size = 0;
int ret = 0;
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (!entry)
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD(&entry->entry);
entry->priority = msg->priority;
entry->dst_name_id = kmsg->dst_name_id;
entry->msg_res = kdbus_msg_resources_ref(res);
entry->proc_meta = kdbus_meta_proc_ref(kmsg->proc_meta);
entry->conn_meta = kdbus_meta_conn_ref(kmsg->conn_meta);
entry->conn = kdbus_conn_ref(conn_dst);
if (kmsg->msg.src_id == KDBUS_SRC_ID_KERNEL)
msg_size = msg->size;
else
msg_size = offsetof(struct kdbus_msg, items);
/* sum up the size of the needed slice */
size = msg_size;
if (res) {
size += res->vec_count *
KDBUS_ITEM_SIZE(sizeof(struct kdbus_vec));
if (res->memfd_count) {
entry->memfd_offset =
kcalloc(res->memfd_count, sizeof(size_t),
GFP_KERNEL);
if (!entry->memfd_offset) {
ret = -ENOMEM;
goto exit_free_entry;
}
size += res->memfd_count *
KDBUS_ITEM_SIZE(sizeof(struct kdbus_memfd));
}
if (res->fds_count)
size += KDBUS_ITEM_SIZE(sizeof(int) * res->fds_count);
if (res->dst_name)
size += KDBUS_ITEM_SIZE(strlen(res->dst_name) + 1);
}
/*
* Remember the offset of the metadata part, so we can override
* this part later during kdbus_queue_entry_install().
*/
entry->meta_offset = size;
if (entry->proc_meta || entry->conn_meta) {
entry->attach_flags =
atomic64_read(&conn_dst->attach_flags_recv);
ret = kdbus_meta_export_prepare(entry->proc_meta,
entry->conn_meta,
&entry->attach_flags,
&meta_size);
if (ret < 0)
goto exit_free_entry;
size += meta_size;
}
payload_off = size;
size += kmsg->pool_size;
size = KDBUS_ALIGN8(size);
ret = kdbus_conn_quota_inc(conn_dst, user, size,
res ? res->fds_count : 0);
if (ret < 0)
goto exit_free_entry;
entry->slice = kdbus_pool_slice_alloc(conn_dst->pool, size, true);
if (IS_ERR(entry->slice)) {
ret = PTR_ERR(entry->slice);
entry->slice = NULL;
kdbus_conn_quota_dec(conn_dst, user, size,
res ? res->fds_count : 0);
goto exit_free_entry;
}
/* we accounted for exactly 'size' bytes, make sure it didn't grow */
WARN_ON(kdbus_pool_slice_size(entry->slice) != size);
entry->user = kdbus_user_ref(user);
/* copy message header */
kvec[0].iov_base = (char *)msg;
kvec[0].iov_len = msg_size;
ret = kdbus_pool_slice_copy_kvec(entry->slice, 0, kvec, 1, msg_size);
if (ret < 0)
goto exit_free_entry;
/* 'size' will now track the write position */
size = msg_size;
/* create message payload items */
if (res) {
size_t dst_name_len = 0;
unsigned int i;
size_t sz = 0;
if (res->dst_name) {
dst_name_len = strlen(res->dst_name) + 1;
sz += KDBUS_ITEM_SIZE(dst_name_len);
}
for (i = 0; i < res->data_count; ++i) {
struct kdbus_vec v;
struct kdbus_memfd m;
switch (res->data[i].type) {
case KDBUS_MSG_DATA_VEC:
sz += KDBUS_ITEM_SIZE(sizeof(v));
break;
case KDBUS_MSG_DATA_MEMFD:
sz += KDBUS_ITEM_SIZE(sizeof(m));
break;
}
}
if (sz) {
struct kdbus_item *items, *item;
items = kmalloc(sz, GFP_KERNEL);
if (!items) {
ret = -ENOMEM;
goto exit_free_entry;
}
item = items;
if (res->dst_name)
item = kdbus_item_set(item, KDBUS_ITEM_DST_NAME,
res->dst_name,
dst_name_len);
for (i = 0; i < res->data_count; ++i) {
struct kdbus_msg_data *d = res->data + i;
struct kdbus_memfd m = {};
struct kdbus_vec v = {};
switch (d->type) {
case KDBUS_MSG_DATA_VEC:
v.size = d->size;
v.offset = d->vec.off;
if (v.offset != ~0ULL)
v.offset += payload_off;
item = kdbus_item_set(item,
KDBUS_ITEM_PAYLOAD_OFF,
&v, sizeof(v));
break;
case KDBUS_MSG_DATA_MEMFD:
/*
* Remember the location of memfds, so
* we can override the content from
* kdbus_queue_entry_install().
*/
entry->memfd_offset[memfd_cnt++] =
msg_size +
(char *)item - (char *)items +
offsetof(struct kdbus_item,
memfd);
item = kdbus_item_set(item,
KDBUS_ITEM_PAYLOAD_MEMFD,
&m, sizeof(m));
break;
}
}
kvec[0].iov_base = items;
kvec[0].iov_len = sz;
ret = kdbus_pool_slice_copy_kvec(entry->slice, size,
kvec, 1, sz);
kfree(items);
if (ret < 0)
goto exit_free_entry;
size += sz;
}
/*
* Remember the location of the FD part, so we can override the
* content in kdbus_queue_entry_install().
*/
if (res->fds_count) {
entry->fds_offset = size;
size += KDBUS_ITEM_SIZE(sizeof(int) * res->fds_count);
}
}
/* finally, copy over the actual message payload */
if (kmsg->iov_count) {
ret = kdbus_pool_slice_copy_iovec(entry->slice, payload_off,
kmsg->iov,
kmsg->iov_count,
kmsg->pool_size);
if (ret < 0)
goto exit_free_entry;
}
return entry;
exit_free_entry:
kdbus_queue_entry_free(entry);
return ERR_PTR(ret);
}
/**
* kdbus_queue_entry_free() - free resources of an entry
* @entry: The entry to free
*
* Removes resources allocated by a queue entry, along with the entry itself.
* Note that the entry's slice is not freed at this point.
*/
void kdbus_queue_entry_free(struct kdbus_queue_entry *entry)
{
if (!entry)
return;
lockdep_assert_held(&entry->conn->lock);
kdbus_queue_entry_unlink(entry);
kdbus_reply_unref(entry->reply);
if (entry->slice) {
kdbus_conn_quota_dec(entry->conn, entry->user,
kdbus_pool_slice_size(entry->slice),
entry->msg_res ?
entry->msg_res->fds_count : 0);
kdbus_pool_slice_release(entry->slice);
kdbus_user_unref(entry->user);
}
kdbus_msg_resources_unref(entry->msg_res);
kdbus_meta_conn_unref(entry->conn_meta);
kdbus_meta_proc_unref(entry->proc_meta);
kdbus_conn_unref(entry->conn);
kfree(entry->memfd_offset);
kfree(entry);
}
/**
* kdbus_queue_entry_install() - install message components into the
* receiver's process
* @entry: The queue entry to install
* @return_flags: Pointer to store the return flags for userspace
* @install_fds: Whether or not to install associated file descriptors
*
* This function will create a slice to transport the message header, the
* metadata items and other items for information stored in @entry, and
* store it as entry->slice.
*
* If @install_fds is %true, file descriptors will as well be installed.
* This function must always be called from the task context of the receiver.
*
* Return: 0 on success.
*/
int kdbus_queue_entry_install(struct kdbus_queue_entry *entry,
u64 *return_flags, bool install_fds)
{
u64 msg_size = entry->meta_offset;
struct kdbus_conn *conn_dst = entry->conn;
struct kdbus_msg_resources *res;
bool incomplete_fds = false;
struct kvec kvec[2];
size_t memfds = 0;
int i, ret;
lockdep_assert_held(&conn_dst->lock);
if (entry->proc_meta || entry->conn_meta) {
size_t meta_size;
ret = kdbus_meta_export(entry->proc_meta,
entry->conn_meta,
entry->attach_flags,
entry->slice,
entry->meta_offset,
&meta_size);
if (ret < 0)
return ret;
msg_size += meta_size;
}
/* Update message size at offset 0 */
kvec[0].iov_base = &msg_size;
kvec[0].iov_len = sizeof(msg_size);
ret = kdbus_pool_slice_copy_kvec(entry->slice, 0, kvec, 1,
sizeof(msg_size));
if (ret < 0)
return ret;
res = entry->msg_res;
if (!res)
return 0;
if (res->fds_count) {
struct kdbus_item_header hdr;
size_t off;
int *fds;
fds = kmalloc_array(res->fds_count, sizeof(int), GFP_KERNEL);
if (!fds)
return -ENOMEM;
for (i = 0; i < res->fds_count; i++) {
if (install_fds) {
fds[i] = get_unused_fd_flags(O_CLOEXEC);
if (fds[i] >= 0)
fd_install(fds[i],
get_file(res->fds[i]));
else
incomplete_fds = true;
} else {
fds[i] = -1;
}
}
off = entry->fds_offset;
hdr.type = KDBUS_ITEM_FDS;
hdr.size = KDBUS_ITEM_HEADER_SIZE +
sizeof(int) * res->fds_count;
kvec[0].iov_base = &hdr;
kvec[0].iov_len = sizeof(hdr);
kvec[1].iov_base = fds;
kvec[1].iov_len = sizeof(int) * res->fds_count;
ret = kdbus_pool_slice_copy_kvec(entry->slice, off,
kvec, 2, hdr.size);
kfree(fds);
if (ret < 0)
return ret;
}
for (i = 0; i < res->data_count; ++i) {
struct kdbus_msg_data *d = res->data + i;
struct kdbus_memfd m;
if (d->type != KDBUS_MSG_DATA_MEMFD)
continue;
m.start = d->memfd.start;
m.size = d->size;
m.fd = -1;
if (install_fds) {
m.fd = get_unused_fd_flags(O_CLOEXEC);
if (m.fd < 0) {
m.fd = -1;
incomplete_fds = true;
} else {
fd_install(m.fd,
get_file(d->memfd.file));
}
}
kvec[0].iov_base = &m;
kvec[0].iov_len = sizeof(m);
ret = kdbus_pool_slice_copy_kvec(entry->slice,
entry->memfd_offset[memfds++],
kvec, 1, sizeof(m));
if (ret < 0)
return ret;
}
if (incomplete_fds)
*return_flags |= KDBUS_RECV_RETURN_INCOMPLETE_FDS;
return 0;
}
/**
* kdbus_queue_entry_enqueue() - enqueue an entry
* @entry: entry to enqueue
* @reply: reply to link to this entry (or NULL if none)
*
* This enqueues an unqueued entry into the message queue of the linked
* connection. It also binds a reply object to the entry so we can remember it
* when the message is moved.
*
* Once this call returns (and the connection lock is released), this entry can
* be dequeued by the target connection. Note that the entry will not be removed
* from the queue until it is destroyed.
*/
void kdbus_queue_entry_enqueue(struct kdbus_queue_entry *entry,
struct kdbus_reply *reply)
{
lockdep_assert_held(&entry->conn->lock);
if (WARN_ON(entry->reply) || WARN_ON(!list_empty(&entry->entry)))
return;
entry->reply = kdbus_reply_ref(reply);
kdbus_queue_entry_link(entry);
}
/**
* kdbus_queue_entry_move() - move queue entry
* @e: queue entry to move
* @dst: destination connection to queue the entry on
*
* This moves a queue entry onto a different connection. It allocates a new
* slice on the target connection and copies the message over. If the copy
* succeeded, we move the entry from @src to @dst.
*
* On failure, the entry is left untouched.
*
* The queue entry must be queued right now, and after the call succeeds it will
* be queued on the destination, but no longer on the source.
*
* The caller must hold the connection lock of the source *and* destination.
*
* Return: 0 on success, negative error code on failure.
*/
int kdbus_queue_entry_move(struct kdbus_queue_entry *e,
struct kdbus_conn *dst)
{
struct kdbus_pool_slice *slice = NULL;
struct kdbus_conn *src = e->conn;
size_t size, fds;
int ret;
lockdep_assert_held(&src->lock);
lockdep_assert_held(&dst->lock);
if (WARN_ON(IS_ERR(e->user)) || WARN_ON(list_empty(&e->entry)))
return -EINVAL;
if (src == dst)
return 0;
size = kdbus_pool_slice_size(e->slice);
fds = e->msg_res ? e->msg_res->fds_count : 0;
ret = kdbus_conn_quota_inc(dst, e->user, size, fds);
if (ret < 0)
return ret;
slice = kdbus_pool_slice_alloc(dst->pool, size, true);
if (IS_ERR(slice)) {
ret = PTR_ERR(slice);
slice = NULL;
goto error;
}
ret = kdbus_pool_slice_copy(slice, e->slice);
if (ret < 0)
goto error;
kdbus_queue_entry_unlink(e);
kdbus_conn_quota_dec(src, e->user, size, fds);
kdbus_pool_slice_release(e->slice);
kdbus_conn_unref(e->conn);
e->slice = slice;
e->conn = kdbus_conn_ref(dst);
kdbus_queue_entry_link(e);
return 0;
error:
kdbus_pool_slice_release(slice);
kdbus_conn_quota_dec(dst, e->user, size, fds);
return ret;
}