Inter-revision diff: patch 2

Comparing v14 (message) to v11 (message)

--- v14
+++ v11
@@ -1,747 +1,402 @@
-A Landlock ruleset is mainly a red-black tree with Landlock rules as
-nodes.  This enables quick update and lookup to match a requested access
-e.g., to a file.  A ruleset is usable through a dedicated file
-descriptor (cf. following commit adding the syscall) which enables a
-process to build it by adding new rules.
+A Landlock domain is a set of eBPF programs.  There is a list for each
+different program types that can be run on a specific Landlock hook
+(e.g. ptrace).  A domain is tied to a set of subjects (i.e. tasks).  A
+Landlock program should not try (nor be able) to infer which subject is
+currently enforced, but to have a unique security policy for all
+subjects tied to the same domain.  This make the reasoning much easier
+and help avoid pitfalls.
 
-A domain is a ruleset tied to a set of processes.  This group of rules
-defined the security policy enforced on these processes and their future
-children.  A domain can transition to a new domain which is the merge of
-itself with a ruleset provided by the current process.  This merge is
-the intersection of all the constraints, which means that a process can
-only gain more constraints over time.
+The next commits tie a domain to a task's credentials thanks to
+seccomp(2), but we could use cgroups or a security file-system to
+enforce a sysadmin-defined policy .
 
 Signed-off-by: Mickaël Salaün <mic@digikod.net>
+Cc: Alexei Starovoitov <ast@kernel.org>
 Cc: Andy Lutomirski <luto@amacapital.net>
+Cc: Daniel Borkmann <daniel@iogearbox.net>
 Cc: James Morris <jmorris@namei.org>
 Cc: Kees Cook <keescook@chromium.org>
 Cc: Serge E. Hallyn <serge@hallyn.com>
+Cc: Will Drewry <wad@chromium.org>
 ---
 
-Changes since v13:
-* New implementation, inspired by the previous inode eBPF map, but
-  agnostic to the underlying kernel object.
+Changes since v10:
+* rename files and names to clearly define a domain
+* create a standalone patch to ease review
+---
+ security/landlock/Makefile        |   3 +-
+ security/landlock/common.h        |  38 +++++
+ security/landlock/domain_manage.c | 265 ++++++++++++++++++++++++++++++
+ security/landlock/domain_manage.h |  23 +++
+ 4 files changed, 328 insertions(+), 1 deletion(-)
+ create mode 100644 security/landlock/domain_manage.c
+ create mode 100644 security/landlock/domain_manage.h
 
-Previous version:
-https://lore.kernel.org/lkml/20190721213116.23476-7-mic@digikod.net/
----
- MAINTAINERS                   |   1 +
- include/uapi/linux/landlock.h | 102 ++++++++
- security/landlock/Makefile    |   2 +-
- security/landlock/ruleset.c   | 460 ++++++++++++++++++++++++++++++++++
- security/landlock/ruleset.h   | 106 ++++++++
- 5 files changed, 670 insertions(+), 1 deletion(-)
- create mode 100644 include/uapi/linux/landlock.h
- create mode 100644 security/landlock/ruleset.c
- create mode 100644 security/landlock/ruleset.h
-
-diff --git a/MAINTAINERS b/MAINTAINERS
-index 206f85768cd9..937257925e65 100644
---- a/MAINTAINERS
-+++ b/MAINTAINERS
-@@ -9366,6 +9366,7 @@ L:	linux-security-module@vger.kernel.org
- W:	https://landlock.io
- T:	git https://github.com/landlock-lsm/linux.git
- S:	Supported
-+F:	include/uapi/linux/landlock.h
- F:	security/landlock/
- K:	landlock
- K:	LANDLOCK
-diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
-new file mode 100644
-index 000000000000..92760aca3645
---- /dev/null
-+++ b/include/uapi/linux/landlock.h
-@@ -0,0 +1,102 @@
-+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
-+/*
-+ * Landlock - UAPI headers
-+ *
-+ * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
-+ * Copyright © 2018-2020 ANSSI
-+ */
-+
-+#ifndef _UAPI__LINUX_LANDLOCK_H__
-+#define _UAPI__LINUX_LANDLOCK_H__
-+
-+/**
-+ * DOC: fs_access
-+ *
-+ * A set of actions on kernel objects may be defined by an attribute (e.g.
-+ * &struct landlock_attr_path_beneath) and a bitmask of access.
-+ *
-+ * Filesystem flags
-+ * ~~~~~~~~~~~~~~~~
-+ *
-+ * These flags enable to restrict a sandbox process to a set of of actions on
-+ * files and directories.  Files or directories opened before the sandboxing
-+ * are not subject to these restrictions.
-+ *
-+ * - %LANDLOCK_ACCESS_FS_READ: Open or map a file with read access.
-+ * - %LANDLOCK_ACCESS_FS_READDIR: List the content of a directory.
-+ * - %LANDLOCK_ACCESS_FS_GETATTR: Read metadata of a file or a directory.
-+ * - %LANDLOCK_ACCESS_FS_WRITE: Write to a file.
-+ * - %LANDLOCK_ACCESS_FS_TRUNCATE: Truncate a file.
-+ * - %LANDLOCK_ACCESS_FS_LOCK: Lock a file.
-+ * - %LANDLOCK_ACCESS_FS_CHMOD: Change DAC permissions on a file or a
-+ *   directory.
-+ * - %LANDLOCK_ACCESS_FS_CHOWN: Change the owner of a file or a directory.
-+ * - %LANDLOCK_ACCESS_FS_CHGRP: Change the group of a file or a directory.
-+ * - %LANDLOCK_ACCESS_FS_IOCTL: Send various command to a special file, cf.
-+ *   :manpage:`ioctl(2)`.
-+ * - %LANDLOCK_ACCESS_FS_LINK_TO: Link a file into a directory.
-+ * - %LANDLOCK_ACCESS_FS_RENAME_FROM: Rename a file or a directory.
-+ * - %LANDLOCK_ACCESS_FS_RENAME_TO: Rename a file or a directory.
-+ * - %LANDLOCK_ACCESS_FS_RMDIR: Remove an empty directory.
-+ * - %LANDLOCK_ACCESS_FS_UNLINK: Remove a file.
-+ * - %LANDLOCK_ACCESS_FS_MAKE_CHAR: Create a character device.
-+ * - %LANDLOCK_ACCESS_FS_MAKE_DIR: Create a directory.
-+ * - %LANDLOCK_ACCESS_FS_MAKE_REG: Create a regular file.
-+ * - %LANDLOCK_ACCESS_FS_MAKE_SOCK: Create a UNIX domain socket.
-+ * - %LANDLOCK_ACCESS_FS_MAKE_FIFO: Create a named pipe.
-+ * - %LANDLOCK_ACCESS_FS_MAKE_BLOCK: Create a block device.
-+ * - %LANDLOCK_ACCESS_FS_MAKE_SYM: Create a symbolic link.
-+ * - %LANDLOCK_ACCESS_FS_EXECUTE: Execute a file.
-+ * - %LANDLOCK_ACCESS_FS_CHROOT: Change the root directory of the current
-+ *   process.
-+ * - %LANDLOCK_ACCESS_FS_OPEN: Open a file or a directory.  This flag is set
-+ *   for any actions (e.g. read, write, execute) requested to open a file or
-+ *   directory.
-+ * - %LANDLOCK_ACCESS_FS_MAP: Map a file.  This flag is set for any actions
-+ *   (e.g. read, write, execute) requested to map a file.
-+ *
-+ * There is currently no restriction for directory walking e.g.,
-+ * :manpage:`chdir(2)`.
-+ */
-+#define LANDLOCK_ACCESS_FS_READ			(1ULL << 0)
-+#define LANDLOCK_ACCESS_FS_READDIR		(1ULL << 1)
-+#define LANDLOCK_ACCESS_FS_GETATTR		(1ULL << 2)
-+#define LANDLOCK_ACCESS_FS_WRITE		(1ULL << 3)
-+#define LANDLOCK_ACCESS_FS_TRUNCATE		(1ULL << 4)
-+#define LANDLOCK_ACCESS_FS_LOCK			(1ULL << 5)
-+#define LANDLOCK_ACCESS_FS_CHMOD		(1ULL << 6)
-+#define LANDLOCK_ACCESS_FS_CHOWN		(1ULL << 7)
-+#define LANDLOCK_ACCESS_FS_CHGRP		(1ULL << 8)
-+#define LANDLOCK_ACCESS_FS_IOCTL		(1ULL << 9)
-+#define LANDLOCK_ACCESS_FS_LINK_TO		(1ULL << 10)
-+#define LANDLOCK_ACCESS_FS_RENAME_FROM		(1ULL << 11)
-+#define LANDLOCK_ACCESS_FS_RENAME_TO		(1ULL << 12)
-+#define LANDLOCK_ACCESS_FS_RMDIR		(1ULL << 13)
-+#define LANDLOCK_ACCESS_FS_UNLINK		(1ULL << 14)
-+#define LANDLOCK_ACCESS_FS_MAKE_CHAR		(1ULL << 15)
-+#define LANDLOCK_ACCESS_FS_MAKE_DIR		(1ULL << 16)
-+#define LANDLOCK_ACCESS_FS_MAKE_REG		(1ULL << 17)
-+#define LANDLOCK_ACCESS_FS_MAKE_SOCK		(1ULL << 18)
-+#define LANDLOCK_ACCESS_FS_MAKE_FIFO		(1ULL << 19)
-+#define LANDLOCK_ACCESS_FS_MAKE_BLOCK		(1ULL << 20)
-+#define LANDLOCK_ACCESS_FS_MAKE_SYM		(1ULL << 21)
-+#define LANDLOCK_ACCESS_FS_EXECUTE		(1ULL << 22)
-+#define LANDLOCK_ACCESS_FS_CHROOT		(1ULL << 23)
-+#define LANDLOCK_ACCESS_FS_OPEN			(1ULL << 24)
-+#define LANDLOCK_ACCESS_FS_MAP			(1ULL << 25)
-+
-+/*
-+ * Potential future access:
-+ * - %LANDLOCK_ACCESS_FS_SETATTR
-+ * - %LANDLOCK_ACCESS_FS_APPEND
-+ * - %LANDLOCK_ACCESS_FS_LINK_FROM
-+ * - %LANDLOCK_ACCESS_FS_MOUNT_FROM
-+ * - %LANDLOCK_ACCESS_FS_MOUNT_TO
-+ * - %LANDLOCK_ACCESS_FS_UNMOUNT
-+ * - %LANDLOCK_ACCESS_FS_TRANSFER
-+ * - %LANDLOCK_ACCESS_FS_RECEIVE
-+ * - %LANDLOCK_ACCESS_FS_CHDIR
-+ * - %LANDLOCK_ACCESS_FS_FCNTL
-+ */
-+
-+#endif /* _UAPI__LINUX_LANDLOCK_H__ */
 diff --git a/security/landlock/Makefile b/security/landlock/Makefile
-index cb6deefbf4c0..d846eba445bb 100644
+index 682b798c6b76..dd5f70185778 100644
 --- a/security/landlock/Makefile
 +++ b/security/landlock/Makefile
-@@ -1,3 +1,3 @@
+@@ -1,4 +1,5 @@
  obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
  
--landlock-y := object.o
-+landlock-y := object.o ruleset.o
-diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
+ landlock-y := \
+-	bpf_verify.o bpf_ptrace.o
++	bpf_verify.o bpf_ptrace.o \
++	domain_manage.o
+diff --git a/security/landlock/common.h b/security/landlock/common.h
+index 0234c4bc4acd..fb2990eb5fb4 100644
+--- a/security/landlock/common.h
++++ b/security/landlock/common.h
+@@ -11,11 +11,49 @@
+ 
+ #include <linux/bpf.h>
+ #include <linux/filter.h>
++#include <linux/refcount.h>
+ 
+ enum landlock_hook_type {
+ 	LANDLOCK_HOOK_PTRACE = 1,
+ };
+ 
++#define _LANDLOCK_HOOK_LAST LANDLOCK_HOOK_PTRACE
++
++struct landlock_prog_list {
++	struct landlock_prog_list *prev;
++	struct bpf_prog *prog;
++	refcount_t usage;
++};
++
++/**
++ * struct landlock_domain - Landlock programs enforced on a set of tasks
++ *
++ * When prepending a new program, if &struct landlock_domain is shared with
++ * other tasks, then duplicate it and prepend the program to this new &struct
++ * landlock_domain.
++ *
++ * @usage: reference count to manage the object lifetime. When a task needs to
++ *	   add Landlock programs and if @usage is greater than 1, then the
++ *	   task must duplicate &struct landlock_domain to not change the
++ *	   children's programs as well.
++ * @programs: array of non-NULL &struct landlock_prog_list pointers
++ */
++struct landlock_domain {
++	struct landlock_prog_list *programs[_LANDLOCK_HOOK_LAST];
++	refcount_t usage;
++};
++
++/**
++ * get_hook_index - get an index for the programs of struct landlock_prog_set
++ *
++ * @type: a Landlock hook type
++ */
++static inline size_t get_hook_index(enum landlock_hook_type type)
++{
++	/* type ID > 0 for loaded programs */
++	return type - 1;
++}
++
+ static inline enum landlock_hook_type get_hook_type(const struct bpf_prog *prog)
+ {
+ 	switch (prog->expected_attach_type) {
+diff --git a/security/landlock/domain_manage.c b/security/landlock/domain_manage.c
 new file mode 100644
-index 000000000000..5ec013a4188d
+index 000000000000..c955b9c95c84
 --- /dev/null
-+++ b/security/landlock/ruleset.c
-@@ -0,0 +1,460 @@
++++ b/security/landlock/domain_manage.c
+@@ -0,0 +1,265 @@
 +// SPDX-License-Identifier: GPL-2.0-only
 +/*
-+ * Landlock LSM - Ruleset management
-+ *
-+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
-+ * Copyright © 2018-2020 ANSSI
-+ */
-+
-+#include <linux/bug.h>
++ * Landlock LSM - domain management
++ *
++ * Copyright © 2016-2019 Mickaël Salaün <mic@digikod.net>
++ * Copyright © 2018-2019 ANSSI
++ */
++
 +#include <linux/err.h>
 +#include <linux/errno.h>
-+#include <linux/kernel.h>
-+#include <linux/list.h>
-+#include <linux/rbtree.h>
-+#include <linux/rcupdate.h>
 +#include <linux/refcount.h>
 +#include <linux/slab.h>
 +#include <linux/spinlock.h>
-+#include <linux/workqueue.h>
-+
-+#include "object.h"
-+#include "ruleset.h"
-+
-+static struct landlock_ruleset *create_ruleset(void)
-+{
-+	struct landlock_ruleset *ruleset;
-+
-+	ruleset = kzalloc(sizeof(*ruleset), GFP_KERNEL);
-+	if (!ruleset)
++
++#include "common.h"
++#include "domain_manage.h"
++
++void landlock_get_domain(struct landlock_domain *dom)
++{
++	if (!dom)
++		return;
++	refcount_inc(&dom->usage);
++}
++
++static void put_landlock_prog_list(struct landlock_prog_list *prog_list)
++{
++	struct landlock_prog_list *orig = prog_list;
++
++	/* clean up single-reference branches iteratively */
++	while (orig && refcount_dec_and_test(&orig->usage)) {
++		struct landlock_prog_list *freeme = orig;
++
++		if (orig->prog)
++			bpf_prog_put(orig->prog);
++		orig = orig->prev;
++		kfree(freeme);
++	}
++}
++
++void landlock_put_domain(struct landlock_domain *domain)
++{
++	if (domain && refcount_dec_and_test(&domain->usage)) {
++		size_t i;
++
++		for (i = 0; i < ARRAY_SIZE(domain->programs); i++)
++			put_landlock_prog_list(domain->programs[i]);
++		kfree(domain);
++	}
++}
++
++static struct landlock_domain *new_landlock_domain(void)
++{
++	struct landlock_domain *domain;
++
++	/* array filled with NULL values */
++	domain = kzalloc(sizeof(*domain), GFP_KERNEL);
++	if (!domain)
 +		return ERR_PTR(-ENOMEM);
-+	refcount_set(&ruleset->usage, 1);
-+	mutex_init(&ruleset->lock);
-+	atomic_set(&ruleset->nb_rules, 0);
-+	ruleset->root = RB_ROOT;
-+	return ruleset;
-+}
-+
-+struct landlock_ruleset *landlock_create_ruleset(u64 fs_access_mask)
-+{
-+	struct landlock_ruleset *ruleset;
-+
-+	/* Safely handles 32-bits conversion. */
-+	BUILD_BUG_ON(!__same_type(fs_access_mask, _LANDLOCK_ACCESS_FS_LAST));
-+
-+	/* Checks content. */
-+	if ((fs_access_mask | _LANDLOCK_ACCESS_FS_MASK) !=
-+			_LANDLOCK_ACCESS_FS_MASK)
++	refcount_set(&domain->usage, 1);
++	return domain;
++}
++
++/**
++ * store_landlock_prog - prepend and deduplicate a Landlock prog_list
++ *
++ * Prepend @prog to @init_domain while ignoring @prog if they are already in
++ * @ref_domain.  Whatever is the result of this function call, you can call
++ * bpf_prog_put(@prog) after.
++ *
++ * @init_domain: empty domain to prepend to
++ * @ref_domain: domain to check for duplicate programs
++ * @prog: program to prepend
++ *
++ * Return -errno on error or 0 if @prog was successfully stored.
++ */
++static int store_landlock_prog(struct landlock_domain *init_domain,
++		const struct landlock_domain *ref_domain,
++		struct bpf_prog *prog)
++{
++	struct landlock_prog_list *tmp_list = NULL;
++	int err;
++	size_t hook;
++	enum landlock_hook_type last_type;
++	struct bpf_prog *new = prog;
++
++	/* allocate all the memory we need */
++	struct landlock_prog_list *new_list;
++
++	last_type = get_hook_type(new);
++
++	/* ignore duplicate programs */
++	if (ref_domain) {
++		struct landlock_prog_list *ref;
++
++		hook = get_hook_index(get_hook_type(new));
++		for (ref = ref_domain->programs[hook]; ref;
++				ref = ref->prev) {
++			if (ref->prog == new)
++				return -EINVAL;
++		}
++	}
++
++	new = bpf_prog_inc(new);
++	if (IS_ERR(new)) {
++		err = PTR_ERR(new);
++		goto put_tmp_list;
++	}
++	new_list = kzalloc(sizeof(*new_list), GFP_KERNEL);
++	if (!new_list) {
++		bpf_prog_put(new);
++		err = -ENOMEM;
++		goto put_tmp_list;
++	}
++	/* ignore Landlock types in this tmp_list */
++	new_list->prog = new;
++	new_list->prev = tmp_list;
++	refcount_set(&new_list->usage, 1);
++	tmp_list = new_list;
++
++	if (!tmp_list)
++		/* inform user space that this program was already added */
++		return -EEXIST;
++
++	/* properly store the list (without error cases) */
++	while (tmp_list) {
++		struct landlock_prog_list *new_list;
++
++		new_list = tmp_list;
++		tmp_list = tmp_list->prev;
++		/* do not increment the previous prog list usage */
++		hook = get_hook_index(get_hook_type(new_list->prog));
++		new_list->prev = init_domain->programs[hook];
++		/* no need to add from the last program to the first because
++		 * each of them are a different Landlock type */
++		smp_store_release(&init_domain->programs[hook], new_list);
++	}
++	return 0;
++
++put_tmp_list:
++	put_landlock_prog_list(tmp_list);
++	return err;
++}
++
++/* limit Landlock programs set to 256KB */
++#define LANDLOCK_PROGRAMS_MAX_PAGES (1 << 6)
++
++/**
++ * landlock_prepend_prog - attach a Landlock prog_list to @current_domain
++ *
++ * Whatever is the result of this function call, you can call
++ * bpf_prog_put(@prog) after.
++ *
++ * @current_domain: landlock_domain pointer, must be (RCU-)locked (if needed)
++ *                  to prevent a concurrent put/free. This pointer must not be
++ *                  freed after the call.
++ * @prog: non-NULL Landlock prog_list to prepend to @current_domain. @prog will
++ *        be owned by landlock_prepend_prog() and freed if an error happened.
++ *
++ * Return @current_domain or a new pointer when OK. Return a pointer error
++ * otherwise.
++ */
++struct landlock_domain *landlock_prepend_prog(
++		struct landlock_domain *current_domain,
++		struct bpf_prog *prog)
++{
++	struct landlock_domain *new_domain = current_domain;
++	unsigned long pages;
++	int err;
++	size_t i;
++	struct landlock_domain tmp_domain = {};
++
++	if (prog->type != BPF_PROG_TYPE_LANDLOCK_HOOK)
 +		return ERR_PTR(-EINVAL);
-+	/* Informs about useless ruleset. */
-+	if (!fs_access_mask)
-+		return ERR_PTR(-ENOMSG);
-+	ruleset = create_ruleset();
-+	if (!IS_ERR(ruleset))
-+		ruleset->fs_access_mask = fs_access_mask;
-+	return ruleset;
-+}
-+
-+/*
-+ * The underlying kernel object must be held by the caller.
-+ */
-+static struct landlock_ruleset_elem *create_ruleset_elem(
-+		struct landlock_object *object)
-+{
-+	struct landlock_ruleset_elem *ruleset_elem;
-+
-+	ruleset_elem = kzalloc(sizeof(*ruleset_elem), GFP_KERNEL);
-+	if (!ruleset_elem)
-+		return ERR_PTR(-ENOMEM);
-+	RB_CLEAR_NODE(&ruleset_elem->node);
-+	RCU_INIT_POINTER(ruleset_elem->ref.object, object);
-+	return ruleset_elem;
-+}
-+
-+static struct landlock_rule *create_rule(struct landlock_object *object,
-+		struct landlock_access *access)
-+{
-+	struct landlock_rule *new_rule;
-+
-+	if (WARN_ON_ONCE(!object))
-+		return ERR_PTR(-EFAULT);
-+	if (WARN_ON_ONCE(!access))
-+		return ERR_PTR(-EFAULT);
-+	new_rule = kzalloc(sizeof(*new_rule), GFP_KERNEL);
-+	if (!new_rule)
-+		return ERR_PTR(-ENOMEM);
-+	refcount_set(&new_rule->usage, 1);
-+	INIT_LIST_HEAD(&new_rule->list);
-+	new_rule->access = *access;
-+
-+	spin_lock(&object->lock);
-+	list_add_tail(&new_rule->list, &object->rules);
-+	spin_unlock(&object->lock);
-+	return new_rule;
-+}
-+
-+/*
-+ * An inserted rule can not be removed, only disabled (cf. struct
-+ * landlock_ruleset_elem).
-+ *
-+ * The underlying kernel object must be held by the caller.
-+ *
-+ * @rule: Allocated struct owned by this function. The caller must hold the
-+ * underlying kernel object (e.g., with a FD).
-+ */
-+int landlock_insert_ruleset_rule(struct landlock_ruleset *ruleset,
-+		struct landlock_object *object, struct landlock_access *access,
-+		struct landlock_rule *rule)
-+{
-+	struct rb_node **new;
-+	struct rb_node *parent = NULL;
-+	struct landlock_ruleset_elem *ruleset_elem;
-+	struct landlock_rule *new_rule;
-+
-+	might_sleep();
-+	/* Accesses may be set when creating a new rule. */
-+	if (rule) {
-+		if (WARN_ON_ONCE(access))
-+			return -EINVAL;
-+	} else {
-+		if (WARN_ON_ONCE(!access))
-+			return -EFAULT;
-+	}
-+
-+	lockdep_assert_held(&ruleset->lock);
-+	new = &(ruleset->root.rb_node);
-+	while (*new) {
-+		struct landlock_ruleset_elem *this = rb_entry(*new,
-+				struct landlock_ruleset_elem, node);
-+		uintptr_t this_object;
-+		struct landlock_rule *this_rule;
-+		struct landlock_access new_access;
-+
-+		this_object = (uintptr_t)rcu_access_pointer(this->ref.object);
-+		if (this_object != (uintptr_t)object) {
-+			parent = *new;
-+			if (this_object < (uintptr_t)object)
-+				new = &((*new)->rb_right);
-+			else
-+				new = &((*new)->rb_left);
-+			continue;
++
++	/* validate memory size allocation */
++	pages = prog->pages;
++	if (current_domain) {
++		size_t i;
++
++		for (i = 0; i < ARRAY_SIZE(current_domain->programs); i++) {
++			struct landlock_prog_list *walker_p;
++
++			for (walker_p = current_domain->programs[i];
++					walker_p; walker_p = walker_p->prev)
++				pages += walker_p->prog->pages;
 +		}
-+
-+		/* Do not increment ruleset->nb_rules. */
-+		this_rule = rcu_dereference_protected(this->ref.rule,
-+				lockdep_is_held(&ruleset->lock));
++		/* count a struct landlock_domain if we need to allocate one */
++		if (refcount_read(&current_domain->usage) != 1)
++			pages += round_up(sizeof(*current_domain), PAGE_SIZE)
++				/ PAGE_SIZE;
++	}
++	if (pages > LANDLOCK_PROGRAMS_MAX_PAGES)
++		return ERR_PTR(-E2BIG);
++
++	/* ensure early that we can allocate enough memory for the new
++	 * prog_lists */
++	err = store_landlock_prog(&tmp_domain, current_domain, prog);
++	if (err)
++		return ERR_PTR(err);
++
++	/*
++	 * Each task_struct points to an array of prog list pointers.  These
++	 * tables are duplicated when additions are made (which means each
++	 * table needs to be refcounted for the processes using it). When a new
++	 * table is created, all the refcounters on the prog_list are bumped
++	 * (to track each table that references the prog). When a new prog is
++	 * added, it's just prepended to the list for the new table to point
++	 * at.
++	 *
++	 * Manage all the possible errors before this step to not uselessly
++	 * duplicate current_domain and avoid a rollback.
++	 */
++	if (!new_domain) {
 +		/*
-+		 * Checks if it is a new object with the same address as a
-+		 * previously disabled one.  There is no possible race
-+		 * condition because an object can not be disabled/deleted
-+		 * while being inserted in this tree.
++		 * If there is no Landlock domain used by the current task,
++		 * then create a new one.
 +		 */
-+		if (landlock_rule_is_disabled(this_rule)) {
-+			if (rule) {
-+				refcount_inc(&rule->usage);
-+				new_rule = rule;
-+			} else {
-+				/* Replace the previous rule with a new one. */
-+				new_rule = create_rule(object, access);
-+				if (IS_ERR(new_rule))
-+					return PTR_ERR(new_rule);
-+			}
-+			rcu_assign_pointer(this->ref.rule, new_rule);
-+			landlock_put_rule(object, this_rule);
-+			return 0;
++		new_domain = new_landlock_domain();
++		if (IS_ERR(new_domain))
++			goto put_tmp_lists;
++	} else if (refcount_read(&current_domain->usage) > 1) {
++		/*
++		 * If the current task is not the sole user of its Landlock
++		 * domain, then duplicate it.
++		 */
++		new_domain = new_landlock_domain();
++		if (IS_ERR(new_domain))
++			goto put_tmp_lists;
++		for (i = 0; i < ARRAY_SIZE(new_domain->programs); i++) {
++			new_domain->programs[i] =
++				READ_ONCE(current_domain->programs[i]);
++			if (new_domain->programs[i])
++				refcount_inc(&new_domain->programs[i]->usage);
 +		}
 +
-+		/* this_rule is potentially enabled. */
-+		if (refcount_read(&this_rule->usage) == 1) {
-+			if (rule) {
-+				/* merge rule: intersection of access rights */
-+				this_rule->access.self &= rule->access.self;
-+				this_rule->access.beneath &=
-+					rule->access.beneath;
-+			} else {
-+				/* extend rule: union of access rights */
-+				this_rule->access.self |= access->self;
-+				this_rule->access.beneath |= access->beneath;
-+			}
-+			return 0;
++		/*
++		 * Landlock domain from the current task will not be freed here
++		 * because the usage is strictly greater than 1. It is only
++		 * prevented to be freed by another task thanks to the caller
++		 * of landlock_prepend_prog() which should be locked if needed.
++		 */
++		landlock_put_domain(current_domain);
++	}
++
++	/* prepend tmp_domain to new_domain */
++	for (i = 0; i < ARRAY_SIZE(tmp_domain.programs); i++) {
++		/* get the last new list */
++		struct landlock_prog_list *last_list =
++			tmp_domain.programs[i];
++
++		if (last_list) {
++			while (last_list->prev)
++				last_list = last_list->prev;
++			/* no need to increment usage (pointer replacement) */
++			last_list->prev = new_domain->programs[i];
++			new_domain->programs[i] = tmp_domain.programs[i];
 +		}
-+
-+		/*
-+		 * If this_rule is shared with another ruleset, then create a
-+		 * new object rule.
-+		 */
-+		if (rule) {
-+			/* Merging a rule means an intersection of access. */
-+			new_access.self = this_rule->access.self &
-+				rule->access.self;
-+			new_access.beneath = this_rule->access.beneath &
-+				rule->access.beneath;
-+		} else {
-+			/* Extending a rule means a union of access. */
-+			new_access.self = this_rule->access.self |
-+				access->self;
-+			new_access.beneath = this_rule->access.self |
-+				access->beneath;
-+		}
-+		new_rule = create_rule(object, &new_access);
-+		if (IS_ERR(new_rule))
-+			return PTR_ERR(new_rule);
-+		rcu_assign_pointer(this->ref.rule, new_rule);
-+		landlock_put_rule(object, this_rule);
-+		return 0;
-+	}
-+
-+	/* There is no match for @object. */
-+	ruleset_elem = create_ruleset_elem(object);
-+	if (IS_ERR(ruleset_elem))
-+		return PTR_ERR(ruleset_elem);
-+	if (rule) {
-+		refcount_inc(&rule->usage);
-+		new_rule = rule;
-+	} else {
-+		new_rule = create_rule(object, access);
-+		if (IS_ERR(new_rule)) {
-+			kfree(ruleset_elem);
-+			return PTR_ERR(new_rule);
-+		}
-+	}
-+	RCU_INIT_POINTER(ruleset_elem->ref.rule, new_rule);
-+	/*
-+	 * Because of the missing RCU context annotation in struct rb_node,
-+	 * Sparse emits a warning when encountering rb_link_node_rcu(), but
-+	 * this function call is still safe.
-+	 */
-+	rb_link_node_rcu(&ruleset_elem->node, parent, new);
-+	rb_insert_color(&ruleset_elem->node, &ruleset->root);
-+	atomic_inc(&ruleset->nb_rules);
-+	return 0;
-+}
-+
-+static int merge_ruleset(struct landlock_ruleset *dst,
-+		struct landlock_ruleset *src)
-+{
-+	struct rb_node *node;
-+	int err = 0;
-+
-+	might_sleep();
-+	if (!src)
-+		return 0;
-+	if (WARN_ON_ONCE(!dst))
-+		return -EFAULT;
-+	if (WARN_ON_ONCE(!dst->hierarchy))
-+		return -EINVAL;
-+
-+	mutex_lock(&dst->lock);
-+	mutex_lock_nested(&src->lock, 1);
-+	dst->fs_access_mask |= src->fs_access_mask;
-+	for (node = rb_first(&src->root); node; node = rb_next(node)) {
-+		struct landlock_ruleset_elem *elem = rb_entry(node,
-+				struct landlock_ruleset_elem, node);
-+		struct landlock_object *object =
-+			rcu_dereference_protected(elem->ref.object,
-+					lockdep_is_held(&src->lock));
-+		struct landlock_rule *rule =
-+			rcu_dereference_protected(elem->ref.rule,
-+					lockdep_is_held(&src->lock));
-+
-+		err = landlock_insert_ruleset_rule(dst, object, NULL, rule);
-+		if (err)
-+			goto out_unlock;
-+	}
-+
-+out_unlock:
-+	mutex_unlock(&src->lock);
-+	mutex_unlock(&dst->lock);
-+	return err;
-+}
-+
-+void landlock_get_ruleset(struct landlock_ruleset *ruleset)
-+{
-+	if (!ruleset)
-+		return;
-+	refcount_inc(&ruleset->usage);
-+}
-+
-+static void put_hierarchy(struct landlock_hierarchy *hierarchy)
-+{
-+	if (hierarchy && refcount_dec_and_test(&hierarchy->usage))
-+		kfree(hierarchy);
-+}
-+
-+static void put_ruleset(struct landlock_ruleset *ruleset)
-+{
-+	struct rb_node *orig;
-+
-+	might_sleep();
-+	for (orig = rb_first(&ruleset->root); orig; orig = rb_next(orig)) {
-+		struct landlock_ruleset_elem *freeme;
-+		struct landlock_object *object;
-+		struct landlock_rule *rule;
-+
-+		freeme = rb_entry(orig, struct landlock_ruleset_elem, node);
-+		object = rcu_dereference_protected(freeme->ref.object,
-+				refcount_read(&ruleset->usage) == 0);
-+		rule = rcu_dereference_protected(freeme->ref.rule,
-+				refcount_read(&ruleset->usage) == 0);
-+		landlock_put_rule(object, rule);
-+		kfree_rcu(freeme, rcu_free);
-+	}
-+	put_hierarchy(ruleset->hierarchy);
-+	kfree_rcu(ruleset, rcu_free);
-+}
-+
-+void landlock_put_ruleset(struct landlock_ruleset *ruleset)
-+{
-+	might_sleep();
-+	if (ruleset && refcount_dec_and_test(&ruleset->usage))
-+		put_ruleset(ruleset);
-+}
-+
-+static void put_ruleset_work(struct work_struct *work)
-+{
-+	struct landlock_ruleset *ruleset;
-+
-+	ruleset = container_of(work, struct landlock_ruleset, work_put);
-+	/*
-+	 * Clean up rcu_free because of previous use through union work_put.
-+	 * ruleset->rcu_free.func is already NULLed by __rcu_reclaim().
-+	 */
-+	ruleset->rcu_free.next = NULL;
-+	put_ruleset(ruleset);
-+}
-+
-+void landlock_put_ruleset_enqueue(struct landlock_ruleset *ruleset)
-+{
-+	if (ruleset && refcount_dec_and_test(&ruleset->usage)) {
-+		INIT_WORK(&ruleset->work_put, put_ruleset_work);
-+		schedule_work(&ruleset->work_put);
-+	}
-+}
-+
-+static bool clean_ref(struct landlock_ref *ref)
-+{
-+	struct landlock_rule *rule;
-+
-+	rule = rcu_dereference(ref->rule);
-+	if (!rule)
-+		return false;
-+	if (!landlock_rule_is_disabled(rule))
-+		return false;
-+	rcu_assign_pointer(ref->rule, NULL);
-+	/*
-+	 * landlock_put_rule() will not sleep because we already checked
-+	 * !landlock_rule_is_disabled(rule).
-+	 */
-+	landlock_put_rule(rcu_dereference(ref->object), rule);
-+	return true;
-+}
-+
-+static void clean_ruleset(struct landlock_ruleset *ruleset)
-+{
-+	struct rb_node *node;
-+
-+	if (!ruleset)
-+		return;
-+	/* We must lock the ruleset to not have a wrong nb_rules counter. */
-+	mutex_lock(&ruleset->lock);
-+	rcu_read_lock();
-+	for (node = rb_first(&ruleset->root); node; node = rb_next(node)) {
-+		struct landlock_ruleset_elem *elem = rb_entry(node,
-+				struct landlock_ruleset_elem, node);
-+
-+		if (clean_ref(&elem->ref)) {
-+			rb_erase(&elem->node, &ruleset->root);
-+			kfree_rcu(elem, rcu_free);
-+			atomic_dec(&ruleset->nb_rules);
-+		}
-+	}
-+	rcu_read_unlock();
-+	mutex_unlock(&ruleset->lock);
-+}
-+
-+/*
-+ * Creates a new ruleset, merged of @parent and @ruleset, or return @parent if
-+ * @ruleset is empty.  If @parent is empty, return a duplicate of @ruleset.
-+ *
-+ * @parent: Must not be modified (i.e. locked or read-only).
-+ */
-+struct landlock_ruleset *landlock_merge_ruleset(
-+		struct landlock_ruleset *parent,
-+		struct landlock_ruleset *ruleset)
-+{
-+	struct landlock_ruleset *new_dom;
-+	int err;
-+
-+	might_sleep();
-+	/* Opportunistically put disabled rules. */
-+	clean_ruleset(ruleset);
-+
-+	if (parent && WARN_ON_ONCE(!parent->hierarchy))
-+		return ERR_PTR(-EINVAL);
-+	if (!ruleset || atomic_read(&ruleset->nb_rules) == 0 ||
-+			parent == ruleset) {
-+		landlock_get_ruleset(parent);
-+		return parent;
-+	}
-+
-+	new_dom = create_ruleset();
-+	if (IS_ERR(new_dom))
-+		return new_dom;
-+	new_dom->hierarchy = kzalloc(sizeof(*new_dom->hierarchy), GFP_KERNEL);
-+	if (!new_dom->hierarchy) {
-+		landlock_put_ruleset(new_dom);
-+		return ERR_PTR(-ENOMEM);
-+	}
-+	refcount_set(&new_dom->hierarchy->usage, 1);
-+
-+	if (parent) {
-+		new_dom->hierarchy->parent = parent->hierarchy;
-+		refcount_inc(&parent->hierarchy->usage);
-+		err = merge_ruleset(new_dom, parent);
-+		if (err) {
-+			landlock_put_ruleset(new_dom);
-+			return ERR_PTR(err);
-+		}
-+	}
-+	err = merge_ruleset(new_dom, ruleset);
-+	if (err) {
-+		landlock_put_ruleset(new_dom);
-+		return ERR_PTR(err);
-+	}
-+	return new_dom;
-+}
-+
-+/*
-+ * The return pointer must only be used in a RCU-read block.
-+ */
-+const struct landlock_access *landlock_find_access(
-+		const struct landlock_ruleset *ruleset,
-+		const struct landlock_object *object)
-+{
-+	struct rb_node *node;
-+
-+	WARN_ON_ONCE(!rcu_read_lock_held());
-+	if (!object)
-+		return NULL;
-+	node = ruleset->root.rb_node;
-+	while (node) {
-+		struct landlock_ruleset_elem *this = rb_entry(node,
-+				struct landlock_ruleset_elem, node);
-+		uintptr_t this_object =
-+			(uintptr_t)rcu_access_pointer(this->ref.object);
-+
-+		if (this_object == (uintptr_t)object) {
-+			struct landlock_rule *rule;
-+
-+			rule = rcu_dereference(this->ref.rule);
-+			if (!landlock_rule_is_disabled(rule))
-+				return &rule->access;
-+			return NULL;
-+		}
-+		if (this_object < (uintptr_t)object)
-+			node = node->rb_right;
-+		else
-+			node = node->rb_left;
-+	}
-+	return NULL;
-+}
-diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
++	}
++	return new_domain;
++
++put_tmp_lists:
++	for (i = 0; i < ARRAY_SIZE(tmp_domain.programs); i++)
++		put_landlock_prog_list(tmp_domain.programs[i]);
++	return new_domain;
++}
+diff --git a/security/landlock/domain_manage.h b/security/landlock/domain_manage.h
 new file mode 100644
-index 000000000000..afc88dbb8b4b
+index 000000000000..5b5b49f6e3e8
 --- /dev/null
-+++ b/security/landlock/ruleset.h
-@@ -0,0 +1,106 @@
++++ b/security/landlock/domain_manage.h
+@@ -0,0 +1,23 @@
 +/* SPDX-License-Identifier: GPL-2.0-only */
 +/*
-+ * Landlock LSM - Ruleset management
-+ *
-+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
-+ * Copyright © 2018-2020 ANSSI
-+ */
-+
-+#ifndef _SECURITY_LANDLOCK_RULESET_H
-+#define _SECURITY_LANDLOCK_RULESET_H
-+
-+#include <linux/compiler.h>
-+#include <linux/mutex.h>
-+#include <linux/poison.h>
-+#include <linux/rbtree.h>
-+#include <linux/rcupdate.h>
-+#include <linux/refcount.h>
-+#include <linux/types.h>
-+#include <linux/workqueue.h>
-+#include <uapi/linux/landlock.h>
-+
-+#include "object.h"
-+
-+#define _LANDLOCK_ACCESS_FS_LAST	LANDLOCK_ACCESS_FS_MAP
-+#define _LANDLOCK_ACCESS_FS_MASK	((_LANDLOCK_ACCESS_FS_LAST << 1) - 1)
-+
-+struct landlock_ref {
-+	/*
-+	 * @object: Identify a kernel object (e.g. an inode).  This is used as
-+	 * a key for a ruleset tree (cf. struct landlock_ruleset_elem).  This
-+	 * pointer is set once and never modified.  It may point to a deleted
-+	 * object and should then be dereferenced with great care, thanks to a
-+	 * call to landlock_rule_is_disabled(@rule) from inside an RCU-read
-+	 * block, cf. landlock_put_rule().
-+	 */
-+	struct landlock_object __rcu *object;
-+	/*
-+	 * @rule: Ties a rule to an object. Set once with an allocated rule,
-+	 * but can be NULLed if the rule is disabled.
-+	 */
-+	struct landlock_rule __rcu *rule;
-+};
-+
-+/*
-+ * Red-black tree element used in a landlock_ruleset.
-+ */
-+struct landlock_ruleset_elem {
-+	struct landlock_ref ref;
-+	struct rb_node node;
-+	struct rcu_head rcu_free;
-+};
-+
-+/*
-+ * Enable hierarchy identification even when a parent domain vanishes.  This is
-+ * needed for the ptrace protection.
-+ */
-+struct landlock_hierarchy {
-+	struct landlock_hierarchy *parent;
-+	refcount_t usage;
-+};
-+
-+/*
-+ * Kernel representation of a ruleset.  This data structure must contains
-+ * unique entries, be updatable, and quick to match an object.
-+ */
-+struct landlock_ruleset {
-+	/*
-+	 * @fs_access_mask: Contains the subset of filesystem actions which are
-+	 * restricted by a ruleset.  This is used when merging rulesets and for
-+	 * userspace backward compatibility (i.e. future-proof).  Set once and
-+	 * never changed for the lifetime of the ruleset.
-+	 */
-+	u32 fs_access_mask;
-+	struct landlock_hierarchy *hierarchy;
-+	refcount_t usage;
-+	union {
-+		struct rcu_head	rcu_free;
-+		struct work_struct work_put;
-+	};
-+	struct mutex lock;
-+	atomic_t nb_rules;
-+	/*
-+	 * @root: Red-black tree containing landlock_ruleset_elem nodes.
-+	 */
-+	struct rb_root root;
-+};
-+
-+struct landlock_ruleset *landlock_create_ruleset(u64 fs_access_mask);
-+
-+void landlock_get_ruleset(struct landlock_ruleset *ruleset);
-+void landlock_put_ruleset(struct landlock_ruleset *ruleset);
-+void landlock_put_ruleset_enqueue(struct landlock_ruleset *ruleset);
-+
-+int landlock_insert_ruleset_rule(struct landlock_ruleset *ruleset,
-+		struct landlock_object *object, struct landlock_access *access,
-+		struct landlock_rule *rule);
-+
-+struct landlock_ruleset *landlock_merge_ruleset(
-+		struct landlock_ruleset *domain,
-+		struct landlock_ruleset *ruleset);
-+
-+const struct landlock_access *landlock_find_access(
-+		const struct landlock_ruleset *ruleset,
-+		const struct landlock_object *object);
-+
-+#endif /* _SECURITY_LANDLOCK_RULESET_H */
++ * Landlock LSM - domain management headers
++ *
++ * Copyright © 2016-2019 Mickaël Salaün <mic@digikod.net>
++ * Copyright © 2018-2019 ANSSI
++ */
++
++#ifndef _SECURITY_LANDLOCK_DOMAIN_MANAGE_H
++#define _SECURITY_LANDLOCK_DOMAIN_MANAGE_H
++
++#include <linux/filter.h>
++
++#include "common.h"
++
++void landlock_get_domain(struct landlock_domain *dom);
++void landlock_put_domain(struct landlock_domain *dom);
++
++struct landlock_domain *landlock_prepend_prog(
++		struct landlock_domain *current_domain,
++		struct bpf_prog *prog);
++
++#endif /* _SECURITY_LANDLOCK_DOMAIN_MANAGE_H */
 -- 
-2.25.0
+2.23.0
 
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help