--- v17
+++ v11
@@ -1,725 +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 implementing the syscall) which enables
-a process to create and populate a ruleset with 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
-define 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.
+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: Jann Horn <jannh@google.com>
Cc: Kees Cook <keescook@chromium.org>
Cc: Serge E. Hallyn <serge@hallyn.com>
+Cc: Will Drewry <wad@chromium.org>
---
-Changes since v16:
-* Allow enforcement of empty ruleset, which enables deny-all policies.
+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
-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/
----
- MAINTAINERS | 1 +
- include/uapi/linux/landlock.h | 78 ++++++++
- security/landlock/Makefile | 2 +-
- security/landlock/ruleset.c | 342 ++++++++++++++++++++++++++++++++++
- security/landlock/ruleset.h | 161 ++++++++++++++++
- 5 files changed, 583 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 011349c9c6c6..3de0e01de0c4 100644
---- a/MAINTAINERS
-+++ b/MAINTAINERS
-@@ -9493,6 +9493,7 @@ L: linux-security-module@vger.kernel.org
- S: Supported
- W: https://landlock.io
- T: git https://github.com/landlock-lsm/linux.git
-+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..4b7e69a8806b
---- /dev/null
-+++ b/include/uapi/linux/landlock.h
-@@ -0,0 +1,78 @@
-+/* 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 actions on
-+ * files and directories. Files or directories opened before the sandboxing
-+ * are not subject to these restrictions.
-+ *
-+ * A file can only receive these access rights:
-+ *
-+ * - %LANDLOCK_ACCESS_FS_EXECUTE: Execute a file.
-+ * - %LANDLOCK_ACCESS_FS_WRITE_FILE: Write to a file.
-+ * - %LANDLOCK_ACCESS_FS_READ_FILE: Open a file with read access.
-+ *
-+ * A directory can receive access rights related to files or directories. This
-+ * set of access rights is applied to the directory itself, and the directories
-+ * beneath it:
-+ *
-+ * - %LANDLOCK_ACCESS_FS_READ_DIR: Open a directory or list its content.
-+ * - %LANDLOCK_ACCESS_FS_CHROOT: Change the root directory of the current
-+ * process.
-+ *
-+ * However, the following access rights only apply to the content of a
-+ * directory, not the directory itself:
-+ *
-+ * - %LANDLOCK_ACCESS_FS_REMOVE_DIR: Remove an empty directory or rename one.
-+ * - %LANDLOCK_ACCESS_FS_REMOVE_FILE: Unlink (or rename) a file.
-+ * - %LANDLOCK_ACCESS_FS_MAKE_CHAR: Create (or rename or link) a character
-+ * device.
-+ * - %LANDLOCK_ACCESS_FS_MAKE_DIR: Create (or rename or link) a directory.
-+ * - %LANDLOCK_ACCESS_FS_MAKE_REG: Create (or rename or link) a regular file.
-+ * - %LANDLOCK_ACCESS_FS_MAKE_SOCK: Create (or rename or link) a UNIX domain
-+ * socket.
-+ * - %LANDLOCK_ACCESS_FS_MAKE_FIFO: Create (or rename or link) a named pipe.
-+ * - %LANDLOCK_ACCESS_FS_MAKE_BLOCK: Create (or rename or link) a block device.
-+ * - %LANDLOCK_ACCESS_FS_MAKE_SYM: Create (or rename or link) a symbolic link.
-+ *
-+ * .. warning::
-+ *
-+ * It is currently not possible to restrict some file-related actions
-+ * accessible through these syscall families: :manpage:`chdir(2)`,
-+ * :manpage:`truncate(2)`, :manpage:`stat(2)`, :manpage:`flock(2)`,
-+ * :manpage:`chmod(2)`, :manpage:`chown(2)`, :manpage:`setxattr(2)`,
-+ * :manpage:`ioctl(2)`, :manpage:`fcntl(2)`.
-+ * Future Landlock evolutions will enable to restrict them.
-+ */
-+#define LANDLOCK_ACCESS_FS_EXECUTE (1ULL << 0)
-+#define LANDLOCK_ACCESS_FS_WRITE_FILE (1ULL << 1)
-+#define LANDLOCK_ACCESS_FS_READ_FILE (1ULL << 2)
-+#define LANDLOCK_ACCESS_FS_READ_DIR (1ULL << 3)
-+#define LANDLOCK_ACCESS_FS_CHROOT (1ULL << 4)
-+#define LANDLOCK_ACCESS_FS_REMOVE_DIR (1ULL << 5)
-+#define LANDLOCK_ACCESS_FS_REMOVE_FILE (1ULL << 6)
-+#define LANDLOCK_ACCESS_FS_MAKE_CHAR (1ULL << 7)
-+#define LANDLOCK_ACCESS_FS_MAKE_DIR (1ULL << 8)
-+#define LANDLOCK_ACCESS_FS_MAKE_REG (1ULL << 9)
-+#define LANDLOCK_ACCESS_FS_MAKE_SOCK (1ULL << 10)
-+#define LANDLOCK_ACCESS_FS_MAKE_FIFO (1ULL << 11)
-+#define LANDLOCK_ACCESS_FS_MAKE_BLOCK (1ULL << 12)
-+#define LANDLOCK_ACCESS_FS_MAKE_SYM (1ULL << 13)
-+
-+#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..9bd474291d8f
+index 000000000000..c955b9c95c84
--- /dev/null
-+++ b/security/landlock/ruleset.c
-@@ -0,0 +1,342 @@
++++ 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/bits.h>
-+#include <linux/bug.h>
-+#include <linux/compiler_types.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/limits.h>
-+#include <linux/rbtree.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);
++ 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);
++
++ /* 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(¤t_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);
++
+ /*
-+ * root = RB_ROOT
-+ * hierarchy = NULL
-+ * nb_rules = 0
-+ * nb_layers = 0
-+ * fs_access_mask = 0
++ * 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.
+ */
-+ return ruleset;
-+}
-+
-+struct landlock_ruleset *landlock_create_ruleset(const u32 fs_access_mask)
-+{
-+ struct landlock_ruleset *ruleset;
-+
-+ /* 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;
-+}
-+
-+static struct landlock_rule *duplicate_rule(struct landlock_rule *const src)
-+{
-+ struct landlock_rule *new_rule;
-+
-+ new_rule = kzalloc(sizeof(*new_rule), GFP_KERNEL);
-+ if (!new_rule)
-+ return ERR_PTR(-ENOMEM);
-+ RB_CLEAR_NODE(&new_rule->node);
-+ landlock_get_object(src->object);
-+ new_rule->object = src->object;
-+ new_rule->access = src->access;
-+ new_rule->layers = src->layers;
-+ return new_rule;
-+}
-+
-+static void put_rule(struct landlock_rule *const rule)
-+{
-+ might_sleep();
-+ if (!rule)
-+ return;
-+ landlock_put_object(rule->object);
-+ kfree(rule);
-+}
-+
-+/*
-+ * Assumptions:
-+ * - An inserted rule can not be removed.
-+ * - The underlying kernel object must be held by the caller.
-+ *
-+ * @rule: Read-only payload to be inserted (not own by this function).
-+ * @is_merge: If true, intersects access rights and updates the rule's layers
-+ * (e.g. merge two rulesets), else do a union of access rights and keep the
-+ * rule's layers (e.g. extend a ruleset)
-+ */
-+int landlock_insert_rule(struct landlock_ruleset *const ruleset,
-+ struct landlock_rule *const rule, const bool is_merge)
-+{
-+ struct rb_node **walker_node;
-+ struct rb_node *parent_node = NULL;
-+ struct landlock_rule *new_rule;
-+
-+ might_sleep();
-+ lockdep_assert_held(&ruleset->lock);
-+ 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 != rule->object) {
-+ parent_node = *walker_node;
-+ if (this->object < rule->object)
-+ walker_node = &((*walker_node)->rb_right);
-+ else
-+ walker_node = &((*walker_node)->rb_left);
-+ continue;
++ 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(¤t_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);
+ }
+
-+ /* If there is a matching rule, updates it. */
-+ if (is_merge) {
-+ /* Intersects access rights. */
-+ this->access &= rule->access;
-+
-+ /* Updates the rule layers with the next one. */
-+ this->layers |= BIT_ULL(ruleset->nb_layers);
-+ } else {
-+ /* Extends access rights. */
-+ this->access |= rule->access;
++ /*
++ * 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 0;
-+ }
-+
-+ /* There is no match for @rule->object. */
-+ if (ruleset->nb_rules == U32_MAX)
-+ return -E2BIG;
-+ new_rule = duplicate_rule(rule);
-+ if (IS_ERR(new_rule))
-+ return PTR_ERR(new_rule);
-+ if (is_merge)
-+ /* Sets the rule layer to the next one. */
-+ new_rule->layers = BIT_ULL(ruleset->nb_layers);
-+ rb_link_node(&new_rule->node, parent_node, walker_node);
-+ rb_insert_color(&new_rule->node, &ruleset->root);
-+ ruleset->nb_rules++;
-+ return 0;
-+}
-+
-+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);
-+ }
-+}
-+
-+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();
-+ if (!src)
-+ return 0;
-+ /* Only merge into a domain. */
-+ if (WARN_ON_ONCE(!dst || !dst->hierarchy))
-+ return -EFAULT;
-+
-+ mutex_lock(&dst->lock);
-+ mutex_lock_nested(&src->lock, 1);
-+ /*
-+ * Makes a new layer, but only increments the number of layers after
-+ * the rules are inserted.
-+ */
-+ if (dst->nb_layers == sizeof(walker_rule->layers) * BITS_PER_BYTE) {
-+ err = -E2BIG;
-+ goto out_unlock;
-+ }
-+ dst->fs_access_mask |= src->fs_access_mask;
-+
-+ /* Merges the @src tree. */
-+ rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
-+ &src->root, node) {
-+ err = landlock_insert_rule(dst, walker_rule, true);
-+ if (err)
-+ goto out_unlock;
-+ }
-+ dst->nb_layers++;
-+
-+out_unlock:
-+ mutex_unlock(&src->lock);
-+ mutex_unlock(&dst->lock);
-+ return err;
-+}
-+
-+static struct landlock_ruleset *inherit_ruleset(
-+ struct landlock_ruleset *const parent)
-+{
-+ struct landlock_rule *walker_rule, *next_rule;
-+ struct landlock_ruleset *new_ruleset;
-+ int err = 0;
-+
-+ might_sleep();
-+ new_ruleset = create_ruleset();
-+ if (IS_ERR(new_ruleset))
-+ return new_ruleset;
-+
-+ new_ruleset->hierarchy = kzalloc(sizeof(*new_ruleset->hierarchy),
-+ GFP_KERNEL);
-+ if (!new_ruleset->hierarchy) {
-+ err = -ENOMEM;
-+ goto out_put_ruleset;
-+ }
-+ refcount_set(&new_ruleset->hierarchy->usage, 1);
-+ if (!parent)
-+ return new_ruleset;
-+
-+ mutex_lock(&new_ruleset->lock);
-+ mutex_lock_nested(&parent->lock, 1);
-+ new_ruleset->nb_layers = parent->nb_layers;
-+ new_ruleset->fs_access_mask = parent->fs_access_mask;
-+ WARN_ON_ONCE(!parent->hierarchy);
-+ get_hierarchy(parent->hierarchy);
-+ new_ruleset->hierarchy->parent = parent->hierarchy;
-+
-+ /* Copies the @parent tree. */
-+ rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
-+ &parent->root, node) {
-+ err = landlock_insert_rule(new_ruleset, walker_rule, false);
-+ if (err)
-+ goto out_unlock;
-+ }
-+ mutex_unlock(&parent->lock);
-+ mutex_unlock(&new_ruleset->lock);
-+ return new_ruleset;
-+
-+out_unlock:
-+ mutex_unlock(&parent->lock);
-+ mutex_unlock(&new_ruleset->lock);
-+
-+out_put_ruleset:
-+ landlock_put_ruleset(new_ruleset);
-+ return ERR_PTR(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);
-+ }
-+}
-+
-+/*
-+ * Creates a new transition domain, intersection of @parent and @ruleset, or
-+ * return @parent if @ruleset is empty. If @parent is empty, returns a
-+ * duplicate of @ruleset.
-+ */
-+struct landlock_ruleset *landlock_merge_ruleset(
-+ struct landlock_ruleset *const parent,
-+ struct landlock_ruleset *const ruleset)
-+{
-+ struct landlock_ruleset *new_dom;
-+ int err;
-+
-+ might_sleep();
-+ /*
-+ * Merging duplicates a ruleset, so a new ruleset can't be
-+ * the same as the parent, but they can have similar content.
-+ */
-+ if (WARN_ON_ONCE(!ruleset || parent == ruleset)) {
-+ landlock_get_ruleset(parent);
-+ return parent;
-+ }
-+
-+ new_dom = inherit_ruleset(parent);
-+ if (IS_ERR(new_dom))
-+ return new_dom;
-+
-+ err = merge_ruleset(new_dom, ruleset);
-+ if (err) {
-+ landlock_put_ruleset(new_dom);
-+ return ERR_PTR(err);
-+ }
-+ return new_dom;
-+}
-+
-+/*
-+ * 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
++ }
++ 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..206814974525
+index 000000000000..5b5b49f6e3e8
--- /dev/null
-+++ b/security/landlock/ruleset.h
-@@ -0,0 +1,161 @@
++++ 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/mutex.h>
-+#include <linux/rbtree.h>
-+#include <linux/refcount.h>
-+#include <linux/workqueue.h>
-+#include <uapi/linux/landlock.h>
-+
-+#include "object.h"
-+
-+#define _LANDLOCK_ACCESS_FS_LAST LANDLOCK_ACCESS_FS_MAKE_SYM
-+#define _LANDLOCK_ACCESS_FS_MASK ((_LANDLOCK_ACCESS_FS_LAST << 1) - 1)
-+
-+/**
-+ * struct landlock_rule - Access rights tied to an object
-+ *
-+ * When enforcing a ruleset (i.e. merging a ruleset into the current domain),
-+ * the layer level of a new rule is the incremented top layer level (cf.
-+ * &struct landlock_ruleset). If there is no rule (from this domain) tied to
-+ * the same object, then the depth of the new rule is 1. However, if there is
-+ * already a rule tied to the same object and if this rule's layer level is the
-+ * previous top layer level, then the depth and the layer level are both
-+ * incremented and the rule is updated with the new access rights (boolean
-+ * AND).
-+ */
-+struct landlock_rule {
-+ /**
-+ * @node: Node in the 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 point to an allocated object because
-+ * each rule increment the refcount of there object.
-+ */
-+ struct landlock_object *object;
-+ /**
-+ * @access: Bitfield of allowed actions on the kernel object. They are
-+ * relative to the object type (e.g. %LANDLOCK_ACTION_FS_READ). This
-+ * may be the result of the merged access rights (boolean AND) from
-+ * multiple layers referring to the same object.
-+ */
-+ u32 access;
-+ /**
-+ * @layers: Bitfield to identify the layers which resulted to @access
-+ * from different consecutive intersections.
-+ */
-+ u64 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 Lanlock
-+ * 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 contains 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.
-+ */
-+ 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 @usage, @lock, @nb_layers, @nb_rules and
-+ * @fs_access_mask are then unused.
-+ */
-+ struct work_struct work_free;
-+ struct {
-+ /**
-+ * @usage: Number of processes (i.e. domains) or file
-+ * descriptors referencing this ruleset.
-+ */
-+ refcount_t usage;
-+ /**
-+ * @lock: Guards against concurrent modifications of
-+ * @root, if @usage is greater than zero.
-+ */
-+ struct mutex lock;
-+ /**
-+ * @nb_rules: Number of non-overlapping (i.e. not for
-+ * the same object) rules in this ruleset.
-+ */
-+ u32 nb_rules;
-+ /**
-+ * @nb_layers: Number of layers which are used in this
-+ * ruleset. This enables to check that all the layers
-+ * allow an access request. A value of 0 identify a
-+ * non-merged ruleset (i.e. not a domain).
-+ */
-+ u32 nb_layers;
-+ /**
-+ * @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_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_rule *const rule, const bool is_merge);
-+
-+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 */
++ * 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.26.2
+2.23.0