From: David Herrmann <dh.herrmann@gmail.com>
To: linux-kernel@vger.kernel.org
Cc: Andy Lutomirski <luto@amacapital.net>,
Jiri Kosina <jikos@kernel.org>, Greg KH <greg@kroah.com>,
Hannes Reinecke <hare@suse.com>,
Steven Rostedt <rostedt@goodmis.org>,
Arnd Bergmann <arnd@arndb.de>, Tom Gundersen <teg@jklm.no>,
David Herrmann <dh.herrmann@gmail.com>,
Josh Triplett <josh@joshtriplett.org>,
Linus Torvalds <torvalds@linux-foundation.org>,
Andrew Morton <akpm@linux-foundation.org>
Subject: [RFC v1 11/14] bus1: implement message transmission
Date: Wed, 26 Oct 2016 21:18:07 +0200 [thread overview]
Message-ID: <20161026191810.12275-12-dh.herrmann@gmail.com> (raw)
In-Reply-To: <20161026191810.12275-1-dh.herrmann@gmail.com>
From: Tom Gundersen <teg@jklm.no>
While notifications already work and simply require linking bus1_handle
objects into the destination queue, real messages require proper
payloads. This implements two core objects: Message objects and
factories.
The message factory is similar to transaction contexts, and lives
completely on the stack. It is used to import the parameters given by
user-space in a SEND ioctl. It parses and validates them. With this
message factors we can now instantiate many messages, one for each
destination of a multicast.
Messages need to carry a bunch of data, mainly:
- metadata: This just matches what Unix-sockets do (uid, gid, pid,
tid, and secctx)
- payload: Random memory passed in as iovec-array by user-space
- files: Set of file-descriptors, very similar to SCM_RIGHTS
- handles: Set of local handles to transfer to the destination
Signed-off-by: Tom Gundersen <teg@jklm.no>
Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
---
ipc/bus1/Makefile | 1 +
ipc/bus1/message.c | 613 +++++++++++++++++++++++++++++++++++++++++++++++++++++
ipc/bus1/message.h | 171 +++++++++++++++
ipc/bus1/peer.c | 2 +
ipc/bus1/peer.h | 2 +
ipc/bus1/util.c | 162 ++++++++++++++
ipc/bus1/util.h | 7 +
7 files changed, 958 insertions(+)
create mode 100644 ipc/bus1/message.c
create mode 100644 ipc/bus1/message.h
diff --git a/ipc/bus1/Makefile b/ipc/bus1/Makefile
index b87cddb..05434bda 100644
--- a/ipc/bus1/Makefile
+++ b/ipc/bus1/Makefile
@@ -1,6 +1,7 @@
bus1-y := \
handle.o \
main.o \
+ message.o \
peer.o \
tx.o \
user.o \
diff --git a/ipc/bus1/message.c b/ipc/bus1/message.c
new file mode 100644
index 0000000..4c5c905
--- /dev/null
+++ b/ipc/bus1/message.c
@@ -0,0 +1,613 @@
+/*
+ * Copyright (C) 2013-2016 Red Hat, Inc.
+ *
+ * This program 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/cred.h>
+#include <linux/err.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/kref.h>
+#include <linux/pid.h>
+#include <linux/pid_namespace.h>
+#include <linux/sched.h>
+#include <linux/security.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/uidgid.h>
+#include <linux/uio.h>
+#include <uapi/linux/bus1.h>
+#include "handle.h"
+#include "message.h"
+#include "peer.h"
+#include "tx.h"
+#include "user.h"
+#include "util.h"
+#include "util/flist.h"
+#include "util/pool.h"
+#include "util/queue.h"
+
+static size_t bus1_factory_size(struct bus1_cmd_send *param)
+{
+ /* make sure @size cannot overflow */
+ BUILD_BUG_ON(UIO_MAXIOV > U16_MAX);
+ BUILD_BUG_ON(BUS1_FD_MAX > U16_MAX);
+
+ /* make sure we do not violate alignment rules */
+ BUILD_BUG_ON(__alignof(struct bus1_flist) < __alignof(struct iovec));
+ BUILD_BUG_ON(__alignof(struct iovec) < __alignof(struct file *));
+
+ return sizeof(struct bus1_factory) +
+ bus1_flist_inline_size(param->n_handles) +
+ param->n_vecs * sizeof(struct iovec) +
+ param->n_fds * sizeof(struct file *);
+}
+
+/**
+ * bus1_factory_new() - create new message factory
+ * @peer: peer to operate as
+ * @param: factory parameters
+ * @stack: optional stack for factory, or NULL
+ * @n_stack: size of space at @stack
+ *
+ * This allocates a new message factory. It imports data from @param and
+ * prepares the factory for a transaction. From this factory, messages can be
+ * instantiated. This is used both for unicasts and multicasts.
+ *
+ * If @stack is given, this tries to place the factory on the specified stack
+ * space. The caller must guarantee that the factory does not outlive the stack
+ * frame. If this is not wanted, pass 0 as @n_stack.
+ * In either case, if the stack frame is too small, this will allocate the
+ * factory on the heap.
+ *
+ * Return: Pointer to factory, or ERR_PTR on failure.
+ */
+struct bus1_factory *bus1_factory_new(struct bus1_peer *peer,
+ struct bus1_cmd_send *param,
+ void *stack,
+ size_t n_stack)
+{
+ const struct iovec __user *ptr_vecs;
+ const u64 __user *ptr_handles;
+ const int __user *ptr_fds;
+ struct bus1_factory *f;
+ struct bus1_flist *e;
+ struct file *file;
+ size_t i, size;
+ bool is_new;
+ int r, fd;
+ u32 sid;
+ u64 id;
+
+ lockdep_assert_held(&peer->local.lock);
+
+ size = bus1_factory_size(param);
+ if (unlikely(size > n_stack)) {
+ f = kmalloc(size, GFP_TEMPORARY);
+ if (!f)
+ return ERR_PTR(-ENOMEM);
+
+ f->on_stack = false;
+ } else {
+ f = stack;
+ f->on_stack = true;
+ }
+
+ /* set to default first, so the destructor can be called anytime */
+ f->peer = peer;
+ f->param = param;
+ f->cred = current_cred();
+ f->pid = task_tgid(current);
+ f->tid = task_pid(current);
+
+ f->has_secctx = false;
+
+ f->length_vecs = 0;
+ f->n_vecs = param->n_vecs;
+ f->n_handles = 0;
+ f->n_handles_charge = 0;
+ f->n_files = 0;
+ f->n_secctx = 0;
+ f->vecs = (void *)(f + 1) + bus1_flist_inline_size(param->n_handles);
+ f->files = (void *)(f->vecs + param->n_vecs);
+ f->secctx = NULL;
+ bus1_flist_init(f->handles, f->param->n_handles);
+
+ /* import vecs */
+ ptr_vecs = (const struct iovec __user *)(unsigned long)param->ptr_vecs;
+ r = bus1_import_vecs(f->vecs, &f->length_vecs, ptr_vecs, f->n_vecs);
+ if (r < 0)
+ goto error;
+
+ /* import handles */
+ r = bus1_flist_populate(f->handles, f->param->n_handles, GFP_TEMPORARY);
+ if (r < 0)
+ goto error;
+
+ ptr_handles = (const u64 __user *)(unsigned long)param->ptr_handles;
+ for (i = 0, e = f->handles;
+ i < f->param->n_handles;
+ e = bus1_flist_next(e, &i)) {
+ if (get_user(id, ptr_handles + f->n_handles)) {
+ r = -EFAULT;
+ goto error;
+ }
+
+ e->ptr = bus1_handle_import(peer, id, &is_new);
+ if (IS_ERR(e->ptr)) {
+ r = PTR_ERR(e->ptr);
+ goto error;
+ }
+
+ ++f->n_handles;
+ if (is_new)
+ ++f->n_handles_charge;
+ }
+
+ /* import files */
+ ptr_fds = (const int __user *)(unsigned long)param->ptr_fds;
+ while (f->n_files < param->n_fds) {
+ if (get_user(fd, ptr_fds + f->n_files)) {
+ r = -EFAULT;
+ goto error;
+ }
+
+ file = bus1_import_fd(fd);
+ if (IS_ERR(file)) {
+ r = PTR_ERR(file);
+ goto error;
+ }
+
+ f->files[f->n_files++] = file;
+ }
+
+ /* import secctx */
+ security_task_getsecid(current, &sid);
+ r = security_secid_to_secctx(sid, &f->secctx, &f->n_secctx);
+ if (r != -EOPNOTSUPP) {
+ if (r < 0)
+ goto error;
+
+ f->has_secctx = true;
+ }
+
+ return f;
+
+error:
+ bus1_factory_free(f);
+ return ERR_PTR(r);
+}
+
+/**
+ * bus1_factory_free() - destroy message factory
+ * @f: factory to operate on, or NULL
+ *
+ * This destroys the message factory @f, previously created via
+ * bus1_factory_new(). All pinned resources are freed. Messages created via the
+ * factory are unaffected.
+ *
+ * If @f is NULL, this is a no-op.
+ *
+ * Return: NULL is returned.
+ */
+struct bus1_factory *bus1_factory_free(struct bus1_factory *f)
+{
+ struct bus1_flist *e;
+ size_t i;
+
+ if (f) {
+ lockdep_assert_held(&f->peer->local.lock);
+
+ if (f->has_secctx)
+ security_release_secctx(f->secctx, f->n_secctx);
+
+ for (i = 0; i < f->n_files; ++i)
+ fput(f->files[i]);
+
+ /* Iterate and forget imported handles (f->n_handles)... */
+ for (i = 0, e = f->handles;
+ i < f->n_handles;
+ e = bus1_flist_next(e, &i)) {
+ bus1_handle_forget(e->ptr);
+ bus1_handle_unref(e->ptr);
+ }
+ /* ...but free total space (f->param->n_handles). */
+ bus1_flist_deinit(f->handles, f->param->n_handles);
+
+ if (!f->on_stack)
+ kfree(f);
+ }
+
+ return NULL;
+}
+
+/**
+ * bus1_factory_seal() - charge and commit local resources
+ * @f: factory to use
+ *
+ * The factory needs to pin and possibly create local peer resources. This
+ * commits those resources. You should call this after you instantiated all
+ * messages, since you cannot undo it easily.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int bus1_factory_seal(struct bus1_factory *f)
+{
+ struct bus1_handle *h;
+ struct bus1_flist *e;
+ size_t i;
+
+ lockdep_assert_held(&f->peer->local.lock);
+
+ for (i = 0, e = f->handles;
+ i < f->n_handles;
+ e = bus1_flist_next(e, &i)) {
+ h = e->ptr;
+ if (bus1_handle_is_public(h))
+ continue;
+
+ --f->n_handles_charge;
+ WARN_ON(h != bus1_handle_acquire(h, false));
+ WARN_ON(atomic_inc_return(&h->n_user) != 1);
+ }
+
+ return 0;
+}
+
+/**
+ * bus1_factory_instantiate() - instantiate a message from a factory
+ * @f: factory to use
+ * @handle: destination handle
+ * @peer: destination peer
+ *
+ * This instantiates a new message targetted at @handle, based on the plans in
+ * the message factory @f.
+ *
+ * The newly created message is not linked into any contexts, but is available
+ * for free use to the caller.
+ *
+ * Return: Pointer to new message, or ERR_PTR on failure.
+ */
+struct bus1_message *bus1_factory_instantiate(struct bus1_factory *f,
+ struct bus1_handle *handle,
+ struct bus1_peer *peer)
+{
+ struct bus1_flist *src_e, *dst_e;
+ struct bus1_message *m;
+ bool transmit_secctx;
+ struct kvec vec;
+ size_t size, i, j;
+ u64 offset;
+ int r;
+
+ lockdep_assert_held(&f->peer->local.lock);
+
+ transmit_secctx = f->has_secctx &&
+ (READ_ONCE(peer->flags) & BUS1_PEER_FLAG_WANT_SECCTX);
+
+ size = sizeof(*m) + bus1_flist_inline_size(f->n_handles) +
+ f->n_files * sizeof(struct file *);
+ m = kmalloc(size, GFP_KERNEL);
+ if (!m)
+ return ERR_PTR(-ENOMEM);
+
+ /* set to default first, so the destructor can be called anytime */
+ kref_init(&m->ref);
+ bus1_queue_node_init(&m->qnode, BUS1_MSG_DATA);
+ m->qnode.owner = peer;
+ m->dst = bus1_handle_ref(handle);
+ m->user = bus1_user_ref(f->peer->user);
+
+ m->flags = 0;
+ m->uid = from_kuid_munged(peer->cred->user_ns, f->cred->uid);
+ m->gid = from_kgid_munged(peer->cred->user_ns, f->cred->gid);
+ m->pid = pid_nr_ns(f->pid, peer->pid_ns);
+ m->tid = pid_nr_ns(f->tid, peer->pid_ns);
+
+ m->n_bytes = f->length_vecs;
+ m->n_handles = 0;
+ m->n_handles_charge = f->n_handles;
+ m->n_files = 0;
+ m->n_secctx = 0;
+ m->slice = NULL;
+ m->files = (void *)(m + 1) + bus1_flist_inline_size(f->n_handles);
+ bus1_flist_init(m->handles, f->n_handles);
+
+ /* allocate pool slice */
+ size = max_t(size_t, 8,
+ ALIGN(m->n_bytes, 8) +
+ ALIGN(f->n_handles * sizeof(u64), 8) +
+ ALIGN(f->n_files * sizeof(int), 8) +
+ ALIGN(f->n_secctx, 8));
+ mutex_lock(&peer->data.lock);
+ m->slice = bus1_pool_alloc(&peer->data.pool, size);
+ mutex_unlock(&peer->data.lock);
+ if (IS_ERR(m->slice)) {
+ r = PTR_ERR(m->slice);
+ m->slice = NULL;
+ goto error;
+ }
+
+ /* import blob */
+ r = bus1_pool_write_iovec(&peer->data.pool, m->slice, 0, f->vecs,
+ f->n_vecs, f->length_vecs);
+ if (r < 0)
+ goto error;
+
+ /* import handles */
+ r = bus1_flist_populate(m->handles, f->n_handles, GFP_KERNEL);
+ if (r < 0)
+ goto error;
+
+ r = 0;
+ m->n_handles = f->n_handles;
+ i = 0;
+ j = 0;
+ src_e = f->handles;
+ dst_e = m->handles;
+ while (i < f->n_handles) {
+ WARN_ON(i != j);
+
+ dst_e->ptr = bus1_handle_ref_by_other(peer, src_e->ptr);
+ if (!dst_e->ptr) {
+ dst_e->ptr = bus1_handle_new_remote(peer, src_e->ptr);
+ if (IS_ERR(dst_e->ptr) && r >= 0) {
+ /*
+ * Continue on error until we imported all
+ * handles. Otherwise, trailing entries in the
+ * array will be stale, and the destructor
+ * cannot tell which.
+ */
+ r = PTR_ERR(dst_e->ptr);
+ }
+ }
+
+ src_e = bus1_flist_next(src_e, &i);
+ dst_e = bus1_flist_next(dst_e, &j);
+ }
+ if (r < 0)
+ goto error;
+
+ /* import files */
+ while (m->n_files < f->n_files) {
+ m->files[m->n_files] = get_file(f->files[m->n_files]);
+ ++m->n_files;
+ }
+
+ /* import secctx */
+ if (transmit_secctx) {
+ offset = ALIGN(m->n_bytes, 8) +
+ ALIGN(m->n_handles * sizeof(u64), 8) +
+ ALIGN(m->n_files * sizeof(int), 8);
+ vec = (struct kvec){
+ .iov_base = f->secctx,
+ .iov_len = f->n_secctx,
+ };
+
+ r = bus1_pool_write_kvec(&peer->data.pool, m->slice, offset,
+ &vec, 1, vec.iov_len);
+ if (r < 0)
+ goto error;
+
+ m->n_secctx = f->n_secctx;
+ m->flags |= BUS1_MSG_FLAG_HAS_SECCTX;
+ }
+
+ return m;
+
+error:
+ bus1_message_unref(m);
+ return ERR_PTR(r);
+}
+
+/**
+ * bus1_message_free() - destroy message
+ * @k: kref belonging to a message
+ *
+ * This frees the message belonging to the reference counter @k. It is supposed
+ * to be used with kref_put(). See bus1_message_unref(). Like all queue nodes,
+ * the memory deallocation is rcu-delayed.
+ */
+void bus1_message_free(struct kref *k)
+{
+ struct bus1_message *m = container_of(k, struct bus1_message, ref);
+ struct bus1_peer *peer = m->qnode.owner;
+ struct bus1_flist *e;
+ size_t i;
+
+ WARN_ON(!peer);
+ lockdep_assert_held(&peer->active);
+
+ for (i = 0; i < m->n_files; ++i)
+ fput(m->files[i]);
+
+ for (i = 0, e = m->handles;
+ i < m->n_handles;
+ e = bus1_flist_next(e, &i)) {
+ if (!IS_ERR_OR_NULL(e->ptr)) {
+ if (m->qnode.group)
+ bus1_handle_release(e->ptr, true);
+ bus1_handle_unref(e->ptr);
+ }
+ }
+ bus1_flist_deinit(m->handles, m->n_handles);
+
+ if (m->slice) {
+ mutex_lock(&peer->data.lock);
+ bus1_pool_release_kernel(&peer->data.pool, m->slice);
+ mutex_unlock(&peer->data.lock);
+ }
+
+ bus1_user_unref(m->user);
+ bus1_handle_unref(m->dst);
+ bus1_queue_node_deinit(&m->qnode);
+ kfree_rcu(m, qnode.rcu);
+}
+
+/**
+ * bus1_message_stage() - stage message
+ * @m: message to operate on
+ * @tx: transaction to stage on
+ *
+ * This acquires all resources of the message @m and then stages the message on
+ * @tx. Like all stage operations, this cannot be undone. Hence, you must make
+ * sure you can continue to commit the transaction without erroring-out in
+ * between.
+ *
+ * This consumes the caller's reference on @m, plus the active reference on the
+ * destination peer.
+ */
+void bus1_message_stage(struct bus1_message *m, struct bus1_tx *tx)
+{
+ struct bus1_peer *peer = m->qnode.owner;
+ struct bus1_flist *e;
+ size_t i;
+
+ WARN_ON(!peer);
+ lockdep_assert_held(&peer->active);
+
+ for (i = 0, e = m->handles;
+ i < m->n_handles;
+ e = bus1_flist_next(e, &i))
+ e->ptr = bus1_handle_acquire(e->ptr, true);
+
+ /* this consumes an active reference on m->qnode.owner */
+ bus1_tx_stage_sync(tx, &m->qnode);
+}
+
+/**
+ * bus1_message_install() - install message payload into target process
+ * @m: message to operate on
+ * @inst_fds: whether to install FDs
+ *
+ * This installs the payload FDs and handles of @message into the receiving
+ * peer and the calling process. Handles are always installed, FDs are only
+ * installed if explicitly requested via @param.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int bus1_message_install(struct bus1_message *m, struct bus1_cmd_recv *param)
+{
+ size_t i, j, n, size, offset, n_handles = 0, n_fds = 0;
+ const bool inst_fds = param->flags & BUS1_RECV_FLAG_INSTALL_FDS;
+ const bool peek = param->flags & BUS1_RECV_FLAG_PEEK;
+ struct bus1_peer *peer = m->qnode.owner;
+ struct bus1_handle *h;
+ struct bus1_flist *e;
+ struct kvec vec;
+ u64 ts, *handles;
+ u8 stack[512];
+ void *buffer = stack;
+ int r, *fds;
+
+ WARN_ON(!peer);
+ lockdep_assert_held(&peer->local.lock);
+
+ size = max(m->n_files, min_t(size_t, m->n_handles, BUS1_FLIST_BATCH));
+ size *= max(sizeof(*fds), sizeof(*handles));
+ if (unlikely(size > sizeof(stack))) {
+ buffer = kmalloc(size, GFP_TEMPORARY);
+ if (!buffer)
+ return -ENOMEM;
+ }
+
+ if (m->n_handles > 0) {
+ handles = buffer;
+ ts = bus1_queue_node_get_timestamp(&m->qnode);
+ offset = ALIGN(m->n_bytes, 8);
+
+ i = 0;
+ while ((n = bus1_flist_walk(m->handles, m->n_handles,
+ &e, &i)) > 0) {
+ WARN_ON(i > m->n_handles);
+ WARN_ON(i > BUS1_FLIST_BATCH);
+
+ for (j = 0; j < n; ++j) {
+ h = e[j].ptr;
+ if (h && bus1_handle_is_live_at(h, ts)) {
+ handles[j] = bus1_handle_identify(h);
+ ++n_handles;
+ } else {
+ bus1_handle_release(h, true);
+ e[j].ptr = bus1_handle_unref(h);
+ handles[j] = BUS1_HANDLE_INVALID;
+ }
+ }
+
+ vec.iov_base = buffer;
+ vec.iov_len = n * sizeof(u64);
+
+ r = bus1_pool_write_kvec(&peer->data.pool, m->slice,
+ offset, &vec, 1, vec.iov_len);
+ if (r < 0)
+ goto exit;
+
+ offset += n * sizeof(u64);
+ }
+ }
+
+ if (inst_fds && m->n_files > 0) {
+ fds = buffer;
+
+ for ( ; n_fds < m->n_files; ++n_fds) {
+ r = get_unused_fd_flags(O_CLOEXEC);
+ if (r < 0)
+ goto exit;
+
+ fds[n_fds] = r;
+ }
+
+ vec.iov_base = fds;
+ vec.iov_len = n_fds * sizeof(int);
+ offset = ALIGN(m->n_bytes, 8) +
+ ALIGN(m->n_handles * sizeof(u64), 8);
+
+ r = bus1_pool_write_kvec(&peer->data.pool, m->slice, offset,
+ &vec, 1, vec.iov_len);
+ if (r < 0)
+ goto exit;
+ }
+
+ /* charge resources */
+ if (!peek) {
+ WARN_ON(n_handles < m->n_handles_charge);
+ m->n_handles_charge -= n_handles;
+ }
+
+ /* publish pool slice */
+ mutex_lock(&peer->data.lock);
+ bus1_pool_publish(&peer->data.pool, m->slice);
+ mutex_unlock(&peer->data.lock);
+
+ /* commit handles */
+ for (i = 0, e = m->handles;
+ i < m->n_handles;
+ e = bus1_flist_next(e, &i)) {
+ h = e->ptr;
+ if (!IS_ERR_OR_NULL(h)) {
+ WARN_ON(h != bus1_handle_acquire(h, true));
+ WARN_ON(atomic_inc_return(&h->n_user) < 1);
+ }
+ }
+
+ /* commit FDs */
+ while (n_fds > 0) {
+ --n_fds;
+ fd_install(fds[n_fds], get_file(m->files[n_fds]));
+ }
+
+ r = 0;
+
+exit:
+ while (n_fds-- > 0)
+ put_unused_fd(fds[n_fds]);
+ if (buffer != stack)
+ kfree(buffer);
+ return r;
+}
diff --git a/ipc/bus1/message.h b/ipc/bus1/message.h
new file mode 100644
index 0000000..e8c982f
--- /dev/null
+++ b/ipc/bus1/message.h
@@ -0,0 +1,171 @@
+#ifndef __BUS1_MESSAGE_H
+#define __BUS1_MESSAGE_H
+
+/*
+ * Copyright (C) 2013-2016 Red Hat, Inc.
+ *
+ * This program 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.
+ */
+
+/**
+ * DOC: Messages
+ *
+ * XXX
+ */
+
+#include <linux/kernel.h>
+#include <linux/kref.h>
+#include "util/flist.h"
+#include "util/queue.h"
+
+struct bus1_cmd_send;
+struct bus1_handle;
+struct bus1_peer;
+struct bus1_pool_slice;
+struct bus1_tx;
+struct bus1_user;
+struct cred;
+struct file;
+struct iovec;
+struct pid;
+
+/**
+ * struct bus1_factory - message factory
+ * @peer: sending peer
+ * @param: factory parameters
+ * @cred: sender credentials
+ * @pid: sender PID
+ * @tid: sender TID
+ * @on_stack: whether object lives on stack
+ * @has_secctx: whether secctx has been set
+ * @length_vecs: total length of data in vectors
+ * @n_vecs: number of vectors
+ * @n_handles: number of handles
+ * @n_handles_charge: number of handles to charge on commit
+ * @n_files: number of files
+ * @n_secctx: length of secctx
+ * @vecs: vector array
+ * @files: file array
+ * @secctx: allocated secctx
+ * @handles: handle array
+ */
+struct bus1_factory {
+ struct bus1_peer *peer;
+ struct bus1_cmd_send *param;
+ const struct cred *cred;
+ struct pid *pid;
+ struct pid *tid;
+
+ bool on_stack : 1;
+ bool has_secctx : 1;
+
+ size_t length_vecs;
+ size_t n_vecs;
+ size_t n_handles;
+ size_t n_handles_charge;
+ size_t n_files;
+ u32 n_secctx;
+ struct iovec *vecs;
+ struct file **files;
+ char *secctx;
+
+ struct bus1_flist handles[];
+};
+
+/**
+ * struct bus1_message - data messages
+ * @ref: reference counter
+ * @qnode: embedded queue node
+ * @dst: destination handle
+ * @user: sending user
+ * @flags: message flags
+ * @uid: sender UID
+ * @gid: sender GID
+ * @pid: sender PID
+ * @tid: sender TID
+ * @n_bytes: number of user-bytes transmitted
+ * @n_handles: number of handles transmitted
+ * @n_handles_charge: number of handle charges
+ * @n_files: number of files transmitted
+ * @n_secctx: number of bytes of security context transmitted
+ * @slice: actual message data
+ * @files: passed file descriptors
+ * @handles: passed handles
+ */
+struct bus1_message {
+ struct kref ref;
+ struct bus1_queue_node qnode;
+ struct bus1_handle *dst;
+ struct bus1_user *user;
+
+ u64 flags;
+ uid_t uid;
+ gid_t gid;
+ pid_t pid;
+ pid_t tid;
+
+ size_t n_bytes;
+ size_t n_handles;
+ size_t n_handles_charge;
+ size_t n_files;
+ size_t n_secctx;
+ struct bus1_pool_slice *slice;
+ struct file **files;
+
+ struct bus1_flist handles[];
+};
+
+struct bus1_factory *bus1_factory_new(struct bus1_peer *peer,
+ struct bus1_cmd_send *param,
+ void *stack,
+ size_t n_stack);
+struct bus1_factory *bus1_factory_free(struct bus1_factory *f);
+int bus1_factory_seal(struct bus1_factory *f);
+struct bus1_message *bus1_factory_instantiate(struct bus1_factory *f,
+ struct bus1_handle *handle,
+ struct bus1_peer *peer);
+
+void bus1_message_free(struct kref *k);
+void bus1_message_stage(struct bus1_message *m, struct bus1_tx *tx);
+int bus1_message_install(struct bus1_message *m, struct bus1_cmd_recv *param);
+
+/**
+ * bus1_message_ref() - acquire object reference
+ * @m: message to operate on, or NULL
+ *
+ * This acquires a single reference to @m. The caller must already hold a
+ * reference when calling this.
+ *
+ * If @m is NULL, this is a no-op.
+ *
+ * Return: @m is returned.
+ */
+static inline struct bus1_message *bus1_message_ref(struct bus1_message *m)
+{
+ if (m)
+ kref_get(&m->ref);
+ return m;
+}
+
+/**
+ * bus1_message_unref() - release object reference
+ * @m: message to operate on, or NULL
+ *
+ * This releases a single object reference to @m. If the reference counter
+ * drops to 0, the message is destroyed.
+ *
+ * If @m is NULL, this is a no-op.
+ *
+ * Return: NULL is returned.
+ */
+static inline struct bus1_message *bus1_message_unref(struct bus1_message *m)
+{
+ if (m)
+ kref_put(&m->ref, bus1_message_free);
+ return NULL;
+}
+
+#endif /* __BUS1_MESSAGE_H */
diff --git a/ipc/bus1/peer.c b/ipc/bus1/peer.c
index a1525cb..0ff7a98 100644
--- a/ipc/bus1/peer.c
+++ b/ipc/bus1/peer.c
@@ -70,6 +70,7 @@ struct bus1_peer *bus1_peer_new(void)
/* initialize data section */
mutex_init(&peer->data.lock);
+ peer->data.pool = BUS1_POOL_NULL;
bus1_queue_init(&peer->data.queue);
/* initialize peer-private section */
@@ -136,6 +137,7 @@ struct bus1_peer *bus1_peer_free(struct bus1_peer *peer)
/* deinitialize data section */
bus1_queue_deinit(&peer->data.queue);
+ bus1_pool_deinit(&peer->data.pool);
mutex_destroy(&peer->data.lock);
/* deinitialize constant fields */
diff --git a/ipc/bus1/peer.h b/ipc/bus1/peer.h
index 655d3ac..5eb558f 100644
--- a/ipc/bus1/peer.h
+++ b/ipc/bus1/peer.h
@@ -54,6 +54,7 @@
#include <linux/wait.h>
#include "user.h"
#include "util/active.h"
+#include "util/pool.h"
#include "util/queue.h"
struct cred;
@@ -88,6 +89,7 @@ struct bus1_peer {
struct {
struct mutex lock;
+ struct bus1_pool pool;
struct bus1_queue queue;
} data;
diff --git a/ipc/bus1/util.c b/ipc/bus1/util.c
index 8acf798..687f40d 100644
--- a/ipc/bus1/util.c
+++ b/ipc/bus1/util.c
@@ -9,12 +9,174 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/atomic.h>
+#include <linux/compat.h>
#include <linux/debugfs.h>
#include <linux/err.h>
+#include <linux/file.h>
#include <linux/fs.h>
#include <linux/kernel.h>
+#include <linux/pagemap.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/uio.h>
+#include <net/sock.h>
+#include "main.h"
#include "util.h"
+/**
+ * bus1_import_vecs() - import vectors from user
+ * @out_vecs: kernel memory to store vecs, preallocated
+ * @out_length: output storage for sum of all vectors lengths
+ * @vecs: user pointer for vectors
+ * @n_vecs: number of vectors to import
+ *
+ * This copies the given vectors from user memory into the preallocated kernel
+ * buffer. Sanity checks are performed on the memory of the vector-array, the
+ * memory pointed to by the vectors and on the overall size calculation.
+ *
+ * If the vectors were copied successfully, @out_length will contain the sum of
+ * all vector-lengths.
+ *
+ * Unlike most other functions, this function might modify its output buffer
+ * even if it fails. That is, @out_vecs might contain garbage if this function
+ * fails. This is done for performance reasons.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int bus1_import_vecs(struct iovec *out_vecs,
+ size_t *out_length,
+ const void __user *vecs,
+ size_t n_vecs)
+{
+ size_t i, length = 0;
+
+ if (n_vecs > UIO_MAXIOV)
+ return -EMSGSIZE;
+ if (n_vecs == 0) {
+ *out_length = 0;
+ return 0;
+ }
+
+ if (IS_ENABLED(CONFIG_COMPAT) && in_compat_syscall()) {
+ /*
+ * Compat types and macros are protected by CONFIG_COMPAT,
+ * rather than providing a fallback. We want compile-time
+ * coverage, so provide fallback types. The IS_ENABLED(COMPAT)
+ * condition guarantees this is collected by the dead-code
+ * elimination, anyway.
+ */
+#if IS_ENABLED(CONFIG_COMPAT)
+ const struct compat_iovec __user *uvecs = vecs;
+ compat_uptr_t v_base;
+ compat_size_t v_len;
+ compat_ssize_t v_slen;
+#else
+ const struct iovec __user *uvecs = vecs;
+ void __user *v_base;
+ size_t v_len;
+ ssize_t v_slen;
+#endif
+ void __user *v_ptr;
+
+ if (unlikely(!access_ok(VERIFY_READ, vecs,
+ sizeof(*uvecs) * n_vecs)))
+ return -EFAULT;
+
+ for (i = 0; i < n_vecs; ++i) {
+ if (unlikely(__get_user(v_base, &uvecs[i].iov_base) ||
+ __get_user(v_len, &uvecs[i].iov_len)))
+ return -EFAULT;
+
+#if IS_ENABLED(CONFIG_COMPAT)
+ v_ptr = compat_ptr(v_base);
+#else
+ v_ptr = v_base;
+#endif
+ v_slen = v_len;
+
+ if (unlikely(v_slen < 0 ||
+ (typeof(v_len))v_slen != v_len))
+ return -EMSGSIZE;
+ if (unlikely(!access_ok(VERIFY_READ, v_ptr, v_len)))
+ return -EFAULT;
+ if (unlikely((size_t)v_len > MAX_RW_COUNT - length))
+ return -EMSGSIZE;
+
+ out_vecs[i].iov_base = v_ptr;
+ out_vecs[i].iov_len = v_len;
+ length += v_len;
+ }
+ } else {
+ void __user *v_base;
+ size_t v_len;
+
+ if (copy_from_user(out_vecs, vecs, sizeof(*out_vecs) * n_vecs))
+ return -EFAULT;
+
+ for (i = 0; i < n_vecs; ++i) {
+ v_base = out_vecs[i].iov_base;
+ v_len = out_vecs[i].iov_len;
+
+ if (unlikely((ssize_t)v_len < 0))
+ return -EMSGSIZE;
+ if (unlikely(!access_ok(VERIFY_READ, v_base, v_len)))
+ return -EFAULT;
+ if (unlikely(v_len > MAX_RW_COUNT - length))
+ return -EMSGSIZE;
+
+ length += v_len;
+ }
+ }
+
+ *out_length = length;
+ return 0;
+}
+
+/**
+ * bus1_import_fd() - import file descriptor from user
+ * @user_fd: pointer to user-supplied file descriptor
+ *
+ * This imports a file-descriptor from the current user-context. The FD number
+ * is copied into kernel-space, then resolved to a file and returned to the
+ * caller. If something goes wrong, an error is returned.
+ *
+ * Neither bus1, nor UDS files are allowed. If those are supplied, EOPNOTSUPP
+ * is returned. Those would require expensive garbage-collection if they're
+ * sent recursively by user-space.
+ *
+ * Return: Pointer to pinned file, ERR_PTR on failure.
+ */
+struct file *bus1_import_fd(int fd)
+{
+ struct file *f, *ret;
+ struct socket *sock;
+ struct inode *inode;
+
+ if (unlikely(fd < 0))
+ return ERR_PTR(-EBADF);
+
+ f = fget_raw(fd);
+ if (unlikely(!f))
+ return ERR_PTR(-EBADF);
+
+ inode = file_inode(f);
+ sock = S_ISSOCK(inode->i_mode) ? SOCKET_I(inode) : NULL;
+
+ if (f->f_mode & FMODE_PATH)
+ ret = f; /* O_PATH is always allowed */
+ else if (f->f_op == &bus1_fops)
+ ret = ERR_PTR(-EOPNOTSUPP); /* disallow bus1 recursion */
+ else if (sock && sock->sk && sock->ops && sock->ops->family == PF_UNIX)
+ ret = ERR_PTR(-EOPNOTSUPP); /* disallow UDS recursion */
+ else
+ ret = f; /* all others are allowed */
+
+ if (f != ret)
+ fput(f);
+
+ return ret;
+}
+
#if defined(CONFIG_DEBUG_FS)
static int bus1_debugfs_atomic_t_get(void *data, u64 *val)
diff --git a/ipc/bus1/util.h b/ipc/bus1/util.h
index c22ecd5..ab41d5e 100644
--- a/ipc/bus1/util.h
+++ b/ipc/bus1/util.h
@@ -26,6 +26,7 @@
#include <linux/types.h>
struct dentry;
+struct iovec;
/**
* BUS1_TAIL - tail pointer in singly-linked lists
@@ -37,6 +38,12 @@ struct dentry;
*/
#define BUS1_TAIL ERR_PTR(-1)
+int bus1_import_vecs(struct iovec *out_vecs,
+ size_t *out_length,
+ const void __user *vecs,
+ size_t n_vecs);
+struct file *bus1_import_fd(int fd);
+
#if defined(CONFIG_DEBUG_FS)
struct dentry *
--
2.10.1
next prev parent reply other threads:[~2016-10-26 19:23 UTC|newest]
Thread overview: 55+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-10-26 19:17 [RFC v1 00/14] Bus1 Kernel Message Bus David Herrmann
2016-10-26 19:17 ` [RFC v1 01/14] bus1: add bus1(7) man-page David Herrmann
2016-10-27 23:12 ` Kirill A. Shutemov
2016-10-26 19:17 ` [RFC v1 02/14] bus1: provide stub cdev /dev/bus1 David Herrmann
2016-10-26 23:19 ` Andy Lutomirski
2016-10-26 23:54 ` Tom Gundersen
2016-10-27 9:11 ` Arnd Bergmann
2016-10-27 15:25 ` Tom Gundersen
2016-10-27 16:37 ` Linus Torvalds
2016-10-27 16:39 ` Tom Gundersen
2016-10-29 22:13 ` Arnd Bergmann
2016-10-26 19:17 ` [RFC v1 03/14] bus1: util - active reference utility library David Herrmann
2016-10-26 19:18 ` [RFC v1 04/14] bus1: util - fixed list " David Herrmann
2016-10-27 12:37 ` Peter Zijlstra
2016-10-27 12:48 ` David Herrmann
2016-10-27 12:56 ` Arnd Bergmann
2016-10-27 13:31 ` David Herrmann
2016-10-26 19:18 ` [RFC v1 05/14] bus1: util - pool " David Herrmann
2016-10-27 12:54 ` Peter Zijlstra
2016-10-27 12:59 ` Peter Zijlstra
2016-10-27 15:00 ` Peter Zijlstra
2016-10-27 15:14 ` Peter Zijlstra
2016-10-26 19:18 ` [RFC v1 06/14] bus1: util - queue " David Herrmann
2016-10-27 15:27 ` Peter Zijlstra
2016-10-27 16:43 ` Peter Zijlstra
2016-10-28 11:33 ` Tom Gundersen
2016-10-28 13:33 ` Peter Zijlstra
2016-10-28 13:47 ` Tom Gundersen
2016-10-28 13:58 ` Peter Zijlstra
2016-10-28 14:33 ` Tom Gundersen
2016-10-28 16:49 ` Peter Zijlstra
2016-10-26 19:18 ` [RFC v1 07/14] bus1: tracking user contexts David Herrmann
2016-10-26 19:18 ` [RFC v1 08/14] bus1: implement peer management context David Herrmann
2016-10-28 12:06 ` Richard Weinberger
2016-10-28 13:18 ` Tom Gundersen
2016-10-28 13:21 ` Richard Weinberger
2016-10-28 13:05 ` Richard Weinberger
2016-10-28 13:23 ` Tom Gundersen
2016-10-28 13:54 ` Richard Weinberger
2016-10-26 19:18 ` [RFC v1 09/14] bus1: provide transaction context for multicasts David Herrmann
2016-10-28 14:37 ` Peter Zijlstra
2016-10-26 19:18 ` [RFC v1 10/14] bus1: add handle management David Herrmann
2016-10-26 19:18 ` David Herrmann [this message]
2016-10-26 19:18 ` [RFC v1 12/14] bus1: hook up file-operations David Herrmann
2016-10-26 19:18 ` [RFC v1 13/14] bus1: limit and protect resources David Herrmann
2016-10-26 19:18 ` [RFC v1 14/14] bus1: basic user-space kselftests David Herrmann
2016-10-26 19:39 ` [RFC v1 00/14] Bus1 Kernel Message Bus Linus Torvalds
2016-10-26 20:34 ` David Herrmann
2016-10-27 0:45 ` Kirill A. Shutemov
2016-10-29 21:04 ` Josh Triplett
2016-11-02 14:45 ` David Herrmann
2017-01-30 22:11 ` Pavel Machek
2016-10-27 11:10 ` Michael Kerrisk
2016-10-28 13:11 ` Richard Weinberger
2016-10-28 13:37 ` Tom Gundersen
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20161026191810.12275-12-dh.herrmann@gmail.com \
--to=dh.herrmann@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=arnd@arndb.de \
--cc=greg@kroah.com \
--cc=hare@suse.com \
--cc=jikos@kernel.org \
--cc=josh@joshtriplett.org \
--cc=linux-kernel@vger.kernel.org \
--cc=luto@amacapital.net \
--cc=rostedt@goodmis.org \
--cc=teg@jklm.no \
--cc=torvalds@linux-foundation.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.