[RFC v1 07/14] bus1: tracking user contexts

From: David Herrmann
Date: Wed Oct 26 2016 - 15:22:01 EST


From: Tom Gundersen <teg@xxxxxxx>

Different users can communicate via bus1, and many resources are shared
between multiple users. The bus1_user object represents the UID of a
user, like "struct user_struct" does in the kernel core. It is used to
account global resources, apply limits, and calculate quotas if
different UIDs communicate with each other.

All dynamic resources have global per-user limits, which cannot be
exceeded by a user. They prevent a single user from exhausting local
resources. Each peer that is created is always owned by the user that
initialized it. All resources allocated on that peer are accounted on
that pinned user. Additionally to global resources, there are local
limits per peer, that can be controlled by each peer individually
(e.g., specifying a maximum pool size). Those local limits allow a user
to distribute the globally available resources across its peer
instances.

Since bus1 allows communication across UID boundaries, any such
transmission of resources must be properly accounted. Bus1 employs
dynamic quotas to fairly distribute available resources. Those quotas
make sure that available resources of a peer cannot be exhausted by
remote UIDs, but are fairly divided among all communicating peers.

This only implements the user tracking, the resource limits will be
added in follow-up patches.

Signed-off-by: Tom Gundersen <teg@xxxxxxx>
Signed-off-by: David Herrmann <dh.herrmann@xxxxxxxxx>
---
ipc/bus1/Makefile | 1 +
ipc/bus1/main.c | 3 ++
ipc/bus1/user.c | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
ipc/bus1/user.h | 67 ++++++++++++++++++++++++
4 files changed, 224 insertions(+)
create mode 100644 ipc/bus1/user.c
create mode 100644 ipc/bus1/user.h

diff --git a/ipc/bus1/Makefile b/ipc/bus1/Makefile
index 3c90657..94d79e0 100644
--- a/ipc/bus1/Makefile
+++ b/ipc/bus1/Makefile
@@ -1,5 +1,6 @@
bus1-y := \
main.o \
+ user.o \
util/active.o \
util/flist.o \
util/pool.o \
diff --git a/ipc/bus1/main.c b/ipc/bus1/main.c
index 02412a7..526347d 100644
--- a/ipc/bus1/main.c
+++ b/ipc/bus1/main.c
@@ -16,6 +16,7 @@
#include <linux/module.h>
#include "main.h"
#include "tests.h"
+#include "user.h"

static int bus1_fop_open(struct inode *inode, struct file *file)
{
@@ -64,6 +65,7 @@ static int __init bus1_modinit(void)

error:
debugfs_remove(bus1_debugdir);
+ bus1_user_modexit();
return r;
}

@@ -71,6 +73,7 @@ static void __exit bus1_modexit(void)
{
misc_deregister(&bus1_misc);
debugfs_remove(bus1_debugdir);
+ bus1_user_modexit();
pr_info("unloaded\n");
}

diff --git a/ipc/bus1/user.c b/ipc/bus1/user.c
new file mode 100644
index 0000000..0498ab4
--- /dev/null
+++ b/ipc/bus1/user.c
@@ -0,0 +1,153 @@
+/*
+ * 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/err.h>
+#include <linux/idr.h>
+#include <linux/kernel.h>
+#include <linux/kref.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/rcupdate.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/uidgid.h>
+#include "user.h"
+
+static DEFINE_MUTEX(bus1_user_lock);
+static DEFINE_IDR(bus1_user_idr);
+
+/**
+ * bus1_user_modexit() - clean up global resources of user accounting
+ *
+ * This function cleans up any remaining global resources that were allocated
+ * by the user accounting helpers. The caller must make sure that no user
+ * object is referenced anymore, before calling this. This function just clears
+ * caches and verifies nothing is leaked.
+ *
+ * This is meant to be called on module-exit.
+ */
+void bus1_user_modexit(void)
+{
+ WARN_ON(!idr_is_empty(&bus1_user_idr));
+ idr_destroy(&bus1_user_idr);
+ idr_init(&bus1_user_idr);
+}
+
+static struct bus1_user *bus1_user_new(void)
+{
+ struct bus1_user *user;
+
+ user = kmalloc(sizeof(*user), GFP_KERNEL);
+ if (!user)
+ return ERR_PTR(-ENOMEM);
+
+ kref_init(&user->ref);
+ user->uid = INVALID_UID;
+ mutex_init(&user->lock);
+
+ return user;
+}
+
+static void bus1_user_free(struct kref *ref)
+{
+ struct bus1_user *user = container_of(ref, struct bus1_user, ref);
+
+ lockdep_assert_held(&bus1_user_lock);
+
+ if (likely(uid_valid(user->uid)))
+ idr_remove(&bus1_user_idr, __kuid_val(user->uid));
+ mutex_destroy(&user->lock);
+ kfree_rcu(user, rcu);
+}
+
+/**
+ * bus1_user_ref_by_uid() - get a user object for a uid
+ * @uid: uid of the user
+ *
+ * Find and return the user object for the uid if it exists, otherwise create
+ * it first.
+ *
+ * Return: A user object for the given uid, ERR_PTR on failure.
+ */
+struct bus1_user *bus1_user_ref_by_uid(kuid_t uid)
+{
+ struct bus1_user *user;
+ int r;
+
+ if (WARN_ON(!uid_valid(uid)))
+ return ERR_PTR(-ENOTRECOVERABLE);
+
+ /* fast-path: acquire reference via rcu */
+ rcu_read_lock();
+ user = idr_find(&bus1_user_idr, __kuid_val(uid));
+ if (user && !kref_get_unless_zero(&user->ref))
+ user = NULL;
+ rcu_read_unlock();
+ if (user)
+ return user;
+
+ /* slow-path: try again with IDR locked */
+ mutex_lock(&bus1_user_lock);
+ user = idr_find(&bus1_user_idr, __kuid_val(uid));
+ if (likely(!bus1_user_ref(user))) {
+ user = bus1_user_new();
+ if (!IS_ERR(user)) {
+ user->uid = uid;
+ r = idr_alloc(&bus1_user_idr, user, __kuid_val(uid),
+ __kuid_val(uid) + 1, GFP_KERNEL);
+ if (r < 0) {
+ user->uid = INVALID_UID; /* couldn't insert */
+ kref_put(&user->ref, bus1_user_free);
+ user = ERR_PTR(r);
+ }
+ }
+ }
+ mutex_unlock(&bus1_user_lock);
+
+ return user;
+}
+
+/**
+ * bus1_user_ref() - acquire reference
+ * @user: user to acquire, or NULL
+ *
+ * Acquire an additional reference to a user-object. The caller must already
+ * own a reference.
+ *
+ * If NULL is passed, this is a no-op.
+ *
+ * Return: @user is returned.
+ */
+struct bus1_user *bus1_user_ref(struct bus1_user *user)
+{
+ if (user)
+ kref_get(&user->ref);
+ return user;
+}
+
+/**
+ * bus1_user_unref() - release reference
+ * @user: user to release, or NULL
+ *
+ * Release a reference to a user-object.
+ *
+ * If NULL is passed, this is a no-op.
+ *
+ * Return: NULL is returned.
+ */
+struct bus1_user *bus1_user_unref(struct bus1_user *user)
+{
+ if (user) {
+ if (kref_put_mutex(&user->ref, bus1_user_free, &bus1_user_lock))
+ mutex_unlock(&bus1_user_lock);
+ }
+
+ return NULL;
+}
diff --git a/ipc/bus1/user.h b/ipc/bus1/user.h
new file mode 100644
index 0000000..6cdc264
--- /dev/null
+++ b/ipc/bus1/user.h
@@ -0,0 +1,67 @@
+#ifndef __BUS1_USER_H
+#define __BUS1_USER_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: Users
+ *
+ * Different users can communicate via bus1, and many resources are shared
+ * between multiple users. The bus1_user object represents the UID of a user,
+ * like "struct user_struct" does in the kernel core. It is used to account
+ * global resources, apply limits, and calculate quotas if different UIDs
+ * communicate with each other.
+ *
+ * All dynamic resources have global per-user limits, which cannot be exceeded
+ * by a user. They prevent a single user from exhausting local resources. Each
+ * peer that is created is always owned by the user that initialized it. All
+ * resources allocated on that peer are accounted on that pinned user.
+ * Additionally to global resources, there are local limits per peer, that can
+ * be controlled by each peer individually (e.g., specifying a maximum pool
+ * size). Those local limits allow a user to distribute the globally available
+ * resources across its peer instances.
+ *
+ * Since bus1 allows communication across UID boundaries, any such transmission
+ * of resources must be properly accounted. Bus1 employs dynamic quotas to
+ * fairly distribute available resources. Those quotas make sure that available
+ * resources of a peer cannot be exhausted by remote UIDs, but are fairly
+ * divided among all communicating peers.
+ */
+
+#include <linux/atomic.h>
+#include <linux/idr.h>
+#include <linux/kref.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/uidgid.h>
+
+/**
+ * struct bus1_user - resource accounting for users
+ * @ref: reference counter
+ * @uid: UID of the user
+ * @lock: object lock
+ * @rcu: rcu
+ */
+struct bus1_user {
+ struct kref ref;
+ kuid_t uid;
+ struct mutex lock;
+ struct rcu_head rcu;
+};
+
+/* module cleanup */
+void bus1_user_modexit(void);
+
+/* users */
+struct bus1_user *bus1_user_ref_by_uid(kuid_t uid);
+struct bus1_user *bus1_user_ref(struct bus1_user *user);
+struct bus1_user *bus1_user_unref(struct bus1_user *user);
+
+#endif /* __BUS1_USER_H */
--
2.10.1