Find file
Fetching contributors…
Cannot retrieve contributors at this time
617 lines (510 sloc) 13.3 KB
/*
* Copyright (C) 2013-2015 Kay Sievers
* Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org>
* Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org>
* Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com>
* Copyright (C) 2013-2015 Linux Foundation
* Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org>
*
* kdbus is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*/
#include <linux/capability.h>
#include <linux/cgroup.h>
#include <linux/cred.h>
#include <linux/file.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/sched.h>
#include <linux/shmem_fs.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <net/sock.h>
#include "bus.h"
#include "connection.h"
#include "domain.h"
#include "endpoint.h"
#include "handle.h"
#include "item.h"
#include "match.h"
#include "message.h"
#include "names.h"
#include "policy.h"
#define KDBUS_KMSG_HEADER_SIZE offsetof(struct kdbus_kmsg, msg)
static struct kdbus_msg_resources *kdbus_msg_resources_new(void)
{
struct kdbus_msg_resources *r;
r = kzalloc(sizeof(*r), GFP_KERNEL);
if (!r)
return ERR_PTR(-ENOMEM);
kref_init(&r->kref);
return r;
}
static void __kdbus_msg_resources_free(struct kref *kref)
{
struct kdbus_msg_resources *r =
container_of(kref, struct kdbus_msg_resources, kref);
size_t i;
for (i = 0; i < r->data_count; ++i) {
switch (r->data[i].type) {
case KDBUS_MSG_DATA_VEC:
/* nothing to do */
break;
case KDBUS_MSG_DATA_MEMFD:
if (r->data[i].memfd.file)
fput(r->data[i].memfd.file);
break;
}
}
for (i = 0; i < r->fds_count; i++)
if (r->fds[i])
fput(r->fds[i]);
kfree(r->dst_name);
kfree(r->data);
kfree(r->fds);
kfree(r);
}
/**
* kdbus_msg_resources_ref() - Acquire reference to msg resources
* @r: resources to acquire ref to
*
* Return: The acquired resource
*/
struct kdbus_msg_resources *
kdbus_msg_resources_ref(struct kdbus_msg_resources *r)
{
if (r)
kref_get(&r->kref);
return r;
}
/**
* kdbus_msg_resources_unref() - Drop reference to msg resources
* @r: resources to drop reference of
*
* Return: NULL
*/
struct kdbus_msg_resources *
kdbus_msg_resources_unref(struct kdbus_msg_resources *r)
{
if (r)
kref_put(&r->kref, __kdbus_msg_resources_free);
return NULL;
}
/**
* kdbus_kmsg_free() - free allocated message
* @kmsg: Message
*/
void kdbus_kmsg_free(struct kdbus_kmsg *kmsg)
{
if (!kmsg)
return;
kdbus_msg_resources_unref(kmsg->res);
kdbus_meta_conn_unref(kmsg->conn_meta);
kdbus_meta_proc_unref(kmsg->proc_meta);
kfree(kmsg->iov);
kfree(kmsg);
}
/**
* kdbus_kmsg_new() - allocate message
* @bus: Bus this message is allocated on
* @extra_size: Additional size to reserve for data
*
* Return: new kdbus_kmsg on success, ERR_PTR on failure.
*/
struct kdbus_kmsg *kdbus_kmsg_new(struct kdbus_bus *bus, size_t extra_size)
{
struct kdbus_kmsg *m;
size_t size;
int ret;
size = sizeof(struct kdbus_kmsg) + KDBUS_ITEM_SIZE(extra_size);
m = kzalloc(size, GFP_KERNEL);
if (!m)
return ERR_PTR(-ENOMEM);
m->seq = atomic64_inc_return(&bus->domain->last_id);
m->msg.size = size - KDBUS_KMSG_HEADER_SIZE;
m->msg.items[0].size = KDBUS_ITEM_SIZE(extra_size);
m->proc_meta = kdbus_meta_proc_new();
if (IS_ERR(m->proc_meta)) {
ret = PTR_ERR(m->proc_meta);
m->proc_meta = NULL;
goto exit;
}
m->conn_meta = kdbus_meta_conn_new();
if (IS_ERR(m->conn_meta)) {
ret = PTR_ERR(m->conn_meta);
m->conn_meta = NULL;
goto exit;
}
return m;
exit:
kdbus_kmsg_free(m);
return ERR_PTR(ret);
}
static int kdbus_handle_check_file(struct file *file)
{
struct inode *inode = file_inode(file);
struct socket *sock;
/*
* Don't allow file descriptors in the transport that themselves allow
* file descriptor queueing. This will eventually be allowed once both
* unix domain sockets and kdbus share a generic garbage collector.
*/
if (file->f_op == &kdbus_handle_ops)
return -EOPNOTSUPP;
if (!S_ISSOCK(inode->i_mode))
return 0;
if (file->f_mode & FMODE_PATH)
return 0;
sock = SOCKET_I(inode);
if (sock->sk && sock->ops && sock->ops->family == PF_UNIX)
return -EOPNOTSUPP;
return 0;
}
static const char * const zeros = "\0\0\0\0\0\0\0";
/*
* kdbus_msg_scan_items() - validate incoming data and prepare parsing
* @kmsg: Message
* @bus: Bus the message is sent over
*
* Return: 0 on success, negative errno on failure.
*
* Files references in MEMFD or FDS items are pinned.
*
* On errors, the caller should drop any taken reference with
* kdbus_kmsg_free()
*/
static int kdbus_msg_scan_items(struct kdbus_kmsg *kmsg,
struct kdbus_bus *bus)
{
struct kdbus_msg_resources *res = kmsg->res;
const struct kdbus_msg *msg = &kmsg->msg;
const struct kdbus_item *item;
size_t n, n_vecs, n_memfds;
bool has_bloom = false;
bool has_name = false;
bool has_fds = false;
bool is_broadcast;
bool is_signal;
u64 vec_size;
is_broadcast = (msg->dst_id == KDBUS_DST_ID_BROADCAST);
is_signal = !!(msg->flags & KDBUS_MSG_SIGNAL);
/* count data payloads */
n_vecs = 0;
n_memfds = 0;
KDBUS_ITEMS_FOREACH(item, msg->items, KDBUS_ITEMS_SIZE(msg, items)) {
switch (item->type) {
case KDBUS_ITEM_PAYLOAD_VEC:
++n_vecs;
break;
case KDBUS_ITEM_PAYLOAD_MEMFD:
++n_memfds;
if (item->memfd.size % 8)
++n_vecs;
break;
default:
break;
}
}
n = n_vecs + n_memfds;
if (n > 0) {
res->data = kcalloc(n, sizeof(*res->data), GFP_KERNEL);
if (!res->data)
return -ENOMEM;
}
if (n_vecs > 0) {
kmsg->iov = kcalloc(n_vecs, sizeof(*kmsg->iov), GFP_KERNEL);
if (!kmsg->iov)
return -ENOMEM;
}
/* import data payloads */
n = 0;
vec_size = 0;
KDBUS_ITEMS_FOREACH(item, msg->items, KDBUS_ITEMS_SIZE(msg, items)) {
size_t payload_size = KDBUS_ITEM_PAYLOAD_SIZE(item);
struct iovec *iov = kmsg->iov + kmsg->iov_count;
if (++n > KDBUS_MSG_MAX_ITEMS)
return -E2BIG;
switch (item->type) {
case KDBUS_ITEM_PAYLOAD_VEC: {
struct kdbus_msg_data *d = res->data + res->data_count;
void __force __user *ptr = KDBUS_PTR(item->vec.address);
size_t size = item->vec.size;
if (vec_size + size < vec_size)
return -EMSGSIZE;
if (vec_size + size > KDBUS_MSG_MAX_PAYLOAD_VEC_SIZE)
return -EMSGSIZE;
d->type = KDBUS_MSG_DATA_VEC;
d->size = size;
if (ptr) {
if (unlikely(!access_ok(VERIFY_READ, ptr,
size)))
return -EFAULT;
d->vec.off = kmsg->pool_size;
iov->iov_base = ptr;
iov->iov_len = size;
} else {
d->vec.off = ~0ULL;
iov->iov_base = (char __user *)zeros;
iov->iov_len = size % 8;
}
if (kmsg->pool_size + iov->iov_len < kmsg->pool_size)
return -EMSGSIZE;
kmsg->pool_size += iov->iov_len;
++kmsg->iov_count;
++res->vec_count;
++res->data_count;
vec_size += size;
break;
}
case KDBUS_ITEM_PAYLOAD_MEMFD: {
struct kdbus_msg_data *d = res->data + res->data_count;
u64 start = item->memfd.start;
u64 size = item->memfd.size;
size_t pad = size % 8;
int seals, mask;
struct file *f;
if (kmsg->pool_size + size % 8 < kmsg->pool_size)
return -EMSGSIZE;
if (start + size < start)
return -EMSGSIZE;
if (item->memfd.fd < 0)
return -EBADF;
if (res->memfd_count >= KDBUS_MSG_MAX_MEMFD_ITEMS)
return -E2BIG;
f = fget(item->memfd.fd);
if (!f)
return -EBADF;
if (pad) {
iov->iov_base = (char __user *)zeros;
iov->iov_len = pad;
kmsg->pool_size += pad;
++kmsg->iov_count;
}
++res->data_count;
++res->memfd_count;
d->type = KDBUS_MSG_DATA_MEMFD;
d->size = size;
d->memfd.start = start;
d->memfd.file = f;
/*
* We only accept a sealed memfd file whose content
* cannot be altered by the sender or anybody else
* while it is shared or in-flight. Other files need
* to be passed with KDBUS_MSG_FDS.
*/
seals = shmem_get_seals(f);
if (seals < 0)
return -EMEDIUMTYPE;
mask = F_SEAL_SHRINK | F_SEAL_GROW |
F_SEAL_WRITE | F_SEAL_SEAL;
if ((seals & mask) != mask)
return -ETXTBSY;
if (start + size > (u64)i_size_read(file_inode(f)))
return -EBADF;
break;
}
case KDBUS_ITEM_FDS: {
unsigned int i;
unsigned int fds_count = payload_size / sizeof(int);
/* do not allow multiple fd arrays */
if (has_fds)
return -EEXIST;
has_fds = true;
/* Do not allow to broadcast file descriptors */
if (is_broadcast)
return -ENOTUNIQ;
if (fds_count > KDBUS_CONN_MAX_FDS_PER_USER)
return -EMFILE;
res->fds = kcalloc(fds_count, sizeof(struct file *),
GFP_KERNEL);
if (!res->fds)
return -ENOMEM;
for (i = 0; i < fds_count; i++) {
int fd = item->fds[i];
int ret;
/*
* Verify the fd and increment the usage count.
* Use fget_raw() to allow passing O_PATH fds.
*/
if (fd < 0)
return -EBADF;
res->fds[i] = fget_raw(fd);
if (!res->fds[i])
return -EBADF;
res->fds_count++;
ret = kdbus_handle_check_file(res->fds[i]);
if (ret < 0)
return ret;
}
break;
}
case KDBUS_ITEM_BLOOM_FILTER: {
u64 bloom_size;
/* do not allow multiple bloom filters */
if (has_bloom)
return -EEXIST;
has_bloom = true;
bloom_size = payload_size -
offsetof(struct kdbus_bloom_filter, data);
/*
* Allow only bloom filter sizes of a multiple of 64bit.
*/
if (!KDBUS_IS_ALIGNED8(bloom_size))
return -EFAULT;
/* do not allow mismatching bloom filter sizes */
if (bloom_size != bus->bloom.size)
return -EDOM;
kmsg->bloom_filter = &item->bloom_filter;
break;
}
case KDBUS_ITEM_DST_NAME:
/* do not allow multiple names */
if (has_name)
return -EEXIST;
has_name = true;
if (!kdbus_name_is_valid(item->str, false))
return -EINVAL;
res->dst_name = kstrdup(item->str, GFP_KERNEL);
if (!res->dst_name)
return -ENOMEM;
break;
default:
return -EINVAL;
}
}
/* name is needed if no ID is given */
if (msg->dst_id == KDBUS_DST_ID_NAME && !has_name)
return -EDESTADDRREQ;
if (is_broadcast) {
/* Broadcasts can't take names */
if (has_name)
return -EBADMSG;
/* All broadcasts have to be signals */
if (!is_signal)
return -EBADMSG;
/* Timeouts are not allowed for broadcasts */
if (msg->timeout_ns > 0)
return -ENOTUNIQ;
}
/*
* Signal messages require a bloom filter, and bloom filters are
* only valid with signals.
*/
if (is_signal ^ has_bloom)
return -EBADMSG;
return 0;
}
/**
* kdbus_kmsg_new_from_cmd() - create kernel message from send payload
* @conn: Connection
* @cmd_send: Payload of KDBUS_CMD_SEND
*
* Return: a new kdbus_kmsg on success, ERR_PTR on failure.
*/
struct kdbus_kmsg *kdbus_kmsg_new_from_cmd(struct kdbus_conn *conn,
struct kdbus_cmd_send *cmd_send)
{
struct kdbus_kmsg *m;
u64 size;
int ret;
ret = kdbus_copy_from_user(&size, KDBUS_PTR(cmd_send->msg_address),
sizeof(size));
if (ret < 0)
return ERR_PTR(ret);
if (size < sizeof(struct kdbus_msg) || size > KDBUS_MSG_MAX_SIZE)
return ERR_PTR(-EINVAL);
m = kmalloc(size + KDBUS_KMSG_HEADER_SIZE, GFP_KERNEL);
if (!m)
return ERR_PTR(-ENOMEM);
memset(m, 0, KDBUS_KMSG_HEADER_SIZE);
m->seq = atomic64_inc_return(&conn->ep->bus->domain->last_id);
m->proc_meta = kdbus_meta_proc_new();
if (IS_ERR(m->proc_meta)) {
ret = PTR_ERR(m->proc_meta);
m->proc_meta = NULL;
goto exit_free;
}
m->conn_meta = kdbus_meta_conn_new();
if (IS_ERR(m->conn_meta)) {
ret = PTR_ERR(m->conn_meta);
m->conn_meta = NULL;
goto exit_free;
}
if (copy_from_user(&m->msg, KDBUS_PTR(cmd_send->msg_address), size)) {
ret = -EFAULT;
goto exit_free;
}
if (m->msg.size != size) {
ret = -EINVAL;
goto exit_free;
}
if (m->msg.flags & ~(KDBUS_MSG_EXPECT_REPLY |
KDBUS_MSG_NO_AUTO_START |
KDBUS_MSG_SIGNAL)) {
ret = -EINVAL;
goto exit_free;
}
ret = kdbus_items_validate(m->msg.items,
KDBUS_ITEMS_SIZE(&m->msg, items));
if (ret < 0)
goto exit_free;
m->res = kdbus_msg_resources_new();
if (IS_ERR(m->res)) {
ret = PTR_ERR(m->res);
m->res = NULL;
goto exit_free;
}
/* do not accept kernel-generated messages */
if (m->msg.payload_type == KDBUS_PAYLOAD_KERNEL) {
ret = -EINVAL;
goto exit_free;
}
if (m->msg.flags & KDBUS_MSG_EXPECT_REPLY) {
/* requests for replies need timeout and cookie */
if (m->msg.timeout_ns == 0 || m->msg.cookie == 0) {
ret = -EINVAL;
goto exit_free;
}
/* replies may not be expected for broadcasts */
if (m->msg.dst_id == KDBUS_DST_ID_BROADCAST) {
ret = -ENOTUNIQ;
goto exit_free;
}
/* replies may not be expected for signals */
if (m->msg.flags & KDBUS_MSG_SIGNAL) {
ret = -EINVAL;
goto exit_free;
}
} else {
/*
* KDBUS_SEND_SYNC_REPLY is only valid together with
* KDBUS_MSG_EXPECT_REPLY
*/
if (cmd_send->flags & KDBUS_SEND_SYNC_REPLY) {
ret = -EINVAL;
goto exit_free;
}
/* replies cannot be signals */
if (m->msg.cookie_reply && (m->msg.flags & KDBUS_MSG_SIGNAL)) {
ret = -EINVAL;
goto exit_free;
}
}
ret = kdbus_msg_scan_items(m, conn->ep->bus);
if (ret < 0)
goto exit_free;
/* patch-in the source of this message */
if (m->msg.src_id > 0 && m->msg.src_id != conn->id) {
ret = -EINVAL;
goto exit_free;
}
m->msg.src_id = conn->id;
return m;
exit_free:
kdbus_kmsg_free(m);
return ERR_PTR(ret);
}