Inter-revision diff: patch 2

Comparing v11 (message) to v30 (message)

--- v11
+++ v30
@@ -1,402 +1,852 @@
-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.
-
-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>
+From: Mickaël Salaün <mic@linux.microsoft.com>
+
+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 implementing syscalls) which enables a
+process to create and populate a ruleset with new rules.
+
+A domain is a ruleset tied to a set of processes.  This group of rules
+defines the security policy enforced on these processes and their future
+children.  A domain can transition to a new domain which is the
+intersection of all its constraints and those of a ruleset provided by
+the current process.  This modification only impact the current process.
+This means that a process can only gain more constraints (i.e. lose
+accesses) over time.
+
 Cc: James Morris <jmorris@namei.org>
+Cc: Jann Horn <jannh@google.com>
 Cc: Kees Cook <keescook@chromium.org>
-Cc: Serge E. Hallyn <serge@hallyn.com>
-Cc: Will Drewry <wad@chromium.org>
+Signed-off-by: Mickaël Salaün <mic@linux.microsoft.com>
+Acked-by: Serge Hallyn <serge@hallyn.com>
+Link: https://lore.kernel.org/r/20210316204252.427806-3-mic@digikod.net
 ---
 
-Changes since v10:
-* rename files and names to clearly define a domain
-* create a standalone patch to ease review
+Changes since v28:
+* Add Acked-by Serge Hallyn.
+* Clean up comment.
+
+Changes since v27:
+* Fix domains with layers of non-overlapping access rights.
+* Add stricter limit checks (same semantic).
+* Change the grow direction of a rule layer stack to make it the same as
+  the new ruleset fs_access_masks stack (cosmetic change).
+* Cosmetic fix for a comment block.
+
+Changes since v26:
+* Fix spelling.
+
+Changes since v25:
+* Add build-time checks for the num_layers and num_rules variables
+  according to LANDLOCK_MAX_NUM_LAYERS and LANDLOCK_MAX_NUM_RULES, and
+  move these limits to a dedicated file.
+* Cosmetic variable renames.
+
+Changes since v24:
+* Update struct landlock_rule with a layer stack.  This reverts "Always
+  intersect access rights" from v24 and also adds the ability to tie
+  access rights with their policy layer.  As noted by Jann Horn, always
+  intersecting access rights made some use cases uselessly more
+  difficult to handle in user space.  Thanks to this new stack, we still
+  have a deterministic policy behavior whatever their level in the stack
+  of policies, while using a "union" of accesses when building a
+  ruleset.  The implementation use a FAM to keep the access checks quick
+  and memory efficient (4 bytes per layer per inode).  Update
+  insert_rule() accordingly.
+
+Changes since v23:
+* Always intersect access rights.  Following the filesystem change
+  logic, make ruleset updates more consistent by always intersecting
+  access rights (boolean AND) instead of combining them (boolean OR) for
+  the same layer.  This defensive approach could also help avoid user
+  space to inadvertently allow multiple access rights for the same
+  object (e.g.  write and execute access on a path hierarchy) instead of
+  dealing with such inconsistency.  This can happen when there is no
+  deduplication of objects (e.g. paths and underlying inodes) whereas
+  they get different access rights with landlock_add_rule(2).
+* Add extra checks to make sure that:
+  - there is always an (allocated) object in each used rules;
+  - when updating a ruleset with a new rule (i.e. not merging two
+    rulesets), the ruleset doesn't contain multiple layers.
+* Hide merge parameter from the public landlock_insert_rule() API.  This
+  helps avoid misuse of this function.
+* Replace a remaining hardcoded 1 with SINGLE_DEPTH_NESTING.
+
+Changes since v22:
+* Explicitely use RB_ROOT and SINGLE_DEPTH_NESTING (suggested by Jann
+  Horn).
+* Improve comments and fix spelling (suggested by Jann Horn).
+
+Changes since v21:
+* Add and clean up comments.
+
+Changes since v18:
+* Account rulesets to kmemcg.
+* Remove struct holes.
+* Cosmetic changes.
+
+Changes since v17:
+* Move include/uapi/linux/landlock.h and _LANDLOCK_ACCESS_FS_* to a
+  following patch.
+
+Changes since v16:
+* Allow enforcement of empty ruleset, which enables deny-all policies.
+
+Changes since v15:
+* Replace layer_levels and layer_depth with a bitfield of layers, cf.
+  filesystem commit.
+* Rename the LANDLOCK_ACCESS_FS_{UNLINK,RMDIR} with
+  LANDLOCK_ACCESS_FS_REMOVE_{FILE,DIR} because it makes sense to use
+  them for the action of renaming a file or a directory, which may lead
+  to the removal of the source file or directory.  Removes the
+  LANDLOCK_ACCESS_FS_{LINK_TO,RENAME_FROM,RENAME_TO} which are now
+  replaced with LANDLOCK_ACCESS_FS_REMOVE_{FILE,DIR} and
+  LANDLOCK_ACCESS_FS_MAKE_* .
+* Update the documentation accordingly and highlight how the access
+  rights are taken into account.
+* Change nb_rules from atomic_t to u32 because it is not use anymore by
+  show_fdinfo().
+* Add safeguard for level variables types.
+* Check max number of rules.
+* Replace struct landlock_access (self and beneath bitfields) with one
+  bitfield.
+* Remove useless variable.
+* Add comments.
+
+Changes since v14:
+* Simplify the object, rule and ruleset management at the expense of a
+  less aggressive memory freeing (contributed by Jann Horn, with
+  additional modifications):
+  - Make a domain immutable (remove the opportunistic cleaning).
+  - Remove RCU pointers.
+  - Merge struct landlock_ref and struct landlock_ruleset_elem into
+    landlock_rule: get ride of rule's RCU.
+  - Adjust union.
+  - Remove the landlock_insert_rule() check about a new object with the
+    same address as a previously disabled one, because it is not
+    possible to disable a rule anymore.
+  Cf. https://lore.kernel.org/lkml/CAG48ez21bEn0wL1bbmTiiu8j9jP5iEWtHOwz4tURUJ+ki0ydYw@mail.gmail.com/
+* Fix nested domains by implementing a notion of layer level and depth:
+  - Update landlock_insert_rule() to manage such layers.
+  - Add an inherit_ruleset() helper to properly create a new domain.
+  - Rename landlock_find_access() to landlock_find_rule() and return a
+    full rule reference.
+  - Add a layer_level and a layer_depth fields to struct landlock_rule.
+  - Add a top_layer_level field to struct landlock_ruleset.
+* Remove access rights that may be required for FD-only requests:
+  truncate, getattr, lock, chmod, chown, chgrp, ioctl.  This will be
+  handle in a future evolution of Landlock, but right now the goal is to
+  lighten the code to ease review.
+* Remove LANDLOCK_ACCESS_FS_OPEN and rename
+  LANDLOCK_ACCESS_FS_{READ,WRITE} with a FILE suffix.
+* Rename LANDLOCK_ACCESS_FS_READDIR to match the *_FILE pattern.
+* Remove LANDLOCK_ACCESS_FS_MAP which was useless.
+* Fix memory leak in put_hierarchy() (reported by Jann Horn).
+* Fix user-after-free and rename free_ruleset() (reported by Jann Horn).
+* Replace the for loops with rbtree_postorder_for_each_entry_safe().
+* Constify variables.
+* Only use refcount_inc() through getter helpers.
+* Change Landlock_insert_ruleset_access() to
+  Landlock_insert_ruleset_rule().
+* Rename landlock_put_ruleset_enqueue() to landlock_put_ruleset_deferred().
+* Improve kernel documentation and add a warning about the unhandled
+  access/syscall families.
+* Move ABI check to syscall.c .
+
+Changes since v13:
+* New implementation, inspired by the previous inode eBPF map, but
+  agnostic to the underlying kernel object.
+
+Previous changes:
+https://lore.kernel.org/lkml/20190721213116.23476-7-mic@digikod.net/
 ---
- 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
+ security/landlock/Makefile  |   2 +-
+ security/landlock/limits.h  |  17 ++
+ security/landlock/ruleset.c | 469 ++++++++++++++++++++++++++++++++++++
+ security/landlock/ruleset.h | 165 +++++++++++++
+ 4 files changed, 652 insertions(+), 1 deletion(-)
+ create mode 100644 security/landlock/limits.h
+ create mode 100644 security/landlock/ruleset.c
+ create mode 100644 security/landlock/ruleset.h
 
 diff --git a/security/landlock/Makefile b/security/landlock/Makefile
-index 682b798c6b76..dd5f70185778 100644
+index cb6deefbf4c0..d846eba445bb 100644
 --- a/security/landlock/Makefile
 +++ b/security/landlock/Makefile
-@@ -1,4 +1,5 @@
+@@ -1,3 +1,3 @@
  obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
  
- 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
+-landlock-y := object.o
++landlock-y := object.o ruleset.o
+diff --git a/security/landlock/limits.h b/security/landlock/limits.h
+new file mode 100644
+index 000000000000..b734f597bb0e
+--- /dev/null
++++ b/security/landlock/limits.h
+@@ -0,0 +1,17 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/*
++ * Landlock LSM - Limits for different components
 + *
-+ * 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
++ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
++ * Copyright © 2018-2020 ANSSI
++ */
++
++#ifndef _SECURITY_LANDLOCK_LIMITS_H
++#define _SECURITY_LANDLOCK_LIMITS_H
++
++#include <linux/limits.h>
++
++#define LANDLOCK_MAX_NUM_LAYERS		64
++#define LANDLOCK_MAX_NUM_RULES		U32_MAX
++
++#endif /* _SECURITY_LANDLOCK_LIMITS_H */
+diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
 new file mode 100644
-index 000000000000..c955b9c95c84
+index 000000000000..59c86126ea1c
 --- /dev/null
-+++ b/security/landlock/domain_manage.c
-@@ -0,0 +1,265 @@
++++ b/security/landlock/ruleset.c
+@@ -0,0 +1,469 @@
 +// SPDX-License-Identifier: GPL-2.0-only
 +/*
-+ * Landlock LSM - domain management
++ * Landlock LSM - Ruleset management
 + *
-+ * Copyright © 2016-2019 Mickaël Salaün <mic@digikod.net>
-+ * Copyright © 2018-2019 ANSSI
-+ */
-+
++ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
++ * Copyright © 2018-2020 ANSSI
++ */
++
++#include <linux/bits.h>
++#include <linux/bug.h>
++#include <linux/compiler_types.h>
 +#include <linux/err.h>
 +#include <linux/errno.h>
++#include <linux/kernel.h>
++#include <linux/lockdep.h>
++#include <linux/overflow.h>
++#include <linux/rbtree.h>
 +#include <linux/refcount.h>
 +#include <linux/slab.h>
 +#include <linux/spinlock.h>
-+
-+#include "common.h"
-+#include "domain_manage.h"
-+
-+void landlock_get_domain(struct landlock_domain *dom)
-+{
-+	if (!dom)
++#include <linux/workqueue.h>
++
++#include "limits.h"
++#include "object.h"
++#include "ruleset.h"
++
++static struct landlock_ruleset *create_ruleset(const u32 num_layers)
++{
++	struct landlock_ruleset *new_ruleset;
++
++	new_ruleset = kzalloc(struct_size(new_ruleset, fs_access_masks,
++				num_layers), GFP_KERNEL_ACCOUNT);
++	if (!new_ruleset)
++		return ERR_PTR(-ENOMEM);
++	refcount_set(&new_ruleset->usage, 1);
++	mutex_init(&new_ruleset->lock);
++	new_ruleset->root = RB_ROOT;
++	new_ruleset->num_layers = num_layers;
++	/*
++	 * hierarchy = NULL
++	 * num_rules = 0
++	 * fs_access_masks[] = 0
++	 */
++	return new_ruleset;
++}
++
++struct landlock_ruleset *landlock_create_ruleset(const u32 fs_access_mask)
++{
++	struct landlock_ruleset *new_ruleset;
++
++	/* Informs about useless ruleset. */
++	if (!fs_access_mask)
++		return ERR_PTR(-ENOMSG);
++	new_ruleset = create_ruleset(1);
++	if (!IS_ERR(new_ruleset))
++		new_ruleset->fs_access_masks[0] = fs_access_mask;
++	return new_ruleset;
++}
++
++static void build_check_rule(void)
++{
++	const struct landlock_rule rule = {
++		.num_layers = ~0,
++	};
++
++	BUILD_BUG_ON(rule.num_layers < LANDLOCK_MAX_NUM_LAYERS);
++}
++
++static struct landlock_rule *create_rule(
++		struct landlock_object *const object,
++		const struct landlock_layer (*const layers)[],
++		const u32 num_layers,
++		const struct landlock_layer *const new_layer)
++{
++	struct landlock_rule *new_rule;
++	u32 new_num_layers;
++
++	build_check_rule();
++	if (new_layer) {
++		/* Should already be checked by landlock_merge_ruleset(). */
++		if (WARN_ON_ONCE(num_layers >= LANDLOCK_MAX_NUM_LAYERS))
++			return ERR_PTR(-E2BIG);
++		new_num_layers = num_layers + 1;
++	} else {
++		new_num_layers = num_layers;
++	}
++	new_rule = kzalloc(struct_size(new_rule, layers, new_num_layers),
++			GFP_KERNEL_ACCOUNT);
++	if (!new_rule)
++		return ERR_PTR(-ENOMEM);
++	RB_CLEAR_NODE(&new_rule->node);
++	landlock_get_object(object);
++	new_rule->object = object;
++	new_rule->num_layers = new_num_layers;
++	/* Copies the original layer stack. */
++	memcpy(new_rule->layers, layers,
++			flex_array_size(new_rule, layers, num_layers));
++	if (new_layer)
++		/* Adds a copy of @new_layer on the layer stack. */
++		new_rule->layers[new_rule->num_layers - 1] = *new_layer;
++	return new_rule;
++}
++
++static void put_rule(struct landlock_rule *const rule)
++{
++	might_sleep();
++	if (!rule)
 +		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;
++	landlock_put_object(rule->object);
++	kfree(rule);
++}
++
++static void build_check_ruleset(void)
++{
++	const struct landlock_ruleset ruleset = {
++		.num_rules = ~0,
++		.num_layers = ~0,
++	};
++
++	BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES);
++	BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS);
++}
++
++/**
++ * insert_rule - Create and insert a rule in a ruleset
++ *
++ * @ruleset: The ruleset to be updated.
++ * @object: The object to build the new rule with.  The underlying kernel
++ *          object must be held by the caller.
++ * @layers: One or multiple layers to be copied into the new rule.
++ * @num_layers: The number of @layers entries.
++ *
++ * When user space requests to add a new rule to a ruleset, @layers only
++ * contains one entry and this entry is not assigned to any level.  In this
++ * case, the new rule will extend @ruleset, similarly to a boolean OR between
++ * access rights.
++ *
++ * When merging a ruleset in a domain, or copying a domain, @layers will be
++ * added to @ruleset as new constraints, similarly to a boolean AND between
++ * access rights.
++ */
++static int insert_rule(struct landlock_ruleset *const ruleset,
++		struct landlock_object *const object,
++		const struct landlock_layer (*const layers)[],
++		size_t num_layers)
++{
++	struct rb_node **walker_node;
++	struct rb_node *parent_node = NULL;
++	struct landlock_rule *new_rule;
++
++	might_sleep();
++	lockdep_assert_held(&ruleset->lock);
++	if (WARN_ON_ONCE(!object || !layers))
++		return -ENOENT;
++	walker_node = &(ruleset->root.rb_node);
++	while (*walker_node) {
++		struct landlock_rule *const this = rb_entry(*walker_node,
++				struct landlock_rule, node);
++
++		if (this->object != object) {
++			parent_node = *walker_node;
++			if (this->object < object)
++				walker_node = &((*walker_node)->rb_right);
++			else
++				walker_node = &((*walker_node)->rb_left);
++			continue;
++		}
++
++		/* Only a single-level layer should match an existing rule. */
++		if (WARN_ON_ONCE(num_layers != 1))
++			return -EINVAL;
++
++		/* If there is a matching rule, updates it. */
++		if ((*layers)[0].level == 0) {
++			/*
++			 * Extends access rights when the request comes from
++			 * landlock_add_rule(2), i.e. @ruleset is not a domain.
++			 */
++			if (WARN_ON_ONCE(this->num_layers != 1))
++				return -EINVAL;
++			if (WARN_ON_ONCE(this->layers[0].level != 0))
++				return -EINVAL;
++			this->layers[0].access |= (*layers)[0].access;
++			return 0;
++		}
++
++		if (WARN_ON_ONCE(this->layers[0].level == 0))
++			return -EINVAL;
++
++		/*
++		 * Intersects access rights when it is a merge between a
++		 * ruleset and a domain.
++		 */
++		new_rule = create_rule(object, &this->layers, this->num_layers,
++				&(*layers)[0]);
++		if (IS_ERR(new_rule))
++			return PTR_ERR(new_rule);
++		rb_replace_node(&this->node, &new_rule->node, &ruleset->root);
++		put_rule(this);
++		return 0;
++	}
++
++	/* There is no match for @object. */
++	build_check_ruleset();
++	if (ruleset->num_rules >= LANDLOCK_MAX_NUM_RULES)
++		return -E2BIG;
++	new_rule = create_rule(object, layers, num_layers, NULL);
++	if (IS_ERR(new_rule))
++		return PTR_ERR(new_rule);
++	rb_link_node(&new_rule->node, parent_node, walker_node);
++	rb_insert_color(&new_rule->node, &ruleset->root);
++	ruleset->num_rules++;
++	return 0;
++}
++
++static void build_check_layer(void)
++{
++	const struct landlock_layer layer = {
++		.level = ~0,
++	};
++
++	BUILD_BUG_ON(layer.level < LANDLOCK_MAX_NUM_LAYERS);
++}
++
++/* @ruleset must be locked by the caller. */
++int landlock_insert_rule(struct landlock_ruleset *const ruleset,
++		struct landlock_object *const object, const u32 access)
++{
++	struct landlock_layer layers[] = {{
++		.access = access,
++		/* When @level is zero, insert_rule() extends @ruleset. */
++		.level = 0,
++	}};
++
++	build_check_layer();
++	return insert_rule(ruleset, object, &layers, ARRAY_SIZE(layers));
++}
++
++static inline void get_hierarchy(struct landlock_hierarchy *const hierarchy)
++{
++	if (hierarchy)
++		refcount_inc(&hierarchy->usage);
++}
++
++static void put_hierarchy(struct landlock_hierarchy *hierarchy)
++{
++	while (hierarchy && refcount_dec_and_test(&hierarchy->usage)) {
++		const struct landlock_hierarchy *const freeme = hierarchy;
++
++		hierarchy = hierarchy->parent;
 +		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(&domain->usage, 1);
-+	return domain;
++static int merge_ruleset(struct landlock_ruleset *const dst,
++		struct landlock_ruleset *const src)
++{
++	struct landlock_rule *walker_rule, *next_rule;
++	int err = 0;
++
++	might_sleep();
++	/* Should already be checked by landlock_merge_ruleset() */
++	if (WARN_ON_ONCE(!src))
++		return 0;
++	/* Only merge into a domain. */
++	if (WARN_ON_ONCE(!dst || !dst->hierarchy))
++		return -EINVAL;
++
++	/* Locks @dst first because we are its only owner. */
++	mutex_lock(&dst->lock);
++	mutex_lock_nested(&src->lock, SINGLE_DEPTH_NESTING);
++
++	/* Stacks the new layer. */
++	if (WARN_ON_ONCE(src->num_layers != 1 || dst->num_layers < 1)) {
++		err = -EINVAL;
++		goto out_unlock;
++	}
++	dst->fs_access_masks[dst->num_layers - 1] = src->fs_access_masks[0];
++
++	/* Merges the @src tree. */
++	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
++			&src->root, node) {
++		struct landlock_layer layers[] = {{
++			.level = dst->num_layers,
++		}};
++
++		if (WARN_ON_ONCE(walker_rule->num_layers != 1)) {
++			err = -EINVAL;
++			goto out_unlock;
++		}
++		if (WARN_ON_ONCE(walker_rule->layers[0].level != 0)) {
++			err = -EINVAL;
++			goto out_unlock;
++		}
++		layers[0].access = walker_rule->layers[0].access;
++		err = insert_rule(dst, walker_rule->object, &layers,
++				ARRAY_SIZE(layers));
++		if (err)
++			goto out_unlock;
++	}
++
++out_unlock:
++	mutex_unlock(&src->lock);
++	mutex_unlock(&dst->lock);
++	return err;
++}
++
++static int inherit_ruleset(struct landlock_ruleset *const parent,
++		struct landlock_ruleset *const child)
++{
++	struct landlock_rule *walker_rule, *next_rule;
++	int err = 0;
++
++	might_sleep();
++	if (!parent)
++		return 0;
++
++	/* Locks @child first because we are its only owner. */
++	mutex_lock(&child->lock);
++	mutex_lock_nested(&parent->lock, SINGLE_DEPTH_NESTING);
++
++	/* Copies the @parent tree. */
++	rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
++			&parent->root, node) {
++		err = insert_rule(child, walker_rule->object,
++				&walker_rule->layers, walker_rule->num_layers);
++		if (err)
++			goto out_unlock;
++	}
++
++	if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) {
++		err = -EINVAL;
++		goto out_unlock;
++	}
++	/* Copies the parent layer stack and leaves a space for the new layer. */
++	memcpy(child->fs_access_masks, parent->fs_access_masks,
++			flex_array_size(parent, fs_access_masks, parent->num_layers));
++
++	if (WARN_ON_ONCE(!parent->hierarchy)) {
++		err = -EINVAL;
++		goto out_unlock;
++	}
++	get_hierarchy(parent->hierarchy);
++	child->hierarchy->parent = parent->hierarchy;
++
++out_unlock:
++	mutex_unlock(&parent->lock);
++	mutex_unlock(&child->lock);
++	return err;
++}
++
++static void free_ruleset(struct landlock_ruleset *const ruleset)
++{
++	struct landlock_rule *freeme, *next;
++
++	might_sleep();
++	rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root,
++			node)
++		put_rule(freeme);
++	put_hierarchy(ruleset->hierarchy);
++	kfree(ruleset);
++}
++
++void landlock_put_ruleset(struct landlock_ruleset *const ruleset)
++{
++	might_sleep();
++	if (ruleset && refcount_dec_and_test(&ruleset->usage))
++		free_ruleset(ruleset);
++}
++
++static void free_ruleset_work(struct work_struct *const work)
++{
++	struct landlock_ruleset *ruleset;
++
++	ruleset = container_of(work, struct landlock_ruleset, work_free);
++	free_ruleset(ruleset);
++}
++
++void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset)
++{
++	if (ruleset && refcount_dec_and_test(&ruleset->usage)) {
++		INIT_WORK(&ruleset->work_free, free_ruleset_work);
++		schedule_work(&ruleset->work_free);
++	}
 +}
 +
 +/**
-+ * store_landlock_prog - prepend and deduplicate a Landlock prog_list
++ * landlock_merge_ruleset - Merge a ruleset with a domain
 + *
-+ * 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.
++ * @parent: Parent domain.
++ * @ruleset: New ruleset to be merged.
 + *
-+ * @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;
++ * Returns the intersection of @parent and @ruleset, or returns @parent if
++ * @ruleset is empty, or returns a duplicate of @ruleset if @parent is empty.
++ */
++struct landlock_ruleset *landlock_merge_ruleset(
++		struct landlock_ruleset *const parent,
++		struct landlock_ruleset *const ruleset)
++{
++	struct landlock_ruleset *new_dom;
++	u32 num_layers;
 +	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);
++
++	might_sleep();
++	if (WARN_ON_ONCE(!ruleset || parent == ruleset))
++		return ERR_PTR(-EINVAL);
++
++	if (parent) {
++		if (parent->num_layers >= LANDLOCK_MAX_NUM_LAYERS)
++			return ERR_PTR(-E2BIG);
++		num_layers = parent->num_layers + 1;
++	} else {
++		num_layers = 1;
++	}
++
++	/* Creates a new domain... */
++	new_dom = create_ruleset(num_layers);
++	if (IS_ERR(new_dom))
++		return new_dom;
++	new_dom->hierarchy = kzalloc(sizeof(*new_dom->hierarchy),
++			GFP_KERNEL_ACCOUNT);
++	if (!new_dom->hierarchy) {
 +		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);
-+
-+	/* 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;
-+		}
-+		/* 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);
++		goto out_put_dom;
++	}
++	refcount_set(&new_dom->hierarchy->usage, 1);
++
++	/* ...as a child of @parent... */
++	err = inherit_ruleset(parent, new_dom);
 +	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) {
-+		/*
-+		 * If there is no Landlock domain used by the current task,
-+		 * then create a new one.
-+		 */
-+		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);
-+		}
-+
-+		/*
-+		 * 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];
-+		}
-+	}
-+	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
++		goto out_put_dom;
++
++	/* ...and including @ruleset. */
++	err = merge_ruleset(new_dom, ruleset);
++	if (err)
++		goto out_put_dom;
++
++	return new_dom;
++
++out_put_dom:
++	landlock_put_ruleset(new_dom);
++	return ERR_PTR(err);
++}
++
++/*
++ * The returned access has the same lifetime as @ruleset.
++ */
++const struct landlock_rule *landlock_find_rule(
++		const struct landlock_ruleset *const ruleset,
++		const struct landlock_object *const object)
++{
++	const struct rb_node *node;
++
++	if (!object)
++		return NULL;
++	node = ruleset->root.rb_node;
++	while (node) {
++		struct landlock_rule *this = rb_entry(node,
++				struct landlock_rule, node);
++
++		if (this->object == object)
++			return this;
++		if (this->object < object)
++			node = node->rb_right;
++		else
++			node = node->rb_left;
++	}
++	return NULL;
++}
+diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
 new file mode 100644
-index 000000000000..5b5b49f6e3e8
+index 000000000000..2d3ed7ec5a0a
 --- /dev/null
-+++ b/security/landlock/domain_manage.h
-@@ -0,0 +1,23 @@
++++ b/security/landlock/ruleset.h
+@@ -0,0 +1,165 @@
 +/* SPDX-License-Identifier: GPL-2.0-only */
 +/*
-+ * Landlock LSM - domain management headers
++ * Landlock LSM - Ruleset management
 + *
-+ * 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 */
++ * 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/mutex.h>
++#include <linux/rbtree.h>
++#include <linux/refcount.h>
++#include <linux/workqueue.h>
++
++#include "object.h"
++
++/**
++ * struct landlock_layer - Access rights for a given layer
++ */
++struct landlock_layer {
++	/**
++	 * @level: Position of this layer in the layer stack.
++	 */
++	u16 level;
++	/**
++	 * @access: Bitfield of allowed actions on the kernel object.  They are
++	 * relative to the object type (e.g. %LANDLOCK_ACTION_FS_READ).
++	 */
++	u16 access;
++};
++
++/**
++ * struct landlock_rule - Access rights tied to an object
++ */
++struct landlock_rule {
++	/**
++	 * @node: Node in the ruleset's red-black tree.
++	 */
++	struct rb_node node;
++	/**
++	 * @object: Pointer to identify a kernel object (e.g. an inode).  This
++	 * is used as a key for this ruleset element.  This pointer is set once
++	 * and never modified.  It always points to an allocated object because
++	 * each rule increments the refcount of its object.
++	 */
++	struct landlock_object *object;
++	/**
++	 * @num_layers: Number of entries in @layers.
++	 */
++	u32 num_layers;
++	/**
++	 * @layers: Stack of layers, from the latest to the newest, implemented
++	 * as a flexible array member (FAM).
++	 */
++	struct landlock_layer layers[];
++};
++
++/**
++ * struct landlock_hierarchy - Node in a ruleset hierarchy
++ */
++struct landlock_hierarchy {
++	/**
++	 * @parent: Pointer to the parent node, or NULL if it is a root
++	 * Landlock domain.
++	 */
++	struct landlock_hierarchy *parent;
++	/**
++	 * @usage: Number of potential children domains plus their parent
++	 * domain.
++	 */
++	refcount_t usage;
++};
++
++/**
++ * struct landlock_ruleset - Landlock ruleset
++ *
++ * This data structure must contain unique entries, be updatable, and quick to
++ * match an object.
++ */
++struct landlock_ruleset {
++	/**
++	 * @root: Root of a red-black tree containing &struct landlock_rule
++	 * nodes.  Once a ruleset is tied to a process (i.e. as a domain), this
++	 * tree is immutable until @usage reaches zero.
++	 */
++	struct rb_root root;
++	/**
++	 * @hierarchy: Enables hierarchy identification even when a parent
++	 * domain vanishes.  This is needed for the ptrace protection.
++	 */
++	struct landlock_hierarchy *hierarchy;
++	union {
++		/**
++		 * @work_free: Enables to free a ruleset within a lockless
++		 * section.  This is only used by
++		 * landlock_put_ruleset_deferred() when @usage reaches zero.
++		 * The fields @lock, @usage, @num_rules, @num_layers and
++		 * @fs_access_masks are then unused.
++		 */
++		struct work_struct work_free;
++		struct {
++			/**
++			 * @lock: Protects against concurrent modifications of
++			 * @root, if @usage is greater than zero.
++			 */
++			struct mutex lock;
++			/**
++			 * @usage: Number of processes (i.e. domains) or file
++			 * descriptors referencing this ruleset.
++			 */
++			refcount_t usage;
++			/**
++			 * @num_rules: Number of non-overlapping (i.e. not for
++			 * the same object) rules in this ruleset.
++			 */
++			u32 num_rules;
++			/**
++			 * @num_layers: Number of layers that are used in this
++			 * ruleset.  This enables to check that all the layers
++			 * allow an access request.  A value of 0 identifies a
++			 * non-merged ruleset (i.e. not a domain).
++			 */
++			u32 num_layers;
++			/**
++			 * @fs_access_masks: Contains the subset of filesystem
++			 * actions that are restricted by a ruleset.  A domain
++			 * saves all layers of merged rulesets in a stack
++			 * (FAM), starting from the first layer to the last
++			 * one.  These layers are used when merging rulesets,
++			 * for user space backward compatibility (i.e.
++			 * future-proof), and to properly handle merged
++			 * rulesets without overlapping access rights.  These
++			 * layers are set once and never changed for the
++			 * lifetime of the ruleset.
++			 */
++			u16 fs_access_masks[];
++		};
++	};
++};
++
++struct landlock_ruleset *landlock_create_ruleset(const u32 fs_access_mask);
++
++void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
++void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
++
++int landlock_insert_rule(struct landlock_ruleset *const ruleset,
++		struct landlock_object *const object, const u32 access);
++
++struct landlock_ruleset *landlock_merge_ruleset(
++		struct landlock_ruleset *const parent,
++		struct landlock_ruleset *const ruleset);
++
++const struct landlock_rule *landlock_find_rule(
++		const struct landlock_ruleset *const ruleset,
++		const struct landlock_object *const object);
++
++static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
++{
++	if (ruleset)
++		refcount_inc(&ruleset->usage);
++}
++
++#endif /* _SECURITY_LANDLOCK_RULESET_H */
 -- 
-2.23.0
-
+2.30.2
+
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help