Inter-revision diff: patch 7

Comparing v23 (message) to v14 (message)

--- v23
+++ v14
@@ -1,1026 +1,49 @@
-From: Mickaël Salaün <mic@linux.microsoft.com>
+Wire up the landlock() call for x86_64 (for now).
 
-Thanks to the Landlock objects and ruleset, it is possible to identify
-inodes according to a process's domain.  To enable an unprivileged
-process to express a file hierarchy, it first needs to open a directory
-(or a file) and pass this file descriptor to the kernel through
-landlock_add_rule(2).  When checking if a file access request is
-allowed, we walk from the requested dentry to the real root, following
-the different mount layers.  The access to each "tagged" inodes are
-collected according to their rule layer level, and ANDed to create
-access to the requested file hierarchy.  This makes possible to identify
-a lot of files without tagging every inodes nor modifying the
-filesystem, while still following the view and understanding the user
-has from the filesystem.
-
-Add a new ARCH_EPHEMERAL_INODES for UML because it currently does not
-keep the same struct inodes for the same inodes whereas these inodes are
-in use.
-
-This commit adds a minimal set of supported filesystem access-control
-which doesn't enable to restrict all file-related actions.  This is the
-result of multiple discussions to minimize the code of Landlock to ease
-review.  Thanks to the Landlock design, extending this access-control
-without breaking user space will not be a problem.  Moreover, seccomp
-filters can be used to restrict the use of syscall families which may
-not be currently handled by Landlock.
-
-Cc: Al Viro <viro@zeniv.linux.org.uk>
-Cc: Anton Ivanov <anton.ivanov@cambridgegreys.com>
+Signed-off-by: Mickaël Salaün <mic@digikod.net>
+Cc: Andy Lutomirski <luto@amacapital.net>
+Cc: Arnd Bergmann <arnd@arndb.de>
 Cc: James Morris <jmorris@namei.org>
-Cc: Jann Horn <jannh@google.com>
-Cc: Jeff Dike <jdike@addtoit.com>
 Cc: Kees Cook <keescook@chromium.org>
-Cc: Richard Weinberger <richard@nod.at>
 Cc: Serge E. Hallyn <serge@hallyn.com>
-Signed-off-by: Mickaël Salaün <mic@linux.microsoft.com>
 ---
 
-Changes since v22:
-* Simplify check_access_path_continue() (suggested by Jann Horn).
-* Remove prefetch() call for now (suggested by Jann Horn).
-* Fix spelling and remove superfluous comment (spotted by Jann Horn).
-* Cosmetic variable renaming.
+Changes since v13:
+* New implementation.
+---
+ arch/x86/entry/syscalls/syscall_64.tbl | 1 +
+ include/uapi/asm-generic/unistd.h      | 4 +++-
+ 2 files changed, 4 insertions(+), 1 deletion(-)
 
-Changes since v21:
-* Rename ARCH_EPHEMERAL_STATES to ARCH_EPHEMERAL_INODES (suggested by
-  James Morris).
-* Remove the LANDLOCK_ACCESS_FS_CHROOT right because chroot(2) (which
-  requires CAP_SYS_CHROOT) doesn't enable to bypass Landlock (as tests
-  demonstrate it), and because it is often used by sandboxes, it would
-  be counterproductive to forbid it.  This also reduces the code size.
-* Clean up documentation.
+diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
+index 44d510bc9b78..3e759505c8bf 100644
+--- a/arch/x86/entry/syscalls/syscall_64.tbl
++++ b/arch/x86/entry/syscalls/syscall_64.tbl
+@@ -359,6 +359,7 @@
+ 435	common	clone3			__x64_sys_clone3/ptregs
+ 437	common	openat2			__x64_sys_openat2
+ 438	common	pidfd_getfd		__x64_sys_pidfd_getfd
++439	common	landlock		__x64_sys_landlock
+ 
+ #
+ # x32-specific system call numbers start at 512 to avoid cache impact
+diff --git a/include/uapi/asm-generic/unistd.h b/include/uapi/asm-generic/unistd.h
+index 3a3201e4618e..31d5814ddb13 100644
+--- a/include/uapi/asm-generic/unistd.h
++++ b/include/uapi/asm-generic/unistd.h
+@@ -855,9 +855,11 @@ __SYSCALL(__NR_clone3, sys_clone3)
+ __SYSCALL(__NR_openat2, sys_openat2)
+ #define __NR_pidfd_getfd 438
+ __SYSCALL(__NR_pidfd_getfd, sys_pidfd_getfd)
++#define __NR_landlock 439
++__SYSCALL(__NR_landlock, sys_landlock)
+ 
+ #undef __NR_syscalls
+-#define __NR_syscalls 439
++#define __NR_syscalls 440
+ 
+ /*
+  * 32 bit systems traditionally used different
+-- 
+2.25.0
 
-Changes since v19:
-* Fix spelling (spotted by Randy Dunlap).
-
-Changes since v18:
-* Remove useless include.
-* Fix spelling.
-
-Changes since v17:
-* Replace landlock_release_inodes() with security_sb_delete() (requested
-  by James Morris).
-* Replace struct super_block->s_landlock_inode_refs with the LSM
-  infrastructure management of the superblock (requested by James
-  Morris).
-* Fix mknod restriction with a zero mode (spotted by Vincent Dagonneau).
-* Minimize executed code in path_mknod and file_open hooks when the
-  current tasks is not sandboxed.
-* Remove useless checks on the file pointer and inode in
-  hook_file_open() .
-* Constify domain pointers.
-* Rename inode_landlock() to landlock_inode().
-* Import include/uapi/linux/landlock.h and _LANDLOCK_ACCESS_FS_* from
-  the ruleset and domain management patch.
-* Explain the rational of this minimal set of access-control.
-  https://lore.kernel.org/lkml/f646e1c7-33cf-333f-070c-0a40ad0468cd@digikod.net/
-
-Changes since v16:
-* Add ARCH_EPHEMERAL_STATES and enable it for UML.
-
-Changes since v15:
-* Replace layer_levels and layer_depth with a bitfield of layers: this
-  enables to properly manage superset and subset of access rights,
-  whatever their order in the stack of layers.
-  Cf. https://lore.kernel.org/lkml/e07fe473-1801-01cc-12ae-b3167f95250e@digikod.net/
-* Allow to open pipes and similar special files through /proc/self/fd/.
-* Properly handle internal filesystems such as nsfs: always allow these
-  kind of roots because disconnected path cannot be evaluated.
-* Remove the LANDLOCK_ACCESS_FS_LINK_TO and
-  LANDLOCK_ACCESS_FS_RENAME_{TO,FROM}, but use the
-  LANDLOCK_ACCESS_FS_REMOVE_{FILE,DIR} and LANDLOCK_ACCESS_FS_MAKE_*
-  instead.  Indeed, it is not possible for now (and not really useful)
-  to express the semantic of a source and a destination.
-* Check access rights to remove a directory or a file with rename(2).
-* Forbid reparenting when linking or renaming.  This is needed to easily
-  protect against possible privilege escalation by changing the place of
-  a file or directory in relation to an enforced access policy (from the
-  set of layers).  This will be relaxed in the future.
-* Update hooks to take into account replacement of the object's self and
-  beneath access bitfields with one.  Simplify the code.
-* Check file related access rights.
-* Check d_is_negative() instead of !d_backing_inode() in
-  check_access_path_continue(), and continue the path walk while there
-  is no mapped inode e.g., with rename(2).
-* Check private inode in check_access_path().
-* Optimize get_file_access() when dealing with a directory.
-* Add missing atomic.h .
-
-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):
-  - Rewrite release_inode() to use inode->sb->s_landlock_inode_refs.
-  - Remove useless checks in landlock_release_inodes(), clean object
-    pointer according to the new struct landlock_object and wait for all
-    iput() to complete.
-  - Rewrite get_inode_object() according to the new struct
-    landlock_object.  If there is a race-condition when cleaning up an
-    object, we retry until the concurrent thread finished the object
-    cleaning.
-  Cf. https://lore.kernel.org/lkml/CAG48ez21bEn0wL1bbmTiiu8j9jP5iEWtHOwz4tURUJ+ki0ydYw@mail.gmail.com/
-* Fix nested domains by implementing a notion of layer level and depth:
-  - Check for matching level ranges when walking through a file path.
-  - Only allow access if every layer granted the access request.
-* Handles files without mount points (e.g. pipes).
-* Hardens path walk by checking inode pointer values.
-* Prefetches d_parent when walking to the root directory.
-* Remove useless inode_alloc_security hook() (suggested by Jann Horn):
-  already initialized by lsm_inode_alloc().
-* Remove the inode_free_security hook.
-* Remove access checks 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.
-* Constify variables.
-* Move ABI checks into syscall.c .
-* Cosmetic variable renames.
-
-Changes since v11:
-* Add back, revamp and make a fully working filesystem access-control
-  based on paths and inodes.
-* Remove the eBPF dependency.
-
-Previous changes:
-https://lore.kernel.org/lkml/20190721213116.23476-6-mic@digikod.net/
----
- MAINTAINERS                   |   1 +
- arch/Kconfig                  |   7 +
- arch/um/Kconfig               |   1 +
- include/uapi/linux/landlock.h |  75 +++++
- security/landlock/Kconfig     |   2 +-
- security/landlock/Makefile    |   2 +-
- security/landlock/fs.c        | 601 ++++++++++++++++++++++++++++++++++
- security/landlock/fs.h        |  60 ++++
- security/landlock/setup.c     |   7 +
- security/landlock/setup.h     |   2 +
- 10 files changed, 756 insertions(+), 2 deletions(-)
- create mode 100644 include/uapi/linux/landlock.h
- create mode 100644 security/landlock/fs.c
- create mode 100644 security/landlock/fs.h
-
-diff --git a/MAINTAINERS b/MAINTAINERS
-index 38f7d50008e7..d46066e172a1 100644
---- a/MAINTAINERS
-+++ b/MAINTAINERS
-@@ -9832,6 +9832,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/arch/Kconfig b/arch/Kconfig
-index 56b6ccc0e32d..7da605bd966d 100644
---- a/arch/Kconfig
-+++ b/arch/Kconfig
-@@ -884,6 +884,13 @@ config COMPAT_32BIT_TIME
- config ARCH_NO_PREEMPT
- 	bool
- 
-+config ARCH_EPHEMERAL_INODES
-+	def_bool n
-+	help
-+	  An arch should select this symbol if it doesn't keep track of inode
-+	  instances on its own, but instead relies on something else (e.g. the host
-+	  kernel for an UML kernel).
-+
- config ARCH_SUPPORTS_RT
- 	bool
- 
-diff --git a/arch/um/Kconfig b/arch/um/Kconfig
-index 4b799fad8b48..082d0207a7be 100644
---- a/arch/um/Kconfig
-+++ b/arch/um/Kconfig
-@@ -5,6 +5,7 @@ menu "UML-specific options"
- config UML
- 	bool
- 	default y
-+	select ARCH_EPHEMERAL_INODES
- 	select ARCH_HAS_KCOV
- 	select ARCH_NO_PREEMPT
- 	select HAVE_ARCH_AUDITSYSCALL
-diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
-new file mode 100644
-index 000000000000..d547bd49fe38
---- /dev/null
-+++ b/include/uapi/linux/landlock.h
-@@ -0,0 +1,75 @@
-+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
-+/*
-+ * Landlock - User space API
-+ *
-+ * 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_path_beneath_attr) including a bitmask of access.
-+ *
-+ * Filesystem flags
-+ * ~~~~~~~~~~~~~~~~
-+ *
-+ * These flags enable to restrict a sandboxed 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: Open a file with write access.
-+ * - %LANDLOCK_ACCESS_FS_READ_FILE: Open a file with read access.
-+ *
-+ * A directory can receive access rights related to files or directories.  The
-+ * following access right is applied to the directory itself, and the
-+ * directories beneath it:
-+ *
-+ * - %LANDLOCK_ACCESS_FS_READ_DIR: Open a directory or list its content.
-+ *
-+ * 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) 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_REMOVE_DIR			(1ULL << 4)
-+#define LANDLOCK_ACCESS_FS_REMOVE_FILE			(1ULL << 5)
-+#define LANDLOCK_ACCESS_FS_MAKE_CHAR			(1ULL << 6)
-+#define LANDLOCK_ACCESS_FS_MAKE_DIR			(1ULL << 7)
-+#define LANDLOCK_ACCESS_FS_MAKE_REG			(1ULL << 8)
-+#define LANDLOCK_ACCESS_FS_MAKE_SOCK			(1ULL << 9)
-+#define LANDLOCK_ACCESS_FS_MAKE_FIFO			(1ULL << 10)
-+#define LANDLOCK_ACCESS_FS_MAKE_BLOCK			(1ULL << 11)
-+#define LANDLOCK_ACCESS_FS_MAKE_SYM			(1ULL << 12)
-+
-+#endif /* _UAPI__LINUX_LANDLOCK_H__ */
-diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig
-index 48dd213ca5eb..cbf88bb7fd97 100644
---- a/security/landlock/Kconfig
-+++ b/security/landlock/Kconfig
-@@ -2,7 +2,7 @@
- 
- config SECURITY_LANDLOCK
- 	bool "Landlock support"
--	depends on SECURITY
-+	depends on SECURITY && !ARCH_EPHEMERAL_INODES
- 	select SECURITY_PATH
- 	help
- 	  Landlock is a safe sandboxing mechanism which enables processes to
-diff --git a/security/landlock/Makefile b/security/landlock/Makefile
-index f1d1eb72fa76..92e3d80ab8ed 100644
---- a/security/landlock/Makefile
-+++ b/security/landlock/Makefile
-@@ -1,4 +1,4 @@
- obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
- 
- landlock-y := setup.o object.o ruleset.o \
--	cred.o ptrace.o
-+	cred.o ptrace.o fs.o
-diff --git a/security/landlock/fs.c b/security/landlock/fs.c
-new file mode 100644
-index 000000000000..d8c5d19ac2af
---- /dev/null
-+++ b/security/landlock/fs.c
-@@ -0,0 +1,601 @@
-+// SPDX-License-Identifier: GPL-2.0-only
-+/*
-+ * Landlock LSM - Filesystem management and hooks
-+ *
-+ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
-+ * Copyright © 2018-2020 ANSSI
-+ */
-+
-+#include <linux/atomic.h>
-+#include <linux/compiler_types.h>
-+#include <linux/dcache.h>
-+#include <linux/fs.h>
-+#include <linux/init.h>
-+#include <linux/kernel.h>
-+#include <linux/list.h>
-+#include <linux/lsm_hooks.h>
-+#include <linux/mount.h>
-+#include <linux/namei.h>
-+#include <linux/path.h>
-+#include <linux/rcupdate.h>
-+#include <linux/spinlock.h>
-+#include <linux/stat.h>
-+#include <linux/types.h>
-+#include <linux/wait_bit.h>
-+#include <linux/workqueue.h>
-+#include <uapi/linux/landlock.h>
-+
-+#include "common.h"
-+#include "cred.h"
-+#include "fs.h"
-+#include "object.h"
-+#include "ruleset.h"
-+#include "setup.h"
-+
-+/* Underlying object management */
-+
-+static void release_inode(struct landlock_object *const object)
-+	__releases(object->lock)
-+{
-+	struct inode *const inode = object->underobj;
-+	struct super_block *sb;
-+
-+	if (!inode) {
-+		spin_unlock(&object->lock);
-+		return;
-+	}
-+
-+	spin_lock(&inode->i_lock);
-+	/*
-+	 * Make sure that if the filesystem is concurrently unmounted,
-+	 * hook_sb_delete() will wait for us to finish iput().
-+	 */
-+	sb = inode->i_sb;
-+	atomic_long_inc(&landlock_superblock(sb)->inode_refs);
-+	rcu_assign_pointer(landlock_inode(inode)->object, NULL);
-+	spin_unlock(&inode->i_lock);
-+	spin_unlock(&object->lock);
-+	/*
-+	 * Now, new rules can safely be tied to @inode.
-+	 */
-+
-+	iput(inode);
-+	if (atomic_long_dec_and_test(&landlock_superblock(sb)->inode_refs))
-+		wake_up_var(&landlock_superblock(sb)->inode_refs);
-+}
-+
-+static const struct landlock_object_underops landlock_fs_underops = {
-+	.release = release_inode
-+};
-+
-+/* Ruleset management */
-+
-+static struct landlock_object *get_inode_object(struct inode *const inode)
-+{
-+	struct landlock_object *object, *new_object;
-+	struct landlock_inode_security *inode_sec = landlock_inode(inode);
-+
-+	rcu_read_lock();
-+retry:
-+	object = rcu_dereference(inode_sec->object);
-+	if (object) {
-+		if (likely(refcount_inc_not_zero(&object->usage))) {
-+			rcu_read_unlock();
-+			return object;
-+		}
-+		/*
-+		 * We're racing with release_inode(), the object is going away.
-+		 * Wait for release_inode(), then retry.
-+		 */
-+		spin_lock(&object->lock);
-+		spin_unlock(&object->lock);
-+		goto retry;
-+	}
-+	rcu_read_unlock();
-+
-+	/*
-+	 * If there is no object tied to @inode, then create a new one (without
-+	 * holding any locks).
-+	 */
-+	new_object = landlock_create_object(&landlock_fs_underops, inode);
-+
-+	spin_lock(&inode->i_lock);
-+	object = rcu_dereference_protected(inode_sec->object,
-+			lockdep_is_held(&inode->i_lock));
-+	if (unlikely(object)) {
-+		/* Someone else just created the object, bail out and retry. */
-+		kfree(new_object);
-+		spin_unlock(&inode->i_lock);
-+
-+		rcu_read_lock();
-+		goto retry;
-+	} else {
-+		rcu_assign_pointer(inode_sec->object, new_object);
-+		/*
-+		 * @inode will be released by hook_sb_delete() on its
-+		 * superblock shutdown.
-+		 */
-+		ihold(inode);
-+		spin_unlock(&inode->i_lock);
-+		return new_object;
-+	}
-+}
-+
-+/* All access rights which can be tied to files. */
-+#define ACCESS_FILE ( \
-+	LANDLOCK_ACCESS_FS_EXECUTE | \
-+	LANDLOCK_ACCESS_FS_WRITE_FILE | \
-+	LANDLOCK_ACCESS_FS_READ_FILE)
-+
-+/*
-+ * @path: Should have been checked by get_path_from_fd().
-+ */
-+int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
-+		const struct path *const path, u32 access_rights)
-+{
-+	int err;
-+	struct landlock_rule rule = {};
-+
-+	/* Files only get access rights that make sense. */
-+	if (!d_is_dir(path->dentry) && (access_rights | ACCESS_FILE) !=
-+			ACCESS_FILE)
-+		return -EINVAL;
-+
-+	/* Transforms relative access rights to absolute ones. */
-+	access_rights |= _LANDLOCK_ACCESS_FS_MASK & ~ruleset->fs_access_mask;
-+	rule.access = access_rights;
-+	rule.object = get_inode_object(d_backing_inode(path->dentry));
-+	mutex_lock(&ruleset->lock);
-+	err = landlock_insert_rule(ruleset, &rule, false);
-+	mutex_unlock(&ruleset->lock);
-+	/*
-+	 * No need to check for an error because landlock_insert_rule()
-+	 * increments the refcount for the new rule, if any.
-+	 */
-+	landlock_put_object(rule.object);
-+	return err;
-+}
-+
-+/* Access-control management */
-+
-+static bool check_access_path_continue(
-+		const struct landlock_ruleset *const domain,
-+		const struct path *const path, const u32 access_request,
-+		u64 *const layer_mask)
-+{
-+	const struct landlock_rule *rule;
-+	const struct inode *inode;
-+
-+	if (d_is_negative(path->dentry))
-+		/* Continues to walk while there is no mapped inode. */
-+		return true;
-+	inode = d_backing_inode(path->dentry);
-+	rcu_read_lock();
-+	rule = landlock_find_rule(domain,
-+			rcu_dereference(landlock_inode(inode)->object));
-+	rcu_read_unlock();
-+
-+	/* Checks for matching layers. */
-+	if (rule && (rule->layers | *layer_mask)) {
-+		if ((rule->access & access_request) == access_request) {
-+			*layer_mask &= ~rule->layers;
-+			return true;
-+		} else {
-+			return false;
-+		}
-+	}
-+	return true;
-+}
-+
-+static int check_access_path(const struct landlock_ruleset *const domain,
-+		const struct path *const path, u32 access_request)
-+{
-+	bool allowed = false;
-+	struct path walker_path;
-+	u64 layer_mask;
-+
-+	if (WARN_ON_ONCE(!domain || !path))
-+		return 0;
-+	/*
-+	 * Allows access to pseudo filesystems that will never be mountable
-+	 * (e.g. sockfs, pipefs), but can still be reachable through
-+	 * /proc/self/fd .
-+	 */
-+	if ((path->dentry->d_sb->s_flags & SB_NOUSER) ||
-+			(d_is_positive(path->dentry) &&
-+			 unlikely(IS_PRIVATE(d_backing_inode(path->dentry)))))
-+		return 0;
-+	if (WARN_ON_ONCE(domain->nb_layers < 1))
-+		return -EACCES;
-+
-+	layer_mask = GENMASK_ULL(domain->nb_layers - 1, 0);
-+	/*
-+	 * An access request which is not handled by the domain should be
-+	 * allowed.
-+	 */
-+	access_request &= domain->fs_access_mask;
-+	if (access_request == 0)
-+		return 0;
-+	walker_path = *path;
-+	path_get(&walker_path);
-+	/*
-+	 * We need to walk through all the hierarchy to not miss any relevant
-+	 * restriction.
-+	 */
-+	while (check_access_path_continue(domain, &walker_path, access_request,
-+				&layer_mask)) {
-+		struct dentry *parent_dentry;
-+
-+		/* Stops when a rule from each layer granted access. */
-+		if (layer_mask == 0) {
-+			allowed = true;
-+			break;
-+		}
-+
-+jump_up:
-+		/*
-+		 * Does not work with orphaned/private mounts like overlayfs
-+		 * layers for now (cf. ovl_path_real() and ovl_path_open()).
-+		 */
-+		if (walker_path.dentry == walker_path.mnt->mnt_root) {
-+			if (follow_up(&walker_path)) {
-+				/* Ignores hidden mount points. */
-+				goto jump_up;
-+			} else {
-+				/*
-+				 * Stops at the real root.  Denies access
-+				 * because not all layers have granted access.
-+				 */
-+				allowed = false;
-+				break;
-+			}
-+		}
-+		if (unlikely(IS_ROOT(walker_path.dentry))) {
-+			/*
-+			 * Stops at disconnected root directories.  Only allows
-+			 * access to internal filesystems (e.g. nsfs which is
-+			 * reachable through /proc/self/ns).
-+			 */
-+			allowed = !!(walker_path.mnt->mnt_flags & MNT_INTERNAL);
-+			break;
-+		}
-+		parent_dentry = dget_parent(walker_path.dentry);
-+		dput(walker_path.dentry);
-+		walker_path.dentry = parent_dentry;
-+	}
-+	path_put(&walker_path);
-+	return allowed ? 0 : -EACCES;
-+}
-+
-+static inline int current_check_access_path(const struct path *const path,
-+		const u32 access_request)
-+{
-+	const struct landlock_ruleset *const dom =
-+		landlock_get_current_domain();
-+
-+	if (!dom)
-+		return 0;
-+	return check_access_path(dom, path, access_request);
-+}
-+
-+/* Super-block hooks */
-+
-+/*
-+ * Release the inodes used in a security policy.
-+ *
-+ * Cf. fsnotify_unmount_inodes()
-+ */
-+static void hook_sb_delete(struct super_block *const sb)
-+{
-+	struct inode *inode, *iput_inode = NULL;
-+
-+	if (!landlock_initialized)
-+		return;
-+
-+	spin_lock(&sb->s_inode_list_lock);
-+	list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
-+		struct landlock_inode_security *inode_sec =
-+			landlock_inode(inode);
-+		struct landlock_object *object;
-+		bool do_put = false;
-+
-+		rcu_read_lock();
-+		object = rcu_dereference(inode_sec->object);
-+		if (!object) {
-+			rcu_read_unlock();
-+			continue;
-+		}
-+
-+		spin_lock(&object->lock);
-+		if (object->underobj) {
-+			object->underobj = NULL;
-+			do_put = true;
-+			spin_lock(&inode->i_lock);
-+			rcu_assign_pointer(inode_sec->object, NULL);
-+			spin_unlock(&inode->i_lock);
-+		}
-+		spin_unlock(&object->lock);
-+		rcu_read_unlock();
-+		if (!do_put)
-+			/*
-+			 * A concurrent iput() in release_inode() is ongoing
-+			 * and we will just wait for it to finish.
-+			 */
-+			continue;
-+
-+		/*
-+		 * At this point, we own the ihold() reference that was
-+		 * originally set up by get_inode_object(). Therefore we can
-+		 * drop the list lock and know that the inode won't disappear
-+		 * from under us until the next loop walk.
-+		 */
-+		spin_unlock(&sb->s_inode_list_lock);
-+		/*
-+		 * We can now actually put the previous inode, which is not
-+		 * needed anymore for the loop walk.
-+		 */
-+		if (iput_inode)
-+			iput(iput_inode);
-+		iput_inode = inode;
-+		spin_lock(&sb->s_inode_list_lock);
-+	}
-+	spin_unlock(&sb->s_inode_list_lock);
-+	if (iput_inode)
-+		iput(iput_inode);
-+
-+	/*
-+	 * Wait for pending iput() in release_inode().
-+	 */
-+	wait_var_event(&landlock_superblock(sb)->inode_refs, !atomic_long_read(
-+				&landlock_superblock(sb)->inode_refs));
-+}
-+
-+/*
-+ * Because a Landlock security policy is defined according to the filesystem
-+ * layout (i.e. the mount namespace), changing it may grant access to files not
-+ * previously allowed.
-+ *
-+ * To make it simple, deny any filesystem layout modification by landlocked
-+ * processes.  Non-landlocked processes may still change the namespace of a
-+ * landlocked process, but this kind of threat must be handled by a system-wide
-+ * access-control security policy.
-+ *
-+ * This could be lifted in the future if Landlock can safely handle mount
-+ * namespace updates requested by a landlocked process.  Indeed, we could
-+ * update the current domain (which is currently read-only) by taking into
-+ * account the accesses of the source and the destination of a new mount point.
-+ * However, it would also require to make all the child domains dynamically
-+ * inherit these new constraints.  Anyway, for backward compatibility reasons,
-+ * a dedicated user space option would be required (e.g. as a ruleset command
-+ * option).
-+ */
-+static int hook_sb_mount(const char *const dev_name,
-+		const struct path *const path, const char *const type,
-+		const unsigned long flags, void *const data)
-+{
-+	if (!landlock_get_current_domain())
-+		return 0;
-+	return -EPERM;
-+}
-+
-+static int hook_move_mount(const struct path *const from_path,
-+		const struct path *const to_path)
-+{
-+	if (!landlock_get_current_domain())
-+		return 0;
-+	return -EPERM;
-+}
-+
-+/*
-+ * Removing a mount point may reveal a previously hidden file hierarchy, which
-+ * may then grant access to files, which may have previously been forbidden.
-+ */
-+static int hook_sb_umount(struct vfsmount *const mnt, const int flags)
-+{
-+	if (!landlock_get_current_domain())
-+		return 0;
-+	return -EPERM;
-+}
-+
-+static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts)
-+{
-+	if (!landlock_get_current_domain())
-+		return 0;
-+	return -EPERM;
-+}
-+
-+/*
-+ * pivot_root(2), like mount(2), changes the current mount namespace.  It must
-+ * then be forbidden for a landlocked process.
-+ *
-+ * However, chroot(2) may be allowed because it only changes the relative root
-+ * directory of the current process.  Moreover, it can be used to restrict the
-+ * view of the filesystem.
-+ */
-+static int hook_sb_pivotroot(const struct path *const old_path,
-+		const struct path *const new_path)
-+{
-+	if (!landlock_get_current_domain())
-+		return 0;
-+	return -EPERM;
-+}
-+
-+/* Path hooks */
-+
-+static inline u32 get_mode_access(const umode_t mode)
-+{
-+	switch (mode & S_IFMT) {
-+	case S_IFLNK:
-+		return LANDLOCK_ACCESS_FS_MAKE_SYM;
-+	case 0:
-+		/* A zero mode translates to S_IFREG. */
-+	case S_IFREG:
-+		return LANDLOCK_ACCESS_FS_MAKE_REG;
-+	case S_IFDIR:
-+		return LANDLOCK_ACCESS_FS_MAKE_DIR;
-+	case S_IFCHR:
-+		return LANDLOCK_ACCESS_FS_MAKE_CHAR;
-+	case S_IFBLK:
-+		return LANDLOCK_ACCESS_FS_MAKE_BLOCK;
-+	case S_IFIFO:
-+		return LANDLOCK_ACCESS_FS_MAKE_FIFO;
-+	case S_IFSOCK:
-+		return LANDLOCK_ACCESS_FS_MAKE_SOCK;
-+	default:
-+		WARN_ON_ONCE(1);
-+		return 0;
-+	}
-+}
-+
-+/*
-+ * Creating multiple links or renaming may lead to privilege escalations if not
-+ * handled properly.  Indeed, we must be sure that the source doesn't gain more
-+ * privileges by being accessible from the destination.  This is getting more
-+ * complex when dealing with multiple layers.  The whole picture can be seen as
-+ * a multilayer partial ordering problem.  A future version of Landlock will
-+ * deal with that.
-+ */
-+static int hook_path_link(struct dentry *const old_dentry,
-+		const struct path *const new_dir,
-+		struct dentry *const new_dentry)
-+{
-+	const struct landlock_ruleset *const dom =
-+		landlock_get_current_domain();
-+
-+	if (!dom)
-+		return 0;
-+	/* The mount points are the same for old and new paths, cf. EXDEV. */
-+	if (old_dentry->d_parent != new_dir->dentry)
-+		/* For now, forbid reparenting. */
-+		return -EACCES;
-+	if (unlikely(d_is_negative(old_dentry)))
-+		return -EACCES;
-+	return check_access_path(dom, new_dir,
-+			get_mode_access(d_backing_inode(old_dentry)->i_mode));
-+}
-+
-+static inline u32 maybe_remove(const struct dentry *const dentry)
-+{
-+	if (d_is_negative(dentry))
-+		return 0;
-+	return d_is_dir(dentry) ? LANDLOCK_ACCESS_FS_REMOVE_DIR :
-+		LANDLOCK_ACCESS_FS_REMOVE_FILE;
-+}
-+
-+static int hook_path_rename(const struct path *const old_dir,
-+		struct dentry *const old_dentry,
-+		const struct path *const new_dir,
-+		struct dentry *const new_dentry)
-+{
-+	const struct landlock_ruleset *const dom =
-+		landlock_get_current_domain();
-+
-+	if (!dom)
-+		return 0;
-+	/* The mount points are the same for old and new paths, cf. EXDEV. */
-+	if (old_dir->dentry != new_dir->dentry)
-+		/* For now, forbid reparenting. */
-+		return -EACCES;
-+	if (WARN_ON_ONCE(d_is_negative(old_dentry)))
-+		return -EACCES;
-+	/* RENAME_EXCHANGE is handled because directories are the same. */
-+	return check_access_path(dom, old_dir, maybe_remove(old_dentry) |
-+			maybe_remove(new_dentry) |
-+			get_mode_access(d_backing_inode(old_dentry)->i_mode));
-+}
-+
-+static int hook_path_mkdir(const struct path *const dir,
-+		struct dentry *const dentry, const umode_t mode)
-+{
-+	return current_check_access_path(dir, LANDLOCK_ACCESS_FS_MAKE_DIR);
-+}
-+
-+static int hook_path_mknod(const struct path *const dir,
-+		struct dentry *const dentry, const umode_t mode,
-+		const unsigned int dev)
-+{
-+	const struct landlock_ruleset *const dom =
-+		landlock_get_current_domain();
-+
-+	if (!dom)
-+		return 0;
-+	return check_access_path(dom, dir, get_mode_access(mode));
-+}
-+
-+static int hook_path_symlink(const struct path *const dir,
-+		struct dentry *const dentry, const char *const old_name)
-+{
-+	return current_check_access_path(dir, LANDLOCK_ACCESS_FS_MAKE_SYM);
-+}
-+
-+static int hook_path_unlink(const struct path *const dir,
-+		struct dentry *const dentry)
-+{
-+	return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_FILE);
-+}
-+
-+static int hook_path_rmdir(const struct path *const dir,
-+		struct dentry *const dentry)
-+{
-+	return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_DIR);
-+}
-+
-+/* File hooks */
-+
-+static inline u32 get_file_access(const struct file *const file)
-+{
-+	u32 access = 0;
-+
-+	if (file->f_mode & FMODE_READ) {
-+		/* A directory can only be opened in read mode. */
-+		if (S_ISDIR(file_inode(file)->i_mode))
-+			return LANDLOCK_ACCESS_FS_READ_DIR;
-+		access = LANDLOCK_ACCESS_FS_READ_FILE;
-+	}
-+	if (file->f_mode & FMODE_WRITE)
-+		access |= LANDLOCK_ACCESS_FS_WRITE_FILE;
-+	/* __FMODE_EXEC is indeed part of f_flags, not f_mode. */
-+	if (file->f_flags & __FMODE_EXEC)
-+		access |= LANDLOCK_ACCESS_FS_EXECUTE;
-+	return access;
-+}
-+
-+static int hook_file_open(struct file *const file)
-+{
-+	const struct landlock_ruleset *const dom =
-+		landlock_get_current_domain();
-+
-+	if (!dom)
-+		return 0;
-+	/*
-+	 * Because a file may be opened with O_PATH, get_file_access() may
-+	 * return 0.  This case will be handled with a future Landlock
-+	 * evolution.
-+	 */
-+	return current_check_access_path(&file->f_path, get_file_access(file));
-+}
-+
-+static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = {
-+	LSM_HOOK_INIT(sb_delete, hook_sb_delete),
-+	LSM_HOOK_INIT(sb_mount, hook_sb_mount),
-+	LSM_HOOK_INIT(move_mount, hook_move_mount),
-+	LSM_HOOK_INIT(sb_umount, hook_sb_umount),
-+	LSM_HOOK_INIT(sb_remount, hook_sb_remount),
-+	LSM_HOOK_INIT(sb_pivotroot, hook_sb_pivotroot),
-+
-+	LSM_HOOK_INIT(path_link, hook_path_link),
-+	LSM_HOOK_INIT(path_rename, hook_path_rename),
-+	LSM_HOOK_INIT(path_mkdir, hook_path_mkdir),
-+	LSM_HOOK_INIT(path_mknod, hook_path_mknod),
-+	LSM_HOOK_INIT(path_symlink, hook_path_symlink),
-+	LSM_HOOK_INIT(path_unlink, hook_path_unlink),
-+	LSM_HOOK_INIT(path_rmdir, hook_path_rmdir),
-+
-+	LSM_HOOK_INIT(file_open, hook_file_open),
-+};
-+
-+__init void landlock_add_hooks_fs(void)
-+{
-+	security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
-+			LANDLOCK_NAME);
-+}
-diff --git a/security/landlock/fs.h b/security/landlock/fs.h
-new file mode 100644
-index 000000000000..58b462eb7f10
---- /dev/null
-+++ b/security/landlock/fs.h
-@@ -0,0 +1,60 @@
-+/* SPDX-License-Identifier: GPL-2.0-only */
-+/*
-+ * Landlock LSM - Filesystem management and hooks
-+ *
-+ * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
-+ * Copyright © 2018-2020 ANSSI
-+ */
-+
-+#ifndef _SECURITY_LANDLOCK_FS_H
-+#define _SECURITY_LANDLOCK_FS_H
-+
-+#include <linux/fs.h>
-+#include <linux/init.h>
-+#include <linux/rcupdate.h>
-+#include <uapi/linux/landlock.h>
-+
-+#include "ruleset.h"
-+#include "setup.h"
-+
-+#define _LANDLOCK_ACCESS_FS_LAST	LANDLOCK_ACCESS_FS_MAKE_SYM
-+#define _LANDLOCK_ACCESS_FS_MASK	((_LANDLOCK_ACCESS_FS_LAST << 1) - 1)
-+
-+struct landlock_inode_security {
-+	/*
-+	 * @object: Weak pointer to an allocated object.  All writes (i.e.
-+	 * creating a new object or removing one) are protected by the
-+	 * underlying inode->i_lock.  Disassociating @object from the inode is
-+	 * additionally protected by @object->lock, from the time @object's
-+	 * usage refcount drops to zero to the time this pointer is nulled out.
-+	 * Cf. release_inode().
-+	 */
-+	struct landlock_object __rcu *object;
-+};
-+
-+struct landlock_superblock_security {
-+	/*
-+	 * @inode_refs: References to Landlock underlying objects.
-+	 * Cf. struct super_block->s_fsnotify_inode_refs .
-+	 */
-+	atomic_long_t inode_refs;
-+};
-+
-+static inline struct landlock_inode_security *landlock_inode(
-+		const struct inode *const inode)
-+{
-+	return inode->i_security + landlock_blob_sizes.lbs_inode;
-+}
-+
-+static inline struct landlock_superblock_security *landlock_superblock(
-+		const struct super_block *const superblock)
-+{
-+	return superblock->s_security + landlock_blob_sizes.lbs_superblock;
-+}
-+
-+__init void landlock_add_hooks_fs(void);
-+
-+int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
-+		const struct path *const path, u32 access_hierarchy);
-+
-+#endif /* _SECURITY_LANDLOCK_FS_H */
-diff --git a/security/landlock/setup.c b/security/landlock/setup.c
-index 5e7540fdeefa..722cbea82324 100644
---- a/security/landlock/setup.c
-+++ b/security/landlock/setup.c
-@@ -11,17 +11,24 @@
- 
- #include "common.h"
- #include "cred.h"
-+#include "fs.h"
- #include "ptrace.h"
- #include "setup.h"
- 
-+bool landlock_initialized __lsm_ro_after_init = false;
-+
- struct lsm_blob_sizes landlock_blob_sizes __lsm_ro_after_init = {
- 	.lbs_cred = sizeof(struct landlock_cred_security),
-+	.lbs_inode = sizeof(struct landlock_inode_security),
-+	.lbs_superblock = sizeof(struct landlock_superblock_security),
- };
- 
- static int __init landlock_init(void)
- {
- 	landlock_add_hooks_cred();
- 	landlock_add_hooks_ptrace();
-+	landlock_add_hooks_fs();
-+	landlock_initialized = true;
- 	pr_info("Up and running.\n");
- 	return 0;
- }
-diff --git a/security/landlock/setup.h b/security/landlock/setup.h
-index 9fdbf33fcc33..1daffab1ab4b 100644
---- a/security/landlock/setup.h
-+++ b/security/landlock/setup.h
-@@ -11,6 +11,8 @@
- 
- #include <linux/lsm_hooks.h>
- 
-+extern bool landlock_initialized;
-+
- extern struct lsm_blob_sizes landlock_blob_sizes;
- 
- #endif /* _SECURITY_LANDLOCK_SETUP_H */
--- 
-2.28.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