[RFC v1 11/14] bus1: implement message transmission
From: David Herrmann
Date: Wed Oct 26 2016 - 15:23:27 EST
From: Tom Gundersen <teg@xxxxxxx>
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@xxxxxxx>
Signed-off-by: David Herrmann <dh.herrmann@xxxxxxxxx>
---
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