Inter-revision diff: patch 10

Comparing v10 (message) to v8 (message)

--- v10
+++ v8
@@ -1,347 +1,1230 @@
-This documentation can be built with the Sphinx framework.
+Test basic context access, ptrace protection and filesystem hooks and
+Landlock program chaining with multiple cases.
 
-Signed-off-by: Mickaël Salaün <mic@digikod.net>
+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: David S. Miller <davem@davemloft.net>
-Cc: James Morris <jmorris@namei.org>
-Cc: Jonathan Corbet <corbet@lwn.net>
+Cc: James Morris <james.l.morris@oracle.com>
 Cc: Kees Cook <keescook@chromium.org>
 Cc: Serge E. Hallyn <serge@hallyn.com>
+Cc: Shuah Khan <shuah@kernel.org>
+Cc: Will Drewry <wad@chromium.org>
 ---
 
-Changes since v9:
-* update with expected attach type and expected attach triggers
-
-Changes since v8:
-* remove documentation related to chaining and tagging according to this
-  patch series
-
 Changes since v7:
-* update documentation according to the Landlock revamp
+* update tests and add new ones for filesystem hierarchy and Landlock
+  chains.
 
 Changes since v6:
-* add a check for ctx->event
+* use the new kselftest_harness.h
+* use const variables
+* replace ASSERT_STEP with ASSERT_*
 * rename BPF_PROG_TYPE_LANDLOCK to BPF_PROG_TYPE_LANDLOCK_RULE
-* rename Landlock version to ABI to better reflect its purpose and add a
-  dedicated changelog section
-* update tables
-* relax no_new_privs recommendations
-* remove ABILITY_WRITE related functions
-* reword rule "appending" to "prepending" and explain it
-* cosmetic fixes
+* force sample library rebuild
+* fix install target
 
 Changes since v5:
-* update the rule hierarchy inheritance explanation
-* briefly explain ctx->arg2
-* add ptrace restrictions
-* explain EPERM
-* update example (subtype)
-* use ":manpage:"
+* add subtype test
+* add ptrace tests
+* split and rename files
+* cleanup and rebase
 ---
- Documentation/security/index.rst           |   1 +
- Documentation/security/landlock/index.rst  |  20 +++
- Documentation/security/landlock/kernel.rst |  99 ++++++++++++++
- Documentation/security/landlock/user.rst   | 147 +++++++++++++++++++++
- 4 files changed, 267 insertions(+)
- create mode 100644 Documentation/security/landlock/index.rst
- create mode 100644 Documentation/security/landlock/kernel.rst
- create mode 100644 Documentation/security/landlock/user.rst
+ tools/testing/selftests/Makefile               |   1 +
+ tools/testing/selftests/bpf/bpf_helpers.h      |   7 +
+ tools/testing/selftests/bpf/test_verifier.c    |  84 +++++
+ tools/testing/selftests/landlock/.gitignore    |   5 +
+ tools/testing/selftests/landlock/Makefile      |  35 ++
+ tools/testing/selftests/landlock/test.h        |  31 ++
+ tools/testing/selftests/landlock/test_base.c   |  27 ++
+ tools/testing/selftests/landlock/test_chain.c  | 249 +++++++++++++
+ tools/testing/selftests/landlock/test_fs.c     | 492 +++++++++++++++++++++++++
+ tools/testing/selftests/landlock/test_ptrace.c | 158 ++++++++
+ 10 files changed, 1089 insertions(+)
+ create mode 100644 tools/testing/selftests/landlock/.gitignore
+ create mode 100644 tools/testing/selftests/landlock/Makefile
+ create mode 100644 tools/testing/selftests/landlock/test.h
+ create mode 100644 tools/testing/selftests/landlock/test_base.c
+ create mode 100644 tools/testing/selftests/landlock/test_chain.c
+ create mode 100644 tools/testing/selftests/landlock/test_fs.c
+ create mode 100644 tools/testing/selftests/landlock/test_ptrace.c
 
-diff --git a/Documentation/security/index.rst b/Documentation/security/index.rst
-index aad6d92ffe31..32b4c1db2325 100644
---- a/Documentation/security/index.rst
-+++ b/Documentation/security/index.rst
-@@ -12,3 +12,4 @@ Security Documentation
-    SCTP
-    self-protection
-    tpm/index
-+   landlock/index
-diff --git a/Documentation/security/landlock/index.rst b/Documentation/security/landlock/index.rst
+diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
+index 7442dfb73b7f..5d00deb3cab6 100644
+--- a/tools/testing/selftests/Makefile
++++ b/tools/testing/selftests/Makefile
+@@ -14,6 +14,7 @@ TARGETS += gpio
+ TARGETS += intel_pstate
+ TARGETS += ipc
+ TARGETS += kcmp
++TARGETS += landlock
+ TARGETS += lib
+ TARGETS += membarrier
+ TARGETS += memfd
+diff --git a/tools/testing/selftests/bpf/bpf_helpers.h b/tools/testing/selftests/bpf/bpf_helpers.h
+index dde2c11d7771..414e267491f7 100644
+--- a/tools/testing/selftests/bpf/bpf_helpers.h
++++ b/tools/testing/selftests/bpf/bpf_helpers.h
+@@ -86,6 +86,13 @@ static int (*bpf_perf_prog_read_value)(void *ctx, void *buf,
+ 	(void *) BPF_FUNC_perf_prog_read_value;
+ static int (*bpf_override_return)(void *ctx, unsigned long rc) =
+ 	(void *) BPF_FUNC_override_return;
++static unsigned long long (*bpf_inode_map_lookup)(void *map, void *key) =
++	(void *) BPF_FUNC_inode_map_lookup;
++static unsigned long long (*bpf_inode_get_tag)(void *inode, void *chain) =
++	(void *) BPF_FUNC_inode_get_tag;
++static unsigned long long (*bpf_landlock_set_tag)(void *tag_obj, void *chain,
++						  unsigned long long value) =
++	(void *) BPF_FUNC_landlock_set_tag;
+ 
+ /* llvm builtin functions that eBPF C program may use to
+  * emit BPF_LD_ABS and BPF_LD_IND instructions
+diff --git a/tools/testing/selftests/bpf/test_verifier.c b/tools/testing/selftests/bpf/test_verifier.c
+index 3c24a5a7bafc..5f68b95187fe 100644
+--- a/tools/testing/selftests/bpf/test_verifier.c
++++ b/tools/testing/selftests/bpf/test_verifier.c
+@@ -31,6 +31,7 @@
+ #include <linux/bpf_perf_event.h>
+ #include <linux/bpf.h>
+ #include <linux/if_ether.h>
++#include <linux/landlock.h>
+ 
+ #include <bpf/bpf.h>
+ 
+@@ -11240,6 +11241,89 @@ static struct bpf_test tests[] = {
+ 		.result = REJECT,
+ 		.has_prog_subtype = true,
+ 	},
++	{
++		"missing subtype",
++		.insns = {
++			BPF_MOV32_IMM(BPF_REG_0, 0),
++			BPF_EXIT_INSN(),
++		},
++		.errstr = "",
++		.result = REJECT,
++		.prog_type = BPF_PROG_TYPE_LANDLOCK_HOOK,
++	},
++	{
++		"landlock/fs_pick: always accept",
++		.insns = {
++			BPF_MOV32_IMM(BPF_REG_0, 0),
++			BPF_EXIT_INSN(),
++		},
++		.result = ACCEPT,
++		.prog_type = BPF_PROG_TYPE_LANDLOCK_HOOK,
++		.has_prog_subtype = true,
++		.prog_subtype = {
++			.landlock_hook = {
++				.type = LANDLOCK_HOOK_FS_PICK,
++				.triggers = LANDLOCK_TRIGGER_FS_PICK_READ,
++			}
++		},
++	},
++	{
++		"landlock/fs_pick: read context",
++		.insns = {
++			BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
++			BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_6,
++				offsetof(struct landlock_ctx_fs_pick, cookie)),
++			/* test operations on raw values */
++			BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
++			BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_6,
++				offsetof(struct landlock_ctx_fs_pick, inode)),
++			BPF_MOV32_IMM(BPF_REG_0, 0),
++			BPF_EXIT_INSN(),
++		},
++		.result = ACCEPT,
++		.prog_type = BPF_PROG_TYPE_LANDLOCK_HOOK,
++		.has_prog_subtype = true,
++		.prog_subtype = {
++			.landlock_hook = {
++				.type = LANDLOCK_HOOK_FS_PICK,
++				.triggers = LANDLOCK_TRIGGER_FS_PICK_READ,
++			}
++		},
++	},
++	{
++		"landlock/fs_pick: no option for previous program",
++		.insns = {
++			BPF_MOV32_IMM(BPF_REG_0, 0),
++			BPF_EXIT_INSN(),
++		},
++		.errstr = "",
++		.result = REJECT,
++		.prog_type = BPF_PROG_TYPE_LANDLOCK_HOOK,
++		.prog_subtype = {
++			.landlock_hook = {
++				.type = LANDLOCK_HOOK_FS_PICK,
++				.previous = 1,
++			}
++		},
++	},
++	{
++		"landlock/fs_pick: bad previous program FD",
++		.insns = {
++			BPF_MOV32_IMM(BPF_REG_0, 0),
++			BPF_EXIT_INSN(),
++		},
++		.errstr = "",
++		.result = REJECT,
++		.prog_type = BPF_PROG_TYPE_LANDLOCK_HOOK,
++		.prog_subtype = {
++			.landlock_hook = {
++				.type = LANDLOCK_HOOK_FS_PICK,
++				.options = LANDLOCK_OPTION_PREVIOUS,
++				/* assume FD 0 is a TTY or a pipe */
++				.previous = 0,
++			}
++		},
++	},
+ };
+ 
+ static int probe_filter_length(const struct bpf_insn *fp)
+diff --git a/tools/testing/selftests/landlock/.gitignore b/tools/testing/selftests/landlock/.gitignore
 new file mode 100644
-index 000000000000..d0af868d1582
+index 000000000000..d4e365980c9c
 --- /dev/null
-+++ b/Documentation/security/landlock/index.rst
-@@ -0,0 +1,20 @@
-+=========================================
-+Landlock LSM: programmatic access control
-+=========================================
-+
-+Landlock is a stackable Linux Security Module (LSM) that makes it possible to
-+create security sandboxes, programmable access-controls or safe endpoint
-+security agents.  This kind of sandbox is expected to help mitigate the
-+security impact of bugs or unexpected/malicious behaviors in user-space
-+applications.  The current version allows only a process with the global
-+CAP_SYS_ADMIN capability to create such sandboxes but the ultimate goal of
-+Landlock is to empower any process, including unprivileged ones, to securely
-+restrict themselves.  Landlock is inspired by seccomp-bpf but instead of
-+filtering syscalls and their raw arguments, a Landlock rule can inspect the use
-+of kernel objects like files and hence make a decision according to the kernel
-+semantic.
-+
-+.. toctree::
-+
-+    user
-+    kernel
-diff --git a/Documentation/security/landlock/kernel.rst b/Documentation/security/landlock/kernel.rst
++++ b/tools/testing/selftests/landlock/.gitignore
+@@ -0,0 +1,5 @@
++/test_base
++/test_chain
++/test_fs
++/test_ptrace
++/tmp_*
+diff --git a/tools/testing/selftests/landlock/Makefile b/tools/testing/selftests/landlock/Makefile
 new file mode 100644
-index 000000000000..7d1e06d544bf
+index 000000000000..9b2791ded1cc
 --- /dev/null
-+++ b/Documentation/security/landlock/kernel.rst
-@@ -0,0 +1,99 @@
-+==============================
-+Landlock: kernel documentation
-+==============================
-+
-+eBPF properties
-+===============
-+
-+To get an expressive language while still being safe and small, Landlock is
-+based on eBPF. Landlock should be usable by untrusted processes and must
-+therefore expose a minimal attack surface. The eBPF bytecode is minimal,
-+powerful, widely used and designed to be used by untrusted applications. Thus,
-+reusing the eBPF support in the kernel enables a generic approach while
-+minimizing new code.
-+
-+An eBPF program has access to an eBPF context containing some fields used to
-+inspect the current object. These arguments can be used directly (e.g. cookie)
-+or passed to helper functions according to their types (e.g. inode pointer). It
-+is then possible to do complex access checks without race conditions or
-+inconsistent evaluation (i.e.  `incorrect mirroring of the OS code and state
-+<https://www.ndss-symposium.org/ndss2003/traps-and-pitfalls-practical-problems-system-call-interposition-based-security-tools/>`_).
-+
-+A Landlock hook describes a particular access type.  For now, there is two
-+hooks dedicated to filesystem related operations: LANDLOCK_HOOK_FS_PICK and
-+LANDLOCK_HOOK_FS_WALK.  A Landlock program is tied to one hook.  This makes it
-+possible to statically check context accesses, potentially performed by such
-+program, and hence prevents kernel address leaks and ensure the right use of
-+hook arguments with eBPF functions.  Any user can add multiple Landlock
-+programs per Landlock hook.  They are stacked and evaluated one after the
-+other, starting from the most recent program, as seccomp-bpf does with its
-+filters.  Underneath, a hook is an abstraction over a set of LSM hooks.
-+
-+
-+Guiding principles
-+==================
-+
-+Unprivileged use
-+----------------
-+
-+* Landlock helpers and context should be usable by any unprivileged and
-+  untrusted program while following the system security policy enforced by
-+  other access control mechanisms (e.g. DAC, LSM).
-+
-+
-+Landlock hook and context
-+-------------------------
-+
-+* A Landlock hook shall be focused on access control on kernel objects instead
-+  of syscall filtering (i.e. syscall arguments), which is the purpose of
-+  seccomp-bpf.
-+* A Landlock context provided by a hook shall express the minimal and more
-+  generic interface to control an access for a kernel object.
-+* A hook shall guaranty that all the BPF function calls from a program are
-+  safe.  Thus, the related Landlock context arguments shall always be of the
-+  same type for a particular hook.  For example, a network hook could share
-+  helpers with a file hook because of UNIX socket.  However, the same helpers
-+  may not be compatible for a file system handle and a net handle.
-+* Multiple hooks may use the same context interface.
-+
-+
-+Landlock helpers
-+----------------
-+
-+* Landlock helpers shall be as generic as possible while at the same time being
-+  as simple as possible and following the syscall creation principles (cf.
-+  *Documentation/adding-syscalls.txt*).
-+* The only behavior change allowed on a helper is to fix a (logical) bug to
-+  match the initial semantic.
-+* Helpers shall be reentrant, i.e. only take inputs from arguments (e.g. from
-+  the BPF context), to enable a hook to use a cache.  Future program options
-+  might change this cache behavior.
-+* It is quite easy to add new helpers to extend Landlock.  The main concern
-+  should be about the possibility to leak information from the kernel that may
-+  not be accessible otherwise (i.e. side-channel attack).
-+
-+
-+Questions and answers
-+=====================
-+
-+Why not create a custom hook for each kind of action?
-+-----------------------------------------------------
-+
-+Landlock programs can handle these checks.  Adding more exceptions to the
-+kernel code would lead to more code complexity.  A decision to ignore a kind of
-+action can and should be done at the beginning of a Landlock program.
-+
-+
-+Why a program does not return an errno or a kill code?
-+------------------------------------------------------
-+
-+seccomp filters can return multiple kind of code, including an errno value or a
-+kill signal, which may be convenient for access control.  Those return codes
-+are hardwired in the userland ABI.  Instead, Landlock's approach is to return a
-+boolean to allow or deny an action, which is much simpler and more generic.
-+Moreover, we do not really have a choice because, unlike to seccomp, Landlock
-+programs are not enforced at the syscall entry point but may be executed at any
-+point in the kernel (through LSM hooks) where an errno return code may not make
-+sense.  However, with this simple ABI and with the ability to call helpers,
-+Landlock may gain features similar to seccomp-bpf in the future while being
-+compatible with previous programs.
-diff --git a/Documentation/security/landlock/user.rst b/Documentation/security/landlock/user.rst
++++ b/tools/testing/selftests/landlock/Makefile
+@@ -0,0 +1,35 @@
++LIBDIR := ../../../lib
++OBJDIR := ../../../lib/bpf
++BPFOBJS := $(OBJDIR)/bpf.o $(OBJDIR)/nlattr.o
++LOADOBJ := ../../../../samples/bpf/bpf_load.o
++
++CFLAGS += -Wl,-no-as-needed -Wall -O2 -I../../../include/uapi -I$(LIBDIR)
++LDFLAGS += -lelf
++
++test_src = $(wildcard test_*.c)
++
++test_objs := $(test_src:.c=)
++
++TEST_PROGS := $(test_objs)
++
++.PHONY: all clean force
++
++all: $(test_objs)
++
++# force a rebuild of BPFOBJS when its dependencies are updated
++force:
++
++# rebuild bpf.o as a workaround for the samples/bpf bug
++$(BPFOBJS): $(LOADOBJ) force
++	$(MAKE) -C $(OBJDIR)
++
++$(LOADOBJ): force
++	$(MAKE) -C $(dir $(LOADOBJ))
++
++$(test_objs): $(BPFOBJS) $(LOADOBJ) ../kselftest_harness.h
++
++include ../lib.mk
++
++clean:
++	$(RM) $(test_objs)
++
+diff --git a/tools/testing/selftests/landlock/test.h b/tools/testing/selftests/landlock/test.h
 new file mode 100644
-index 000000000000..14c4f3b377bd
+index 000000000000..3046516488d9
 --- /dev/null
-+++ b/Documentation/security/landlock/user.rst
-@@ -0,0 +1,147 @@
-+================================
-+Landlock: userland documentation
-+================================
-+
-+Landlock programs
-+=================
-+
-+eBPF programs are used to create security programs.  They are contained and can
-+call only a whitelist of dedicated functions. Moreover, they can only loop
-+under strict conditions, which protects from denial of service.  More
-+information on BPF can be found in *Documentation/networking/filter.txt*.
-+
-+
-+Writing a program
-+-----------------
-+
-+To enforce a security policy, a thread first needs to create a Landlock program.
-+The easiest way to write an eBPF program depicting a security program is to write
-+it in the C language.  As described in *samples/bpf/README.rst*, LLVM can
-+compile such programs.  Files *samples/bpf/landlock1_kern.c* and those in
-+*tools/testing/selftests/landlock/* can be used as examples.
-+
-+Once the eBPF program is created, the next step is to create the metadata
-+describing the Landlock program.  This metadata includes an expected attach type which
-+contains the hook type to which the program is tied, and expected attach
-+triggers which identify the actions for which the program should be run.
-+
-+A hook is a policy decision point which exposes the same context type for
-+each program evaluation.
-+
-+A Landlock hook describes the kind of kernel object for which a program will be
-+triggered to allow or deny an action.  For example, the hook
-+BPF_LANDLOCK_FS_PICK can be triggered every time a landlocked thread performs a
-+set of action related to the filesystem (e.g. open, read, write, mount...).
-+This actions are identified by the `triggers` bitfield.
-+
-+The next step is to fill a :c:type:`struct bpf_load_program_attr
-+<bpf_load_program_attr>` with BPF_PROG_TYPE_LANDLOCK_HOOK, the expected attach
-+type and other BPF program metadata.  This bpf_attr must then be passed to the
-+:manpage:`bpf(2)` syscall alongside the BPF_PROG_LOAD command.  If everything
-+is deemed correct by the kernel, the thread gets a file descriptor referring to
-+this program.
-+
-+In the following code, the *insn* variable is an array of BPF instructions
-+which can be extracted from an ELF file as is done in bpf_load_file() from
-+*samples/bpf/bpf_load.c*.
-+
-+.. code-block:: c
-+
-+    int prog_fd;
-+    struct bpf_load_program_attr load_attr;
-+
-+    memset(&load_attr, 0, sizeof(struct bpf_load_program_attr));
-+    load_attr.prog_type = BPF_PROG_TYPE_LANDLOCK_HOOK;
-+    load_attr.expected_attach_type = BPF_LANDLOCK_FS_PICK;
-+    load_attr.expected_attach_triggers = LANDLOCK_TRIGGER_FS_PICK_OPEN;
-+    load_attr.insns = insns;
-+    load_attr.insns_cnt = sizeof(insn) / sizeof(struct bpf_insn);
-+    load_attr.license = "GPL";
-+
-+    prog_fd = bpf_load_program_xattr(&load_attr, log_buf, log_buf_sz);
-+    if (prog_fd == -1)
-+        exit(1);
-+
-+
-+Enforcing a program
-+-------------------
-+
-+Once the Landlock program has been created or received (e.g. through a UNIX
-+socket), the thread willing to sandbox itself (and its future children) should
-+perform the following two steps.
-+
-+The thread should first request to never be allowed to get new privileges with a
-+call to :manpage:`prctl(2)` and the PR_SET_NO_NEW_PRIVS option.  More
-+information can be found in *Documentation/prctl/no_new_privs.txt*.
-+
-+.. code-block:: c
-+
-+    if (prctl(PR_SET_NO_NEW_PRIVS, 1, NULL, 0, 0))
-+        exit(1);
-+
-+A thread can apply a program to itself by using the :manpage:`seccomp(2)` syscall.
-+The operation is SECCOMP_PREPEND_LANDLOCK_PROG, the flags must be empty and the
-+*args* argument must point to a valid Landlock program file descriptor.
-+
-+.. code-block:: c
-+
-+    if (seccomp(SECCOMP_PREPEND_LANDLOCK_PROG, 0, &fd))
-+        exit(1);
-+
-+If the syscall succeeds, the program is now enforced on the calling thread and
-+will be enforced on all its subsequently created children of the thread as
-+well.  Once a thread is landlocked, there is no way to remove this security
-+policy, only stacking more restrictions is allowed.  The program evaluation is
-+performed from the newest to the oldest.
-+
-+When a syscall ask for an action on a kernel object, if this action is denied,
-+then an EACCES errno code is returned through the syscall.
-+
-+
-+.. _inherited_programs:
-+
-+Inherited programs
-+------------------
-+
-+Every new thread resulting from a :manpage:`clone(2)` inherits Landlock program
-+restrictions from its parent.  This is similar to the seccomp inheritance as
-+described in *Documentation/prctl/seccomp_filter.txt*.
-+
-+
-+Ptrace restrictions
-+-------------------
-+
-+A landlocked process has less privileges than a non-landlocked process and must
-+then be subject to additional restrictions when manipulating another process.
-+To be allowed to use :manpage:`ptrace(2)` and related syscalls on a target
-+process, a landlocked process must have a subset of the target process programs.
-+
-+
-+Landlock structures and constants
-+=================================
-+
-+Hook types
-+----------
-+
-+.. kernel-doc:: include/uapi/linux/landlock.h
-+    :functions: landlock_hook_type
-+
-+
-+Contexts
-+--------
-+
-+.. kernel-doc:: include/uapi/linux/landlock.h
-+    :functions: landlock_ctx_fs_pick landlock_ctx_fs_walk landlock_ctx_fs_get
-+
-+
-+Triggers for fs_pick
-+--------------------
-+
-+.. kernel-doc:: include/uapi/linux/landlock.h
-+    :functions: landlock_triggers
-+
-+
-+Additional documentation
-+========================
-+
-+See https://landlock.io
++++ b/tools/testing/selftests/landlock/test.h
+@@ -0,0 +1,31 @@
++/*
++ * Landlock helpers
++ *
++ * Copyright ? 2017 Micka?l Sala?n <mic@digikod.net>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2, as
++ * published by the Free Software Foundation.
++ */
++
++#include <errno.h>
++#include <linux/landlock.h>
++#include <linux/seccomp.h>
++#include <sys/prctl.h>
++#include <sys/syscall.h>
++
++#include "../kselftest_harness.h"
++#include "../../../../samples/bpf/bpf_load.h"
++
++#ifndef SECCOMP_PREPEND_LANDLOCK_PROG
++#define SECCOMP_PREPEND_LANDLOCK_PROG	3
++#endif
++
++#ifndef seccomp
++static int __attribute__((unused)) seccomp(unsigned int op, unsigned int flags,
++		void *args)
++{
++	errno = 0;
++	return syscall(__NR_seccomp, op, flags, args);
++}
++#endif
+diff --git a/tools/testing/selftests/landlock/test_base.c b/tools/testing/selftests/landlock/test_base.c
+new file mode 100644
+index 000000000000..3ad18a779ecf
+--- /dev/null
++++ b/tools/testing/selftests/landlock/test_base.c
+@@ -0,0 +1,27 @@
++/*
++ * Landlock tests - base
++ *
++ * Copyright ? 2017 Micka?l Sala?n <mic@digikod.net>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2, as
++ * published by the Free Software Foundation.
++ */
++
++#define _GNU_SOURCE
++#include <errno.h>
++
++#include "test.h"
++
++TEST(seccomp_landlock)
++{
++	int ret;
++
++	ret = seccomp(SECCOMP_PREPEND_LANDLOCK_PROG, 0, NULL);
++	EXPECT_EQ(-1, ret);
++	EXPECT_EQ(EFAULT, errno) {
++		TH_LOG("Kernel does not support CONFIG_SECURITY_LANDLOCK");
++	}
++}
++
++TEST_HARNESS_MAIN
+diff --git a/tools/testing/selftests/landlock/test_chain.c b/tools/testing/selftests/landlock/test_chain.c
+new file mode 100644
+index 000000000000..916e84802fd4
+--- /dev/null
++++ b/tools/testing/selftests/landlock/test_chain.c
+@@ -0,0 +1,249 @@
++/*
++ * Landlock tests - chain
++ *
++ * Copyright ? 2018 Micka?l Sala?n <mic@digikod.net>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2, as
++ * published by the Free Software Foundation.
++ */
++
++#include <errno.h>
++
++#include "test.h"
++
++static int new_prog(struct __test_metadata *_metadata, int is_valid,
++		__u32 hook_type, int prev)
++{
++	const struct bpf_insn prog_accept[] = {
++		BPF_MOV32_IMM(BPF_REG_0, 0),
++		BPF_EXIT_INSN(),
++	};
++	union bpf_prog_subtype subtype = {
++		.landlock_hook = {
++			.type = hook_type,
++			.triggers = hook_type == LANDLOCK_HOOK_FS_PICK ?
++				LANDLOCK_TRIGGER_FS_PICK_OPEN : 0,
++		}
++	};
++	int prog;
++	char log[256] = "";
++
++	if (prev != -1) {
++		subtype.landlock_hook.options = LANDLOCK_OPTION_PREVIOUS;
++		subtype.landlock_hook.previous = prev;
++	}
++	prog = bpf_load_program(BPF_PROG_TYPE_LANDLOCK_HOOK,
++			(const struct bpf_insn *)&prog_accept,
++			sizeof(prog_accept) / sizeof(struct bpf_insn), "GPL",
++			0, log, sizeof(log), &subtype);
++	if (is_valid) {
++		ASSERT_NE(-1, prog) {
++			TH_LOG("Failed to load program: %s\n%s",
++					strerror(errno), log);
++		}
++	} else {
++		ASSERT_EQ(-1, prog) {
++			TH_LOG("Successfully loaded a wrong program\n");
++		}
++		ASSERT_EQ(errno, EINVAL);
++	}
++	return prog;
++}
++
++static void apply_chain(struct __test_metadata *_metadata, int is_valid,
++		int prog)
++{
++	if (is_valid) {
++		ASSERT_EQ(0, seccomp(SECCOMP_PREPEND_LANDLOCK_PROG, 0, &prog)) {
++			TH_LOG("Failed to apply chain: %s", strerror(errno));
++		}
++	} else {
++		ASSERT_NE(0, seccomp(SECCOMP_PREPEND_LANDLOCK_PROG, 0, &prog)) {
++			TH_LOG("Successfully applied a wrong chain");
++		}
++		ASSERT_EQ(errno, EINVAL);
++	}
++}
++
++TEST(chain_fs_good_walk_pick)
++{
++	/* fs_walk1 -> [fs_pick1] */
++	int fs_walk1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_WALK, -1);
++	int fs_pick1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, fs_walk1);
++	apply_chain(_metadata, 1, fs_pick1);
++	EXPECT_EQ(0, close(fs_pick1));
++	EXPECT_EQ(0, close(fs_walk1));
++}
++
++TEST(chain_fs_good_pick_pick)
++{
++	/* fs_pick1 -> [fs_pick2] */
++	int fs_pick1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, -1);
++	int fs_pick2 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, fs_pick1);
++	apply_chain(_metadata, 1, fs_pick2);
++	EXPECT_EQ(0, close(fs_pick2));
++	EXPECT_EQ(0, close(fs_pick1));
++}
++
++TEST(chain_fs_wrong_pick_walk)
++{
++	/* fs_pick1 -> fs_walk1 */
++	int fs_pick1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, -1);
++	new_prog(_metadata, 0, LANDLOCK_HOOK_FS_WALK, fs_pick1);
++	EXPECT_EQ(0, close(fs_pick1));
++}
++
++TEST(chain_fs_wrong_walk_walk)
++{
++	/* fs_walk1 -> fs_walk2 */
++	int fs_walk1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_WALK, -1);
++	new_prog(_metadata, 0, LANDLOCK_HOOK_FS_WALK, fs_walk1);
++	EXPECT_EQ(0, close(fs_walk1));
++}
++
++TEST(chain_fs_good_pick_get)
++{
++	/* fs_pick1 -> [fs_get1] */
++	int fs_pick1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, -1);
++	int fs_get1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_GET, fs_pick1);
++	apply_chain(_metadata, 1, fs_get1);
++	EXPECT_EQ(0, close(fs_get1));
++	EXPECT_EQ(0, close(fs_pick1));
++}
++
++TEST(chain_fs_wrong_get_get)
++{
++	/* fs_get1 -> [fs_get2] */
++	int fs_get1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_WALK, -1);
++	new_prog(_metadata, 0, LANDLOCK_HOOK_FS_GET, fs_get1);
++	EXPECT_EQ(0, close(fs_get1));
++}
++
++TEST(chain_fs_wrong_tree_1)
++{
++	/* [fs_walk1] -> { [fs_pick1] , [fs_pick2] } */
++	int fs_walk1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_WALK, -1);
++	apply_chain(_metadata, 1, fs_walk1);
++	int fs_pick1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, fs_walk1);
++	apply_chain(_metadata, 0, fs_pick1);
++	int fs_pick2 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, fs_walk1);
++	apply_chain(_metadata, 0, fs_pick2);
++	EXPECT_EQ(0, close(fs_pick2));
++	EXPECT_EQ(0, close(fs_pick1));
++	EXPECT_EQ(0, close(fs_walk1));
++}
++
++TEST(chain_fs_wrong_tree_2)
++{
++	/* fs_walk1 -> { [fs_pick1] , [fs_pick2] } */
++	int fs_walk1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_WALK, -1);
++	int fs_pick1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, fs_walk1);
++	apply_chain(_metadata, 1, fs_pick1);
++	int fs_pick2 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, fs_walk1);
++	apply_chain(_metadata, 0, fs_pick2);
++	EXPECT_EQ(0, close(fs_pick2));
++	EXPECT_EQ(0, close(fs_pick1));
++	EXPECT_EQ(0, close(fs_walk1));
++}
++
++TEST(chain_fs_wrong_tree_3)
++{
++	/* fs_walk1 -> [fs_pick1] -> [fs_pick2] */
++	int fs_walk1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_WALK, -1);
++	int fs_pick1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, fs_walk1);
++	apply_chain(_metadata, 1, fs_pick1);
++	int fs_pick2 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, fs_pick1);
++	apply_chain(_metadata, 0, fs_pick2);
++	EXPECT_EQ(0, close(fs_pick2));
++	EXPECT_EQ(0, close(fs_pick1));
++	EXPECT_EQ(0, close(fs_walk1));
++}
++
++TEST(chain_fs_wrong_tree_4)
++{
++	/* fs_walk1 -> fs_pick1 -> fs_pick2 -> { [fs_get1] , [fs_get2] } */
++	int fs_walk1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_WALK, -1);
++	int fs_pick1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, fs_walk1);
++	int fs_pick2 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, fs_pick1);
++	int fs_get1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_GET, fs_pick2);
++	apply_chain(_metadata, 1, fs_get1);
++	int fs_get2 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_GET, fs_pick2);
++	apply_chain(_metadata, 0, fs_get2);
++	EXPECT_EQ(0, close(fs_get2));
++	EXPECT_EQ(0, close(fs_get1));
++	EXPECT_EQ(0, close(fs_pick2));
++	EXPECT_EQ(0, close(fs_pick1));
++	EXPECT_EQ(0, close(fs_walk1));
++}
++
++TEST(chain_fs_wrong_tree_5)
++{
++	/* fs_walk1 -> fs_pick1 -> { [fs_pick2] , [fs_pick3] } */
++	int fs_walk1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_WALK, -1);
++	int fs_pick1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, fs_walk1);
++	int fs_pick2 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, fs_pick1);
++	apply_chain(_metadata, 1, fs_pick2);
++	int fs_pick3 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, fs_pick1);
++	apply_chain(_metadata, 0, fs_pick3);
++	EXPECT_EQ(0, close(fs_pick3));
++	EXPECT_EQ(0, close(fs_pick2));
++	EXPECT_EQ(0, close(fs_pick1));
++	EXPECT_EQ(0, close(fs_walk1));
++}
++
++TEST(chain_fs_wrong_tree_6)
++{
++	/* thread 1: fs_walk1 -> fs_pick1 -> [fs_pick2] */
++	/* thread 2: fs_walk1 -> fs_pick1 -> [fs_pick2] -> [fs_get1] */
++	int child;
++	int fs_walk1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_WALK, -1);
++	int fs_pick1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, fs_walk1);
++	int fs_pick2 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, fs_pick1);
++	apply_chain(_metadata, 1, fs_pick2);
++	child = fork();
++	if (child) {
++		/* parent */
++		int status;
++		waitpid(child, &status, 0);
++		EXPECT_TRUE(WIFEXITED(status) && !WEXITSTATUS(status));
++	} else {
++		/* child */
++		int fs_get1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_GET,
++				fs_pick2);
++		apply_chain(_metadata, 0, fs_get1);
++		_exit(0);
++	}
++	EXPECT_EQ(0, close(fs_pick2));
++	EXPECT_EQ(0, close(fs_pick1));
++	EXPECT_EQ(0, close(fs_walk1));
++}
++
++TEST(chain_fs_good_tree_1)
++{
++	/* fs_walk1 -> fs_pick1 -> [fs_pick2] */
++	int fs_walk1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_WALK, -1);
++	int fs_pick1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, fs_walk1);
++	int fs_pick2 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, fs_pick1);
++	apply_chain(_metadata, 1, fs_pick2);
++	EXPECT_EQ(0, close(fs_pick2));
++	EXPECT_EQ(0, close(fs_pick1));
++	EXPECT_EQ(0, close(fs_walk1));
++}
++
++TEST(chain_fs_good_tree_2)
++{
++	/* fs_walk1 -> fs_pick1 -> [fs_pick2] -> [fs_get1] */
++	int fs_walk1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_WALK, -1);
++	int fs_pick1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, fs_walk1);
++	int fs_pick2 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_PICK, fs_pick1);
++	apply_chain(_metadata, 1, fs_pick2);
++	int fs_get1 = new_prog(_metadata, 1, LANDLOCK_HOOK_FS_GET, fs_pick2);
++	apply_chain(_metadata, 1, fs_get1);
++	EXPECT_EQ(0, close(fs_get1));
++	EXPECT_EQ(0, close(fs_pick2));
++	EXPECT_EQ(0, close(fs_pick1));
++	EXPECT_EQ(0, close(fs_walk1));
++}
++
++TEST_HARNESS_MAIN
+diff --git a/tools/testing/selftests/landlock/test_fs.c b/tools/testing/selftests/landlock/test_fs.c
+new file mode 100644
+index 000000000000..54d85b16aafb
+--- /dev/null
++++ b/tools/testing/selftests/landlock/test_fs.c
+@@ -0,0 +1,492 @@
++/*
++ * Landlock tests - file system
++ *
++ * Copyright ? 2018 Micka?l Sala?n <mic@digikod.net>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2, as
++ * published by the Free Software Foundation.
++ */
++
++#include <fcntl.h> /* O_DIRECTORY */
++#include <sys/stat.h> /* statbuf */
++#include <unistd.h> /* faccessat() */
++
++#include "test.h"
++
++#define TEST_PATH_TRIGGERS ( \
++		LANDLOCK_TRIGGER_FS_PICK_OPEN | \
++		LANDLOCK_TRIGGER_FS_PICK_READDIR | \
++		LANDLOCK_TRIGGER_FS_PICK_EXECUTE | \
++		LANDLOCK_TRIGGER_FS_PICK_GETATTR)
++
++static void enforce_depth(struct __test_metadata *_metadata, int depth)
++{
++	const struct bpf_insn prog_walk[] = {
++		BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1,
++			offsetof(struct landlock_ctx_fs_walk, cookie)),
++		BPF_LDX_MEM(BPF_B, BPF_REG_7, BPF_REG_1,
++			offsetof(struct landlock_ctx_fs_walk, inode_lookup)),
++		BPF_JMP_IMM(BPF_JNE, BPF_REG_7,
++				LANDLOCK_CTX_FS_WALK_INODE_LOOKUP_DOTDOT, 3),
++		/* assume 1 is the root */
++		BPF_JMP_IMM(BPF_JEQ, BPF_REG_6, 1, 4),
++		BPF_ALU64_IMM(BPF_SUB, BPF_REG_6, 1),
++		BPF_JMP_IMM(BPF_JA, 0, 0, 2),
++		BPF_JMP_IMM(BPF_JEQ, BPF_REG_7,
++				LANDLOCK_CTX_FS_WALK_INODE_LOOKUP_DOT, 1),
++		BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 1),
++		BPF_STX_MEM(BPF_DW, BPF_REG_1, BPF_REG_6,
++			offsetof(struct landlock_ctx_fs_walk, cookie)),
++		BPF_MOV32_IMM(BPF_REG_0, LANDLOCK_RET_ALLOW),
++		BPF_EXIT_INSN(),
++	};
++	const struct bpf_insn prog_pick[] = {
++		BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1,
++			offsetof(struct landlock_ctx_fs_pick, cookie)),
++		/* allow without fs_walk */
++		BPF_JMP_IMM(BPF_JEQ, BPF_REG_6, 0, 11),
++		BPF_LDX_MEM(BPF_B, BPF_REG_7, BPF_REG_1,
++			offsetof(struct landlock_ctx_fs_walk, inode_lookup)),
++		BPF_JMP_IMM(BPF_JNE, BPF_REG_7,
++				LANDLOCK_CTX_FS_WALK_INODE_LOOKUP_DOTDOT, 3),
++		/* assume 1 is the root */
++		BPF_JMP_IMM(BPF_JEQ, BPF_REG_6, 1, 4),
++		BPF_ALU64_IMM(BPF_SUB, BPF_REG_6, 1),
++		BPF_JMP_IMM(BPF_JA, 0, 0, 2),
++		BPF_JMP_IMM(BPF_JEQ, BPF_REG_7,
++				LANDLOCK_CTX_FS_WALK_INODE_LOOKUP_DOT, 1),
++		BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 1),
++		BPF_STX_MEM(BPF_DW, BPF_REG_1, BPF_REG_6,
++			offsetof(struct landlock_ctx_fs_walk, cookie)),
++		/* with fs_walk */
++		BPF_JMP_IMM(BPF_JEQ, BPF_REG_6, depth + 1, 2),
++		BPF_MOV32_IMM(BPF_REG_0, LANDLOCK_RET_DENY),
++		BPF_EXIT_INSN(),
++		BPF_MOV32_IMM(BPF_REG_0, LANDLOCK_RET_ALLOW),
++		BPF_EXIT_INSN(),
++	};
++	union bpf_prog_subtype subtype = {
++		.landlock_hook = {
++			.type = LANDLOCK_HOOK_FS_WALK,
++		}
++	};
++	int fd_walk, fd_pick;
++	char log[1030] = "";
++
++	fd_walk = bpf_load_program(BPF_PROG_TYPE_LANDLOCK_HOOK,
++			(const struct bpf_insn *)&prog_walk,
++			sizeof(prog_walk) / sizeof(struct bpf_insn), "GPL",
++			0, log, sizeof(log), &subtype);
++	ASSERT_NE(-1, fd_walk) {
++		TH_LOG("Failed to load fs_walk program: %s\n%s",
++				strerror(errno), log);
++	}
++
++	subtype.landlock_hook.type = LANDLOCK_HOOK_FS_PICK;
++	subtype.landlock_hook.options = LANDLOCK_OPTION_PREVIOUS;
++	subtype.landlock_hook.previous = fd_walk;
++	subtype.landlock_hook.triggers = TEST_PATH_TRIGGERS;
++	fd_pick = bpf_load_program(BPF_PROG_TYPE_LANDLOCK_HOOK,
++			(const struct bpf_insn *)&prog_pick,
++			sizeof(prog_pick) / sizeof(struct bpf_insn), "GPL",
++			0, log, sizeof(log), &subtype);
++	ASSERT_NE(-1, fd_pick) {
++		TH_LOG("Failed to load fs_pick program: %s\n%s",
++				strerror(errno), log);
++	}
++
++	ASSERT_EQ(0, seccomp(SECCOMP_PREPEND_LANDLOCK_PROG, 0, &fd_pick)) {
++		TH_LOG("Failed to apply Landlock chain: %s", strerror(errno));
++	}
++	EXPECT_EQ(0, close(fd_pick));
++	EXPECT_EQ(0, close(fd_walk));
++}
++
++static void test_path_rel(struct __test_metadata *_metadata, int dirfd,
++		const char *path, int ret)
++{
++	int fd;
++	struct stat statbuf;
++
++	ASSERT_EQ(ret, faccessat(dirfd, path, R_OK | X_OK, 0));
++	ASSERT_EQ(ret, fstatat(dirfd, path, &statbuf, 0));
++	fd = openat(dirfd, path, O_DIRECTORY);
++	if (ret) {
++		ASSERT_EQ(-1, fd);
++	} else {
++		ASSERT_NE(-1, fd);
++		EXPECT_EQ(0, close(fd));
++	}
++}
++
++static void test_path(struct __test_metadata *_metadata, const char *path,
++		int ret)
++{
++	return test_path_rel(_metadata, AT_FDCWD, path, ret);
++}
++
++const char d1[] = "/usr";
++const char d1_dotdot1[] = "/usr/share/..";
++const char d1_dotdot2[] = "/usr/../usr/share/..";
++const char d1_dotdot3[] = "/usr/../../usr/share/..";
++const char d1_dotdot4[] = "/usr/../../../usr/share/..";
++const char d1_dotdot5[] = "/usr/../../../usr/share/../.";
++const char d1_dotdot6[] = "/././usr/./share/..";
++const char d2[] = "/usr/share";
++const char d2_dotdot1[] = "/usr/share/doc/..";
++const char d2_dotdot2[] = "/usr/../usr/share";
++const char d3[] = "/usr/share/doc";
++const char d4[] = "/etc";
++
++TEST(fs_depth_free)
++{
++	test_path(_metadata, d1, 0);
++	test_path(_metadata, d2, 0);
++	test_path(_metadata, d3, 0);
++}
++
++TEST(fs_depth_1)
++{
++	enforce_depth(_metadata, 1);
++	test_path(_metadata, d1, 0);
++	test_path(_metadata, d1_dotdot1, 0);
++	test_path(_metadata, d1_dotdot2, 0);
++	test_path(_metadata, d1_dotdot3, 0);
++	test_path(_metadata, d1_dotdot4, 0);
++	test_path(_metadata, d1_dotdot5, 0);
++	test_path(_metadata, d1_dotdot6, 0);
++	test_path(_metadata, d2, -1);
++	test_path(_metadata, d2_dotdot1, -1);
++	test_path(_metadata, d2_dotdot2, -1);
++	test_path(_metadata, d3, -1);
++}
++
++TEST(fs_depth_2)
++{
++	enforce_depth(_metadata, 2);
++	test_path(_metadata, d1, -1);
++	test_path(_metadata, d1_dotdot1, -1);
++	test_path(_metadata, d1_dotdot2, -1);
++	test_path(_metadata, d1_dotdot3, -1);
++	test_path(_metadata, d1_dotdot4, -1);
++	test_path(_metadata, d1_dotdot5, -1);
++	test_path(_metadata, d1_dotdot6, -1);
++	test_path(_metadata, d2, 0);
++	test_path(_metadata, d2_dotdot2, 0);
++	test_path(_metadata, d2_dotdot1, 0);
++	test_path(_metadata, d3, -1);
++}
++
++#define MAP_VALUE_ALLOW 1
++#define COOKIE_VALUE_ALLOW 2
++
++static int create_inode_map(struct __test_metadata *_metadata,
++		const char *const dirs[])
++{
++	int map, key, i;
++	__u64 value = MAP_VALUE_ALLOW;
++
++	ASSERT_NE(NULL, dirs) {
++		TH_LOG("No directory list\n");
++	}
++	ASSERT_NE(NULL, dirs[0]) {
++		TH_LOG("Empty directory list\n");
++	}
++	for (i = 0; dirs[i]; i++);
++	map = bpf_create_map(BPF_MAP_TYPE_INODE, sizeof(key), sizeof(value),
++			i, 0);
++	ASSERT_NE(-1, map) {
++		TH_LOG("Failed to create a map of %d elements: %s\n", i,
++				strerror(errno));
++	}
++	for (i = 0; dirs[i]; i++) {
++		key = open(dirs[i], O_RDONLY | O_CLOEXEC | O_DIRECTORY);
++		ASSERT_NE(-1, key) {
++			TH_LOG("Failed to open directory \"%s\": %s\n", dirs[i],
++					strerror(errno));
++		}
++		ASSERT_EQ(0, bpf_map_update_elem(map, &key, &value, BPF_ANY)) {
++			TH_LOG("Failed to update the map with \"%s\": %s\n",
++					dirs[i], strerror(errno));
++		}
++		close(key);
++	}
++	return map;
++}
++
++#define TAG_VALUE_ALLOW 1
++
++static void enforce_map(struct __test_metadata *_metadata, int map,
++		bool subpath, bool tag)
++{
++	/* do not handle dot nor dotdot */
++	const struct bpf_insn prog_walk[] = {
++		BPF_ALU64_REG(BPF_MOV, BPF_REG_6, BPF_REG_1),
++		/* look at the inode's tag */
++		BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_6,
++			offsetof(struct landlock_ctx_fs_walk, inode)),
++		BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_6,
++			offsetof(struct landlock_ctx_fs_walk, chain)),
++		BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0,
++				BPF_FUNC_inode_get_tag),
++		BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, TAG_VALUE_ALLOW, 5),
++		/* look for the requested inode in the map */
++		BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_6,
++			offsetof(struct landlock_ctx_fs_walk, inode)),
++		BPF_LD_MAP_FD(BPF_REG_1, map), /* 2 instructions */
++		BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0,
++				BPF_FUNC_inode_map_lookup),
++		/* if it is there, then mark the session as such */
++		BPF_JMP_IMM(BPF_JNE, BPF_REG_0, MAP_VALUE_ALLOW, 2),
++		BPF_MOV64_IMM(BPF_REG_7, COOKIE_VALUE_ALLOW),
++		BPF_STX_MEM(BPF_DW, BPF_REG_6, BPF_REG_7,
++			offsetof(struct landlock_ctx_fs_walk, cookie)),
++		/* allow to walk anything... but not to pick anything */
++		BPF_MOV32_IMM(BPF_REG_0, LANDLOCK_RET_ALLOW),
++		BPF_EXIT_INSN(),
++	};
++	/* do not handle dot nor dotdot */
++	const struct bpf_insn prog_pick[] = {
++		BPF_ALU64_REG(BPF_MOV, BPF_REG_6, BPF_REG_1),
++		/* allow if the inode's tag is mark as such */
++		BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_6,
++			offsetof(struct landlock_ctx_fs_pick, inode)),
++		BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_6,
++			offsetof(struct landlock_ctx_fs_pick, chain)),
++		BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0,
++				BPF_FUNC_inode_get_tag),
++		BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, TAG_VALUE_ALLOW, 9),
++		/* look if the walk saw an inode in the whitelist */
++		BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_6,
++			offsetof(struct landlock_ctx_fs_pick, cookie)),
++		/* if it was there, then allow access */
++		BPF_JMP_IMM(BPF_JEQ, BPF_REG_7, COOKIE_VALUE_ALLOW, 7),
++		/* otherwise, look for the requested inode in the map */
++		BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_6,
++			offsetof(struct landlock_ctx_fs_pick, inode)),
++		BPF_LD_MAP_FD(BPF_REG_1, map), /* 2 instructions */
++		BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0,
++				BPF_FUNC_inode_map_lookup),
++		/* if it is there, then allow access */
++		BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, MAP_VALUE_ALLOW, 2),
++		/* otherwise deny access */
++		BPF_MOV32_IMM(BPF_REG_0, LANDLOCK_RET_DENY),
++		BPF_EXIT_INSN(),
++		BPF_MOV32_IMM(BPF_REG_0, LANDLOCK_RET_ALLOW),
++		BPF_EXIT_INSN(),
++	};
++	const struct bpf_insn prog_get[] = {
++		BPF_ALU64_REG(BPF_MOV, BPF_REG_6, BPF_REG_1),
++		/* if prog_pick allowed this prog_get, then keep the state in
++		 * the inode's tag */
++		BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_6,
++			offsetof(struct landlock_ctx_fs_get, tag_object)),
++		BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_6,
++			offsetof(struct landlock_ctx_fs_get, chain)),
++		BPF_MOV64_IMM(BPF_REG_3, TAG_VALUE_ALLOW),
++		BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0,
++				BPF_FUNC_landlock_set_tag),
++		BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2),
++		/* for this test, deny on error */
++		BPF_MOV32_IMM(BPF_REG_0, LANDLOCK_RET_DENY),
++		BPF_EXIT_INSN(),
++		/* the check was previously performed by prog_pick */
++		BPF_MOV32_IMM(BPF_REG_0, LANDLOCK_RET_ALLOW),
++		BPF_EXIT_INSN(),
++	};
++	union bpf_prog_subtype subtype = {};
++	int fd_walk = -1, fd_pick, fd_get, fd_last;
++	char log[1024] = "";
++
++	if (subpath) {
++		subtype.landlock_hook.type = LANDLOCK_HOOK_FS_WALK;
++		fd_walk = bpf_load_program(BPF_PROG_TYPE_LANDLOCK_HOOK,
++				(const struct bpf_insn *)&prog_walk,
++				sizeof(prog_walk) / sizeof(struct bpf_insn),
++				"GPL", 0, log, sizeof(log), &subtype);
++		ASSERT_NE(-1, fd_walk) {
++			TH_LOG("Failed to load fs_walk program: %s\n%s",
++					strerror(errno), log);
++		}
++		subtype.landlock_hook.options = LANDLOCK_OPTION_PREVIOUS;
++		subtype.landlock_hook.previous = fd_walk;
++	}
++
++	subtype.landlock_hook.type = LANDLOCK_HOOK_FS_PICK;
++	subtype.landlock_hook.triggers = TEST_PATH_TRIGGERS;
++	fd_pick = bpf_load_program(BPF_PROG_TYPE_LANDLOCK_HOOK,
++			(const struct bpf_insn *)&prog_pick,
++			sizeof(prog_pick) / sizeof(struct bpf_insn), "GPL", 0,
++			log, sizeof(log), &subtype);
++	ASSERT_NE(-1, fd_pick) {
++		TH_LOG("Failed to load fs_pick program: %s\n%s",
++				strerror(errno), log);
++	}
++	fd_last = fd_pick;
++
++	if (tag) {
++		subtype.landlock_hook.type = LANDLOCK_HOOK_FS_GET;
++		subtype.landlock_hook.triggers = 0;
++		subtype.landlock_hook.options = LANDLOCK_OPTION_PREVIOUS;
++		subtype.landlock_hook.previous = fd_pick;
++		fd_get = bpf_load_program(BPF_PROG_TYPE_LANDLOCK_HOOK,
++				(const struct bpf_insn *)&prog_get,
++				sizeof(prog_get) / sizeof(struct bpf_insn),
++				"GPL", 0, log, sizeof(log), &subtype);
++		ASSERT_NE(-1, fd_get) {
++			TH_LOG("Failed to load fs_get program: %s\n%s",
++					strerror(errno), log);
++		}
++		fd_last = fd_get;
++	}
++
++	ASSERT_EQ(0, seccomp(SECCOMP_PREPEND_LANDLOCK_PROG, 0, &fd_last)) {
++		TH_LOG("Failed to apply Landlock chain: %s", strerror(errno));
++	}
++	if (tag)
++		EXPECT_EQ(0, close(fd_get));
++	EXPECT_EQ(0, close(fd_pick));
++	if (subpath)
++		EXPECT_EQ(0, close(fd_walk));
++}
++
++/* do not handle dot nor dotdot */
++static void check_map_whitelist(struct __test_metadata *_metadata,
++		bool subpath)
++{
++	int map = create_inode_map(_metadata, (const char *const [])
++			{ d2, NULL });
++	ASSERT_NE(-1, map);
++	enforce_map(_metadata, map, subpath, false);
++	test_path(_metadata, d1, -1);
++	test_path(_metadata, d2, 0);
++	test_path(_metadata, d3, subpath ? 0 : -1);
++	EXPECT_EQ(0, close(map));
++}
++
++TEST(fs_map_whitelist_literal)
++{
++	check_map_whitelist(_metadata, false);
++}
++
++TEST(fs_map_whitelist_subpath)
++{
++	check_map_whitelist(_metadata, true);
++}
++
++const char r2[] = ".";
++const char r3[] = "./doc";
++
++enum relative_access {
++	REL_OPEN,
++	REL_CHDIR,
++	REL_CHROOT,
++};
++
++static void check_tag(struct __test_metadata *_metadata,
++		bool enforce, bool with_tag, enum relative_access rel)
++{
++	int dirfd;
++	int map = -1;
++	int access_beneath, access_absolute;
++
++	if (rel == REL_CHROOT) {
++		/* do not tag with the chdir, only with the chroot */
++		ASSERT_NE(-1, chdir(d2));
++	}
++	if (enforce) {
++		map = create_inode_map(_metadata, (const char *const [])
++				{ d1, NULL });
++		ASSERT_NE(-1, map);
++		enforce_map(_metadata, map, true, with_tag);
++	}
++	switch (rel) {
++	case REL_OPEN:
++		dirfd = open(d2, O_DIRECTORY);
++		ASSERT_NE(-1, dirfd);
++		break;
++	case REL_CHDIR:
++		ASSERT_NE(-1, chdir(d2));
++		dirfd = AT_FDCWD;
++		break;
++	case REL_CHROOT:
++		ASSERT_NE(-1, chroot(d2)) {
++			TH_LOG("Failed to chroot: %s\n", strerror(errno));
++		}
++		dirfd = AT_FDCWD;
++		break;
++	default:
++		ASSERT_TRUE(false);
++		return;
++	}
++
++	access_beneath = (!enforce || with_tag) ? 0 : -1;
++	test_path_rel(_metadata, dirfd, r2, access_beneath);
++	test_path_rel(_metadata, dirfd, r3, access_beneath);
++
++	access_absolute = (enforce || rel == REL_CHROOT) ? -1 : 0;
++	test_path(_metadata, d4, access_absolute);
++	test_path_rel(_metadata, dirfd, d4, access_absolute);
++
++	if (rel == REL_OPEN)
++		EXPECT_EQ(0, close(dirfd));
++	if (enforce)
++		EXPECT_EQ(0, close(map));
++}
++
++TEST(fs_notag_allow_open)
++{
++	/* no enforcement, via open */
++	check_tag(_metadata, false, false, REL_OPEN);
++}
++
++TEST(fs_notag_allow_chdir)
++{
++	/* no enforcement, via chdir */
++	check_tag(_metadata, false, false, REL_CHDIR);
++}
++
++TEST(fs_notag_allow_chroot)
++{
++	/* no enforcement, via chroot */
++	check_tag(_metadata, false, false, REL_CHROOT);
++}
++
++TEST(fs_notag_deny_open)
++{
++	/* enforcement without tag, via open */
++	check_tag(_metadata, true, false, REL_OPEN);
++}
++
++TEST(fs_notag_deny_chdir)
++{
++	/* enforcement without tag, via chdir */
++	check_tag(_metadata, true, false, REL_CHDIR);
++}
++
++TEST(fs_notag_deny_chroot)
++{
++	/* enforcement without tag, via chroot */
++	check_tag(_metadata, true, false, REL_CHROOT);
++}
++
++TEST(fs_tag_allow_open)
++{
++	/* enforcement with tag, via open */
++	check_tag(_metadata, true, true, REL_OPEN);
++}
++
++TEST(fs_tag_allow_chdir)
++{
++	/* enforcement with tag, via chdir */
++	check_tag(_metadata, true, true, REL_CHDIR);
++}
++
++TEST(fs_tag_allow_chroot)
++{
++	/* enforcement with tag, via chroot */
++	check_tag(_metadata, true, true, REL_CHROOT);
++}
++
++TEST_HARNESS_MAIN
+diff --git a/tools/testing/selftests/landlock/test_ptrace.c b/tools/testing/selftests/landlock/test_ptrace.c
+new file mode 100644
+index 000000000000..1423a60b6e0a
+--- /dev/null
++++ b/tools/testing/selftests/landlock/test_ptrace.c
+@@ -0,0 +1,158 @@
++/*
++ * Landlock tests - ptrace
++ *
++ * Copyright ? 2017 Micka?l Sala?n <mic@digikod.net>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2, as
++ * published by the Free Software Foundation.
++ */
++
++#define _GNU_SOURCE
++#include <signal.h> /* raise */
++#include <sys/ptrace.h>
++#include <sys/types.h> /* waitpid */
++#include <sys/wait.h> /* waitpid */
++#include <unistd.h> /* fork, pipe */
++
++#include "test.h"
++
++static void apply_null_sandbox(struct __test_metadata *_metadata)
++{
++	const struct bpf_insn prog_accept[] = {
++		BPF_MOV32_IMM(BPF_REG_0, 0),
++		BPF_EXIT_INSN(),
++	};
++	const union bpf_prog_subtype subtype = {
++		.landlock_hook = {
++			.type = LANDLOCK_HOOK_FS_PICK,
++			.triggers = LANDLOCK_TRIGGER_FS_PICK_OPEN,
++		}
++	};
++	int prog;
++	char log[256] = "";
++
++	prog = bpf_load_program(BPF_PROG_TYPE_LANDLOCK_HOOK,
++			(const struct bpf_insn *)&prog_accept,
++			sizeof(prog_accept) / sizeof(struct bpf_insn), "GPL",
++			0, log, sizeof(log), &subtype);
++	ASSERT_NE(-1, prog) {
++		TH_LOG("Failed to load minimal rule: %s\n%s",
++				strerror(errno), log);
++	}
++	ASSERT_EQ(0, seccomp(SECCOMP_PREPEND_LANDLOCK_PROG, 0, &prog)) {
++		TH_LOG("Failed to apply minimal rule: %s", strerror(errno));
++	}
++	EXPECT_EQ(0, close(prog));
++}
++
++/* PTRACE_TRACEME and PTRACE_ATTACH without Landlock rules effect */
++static void check_ptrace(struct __test_metadata *_metadata,
++		int sandbox_both, int sandbox_parent, int sandbox_child,
++		int expect_ptrace)
++{
++	pid_t child;
++	int status;
++	int pipefd[2];
++
++	ASSERT_EQ(0, pipe(pipefd));
++	if (sandbox_both)
++		apply_null_sandbox(_metadata);
++
++	child = fork();
++	ASSERT_LE(0, child);
++	if (child == 0) {
++		char buf;
++
++		EXPECT_EQ(0, close(pipefd[1]));
++		if (sandbox_child)
++			apply_null_sandbox(_metadata);
++
++		/* test traceme */
++		ASSERT_EQ(expect_ptrace, ptrace(PTRACE_TRACEME));
++		if (expect_ptrace) {
++			ASSERT_EQ(EPERM, errno);
++		} else {
++			ASSERT_EQ(0, raise(SIGSTOP));
++		}
++
++		/* sync */
++		ASSERT_EQ(1, read(pipefd[0], &buf, 1)) {
++			TH_LOG("Failed to read() sync from parent");
++		}
++		ASSERT_EQ('.', buf);
++		_exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE);
++	}
++
++	EXPECT_EQ(0, close(pipefd[0]));
++	if (sandbox_parent)
++		apply_null_sandbox(_metadata);
++
++	/* test traceme */
++	if (!expect_ptrace) {
++		ASSERT_EQ(child, waitpid(child, &status, 0));
++		ASSERT_EQ(1, WIFSTOPPED(status));
++		ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0));
++	}
++	/* test attach */
++	ASSERT_EQ(expect_ptrace, ptrace(PTRACE_ATTACH, child, NULL, 0));
++	if (expect_ptrace) {
++		ASSERT_EQ(EPERM, errno);
++	} else {
++		ASSERT_EQ(child, waitpid(child, &status, 0));
++		ASSERT_EQ(1, WIFSTOPPED(status));
++		ASSERT_EQ(0, ptrace(PTRACE_CONT, child, NULL, 0));
++	}
++
++	/* sync */
++	ASSERT_EQ(1, write(pipefd[1], ".", 1)) {
++		TH_LOG("Failed to write() sync to child");
++	}
++	ASSERT_EQ(child, waitpid(child, &status, 0));
++	if (WIFSIGNALED(status) || WEXITSTATUS(status))
++		_metadata->passed = 0;
++}
++
++TEST(ptrace_allow_without_sandbox)
++{
++	/* no sandbox */
++	check_ptrace(_metadata, 0, 0, 0, 0);
++}
++
++TEST(ptrace_allow_with_one_sandbox)
++{
++	/* child sandbox */
++	check_ptrace(_metadata, 0, 0, 1, 0);
++}
++
++TEST(ptrace_allow_with_nested_sandbox)
++{
++	/* inherited and child sandbox */
++	check_ptrace(_metadata, 1, 0, 1, 0);
++}
++
++TEST(ptrace_deny_with_parent_sandbox)
++{
++	/* parent sandbox */
++	check_ptrace(_metadata, 0, 1, 0, -1);
++}
++
++TEST(ptrace_deny_with_nested_and_parent_sandbox)
++{
++	/* inherited and parent sandbox */
++	check_ptrace(_metadata, 1, 1, 0, -1);
++}
++
++TEST(ptrace_deny_with_forked_sandbox)
++{
++	/* inherited, parent and child sandbox */
++	check_ptrace(_metadata, 1, 1, 1, -1);
++}
++
++TEST(ptrace_deny_with_sibling_sandbox)
++{
++	/* parent and child sandbox */
++	check_ptrace(_metadata, 0, 1, 1, -1);
++}
++
++TEST_HARNESS_MAIN
 -- 
-2.22.0
+2.16.2
 
+--
+To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
+the body of a message to majordomo at vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help