summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ipc/kdbus/handle.c110
1 files changed, 83 insertions, 27 deletions
diff --git a/ipc/kdbus/handle.c b/ipc/kdbus/handle.c
index f72dbe513b4a..2676512c2ade 100644
--- a/ipc/kdbus/handle.c
+++ b/ipc/kdbus/handle.c
@@ -18,6 +18,7 @@
#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>
@@ -224,7 +225,7 @@ enum kdbus_handle_type {
/**
* struct kdbus_handle - handle to the kdbus system
- * @rwlock: handle lock
+ * @lock: handle lock
* @type: type of this handle (KDBUS_HANDLE_*)
* @bus_owner: bus this handle owns
* @ep_owner: endpoint this handle owns
@@ -232,7 +233,7 @@ enum kdbus_handle_type {
* @privileged: Flag to mark a handle as privileged
*/
struct kdbus_handle {
- struct rw_semaphore rwlock;
+ struct mutex lock;
enum kdbus_handle_type type;
union {
@@ -260,7 +261,7 @@ static int kdbus_handle_open(struct inode *inode, struct file *file)
goto exit;
}
- init_rwsem(&handle->rwlock);
+ mutex_init(&handle->lock);
handle->type = KDBUS_HANDLE_NONE;
if (node->type == KDBUS_NODE_ENDPOINT) {
@@ -350,8 +351,8 @@ static long kdbus_handle_ioctl_control(struct file *file, unsigned int cmd,
break;
}
- handle->type = KDBUS_HANDLE_BUS_OWNER;
handle->bus_owner = bus;
+ ret = KDBUS_HANDLE_BUS_OWNER;
break;
}
@@ -391,8 +392,8 @@ static long kdbus_handle_ioctl_ep(struct file *file, unsigned int cmd,
break;
}
- handle->type = KDBUS_HANDLE_EP_OWNER;
handle->ep_owner = ep;
+ ret = KDBUS_HANDLE_EP_OWNER;
break;
case KDBUS_CMD_HELLO:
@@ -402,8 +403,8 @@ static long kdbus_handle_ioctl_ep(struct file *file, unsigned int cmd,
break;
}
- handle->type = KDBUS_HANDLE_CONNECTED;
handle->conn = conn;
+ ret = KDBUS_HANDLE_CONNECTED;
break;
default:
@@ -517,19 +518,41 @@ static long kdbus_handle_ioctl(struct file *file, unsigned int cmd,
case KDBUS_CMD_BUS_MAKE:
case KDBUS_CMD_ENDPOINT_MAKE:
case KDBUS_CMD_HELLO:
- /* bail out early if already typed */
- if (handle->type != KDBUS_HANDLE_NONE)
- break;
-
- down_write(&handle->rwlock);
+ 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;
+ }
}
- up_write(&handle->rwlock);
+ mutex_unlock(&handle->lock);
break;
case KDBUS_CMD_ENDPOINT_UPDATE:
@@ -544,14 +567,30 @@ static long kdbus_handle_ioctl(struct file *file, unsigned int cmd,
case KDBUS_CMD_MATCH_REMOVE:
case KDBUS_CMD_SEND:
case KDBUS_CMD_RECV:
- case KDBUS_CMD_FREE:
- down_read(&handle->rwlock);
- if (handle->type == KDBUS_HANDLE_EP_OWNER)
+ 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 (handle->type == KDBUS_HANDLE_CONNECTED)
+ else if (type == KDBUS_HANDLE_CONNECTED)
ret = kdbus_handle_ioctl_connected(file, cmd, argp);
- up_read(&handle->rwlock);
+
break;
+ }
default:
ret = -ENOTTY;
break;
@@ -564,16 +603,23 @@ 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;
int ret;
+ /*
+ * 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 */
- down_read(&handle->rwlock);
- if (handle->type != KDBUS_HANDLE_CONNECTED) {
- up_read(&handle->rwlock);
+ if (type != KDBUS_HANDLE_CONNECTED)
return POLLERR | POLLHUP;
- }
- up_read(&handle->rwlock);
ret = kdbus_conn_acquire(handle->conn);
if (ret < 0)
@@ -593,13 +639,23 @@ static unsigned int kdbus_handle_poll(struct file *file,
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;
- if (down_read_trylock(&handle->rwlock)) {
- if (handle->type == KDBUS_HANDLE_CONNECTED)
- ret = kdbus_pool_mmap(handle->conn->pool, vma);
- up_read(&handle->rwlock);
- }
+ /*
+ * 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;
}