diff mbox

[net-next,v6,10/11] bpf,landlock: Add tests for Landlock

Message ID 20170328234650.19695-11-mic@digikod.net (mailing list archive)
State New, archived
Headers show

Commit Message

Mickaël Salaün March 28, 2017, 11:46 p.m. UTC
Test basic context access, ptrace protection and filesystem event with
multiple cases.

Changes since v5:
* add subtype test
* add ptrace tests
* split and rename files
* cleanup and rebase

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 <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>
---
 tools/testing/selftests/Makefile                   |   1 +
 tools/testing/selftests/bpf/test_verifier.c        |  64 +++++
 tools/testing/selftests/landlock/.gitignore        |   4 +
 tools/testing/selftests/landlock/Makefile          |  47 ++++
 tools/testing/selftests/landlock/rules/Makefile    |  52 ++++
 tools/testing/selftests/landlock/rules/README.rst  |   1 +
 .../testing/selftests/landlock/rules/bpf_helpers.h |   1 +
 .../testing/selftests/landlock/rules/fs_no_open.c  |  31 +++
 .../selftests/landlock/rules/fs_read_only.c        |  31 +++
 tools/testing/selftests/landlock/test.h            |  35 +++
 tools/testing/selftests/landlock/test_base.c       |  31 +++
 tools/testing/selftests/landlock/test_fs.c         | 305 +++++++++++++++++++++
 tools/testing/selftests/landlock/test_ptrace.c     | 161 +++++++++++
 13 files changed, 764 insertions(+)
 create mode 100644 tools/testing/selftests/landlock/.gitignore
 create mode 100644 tools/testing/selftests/landlock/Makefile
 create mode 100644 tools/testing/selftests/landlock/rules/Makefile
 create mode 120000 tools/testing/selftests/landlock/rules/README.rst
 create mode 120000 tools/testing/selftests/landlock/rules/bpf_helpers.h
 create mode 100644 tools/testing/selftests/landlock/rules/fs_no_open.c
 create mode 100644 tools/testing/selftests/landlock/rules/fs_read_only.c
 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_fs.c
 create mode 100644 tools/testing/selftests/landlock/test_ptrace.c

Comments

Kees Cook April 18, 2017, 11:16 p.m. UTC | #1
On Tue, Mar 28, 2017 at 4:46 PM, Mickaël Salaün <mic@digikod.net> wrote:
> Test basic context access, ptrace protection and filesystem event with
> multiple cases.
>
> Changes since v5:
> * add subtype test
> * add ptrace tests
> * split and rename files
> * cleanup and rebase
>
> 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 <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>
> ---
>  tools/testing/selftests/Makefile                   |   1 +
>  tools/testing/selftests/bpf/test_verifier.c        |  64 +++++
>  tools/testing/selftests/landlock/.gitignore        |   4 +
>  tools/testing/selftests/landlock/Makefile          |  47 ++++
>  tools/testing/selftests/landlock/rules/Makefile    |  52 ++++
>  tools/testing/selftests/landlock/rules/README.rst  |   1 +
>  .../testing/selftests/landlock/rules/bpf_helpers.h |   1 +
>  .../testing/selftests/landlock/rules/fs_no_open.c  |  31 +++
>  .../selftests/landlock/rules/fs_read_only.c        |  31 +++
>  tools/testing/selftests/landlock/test.h            |  35 +++
>  tools/testing/selftests/landlock/test_base.c       |  31 +++
>  tools/testing/selftests/landlock/test_fs.c         | 305 +++++++++++++++++++++
>  tools/testing/selftests/landlock/test_ptrace.c     | 161 +++++++++++
>  13 files changed, 764 insertions(+)
>  create mode 100644 tools/testing/selftests/landlock/.gitignore
>  create mode 100644 tools/testing/selftests/landlock/Makefile
>  create mode 100644 tools/testing/selftests/landlock/rules/Makefile
>  create mode 120000 tools/testing/selftests/landlock/rules/README.rst
>  create mode 120000 tools/testing/selftests/landlock/rules/bpf_helpers.h
>  create mode 100644 tools/testing/selftests/landlock/rules/fs_no_open.c
>  create mode 100644 tools/testing/selftests/landlock/rules/fs_read_only.c
>  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_fs.c
>  create mode 100644 tools/testing/selftests/landlock/test_ptrace.c
>
> diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
> index d8593f1251ec..b584ad456428 100644
> --- a/tools/testing/selftests/Makefile
> +++ b/tools/testing/selftests/Makefile
> @@ -12,6 +12,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/test_verifier.c b/tools/testing/selftests/bpf/test_verifier.c
> index daa87dd7c80e..77255b14871e 100644
> --- a/tools/testing/selftests/bpf/test_verifier.c
> +++ b/tools/testing/selftests/bpf/test_verifier.c
> @@ -4536,6 +4536,70 @@ 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,
> +       },
> +       {
> +               "landlock/fs: always accept",
> +               .insns = {
> +                       BPF_MOV32_IMM(BPF_REG_0, 0),
> +                       BPF_EXIT_INSN(),
> +               },
> +               .result = ACCEPT,
> +               .prog_type = BPF_PROG_TYPE_LANDLOCK,
> +               .has_prog_subtype = true,
> +               .prog_subtype = {
> +                       .landlock_rule = {
> +                               .version = 1,
> +                               .event = LANDLOCK_SUBTYPE_EVENT_FS,
> +                       }
> +               },
> +       },
> +       {
> +               "landlock/fs: 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_context, status)),
> +                       /* test operations on raw values */
> +                       BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
> +                       BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
> +                               offsetof(struct landlock_context, arch)),
> +                       BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
> +                       BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
> +                               offsetof(struct landlock_context, syscall_nr)),
> +                       BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
> +                       BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
> +                               offsetof(struct landlock_context, syscall_cmd)),
> +                       BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
> +                       BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
> +                               offsetof(struct landlock_context, event)),
> +                       BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
> +                       BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_6,
> +                               offsetof(struct landlock_context, arg1)),
> +                       BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_6,
> +                               offsetof(struct landlock_context, arg2)),
> +                       BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
> +                       BPF_MOV32_IMM(BPF_REG_0, 0),
> +                       BPF_EXIT_INSN(),
> +               },
> +               .result = ACCEPT,
> +               .prog_type = BPF_PROG_TYPE_LANDLOCK,
> +               .has_prog_subtype = true,
> +               .prog_subtype = {
> +                       .landlock_rule = {
> +                               .version = 1,
> +                               .event = LANDLOCK_SUBTYPE_EVENT_FS,
> +                       }
> +               },
> +       },
>  };
>
>  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..25b9cd834c3c
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/.gitignore
> @@ -0,0 +1,4 @@
> +/test_base
> +/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..9a52c82d64fa
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/Makefile
> @@ -0,0 +1,47 @@
> +LIBDIR := ../../../lib
> +BPFOBJ := $(LIBDIR)/bpf/bpf.o
> +LOADOBJ := ../../../../samples/bpf/bpf_load.o

Is the selftest tarball creation tool okay with this? IIRC, it should
be fine since it'll be a built object already, but it's a random
thought I had while looking at this.

> +
> +CFLAGS += -Wl,-no-as-needed -Wall -O2 -I../../../include/uapi -I$(LIBDIR)
> +LDFLAGS += -lelf
> +
> +test_src = $(wildcard test_*.c)
> +rule_src = $(wildcard rules/*.c)
> +
> +test_objs := $(test_src:.c=)
> +rule_objs := $(rule_src:.c=.o)
> +
> +TEST_PROGS := $(test_objs)
> +
> +.PHONY: all clean clean_tmp force
> +
> +all: $(test_objs) $(rule_objs)
> +
> +# force a rebuild of BPFOBJ when its dependencies are updated
> +force:
> +
> +$(BPFOBJ): force
> +       $(MAKE) -C $(dir $(BPFOBJ))
> +
> +$(LOADOBJ):
> +       $(MAKE) -C $(dir $(LOADOBJ))
> +
> +# minimize builds
> +rules/modules.order: $(rule_src)
> +       $(MAKE) -C rules
> +       @touch $@
> +
> +$(rule_objs): rules/modules.order
> +       @
> +
> +$(test_objs): $(BPFOBJ) $(LOADOBJ)
> +
> +include ../lib.mk
> +
> +clean_tmp:
> +       $(RM) -r tmp_*
> +
> +clean: clean_tmp
> +       $(MAKE) -C rules clean
> +       $(RM) $(test_objs)
> +
> diff --git a/tools/testing/selftests/landlock/rules/Makefile b/tools/testing/selftests/landlock/rules/Makefile
> new file mode 100644
> index 000000000000..8d6ff960ff7c
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/rules/Makefile
> @@ -0,0 +1,52 @@
> +# kbuild trick to avoid linker error. Can be omitted if a module is built.
> +obj- := dummy.o
> +
> +# Tell kbuild to always build the programs
> +always := fs_read_only.o
> +always += fs_no_open.o
> +
> +EXTRA_CFLAGS = -Wall -Wextra
> +
> +# Allows pointing LLC/CLANG to a LLVM backend with bpf support, redefine on cmdline:
> +#  make samples/bpf/ LLC=~/git/llvm/build/bin/llc CLANG=~/git/llvm/build/bin/clang
> +LLC ?= llc
> +CLANG ?= clang
> +
> +# Verify LLVM compiler tools are available and bpf target is supported by llc
> +.PHONY: all clean verify_cmds verify_target_bpf $(CLANG) $(LLC)
> +
> +# Trick to allow make to be run from this directory
> +all:
> +       $(MAKE) -C ../../../../../ $(CURDIR)/
> +
> +clean:
> +       $(MAKE) -C ../../../../../ M=$(CURDIR) clean

Is this really needed? Others don't have it, I think.

> +verify_cmds: $(CLANG) $(LLC)
> +       @for TOOL in $^ ; do \
> +               if ! (which -- "$${TOOL}" > /dev/null 2>&1); then \
> +                       echo "*** ERROR: Cannot find LLVM tool $${TOOL}" ;\
> +                       exit 1; \
> +               else true; fi; \
> +       done
> +
> +verify_target_bpf: verify_cmds
> +       @if ! (${LLC} -march=bpf -mattr=help > /dev/null 2>&1); then \
> +               echo "*** ERROR: LLVM (${LLC}) does not support 'bpf' target" ;\
> +               echo "   NOTICE: LLVM version >= 3.7.1 required" ;\
> +               exit 2; \
> +       else true; fi
> +
> +%_kern.c: verify_target_bpf
> +
> +# asm/sysreg.h - inline assembly used by it is incompatible with llvm.
> +# But, there is no easy way to fix it, so just exclude it since it is
> +# useless for BPF samples.
> +$(obj)/%.o: $(src)/%.c
> +       $(CLANG) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) $(EXTRA_CFLAGS) \
> +               -D__KERNEL__ -D__ASM_SYSREG_H -Wno-unused-value -Wno-pointer-sign \
> +               -Wno-compare-distinct-pointer-types \
> +               -Wno-gnu-variable-sized-type-not-at-end \
> +               -Wno-tautological-compare \
> +               -O2 -emit-llvm -c $< -o -| $(LLC) -march=bpf -filetype=obj -o $@

Is clang required for the samples and the selftests? That needs to be
avoided... there needs to be a way to show people how to build a
landlock rule without requiring clang.

> +
> diff --git a/tools/testing/selftests/landlock/rules/README.rst b/tools/testing/selftests/landlock/rules/README.rst
> new file mode 120000
> index 000000000000..605f48aa6f72
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/rules/README.rst
> @@ -0,0 +1 @@
> +../../../../../samples/bpf/README.rst
> \ No newline at end of file
> diff --git a/tools/testing/selftests/landlock/rules/bpf_helpers.h b/tools/testing/selftests/landlock/rules/bpf_helpers.h
> new file mode 120000
> index 000000000000..0aa1a521b39a
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/rules/bpf_helpers.h
> @@ -0,0 +1 @@
> +../../../../../samples/bpf/bpf_helpers.h
> \ No newline at end of file
> diff --git a/tools/testing/selftests/landlock/rules/fs_no_open.c b/tools/testing/selftests/landlock/rules/fs_no_open.c
> new file mode 100644
> index 000000000000..c6ea305e58a7
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/rules/fs_no_open.c
> @@ -0,0 +1,31 @@
> +/*
> + * Landlock rule - no-open filesystem
> + *
> + * 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 <uapi/linux/bpf.h>
> +#include "bpf_helpers.h"
> +
> +SEC("landlock1")
> +static int landlock_fs_prog1(struct landlock_context *ctx)
> +{
> +       if (!(ctx->arg2 & LANDLOCK_ACTION_FS_GET))
> +               return 0;
> +       return 1;
> +}
> +
> +SEC("subtype")
> +static union bpf_prog_subtype _subtype = {
> +       .landlock_rule = {
> +               .version = 1,
> +               .event = LANDLOCK_SUBTYPE_EVENT_FS,
> +       }
> +};
> +
> +SEC("license")
> +static const char _license[] = "GPL";
> diff --git a/tools/testing/selftests/landlock/rules/fs_read_only.c b/tools/testing/selftests/landlock/rules/fs_read_only.c
> new file mode 100644
> index 000000000000..212dda7c0c27
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/rules/fs_read_only.c
> @@ -0,0 +1,31 @@
> +/*
> + * Landlock rule - read-only filesystem
> + *
> + * 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 <uapi/linux/bpf.h>
> +#include "bpf_helpers.h"
> +
> +SEC("landlock1")
> +static int landlock_fs_prog1(struct landlock_context *ctx)
> +{
> +       if (!(ctx->arg2 & LANDLOCK_ACTION_FS_WRITE))
> +               return 0;
> +       return 1;
> +}
> +
> +SEC("subtype")
> +static union bpf_prog_subtype _subtype = {
> +       .landlock_rule = {
> +               .version = 1,
> +               .event = LANDLOCK_SUBTYPE_EVENT_FS,
> +       }
> +};
> +
> +SEC("license")
> +static const char _license[] = "GPL";
> diff --git a/tools/testing/selftests/landlock/test.h b/tools/testing/selftests/landlock/test.h
> new file mode 100644
> index 000000000000..7a194815391b
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/test.h
> @@ -0,0 +1,35 @@
> +/*
> + * 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 <sys/prctl.h>
> +#include <sys/syscall.h>
> +
> +#include "../seccomp/test_harness.h"
> +#include "../../../../samples/bpf/bpf_load.h"
> +
> +#ifndef SECCOMP_APPEND_LANDLOCK_RULE
> +#define SECCOMP_APPEND_LANDLOCK_RULE   2
> +#endif
> +
> +#ifndef seccomp
> +static int seccomp(unsigned int op, unsigned int flags, void *args)
> +{
> +       errno = 0;
> +       return syscall(__NR_seccomp, op, flags, args);
> +}
> +#endif
> +
> +#define ASSERT_STEP(cond) \
> +       { \
> +               step--; \
> +               if (!(cond)) \
> +                       _exit(step); \
> +       }

Can you explain this in more detail? I'm assuming there is a problem
with writing to the TH_LOG_STREAM fd or something?

> diff --git a/tools/testing/selftests/landlock/test_base.c b/tools/testing/selftests/landlock/test_base.c
> new file mode 100644
> index 000000000000..bdf056edee03
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/test_base.c
> @@ -0,0 +1,31 @@
> +/*
> + * 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 = prctl(PR_SET_NO_NEW_PRIVS, 1, NULL, 0, 0);
> +       ASSERT_EQ(0, ret) {
> +               TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS");
> +       }
> +       ret = seccomp(SECCOMP_APPEND_LANDLOCK_RULE, 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_fs.c b/tools/testing/selftests/landlock/test_fs.c
> new file mode 100644
> index 000000000000..e69eda433716
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/test_fs.c
> @@ -0,0 +1,305 @@
> +/*
> + * Landlock tests - filesystem
> + *
> + * 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 <linux/bpf.h>
> +#include <linux/filter.h>
> +#include <linux/seccomp.h>
> +#include <stddef.h>
> +#include <string.h>
> +#include <sys/prctl.h>
> +#include <sys/syscall.h>
> +
> +#include <fcntl.h> /* open() */
> +#include <sys/mount.h>
> +#include <sys/stat.h> /* mkdir() */
> +#include <sys/mman.h> /* mmap() */
> +
> +#include "test.h"
> +
> +#define TMP_PREFIX "tmp_"
> +
> +struct layout1 {
> +       int file_ro;
> +       int file_rw;
> +       int file_wo;
> +};
> +
> +static void setup_layout1(struct __test_metadata *_metadata,
> +               struct layout1 *l1)
> +{
> +       int fd;
> +       char buf[] = "fs_read_only";
> +
> +       l1->file_ro = -1;
> +       l1->file_rw = -1;
> +       l1->file_wo = -1;
> +
> +       fd = open(TMP_PREFIX "file_created",
> +                       O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0600);
> +       ASSERT_GE(fd, 0);
> +       ASSERT_EQ(sizeof(buf), write(fd, buf, sizeof(buf)));
> +       ASSERT_EQ(0, close(fd));
> +
> +       fd = mkdir(TMP_PREFIX "dir_created", 0600);
> +       ASSERT_GE(fd, 0);
> +       ASSERT_EQ(0, close(fd));
> +
> +       l1->file_ro = open(TMP_PREFIX "file_ro",
> +                       O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0600);
> +       ASSERT_LE(0, l1->file_ro);
> +       ASSERT_EQ(sizeof(buf), write(l1->file_ro, buf, sizeof(buf)));
> +       ASSERT_EQ(0, close(l1->file_ro));
> +       l1->file_ro = open(TMP_PREFIX "file_ro",
> +                       O_RDONLY | O_CLOEXEC, 0600);
> +       ASSERT_LE(0, l1->file_ro);
> +
> +       l1->file_rw = open(TMP_PREFIX "file_rw",
> +                       O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0600);
> +       ASSERT_LE(0, l1->file_rw);
> +       ASSERT_EQ(sizeof(buf), write(l1->file_rw, buf, sizeof(buf)));
> +       ASSERT_EQ(0, lseek(l1->file_rw, 0, SEEK_SET));
> +
> +       l1->file_wo = open(TMP_PREFIX "file_wo",
> +                       O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0600);
> +       ASSERT_LE(0, l1->file_wo);
> +       ASSERT_EQ(sizeof(buf), write(l1->file_wo, buf, sizeof(buf)));
> +       ASSERT_EQ(0, lseek(l1->file_wo, 0, SEEK_SET));
> +}
> +
> +static void cleanup_layout1(void)
> +{
> +       unlink(TMP_PREFIX "file_created");
> +       unlink(TMP_PREFIX "file_ro");
> +       unlink(TMP_PREFIX "file_rw");
> +       unlink(TMP_PREFIX "file_wo");
> +       unlink(TMP_PREFIX "should_not_exist");
> +       rmdir(TMP_PREFIX "dir_created");
> +}
> +
> +FIXTURE(fs_read_only) {
> +       struct layout1 l1;
> +       int prog;
> +};
> +
> +FIXTURE_SETUP(fs_read_only)
> +{
> +       cleanup_layout1();
> +       setup_layout1(_metadata, &self->l1);
> +
> +       ASSERT_EQ(0, load_bpf_file("rules/fs_read_only.o")) {
> +               TH_LOG("%s", bpf_log_buf);
> +       }
> +       self->prog = prog_fd[0];
> +       ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, NULL, 0, 0)) {
> +               TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS");
> +       }
> +}
> +
> +FIXTURE_TEARDOWN(fs_read_only)
> +{
> +       EXPECT_EQ(0, close(self->prog));
> +       /* cleanup_layout1() would be denied here */
> +}
> +
> +TEST_F(fs_read_only, load_prog) {}
> +
> +TEST_F(fs_read_only, read_only_file)
> +{
> +       int fd;
> +       int step = 0;
> +       char buf_write[] = "should not be written";
> +       char buf_read[2];
> +
> +       ASSERT_EQ(-1, write(self->l1.file_ro, buf_write, sizeof(buf_write)));
> +       ASSERT_EQ(EBADF, errno);
> +
> +       ASSERT_EQ(-1, read(self->l1.file_wo, buf_read, sizeof(buf_read)));
> +       ASSERT_EQ(EBADF, errno);
> +
> +       ASSERT_EQ(0, seccomp(SECCOMP_APPEND_LANDLOCK_RULE, 0, &self->prog)) {
> +               TH_LOG("Failed to apply rule fs_read_only: %s",
> +                               strerror(errno));
> +       }
> +
> +       fd = open(".", O_TMPFILE | O_EXCL | O_RDWR | O_CLOEXEC, 0600);
> +       ASSERT_STEP(fd == -1);
> +       ASSERT_STEP(errno != EOPNOTSUPP)
> +       ASSERT_STEP(errno == EPERM);
> +
> +       fd = open(TMP_PREFIX "file_created",
> +                       O_RDONLY | O_CLOEXEC);
> +       ASSERT_STEP(fd >= 0);
> +       ASSERT_STEP(!close(fd));
> +
> +       fd = open(TMP_PREFIX "file_created",
> +                       O_RDWR | O_CLOEXEC);
> +       ASSERT_STEP(fd == -1);
> +       ASSERT_STEP(errno == EPERM);
> +
> +       fd = open(TMP_PREFIX "file_created",
> +                       O_WRONLY | O_CLOEXEC);
> +       ASSERT_STEP(fd == -1);
> +       ASSERT_STEP(errno == EPERM);
> +
> +       fd = open(TMP_PREFIX "should_not_exist",
> +                       O_CREAT | O_EXCL | O_CLOEXEC, 0600);
> +       ASSERT_STEP(fd == -1);
> +       ASSERT_STEP(errno == EPERM);
> +
> +       ASSERT_STEP(-1 ==
> +                       write(self->l1.file_ro, buf_write, sizeof(buf_write)));
> +       ASSERT_STEP(errno == EBADF);
> +       ASSERT_STEP(sizeof(buf_read) ==
> +                       read(self->l1.file_ro, buf_read, sizeof(buf_read)));
> +
> +       ASSERT_STEP(-1 ==
> +                       write(self->l1.file_rw, buf_write, sizeof(buf_write)));
> +       ASSERT_STEP(errno == EPERM);
> +       ASSERT_STEP(sizeof(buf_read) ==
> +                       read(self->l1.file_rw, buf_read, sizeof(buf_read)));
> +
> +       ASSERT_STEP(-1 == write(self->l1.file_wo, buf_write, sizeof(buf_write)));
> +       ASSERT_STEP(errno == EPERM);
> +       ASSERT_STEP(-1 == read(self->l1.file_wo, buf_read, sizeof(buf_read)));
> +       ASSERT_STEP(errno == EBADF);
> +
> +       ASSERT_STEP(-1 == unlink(TMP_PREFIX "file_created"));
> +       ASSERT_STEP(errno == EPERM);
> +       ASSERT_STEP(-1 == rmdir(TMP_PREFIX "dir_created"));
> +       ASSERT_STEP(errno == EPERM);
> +
> +       ASSERT_STEP(0 == close(self->l1.file_ro));
> +       ASSERT_STEP(0 == close(self->l1.file_rw));
> +       ASSERT_STEP(0 == close(self->l1.file_wo));
> +}
> +
> +TEST_F(fs_read_only, read_only_mount)
> +{
> +       int step = 0;
> +
> +       ASSERT_EQ(0, mount(".", TMP_PREFIX "dir_created",
> +                               NULL, MS_BIND, NULL));
> +       ASSERT_EQ(0, umount2(TMP_PREFIX "dir_created", MNT_FORCE));
> +
> +       ASSERT_EQ(0, seccomp(SECCOMP_APPEND_LANDLOCK_RULE, 0, &self->prog)) {
> +               TH_LOG("Failed to apply rule fs_read_only: %s",
> +                               strerror(errno));
> +       }
> +
> +       ASSERT_STEP(-1 == mount(".", TMP_PREFIX "dir_created",
> +                               NULL, MS_BIND, NULL));
> +       ASSERT_STEP(errno == EPERM);
> +       ASSERT_STEP(-1 == umount("/"));
> +       ASSERT_STEP(errno == EPERM);
> +}
> +
> +TEST_F(fs_read_only, read_only_mem)
> +{
> +       int step = 0;
> +       void *addr;
> +
> +       addr = mmap(NULL, 1, PROT_READ | PROT_WRITE,
> +                       MAP_SHARED, self->l1.file_rw, 0);
> +       ASSERT_NE(NULL, addr);
> +       ASSERT_EQ(0, munmap(addr, 1));
> +
> +       ASSERT_EQ(0, seccomp(SECCOMP_APPEND_LANDLOCK_RULE, 0, &self->prog)) {
> +               TH_LOG("Failed to apply rule fs_read_only: %s",
> +                               strerror(errno));
> +       }
> +
> +       addr = mmap(NULL, 1, PROT_READ, MAP_SHARED,
> +                       self->l1.file_rw, 0);
> +       ASSERT_STEP(addr != NULL);
> +       ASSERT_STEP(-1 == mprotect(addr, 1, PROT_WRITE));
> +       ASSERT_STEP(errno == EPERM);
> +       ASSERT_STEP(0 == munmap(addr, 1));
> +
> +       addr = mmap(NULL, 1, PROT_READ | PROT_WRITE, MAP_SHARED,
> +                       self->l1.file_rw, 0);
> +       ASSERT_STEP(addr != NULL);
> +       ASSERT_STEP(errno == EPERM);
> +
> +       addr = mmap(NULL, 1, PROT_READ | PROT_WRITE, MAP_PRIVATE,
> +                       self->l1.file_rw, 0);
> +       ASSERT_STEP(addr != NULL);
> +       ASSERT_STEP(0 == munmap(addr, 1));
> +}
> +
> +FIXTURE(fs_no_open) {
> +       struct layout1 l1;
> +       int prog;
> +};
> +
> +FIXTURE_SETUP(fs_no_open)
> +{
> +       cleanup_layout1();
> +       setup_layout1(_metadata, &self->l1);
> +
> +       ASSERT_EQ(0, load_bpf_file("rules/fs_no_open.o")) {
> +               TH_LOG("%s", bpf_log_buf);
> +       }
> +       self->prog = prog_fd[0];
> +       ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, NULL, 0, 0)) {
> +               TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS");
> +       }
> +}
> +
> +FIXTURE_TEARDOWN(fs_no_open)
> +{
> +       EXPECT_EQ(0, close(self->prog));
> +       cleanup_layout1();
> +}
> +
> +static void landlocked_deny_open(struct __test_metadata *_metadata,
> +               struct layout1 *l1)
> +{
> +       int fd;
> +       void *addr;
> +
> +       fd = open(".", O_DIRECTORY | O_CLOEXEC);
> +       ASSERT_EQ(-1, fd);
> +       ASSERT_EQ(EPERM, errno);
> +
> +       addr = mmap(NULL, 1, PROT_READ | PROT_WRITE,
> +                       MAP_SHARED, l1->file_rw, 0);
> +       ASSERT_NE(NULL, addr);
> +       ASSERT_EQ(0, munmap(addr, 1));
> +}
> +
> +TEST_F(fs_no_open, deny_open_for_hierarchy) {
> +       int fd;
> +       int status;
> +       pid_t child;
> +
> +       fd = open(".", O_DIRECTORY | O_CLOEXEC);
> +       ASSERT_LE(0, fd);
> +       ASSERT_EQ(0, close(fd));
> +
> +       ASSERT_EQ(0, seccomp(SECCOMP_APPEND_LANDLOCK_RULE, 0, &self->prog)) {
> +               TH_LOG("Failed to apply rule fs_no_open: %s", strerror(errno));
> +       }
> +
> +       landlocked_deny_open(_metadata, &self->l1);
> +
> +       child = fork();
> +       ASSERT_LE(0, child);
> +       if (!child) {
> +               landlocked_deny_open(_metadata, &self->l1);
> +               _exit(1);
> +       }
> +       ASSERT_EQ(child, waitpid(child, &status, 0));
> +       ASSERT_TRUE(WIFEXITED(status));
> +       _exit(WEXITSTATUS(status));
> +}
> +
> +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..0c940a7fd3d0
> --- /dev/null
> +++ b/tools/testing/selftests/landlock/test_ptrace.c
> @@ -0,0 +1,161 @@
> +/*
> + * 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_rule = {
> +                       .version = 1,
> +                       .event = LANDLOCK_SUBTYPE_EVENT_FS,
> +               }
> +       };
> +       int prog;
> +       char log[256] = "";
> +
> +       prog = bpf_load_program(BPF_PROG_TYPE_LANDLOCK,
> +                       (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, prctl(PR_SET_NO_NEW_PRIVS, 1, NULL, 0, 0)) {
> +               TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS");
> +       }
> +       ASSERT_EQ(0, seccomp(SECCOMP_APPEND_LANDLOCK_RULE, 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.11.0
>

Awesome. I love to see all these tests, with both positive and
negative checks. Nice!

-Kees
Mickaël Salaün April 18, 2017, 11:53 p.m. UTC | #2
On 19/04/2017 01:16, Kees Cook wrote:
> On Tue, Mar 28, 2017 at 4:46 PM, Mickaël Salaün <mic@digikod.net> wrote:
>> Test basic context access, ptrace protection and filesystem event with
>> multiple cases.
>>
>> Changes since v5:
>> * add subtype test
>> * add ptrace tests
>> * split and rename files
>> * cleanup and rebase
>>
>> 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 <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>
>> ---
>>  tools/testing/selftests/Makefile                   |   1 +
>>  tools/testing/selftests/bpf/test_verifier.c        |  64 +++++
>>  tools/testing/selftests/landlock/.gitignore        |   4 +
>>  tools/testing/selftests/landlock/Makefile          |  47 ++++
>>  tools/testing/selftests/landlock/rules/Makefile    |  52 ++++
>>  tools/testing/selftests/landlock/rules/README.rst  |   1 +
>>  .../testing/selftests/landlock/rules/bpf_helpers.h |   1 +
>>  .../testing/selftests/landlock/rules/fs_no_open.c  |  31 +++
>>  .../selftests/landlock/rules/fs_read_only.c        |  31 +++
>>  tools/testing/selftests/landlock/test.h            |  35 +++
>>  tools/testing/selftests/landlock/test_base.c       |  31 +++
>>  tools/testing/selftests/landlock/test_fs.c         | 305 +++++++++++++++++++++
>>  tools/testing/selftests/landlock/test_ptrace.c     | 161 +++++++++++
>>  13 files changed, 764 insertions(+)
>>  create mode 100644 tools/testing/selftests/landlock/.gitignore
>>  create mode 100644 tools/testing/selftests/landlock/Makefile
>>  create mode 100644 tools/testing/selftests/landlock/rules/Makefile
>>  create mode 120000 tools/testing/selftests/landlock/rules/README.rst
>>  create mode 120000 tools/testing/selftests/landlock/rules/bpf_helpers.h
>>  create mode 100644 tools/testing/selftests/landlock/rules/fs_no_open.c
>>  create mode 100644 tools/testing/selftests/landlock/rules/fs_read_only.c
>>  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_fs.c
>>  create mode 100644 tools/testing/selftests/landlock/test_ptrace.c
>>
>> diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
>> index d8593f1251ec..b584ad456428 100644
>> --- a/tools/testing/selftests/Makefile
>> +++ b/tools/testing/selftests/Makefile
>> @@ -12,6 +12,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/test_verifier.c b/tools/testing/selftests/bpf/test_verifier.c
>> index daa87dd7c80e..77255b14871e 100644
>> --- a/tools/testing/selftests/bpf/test_verifier.c
>> +++ b/tools/testing/selftests/bpf/test_verifier.c
>> @@ -4536,6 +4536,70 @@ 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,
>> +       },
>> +       {
>> +               "landlock/fs: always accept",
>> +               .insns = {
>> +                       BPF_MOV32_IMM(BPF_REG_0, 0),
>> +                       BPF_EXIT_INSN(),
>> +               },
>> +               .result = ACCEPT,
>> +               .prog_type = BPF_PROG_TYPE_LANDLOCK,
>> +               .has_prog_subtype = true,
>> +               .prog_subtype = {
>> +                       .landlock_rule = {
>> +                               .version = 1,
>> +                               .event = LANDLOCK_SUBTYPE_EVENT_FS,
>> +                       }
>> +               },
>> +       },
>> +       {
>> +               "landlock/fs: 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_context, status)),
>> +                       /* test operations on raw values */
>> +                       BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
>> +                       BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
>> +                               offsetof(struct landlock_context, arch)),
>> +                       BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
>> +                       BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
>> +                               offsetof(struct landlock_context, syscall_nr)),
>> +                       BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
>> +                       BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
>> +                               offsetof(struct landlock_context, syscall_cmd)),
>> +                       BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
>> +                       BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
>> +                               offsetof(struct landlock_context, event)),
>> +                       BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
>> +                       BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_6,
>> +                               offsetof(struct landlock_context, arg1)),
>> +                       BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_6,
>> +                               offsetof(struct landlock_context, arg2)),
>> +                       BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
>> +                       BPF_MOV32_IMM(BPF_REG_0, 0),
>> +                       BPF_EXIT_INSN(),
>> +               },
>> +               .result = ACCEPT,
>> +               .prog_type = BPF_PROG_TYPE_LANDLOCK,
>> +               .has_prog_subtype = true,
>> +               .prog_subtype = {
>> +                       .landlock_rule = {
>> +                               .version = 1,
>> +                               .event = LANDLOCK_SUBTYPE_EVENT_FS,
>> +                       }
>> +               },
>> +       },
>>  };
>>
>>  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..25b9cd834c3c
>> --- /dev/null
>> +++ b/tools/testing/selftests/landlock/.gitignore
>> @@ -0,0 +1,4 @@
>> +/test_base
>> +/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..9a52c82d64fa
>> --- /dev/null
>> +++ b/tools/testing/selftests/landlock/Makefile
>> @@ -0,0 +1,47 @@
>> +LIBDIR := ../../../lib
>> +BPFOBJ := $(LIBDIR)/bpf/bpf.o
>> +LOADOBJ := ../../../../samples/bpf/bpf_load.o
> 
> Is the selftest tarball creation tool okay with this? IIRC, it should
> be fine since it'll be a built object already, but it's a random
> thought I had while looking at this.

Hum, I'll check since it's the same for BPF tests.

> 
>> +
>> +CFLAGS += -Wl,-no-as-needed -Wall -O2 -I../../../include/uapi -I$(LIBDIR)
>> +LDFLAGS += -lelf
>> +
>> +test_src = $(wildcard test_*.c)
>> +rule_src = $(wildcard rules/*.c)
>> +
>> +test_objs := $(test_src:.c=)
>> +rule_objs := $(rule_src:.c=.o)
>> +
>> +TEST_PROGS := $(test_objs)
>> +
>> +.PHONY: all clean clean_tmp force
>> +
>> +all: $(test_objs) $(rule_objs)
>> +
>> +# force a rebuild of BPFOBJ when its dependencies are updated
>> +force:
>> +
>> +$(BPFOBJ): force
>> +       $(MAKE) -C $(dir $(BPFOBJ))
>> +
>> +$(LOADOBJ):
>> +       $(MAKE) -C $(dir $(LOADOBJ))
>> +
>> +# minimize builds
>> +rules/modules.order: $(rule_src)
>> +       $(MAKE) -C rules
>> +       @touch $@
>> +
>> +$(rule_objs): rules/modules.order
>> +       @
>> +
>> +$(test_objs): $(BPFOBJ) $(LOADOBJ)
>> +
>> +include ../lib.mk
>> +
>> +clean_tmp:
>> +       $(RM) -r tmp_*
>> +
>> +clean: clean_tmp
>> +       $(MAKE) -C rules clean
>> +       $(RM) $(test_objs)
>> +
>> diff --git a/tools/testing/selftests/landlock/rules/Makefile b/tools/testing/selftests/landlock/rules/Makefile
>> new file mode 100644
>> index 000000000000..8d6ff960ff7c
>> --- /dev/null
>> +++ b/tools/testing/selftests/landlock/rules/Makefile
>> @@ -0,0 +1,52 @@
>> +# kbuild trick to avoid linker error. Can be omitted if a module is built.
>> +obj- := dummy.o
>> +
>> +# Tell kbuild to always build the programs
>> +always := fs_read_only.o
>> +always += fs_no_open.o
>> +
>> +EXTRA_CFLAGS = -Wall -Wextra
>> +
>> +# Allows pointing LLC/CLANG to a LLVM backend with bpf support, redefine on cmdline:
>> +#  make samples/bpf/ LLC=~/git/llvm/build/bin/llc CLANG=~/git/llvm/build/bin/clang
>> +LLC ?= llc
>> +CLANG ?= clang
>> +
>> +# Verify LLVM compiler tools are available and bpf target is supported by llc
>> +.PHONY: all clean verify_cmds verify_target_bpf $(CLANG) $(LLC)
>> +
>> +# Trick to allow make to be run from this directory
>> +all:
>> +       $(MAKE) -C ../../../../../ $(CURDIR)/
>> +
>> +clean:
>> +       $(MAKE) -C ../../../../../ M=$(CURDIR) clean
> 
> Is this really needed? Others don't have it, I think.

This is copied from the BPF tests and yes it's needed.

> 
>> +verify_cmds: $(CLANG) $(LLC)
>> +       @for TOOL in $^ ; do \
>> +               if ! (which -- "$${TOOL}" > /dev/null 2>&1); then \
>> +                       echo "*** ERROR: Cannot find LLVM tool $${TOOL}" ;\
>> +                       exit 1; \
>> +               else true; fi; \
>> +       done
>> +
>> +verify_target_bpf: verify_cmds
>> +       @if ! (${LLC} -march=bpf -mattr=help > /dev/null 2>&1); then \
>> +               echo "*** ERROR: LLVM (${LLC}) does not support 'bpf' target" ;\
>> +               echo "   NOTICE: LLVM version >= 3.7.1 required" ;\
>> +               exit 2; \
>> +       else true; fi
>> +
>> +%_kern.c: verify_target_bpf
>> +
>> +# asm/sysreg.h - inline assembly used by it is incompatible with llvm.
>> +# But, there is no easy way to fix it, so just exclude it since it is
>> +# useless for BPF samples.
>> +$(obj)/%.o: $(src)/%.c
>> +       $(CLANG) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) $(EXTRA_CFLAGS) \
>> +               -D__KERNEL__ -D__ASM_SYSREG_H -Wno-unused-value -Wno-pointer-sign \
>> +               -Wno-compare-distinct-pointer-types \
>> +               -Wno-gnu-variable-sized-type-not-at-end \
>> +               -Wno-tautological-compare \
>> +               -O2 -emit-llvm -c $< -o -| $(LLC) -march=bpf -filetype=obj -o $@
> 
> Is clang required for the samples and the selftests? That needs to be
> avoided... there needs to be a way to show people how to build a
> landlock rule without requiring clang.

I can rewrite this tests without requiring clang but it is already
required for BPF tests…

> 
>> +
>> diff --git a/tools/testing/selftests/landlock/rules/README.rst b/tools/testing/selftests/landlock/rules/README.rst
>> new file mode 120000
>> index 000000000000..605f48aa6f72
>> --- /dev/null
>> +++ b/tools/testing/selftests/landlock/rules/README.rst
>> @@ -0,0 +1 @@
>> +../../../../../samples/bpf/README.rst
>> \ No newline at end of file
>> diff --git a/tools/testing/selftests/landlock/rules/bpf_helpers.h b/tools/testing/selftests/landlock/rules/bpf_helpers.h
>> new file mode 120000
>> index 000000000000..0aa1a521b39a
>> --- /dev/null
>> +++ b/tools/testing/selftests/landlock/rules/bpf_helpers.h
>> @@ -0,0 +1 @@
>> +../../../../../samples/bpf/bpf_helpers.h
>> \ No newline at end of file
>> diff --git a/tools/testing/selftests/landlock/rules/fs_no_open.c b/tools/testing/selftests/landlock/rules/fs_no_open.c
>> new file mode 100644
>> index 000000000000..c6ea305e58a7
>> --- /dev/null
>> +++ b/tools/testing/selftests/landlock/rules/fs_no_open.c
>> @@ -0,0 +1,31 @@
>> +/*
>> + * Landlock rule - no-open filesystem
>> + *
>> + * 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 <uapi/linux/bpf.h>
>> +#include "bpf_helpers.h"
>> +
>> +SEC("landlock1")
>> +static int landlock_fs_prog1(struct landlock_context *ctx)
>> +{
>> +       if (!(ctx->arg2 & LANDLOCK_ACTION_FS_GET))
>> +               return 0;
>> +       return 1;
>> +}
>> +
>> +SEC("subtype")
>> +static union bpf_prog_subtype _subtype = {
>> +       .landlock_rule = {
>> +               .version = 1,
>> +               .event = LANDLOCK_SUBTYPE_EVENT_FS,
>> +       }
>> +};
>> +
>> +SEC("license")
>> +static const char _license[] = "GPL";
>> diff --git a/tools/testing/selftests/landlock/rules/fs_read_only.c b/tools/testing/selftests/landlock/rules/fs_read_only.c
>> new file mode 100644
>> index 000000000000..212dda7c0c27
>> --- /dev/null
>> +++ b/tools/testing/selftests/landlock/rules/fs_read_only.c
>> @@ -0,0 +1,31 @@
>> +/*
>> + * Landlock rule - read-only filesystem
>> + *
>> + * 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 <uapi/linux/bpf.h>
>> +#include "bpf_helpers.h"
>> +
>> +SEC("landlock1")
>> +static int landlock_fs_prog1(struct landlock_context *ctx)
>> +{
>> +       if (!(ctx->arg2 & LANDLOCK_ACTION_FS_WRITE))
>> +               return 0;
>> +       return 1;
>> +}
>> +
>> +SEC("subtype")
>> +static union bpf_prog_subtype _subtype = {
>> +       .landlock_rule = {
>> +               .version = 1,
>> +               .event = LANDLOCK_SUBTYPE_EVENT_FS,
>> +       }
>> +};
>> +
>> +SEC("license")
>> +static const char _license[] = "GPL";
>> diff --git a/tools/testing/selftests/landlock/test.h b/tools/testing/selftests/landlock/test.h
>> new file mode 100644
>> index 000000000000..7a194815391b
>> --- /dev/null
>> +++ b/tools/testing/selftests/landlock/test.h
>> @@ -0,0 +1,35 @@
>> +/*
>> + * 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 <sys/prctl.h>
>> +#include <sys/syscall.h>
>> +
>> +#include "../seccomp/test_harness.h"
>> +#include "../../../../samples/bpf/bpf_load.h"
>> +
>> +#ifndef SECCOMP_APPEND_LANDLOCK_RULE
>> +#define SECCOMP_APPEND_LANDLOCK_RULE   2
>> +#endif
>> +
>> +#ifndef seccomp
>> +static int seccomp(unsigned int op, unsigned int flags, void *args)
>> +{
>> +       errno = 0;
>> +       return syscall(__NR_seccomp, op, flags, args);
>> +}
>> +#endif
>> +
>> +#define ASSERT_STEP(cond) \
>> +       { \
>> +               step--; \
>> +               if (!(cond)) \
>> +                       _exit(step); \
>> +       }
> 
> Can you explain this in more detail? I'm assuming there is a problem
> with writing to the TH_LOG_STREAM fd or something?

It's a trick to use the test framework without requiring to be allowed
to write to an FD (i.e. log stream), but only to exit a code. I use this
to test a Landlock rule which forbid access to any FS objects (including
open FD). This could be used for seccomp too.

> 
>> diff --git a/tools/testing/selftests/landlock/test_base.c b/tools/testing/selftests/landlock/test_base.c
>> new file mode 100644
>> index 000000000000..bdf056edee03
>> --- /dev/null
>> +++ b/tools/testing/selftests/landlock/test_base.c
>> @@ -0,0 +1,31 @@
>> +/*
>> + * 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 = prctl(PR_SET_NO_NEW_PRIVS, 1, NULL, 0, 0);
>> +       ASSERT_EQ(0, ret) {
>> +               TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS");
>> +       }
>> +       ret = seccomp(SECCOMP_APPEND_LANDLOCK_RULE, 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_fs.c b/tools/testing/selftests/landlock/test_fs.c
>> new file mode 100644
>> index 000000000000..e69eda433716
>> --- /dev/null
>> +++ b/tools/testing/selftests/landlock/test_fs.c
>> @@ -0,0 +1,305 @@
>> +/*
>> + * Landlock tests - filesystem
>> + *
>> + * 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 <linux/bpf.h>
>> +#include <linux/filter.h>
>> +#include <linux/seccomp.h>
>> +#include <stddef.h>
>> +#include <string.h>
>> +#include <sys/prctl.h>
>> +#include <sys/syscall.h>
>> +
>> +#include <fcntl.h> /* open() */
>> +#include <sys/mount.h>
>> +#include <sys/stat.h> /* mkdir() */
>> +#include <sys/mman.h> /* mmap() */
>> +
>> +#include "test.h"
>> +
>> +#define TMP_PREFIX "tmp_"
>> +
>> +struct layout1 {
>> +       int file_ro;
>> +       int file_rw;
>> +       int file_wo;
>> +};
>> +
>> +static void setup_layout1(struct __test_metadata *_metadata,
>> +               struct layout1 *l1)
>> +{
>> +       int fd;
>> +       char buf[] = "fs_read_only";
>> +
>> +       l1->file_ro = -1;
>> +       l1->file_rw = -1;
>> +       l1->file_wo = -1;
>> +
>> +       fd = open(TMP_PREFIX "file_created",
>> +                       O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0600);
>> +       ASSERT_GE(fd, 0);
>> +       ASSERT_EQ(sizeof(buf), write(fd, buf, sizeof(buf)));
>> +       ASSERT_EQ(0, close(fd));
>> +
>> +       fd = mkdir(TMP_PREFIX "dir_created", 0600);
>> +       ASSERT_GE(fd, 0);
>> +       ASSERT_EQ(0, close(fd));
>> +
>> +       l1->file_ro = open(TMP_PREFIX "file_ro",
>> +                       O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0600);
>> +       ASSERT_LE(0, l1->file_ro);
>> +       ASSERT_EQ(sizeof(buf), write(l1->file_ro, buf, sizeof(buf)));
>> +       ASSERT_EQ(0, close(l1->file_ro));
>> +       l1->file_ro = open(TMP_PREFIX "file_ro",
>> +                       O_RDONLY | O_CLOEXEC, 0600);
>> +       ASSERT_LE(0, l1->file_ro);
>> +
>> +       l1->file_rw = open(TMP_PREFIX "file_rw",
>> +                       O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0600);
>> +       ASSERT_LE(0, l1->file_rw);
>> +       ASSERT_EQ(sizeof(buf), write(l1->file_rw, buf, sizeof(buf)));
>> +       ASSERT_EQ(0, lseek(l1->file_rw, 0, SEEK_SET));
>> +
>> +       l1->file_wo = open(TMP_PREFIX "file_wo",
>> +                       O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0600);
>> +       ASSERT_LE(0, l1->file_wo);
>> +       ASSERT_EQ(sizeof(buf), write(l1->file_wo, buf, sizeof(buf)));
>> +       ASSERT_EQ(0, lseek(l1->file_wo, 0, SEEK_SET));
>> +}
>> +
>> +static void cleanup_layout1(void)
>> +{
>> +       unlink(TMP_PREFIX "file_created");
>> +       unlink(TMP_PREFIX "file_ro");
>> +       unlink(TMP_PREFIX "file_rw");
>> +       unlink(TMP_PREFIX "file_wo");
>> +       unlink(TMP_PREFIX "should_not_exist");
>> +       rmdir(TMP_PREFIX "dir_created");
>> +}
>> +
>> +FIXTURE(fs_read_only) {
>> +       struct layout1 l1;
>> +       int prog;
>> +};
>> +
>> +FIXTURE_SETUP(fs_read_only)
>> +{
>> +       cleanup_layout1();
>> +       setup_layout1(_metadata, &self->l1);
>> +
>> +       ASSERT_EQ(0, load_bpf_file("rules/fs_read_only.o")) {
>> +               TH_LOG("%s", bpf_log_buf);
>> +       }
>> +       self->prog = prog_fd[0];
>> +       ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, NULL, 0, 0)) {
>> +               TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS");
>> +       }
>> +}
>> +
>> +FIXTURE_TEARDOWN(fs_read_only)
>> +{
>> +       EXPECT_EQ(0, close(self->prog));
>> +       /* cleanup_layout1() would be denied here */
>> +}
>> +
>> +TEST_F(fs_read_only, load_prog) {}
>> +
>> +TEST_F(fs_read_only, read_only_file)
>> +{
>> +       int fd;
>> +       int step = 0;
>> +       char buf_write[] = "should not be written";
>> +       char buf_read[2];
>> +
>> +       ASSERT_EQ(-1, write(self->l1.file_ro, buf_write, sizeof(buf_write)));
>> +       ASSERT_EQ(EBADF, errno);
>> +
>> +       ASSERT_EQ(-1, read(self->l1.file_wo, buf_read, sizeof(buf_read)));
>> +       ASSERT_EQ(EBADF, errno);
>> +
>> +       ASSERT_EQ(0, seccomp(SECCOMP_APPEND_LANDLOCK_RULE, 0, &self->prog)) {
>> +               TH_LOG("Failed to apply rule fs_read_only: %s",
>> +                               strerror(errno));
>> +       }
>> +
>> +       fd = open(".", O_TMPFILE | O_EXCL | O_RDWR | O_CLOEXEC, 0600);
>> +       ASSERT_STEP(fd == -1);
>> +       ASSERT_STEP(errno != EOPNOTSUPP)
>> +       ASSERT_STEP(errno == EPERM);
>> +
>> +       fd = open(TMP_PREFIX "file_created",
>> +                       O_RDONLY | O_CLOEXEC);
>> +       ASSERT_STEP(fd >= 0);
>> +       ASSERT_STEP(!close(fd));
>> +
>> +       fd = open(TMP_PREFIX "file_created",
>> +                       O_RDWR | O_CLOEXEC);
>> +       ASSERT_STEP(fd == -1);
>> +       ASSERT_STEP(errno == EPERM);
>> +
>> +       fd = open(TMP_PREFIX "file_created",
>> +                       O_WRONLY | O_CLOEXEC);
>> +       ASSERT_STEP(fd == -1);
>> +       ASSERT_STEP(errno == EPERM);
>> +
>> +       fd = open(TMP_PREFIX "should_not_exist",
>> +                       O_CREAT | O_EXCL | O_CLOEXEC, 0600);
>> +       ASSERT_STEP(fd == -1);
>> +       ASSERT_STEP(errno == EPERM);
>> +
>> +       ASSERT_STEP(-1 ==
>> +                       write(self->l1.file_ro, buf_write, sizeof(buf_write)));
>> +       ASSERT_STEP(errno == EBADF);
>> +       ASSERT_STEP(sizeof(buf_read) ==
>> +                       read(self->l1.file_ro, buf_read, sizeof(buf_read)));
>> +
>> +       ASSERT_STEP(-1 ==
>> +                       write(self->l1.file_rw, buf_write, sizeof(buf_write)));
>> +       ASSERT_STEP(errno == EPERM);
>> +       ASSERT_STEP(sizeof(buf_read) ==
>> +                       read(self->l1.file_rw, buf_read, sizeof(buf_read)));
>> +
>> +       ASSERT_STEP(-1 == write(self->l1.file_wo, buf_write, sizeof(buf_write)));
>> +       ASSERT_STEP(errno == EPERM);
>> +       ASSERT_STEP(-1 == read(self->l1.file_wo, buf_read, sizeof(buf_read)));
>> +       ASSERT_STEP(errno == EBADF);
>> +
>> +       ASSERT_STEP(-1 == unlink(TMP_PREFIX "file_created"));
>> +       ASSERT_STEP(errno == EPERM);
>> +       ASSERT_STEP(-1 == rmdir(TMP_PREFIX "dir_created"));
>> +       ASSERT_STEP(errno == EPERM);
>> +
>> +       ASSERT_STEP(0 == close(self->l1.file_ro));
>> +       ASSERT_STEP(0 == close(self->l1.file_rw));
>> +       ASSERT_STEP(0 == close(self->l1.file_wo));
>> +}
>> +
>> +TEST_F(fs_read_only, read_only_mount)
>> +{
>> +       int step = 0;
>> +
>> +       ASSERT_EQ(0, mount(".", TMP_PREFIX "dir_created",
>> +                               NULL, MS_BIND, NULL));
>> +       ASSERT_EQ(0, umount2(TMP_PREFIX "dir_created", MNT_FORCE));
>> +
>> +       ASSERT_EQ(0, seccomp(SECCOMP_APPEND_LANDLOCK_RULE, 0, &self->prog)) {
>> +               TH_LOG("Failed to apply rule fs_read_only: %s",
>> +                               strerror(errno));
>> +       }
>> +
>> +       ASSERT_STEP(-1 == mount(".", TMP_PREFIX "dir_created",
>> +                               NULL, MS_BIND, NULL));
>> +       ASSERT_STEP(errno == EPERM);
>> +       ASSERT_STEP(-1 == umount("/"));
>> +       ASSERT_STEP(errno == EPERM);
>> +}
>> +
>> +TEST_F(fs_read_only, read_only_mem)
>> +{
>> +       int step = 0;
>> +       void *addr;
>> +
>> +       addr = mmap(NULL, 1, PROT_READ | PROT_WRITE,
>> +                       MAP_SHARED, self->l1.file_rw, 0);
>> +       ASSERT_NE(NULL, addr);
>> +       ASSERT_EQ(0, munmap(addr, 1));
>> +
>> +       ASSERT_EQ(0, seccomp(SECCOMP_APPEND_LANDLOCK_RULE, 0, &self->prog)) {
>> +               TH_LOG("Failed to apply rule fs_read_only: %s",
>> +                               strerror(errno));
>> +       }
>> +
>> +       addr = mmap(NULL, 1, PROT_READ, MAP_SHARED,
>> +                       self->l1.file_rw, 0);
>> +       ASSERT_STEP(addr != NULL);
>> +       ASSERT_STEP(-1 == mprotect(addr, 1, PROT_WRITE));
>> +       ASSERT_STEP(errno == EPERM);
>> +       ASSERT_STEP(0 == munmap(addr, 1));
>> +
>> +       addr = mmap(NULL, 1, PROT_READ | PROT_WRITE, MAP_SHARED,
>> +                       self->l1.file_rw, 0);
>> +       ASSERT_STEP(addr != NULL);
>> +       ASSERT_STEP(errno == EPERM);
>> +
>> +       addr = mmap(NULL, 1, PROT_READ | PROT_WRITE, MAP_PRIVATE,
>> +                       self->l1.file_rw, 0);
>> +       ASSERT_STEP(addr != NULL);
>> +       ASSERT_STEP(0 == munmap(addr, 1));
>> +}
>> +
>> +FIXTURE(fs_no_open) {
>> +       struct layout1 l1;
>> +       int prog;
>> +};
>> +
>> +FIXTURE_SETUP(fs_no_open)
>> +{
>> +       cleanup_layout1();
>> +       setup_layout1(_metadata, &self->l1);
>> +
>> +       ASSERT_EQ(0, load_bpf_file("rules/fs_no_open.o")) {
>> +               TH_LOG("%s", bpf_log_buf);
>> +       }
>> +       self->prog = prog_fd[0];
>> +       ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, NULL, 0, 0)) {
>> +               TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS");
>> +       }
>> +}
>> +
>> +FIXTURE_TEARDOWN(fs_no_open)
>> +{
>> +       EXPECT_EQ(0, close(self->prog));
>> +       cleanup_layout1();
>> +}
>> +
>> +static void landlocked_deny_open(struct __test_metadata *_metadata,
>> +               struct layout1 *l1)
>> +{
>> +       int fd;
>> +       void *addr;
>> +
>> +       fd = open(".", O_DIRECTORY | O_CLOEXEC);
>> +       ASSERT_EQ(-1, fd);
>> +       ASSERT_EQ(EPERM, errno);
>> +
>> +       addr = mmap(NULL, 1, PROT_READ | PROT_WRITE,
>> +                       MAP_SHARED, l1->file_rw, 0);
>> +       ASSERT_NE(NULL, addr);
>> +       ASSERT_EQ(0, munmap(addr, 1));
>> +}
>> +
>> +TEST_F(fs_no_open, deny_open_for_hierarchy) {
>> +       int fd;
>> +       int status;
>> +       pid_t child;
>> +
>> +       fd = open(".", O_DIRECTORY | O_CLOEXEC);
>> +       ASSERT_LE(0, fd);
>> +       ASSERT_EQ(0, close(fd));
>> +
>> +       ASSERT_EQ(0, seccomp(SECCOMP_APPEND_LANDLOCK_RULE, 0, &self->prog)) {
>> +               TH_LOG("Failed to apply rule fs_no_open: %s", strerror(errno));
>> +       }
>> +
>> +       landlocked_deny_open(_metadata, &self->l1);
>> +
>> +       child = fork();
>> +       ASSERT_LE(0, child);
>> +       if (!child) {
>> +               landlocked_deny_open(_metadata, &self->l1);
>> +               _exit(1);
>> +       }
>> +       ASSERT_EQ(child, waitpid(child, &status, 0));
>> +       ASSERT_TRUE(WIFEXITED(status));
>> +       _exit(WEXITSTATUS(status));
>> +}
>> +
>> +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..0c940a7fd3d0
>> --- /dev/null
>> +++ b/tools/testing/selftests/landlock/test_ptrace.c
>> @@ -0,0 +1,161 @@
>> +/*
>> + * 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_rule = {
>> +                       .version = 1,
>> +                       .event = LANDLOCK_SUBTYPE_EVENT_FS,
>> +               }
>> +       };
>> +       int prog;
>> +       char log[256] = "";
>> +
>> +       prog = bpf_load_program(BPF_PROG_TYPE_LANDLOCK,
>> +                       (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, prctl(PR_SET_NO_NEW_PRIVS, 1, NULL, 0, 0)) {
>> +               TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS");
>> +       }
>> +       ASSERT_EQ(0, seccomp(SECCOMP_APPEND_LANDLOCK_RULE, 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.11.0
>>
> 
> Awesome. I love to see all these tests, with both positive and
> negative checks. Nice!
> 
> -Kees
>
Kees Cook April 18, 2017, 11:59 p.m. UTC | #3
On Tue, Apr 18, 2017 at 4:53 PM, Mickaël Salaün <mic@digikod.net> wrote:
> On 19/04/2017 01:16, Kees Cook wrote:
>> On Tue, Mar 28, 2017 at 4:46 PM, Mickaël Salaün <mic@digikod.net> wrote:
>>> --- /dev/null
>>> +++ b/tools/testing/selftests/landlock/Makefile
>>> @@ -0,0 +1,47 @@
>>> +LIBDIR := ../../../lib
>>> +BPFOBJ := $(LIBDIR)/bpf/bpf.o
>>> +LOADOBJ := ../../../../samples/bpf/bpf_load.o
>>
>> Is the selftest tarball creation tool okay with this? IIRC, it should
>> be fine since it'll be a built object already, but it's a random
>> thought I had while looking at this.
>
> Hum, I'll check since it's the same for BPF tests.

Okay, cool.

>>> +# asm/sysreg.h - inline assembly used by it is incompatible with llvm.
>>> +# But, there is no easy way to fix it, so just exclude it since it is
>>> +# useless for BPF samples.
>>> +$(obj)/%.o: $(src)/%.c
>>> +       $(CLANG) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) $(EXTRA_CFLAGS) \
>>> +               -D__KERNEL__ -D__ASM_SYSREG_H -Wno-unused-value -Wno-pointer-sign \
>>> +               -Wno-compare-distinct-pointer-types \
>>> +               -Wno-gnu-variable-sized-type-not-at-end \
>>> +               -Wno-tautological-compare \
>>> +               -O2 -emit-llvm -c $< -o -| $(LLC) -march=bpf -filetype=obj -o $@
>>
>> Is clang required for the samples and the selftests? That needs to be
>> avoided... there needs to be a way to show people how to build a
>> landlock rule without requiring clang.
>
> I can rewrite this tests without requiring clang but it is already
> required for BPF tests…

So, I guess it's not a big deal for selftests (but it'd be nice, even
for BPF), but I think at least the samples/ should have examples on
how to do it "by hand", etc. Not everyone will build stuff with clang,
and it'd be good to make landlock as available as possible.

>>> +#define ASSERT_STEP(cond) \
>>> +       { \
>>> +               step--; \
>>> +               if (!(cond)) \
>>> +                       _exit(step); \
>>> +       }
>>
>> Can you explain this in more detail? I'm assuming there is a problem
>> with writing to the TH_LOG_STREAM fd or something?
>
> It's a trick to use the test framework without requiring to be allowed
> to write to an FD (i.e. log stream), but only to exit a code. I use this
> to test a Landlock rule which forbid access to any FS objects (including
> open FD). This could be used for seccomp too.

Okay. For seccomp, we just allow the fd. :P I'm not opposed to it; it
just makes some debugging harder without text details, etc.

-Kees
diff mbox

Patch

diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index d8593f1251ec..b584ad456428 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -12,6 +12,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/test_verifier.c b/tools/testing/selftests/bpf/test_verifier.c
index daa87dd7c80e..77255b14871e 100644
--- a/tools/testing/selftests/bpf/test_verifier.c
+++ b/tools/testing/selftests/bpf/test_verifier.c
@@ -4536,6 +4536,70 @@  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,
+	},
+	{
+		"landlock/fs: always accept",
+		.insns = {
+			BPF_MOV32_IMM(BPF_REG_0, 0),
+			BPF_EXIT_INSN(),
+		},
+		.result = ACCEPT,
+		.prog_type = BPF_PROG_TYPE_LANDLOCK,
+		.has_prog_subtype = true,
+		.prog_subtype = {
+			.landlock_rule = {
+				.version = 1,
+				.event = LANDLOCK_SUBTYPE_EVENT_FS,
+			}
+		},
+	},
+	{
+		"landlock/fs: 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_context, status)),
+			/* test operations on raw values */
+			BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
+			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
+				offsetof(struct landlock_context, arch)),
+			BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
+			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
+				offsetof(struct landlock_context, syscall_nr)),
+			BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
+			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
+				offsetof(struct landlock_context, syscall_cmd)),
+			BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
+			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_6,
+				offsetof(struct landlock_context, event)),
+			BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
+			BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_6,
+				offsetof(struct landlock_context, arg1)),
+			BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_6,
+				offsetof(struct landlock_context, arg2)),
+			BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, 1),
+			BPF_MOV32_IMM(BPF_REG_0, 0),
+			BPF_EXIT_INSN(),
+		},
+		.result = ACCEPT,
+		.prog_type = BPF_PROG_TYPE_LANDLOCK,
+		.has_prog_subtype = true,
+		.prog_subtype = {
+			.landlock_rule = {
+				.version = 1,
+				.event = LANDLOCK_SUBTYPE_EVENT_FS,
+			}
+		},
+	},
 };
 
 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..25b9cd834c3c
--- /dev/null
+++ b/tools/testing/selftests/landlock/.gitignore
@@ -0,0 +1,4 @@ 
+/test_base
+/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..9a52c82d64fa
--- /dev/null
+++ b/tools/testing/selftests/landlock/Makefile
@@ -0,0 +1,47 @@ 
+LIBDIR := ../../../lib
+BPFOBJ := $(LIBDIR)/bpf/bpf.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)
+rule_src = $(wildcard rules/*.c)
+
+test_objs := $(test_src:.c=)
+rule_objs := $(rule_src:.c=.o)
+
+TEST_PROGS := $(test_objs)
+
+.PHONY: all clean clean_tmp force
+
+all: $(test_objs) $(rule_objs)
+
+# force a rebuild of BPFOBJ when its dependencies are updated
+force:
+
+$(BPFOBJ): force
+	$(MAKE) -C $(dir $(BPFOBJ))
+
+$(LOADOBJ):
+	$(MAKE) -C $(dir $(LOADOBJ))
+
+# minimize builds
+rules/modules.order: $(rule_src)
+	$(MAKE) -C rules
+	@touch $@
+
+$(rule_objs): rules/modules.order
+	@
+
+$(test_objs): $(BPFOBJ) $(LOADOBJ)
+
+include ../lib.mk
+
+clean_tmp:
+	$(RM) -r tmp_*
+
+clean: clean_tmp
+	$(MAKE) -C rules clean
+	$(RM) $(test_objs)
+
diff --git a/tools/testing/selftests/landlock/rules/Makefile b/tools/testing/selftests/landlock/rules/Makefile
new file mode 100644
index 000000000000..8d6ff960ff7c
--- /dev/null
+++ b/tools/testing/selftests/landlock/rules/Makefile
@@ -0,0 +1,52 @@ 
+# kbuild trick to avoid linker error. Can be omitted if a module is built.
+obj- := dummy.o
+
+# Tell kbuild to always build the programs
+always := fs_read_only.o
+always += fs_no_open.o
+
+EXTRA_CFLAGS = -Wall -Wextra
+
+# Allows pointing LLC/CLANG to a LLVM backend with bpf support, redefine on cmdline:
+#  make samples/bpf/ LLC=~/git/llvm/build/bin/llc CLANG=~/git/llvm/build/bin/clang
+LLC ?= llc
+CLANG ?= clang
+
+# Verify LLVM compiler tools are available and bpf target is supported by llc
+.PHONY: all clean verify_cmds verify_target_bpf $(CLANG) $(LLC)
+
+# Trick to allow make to be run from this directory
+all:
+	$(MAKE) -C ../../../../../ $(CURDIR)/
+
+clean:
+	$(MAKE) -C ../../../../../ M=$(CURDIR) clean
+
+verify_cmds: $(CLANG) $(LLC)
+	@for TOOL in $^ ; do \
+		if ! (which -- "$${TOOL}" > /dev/null 2>&1); then \
+			echo "*** ERROR: Cannot find LLVM tool $${TOOL}" ;\
+			exit 1; \
+		else true; fi; \
+	done
+
+verify_target_bpf: verify_cmds
+	@if ! (${LLC} -march=bpf -mattr=help > /dev/null 2>&1); then \
+		echo "*** ERROR: LLVM (${LLC}) does not support 'bpf' target" ;\
+		echo "   NOTICE: LLVM version >= 3.7.1 required" ;\
+		exit 2; \
+	else true; fi
+
+%_kern.c: verify_target_bpf
+
+# asm/sysreg.h - inline assembly used by it is incompatible with llvm.
+# But, there is no easy way to fix it, so just exclude it since it is
+# useless for BPF samples.
+$(obj)/%.o: $(src)/%.c
+	$(CLANG) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) $(EXTRA_CFLAGS) \
+		-D__KERNEL__ -D__ASM_SYSREG_H -Wno-unused-value -Wno-pointer-sign \
+		-Wno-compare-distinct-pointer-types \
+		-Wno-gnu-variable-sized-type-not-at-end \
+		-Wno-tautological-compare \
+		-O2 -emit-llvm -c $< -o -| $(LLC) -march=bpf -filetype=obj -o $@
+
diff --git a/tools/testing/selftests/landlock/rules/README.rst b/tools/testing/selftests/landlock/rules/README.rst
new file mode 120000
index 000000000000..605f48aa6f72
--- /dev/null
+++ b/tools/testing/selftests/landlock/rules/README.rst
@@ -0,0 +1 @@ 
+../../../../../samples/bpf/README.rst
\ No newline at end of file
diff --git a/tools/testing/selftests/landlock/rules/bpf_helpers.h b/tools/testing/selftests/landlock/rules/bpf_helpers.h
new file mode 120000
index 000000000000..0aa1a521b39a
--- /dev/null
+++ b/tools/testing/selftests/landlock/rules/bpf_helpers.h
@@ -0,0 +1 @@ 
+../../../../../samples/bpf/bpf_helpers.h
\ No newline at end of file
diff --git a/tools/testing/selftests/landlock/rules/fs_no_open.c b/tools/testing/selftests/landlock/rules/fs_no_open.c
new file mode 100644
index 000000000000..c6ea305e58a7
--- /dev/null
+++ b/tools/testing/selftests/landlock/rules/fs_no_open.c
@@ -0,0 +1,31 @@ 
+/*
+ * Landlock rule - no-open filesystem
+ *
+ * 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 <uapi/linux/bpf.h>
+#include "bpf_helpers.h"
+
+SEC("landlock1")
+static int landlock_fs_prog1(struct landlock_context *ctx)
+{
+	if (!(ctx->arg2 & LANDLOCK_ACTION_FS_GET))
+		return 0;
+	return 1;
+}
+
+SEC("subtype")
+static union bpf_prog_subtype _subtype = {
+	.landlock_rule = {
+		.version = 1,
+		.event = LANDLOCK_SUBTYPE_EVENT_FS,
+	}
+};
+
+SEC("license")
+static const char _license[] = "GPL";
diff --git a/tools/testing/selftests/landlock/rules/fs_read_only.c b/tools/testing/selftests/landlock/rules/fs_read_only.c
new file mode 100644
index 000000000000..212dda7c0c27
--- /dev/null
+++ b/tools/testing/selftests/landlock/rules/fs_read_only.c
@@ -0,0 +1,31 @@ 
+/*
+ * Landlock rule - read-only filesystem
+ *
+ * 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 <uapi/linux/bpf.h>
+#include "bpf_helpers.h"
+
+SEC("landlock1")
+static int landlock_fs_prog1(struct landlock_context *ctx)
+{
+	if (!(ctx->arg2 & LANDLOCK_ACTION_FS_WRITE))
+		return 0;
+	return 1;
+}
+
+SEC("subtype")
+static union bpf_prog_subtype _subtype = {
+	.landlock_rule = {
+		.version = 1,
+		.event = LANDLOCK_SUBTYPE_EVENT_FS,
+	}
+};
+
+SEC("license")
+static const char _license[] = "GPL";
diff --git a/tools/testing/selftests/landlock/test.h b/tools/testing/selftests/landlock/test.h
new file mode 100644
index 000000000000..7a194815391b
--- /dev/null
+++ b/tools/testing/selftests/landlock/test.h
@@ -0,0 +1,35 @@ 
+/*
+ * 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 <sys/prctl.h>
+#include <sys/syscall.h>
+
+#include "../seccomp/test_harness.h"
+#include "../../../../samples/bpf/bpf_load.h"
+
+#ifndef SECCOMP_APPEND_LANDLOCK_RULE
+#define SECCOMP_APPEND_LANDLOCK_RULE	2
+#endif
+
+#ifndef seccomp
+static int seccomp(unsigned int op, unsigned int flags, void *args)
+{
+	errno = 0;
+	return syscall(__NR_seccomp, op, flags, args);
+}
+#endif
+
+#define ASSERT_STEP(cond) \
+	{ \
+		step--; \
+		if (!(cond)) \
+			_exit(step); \
+	}
diff --git a/tools/testing/selftests/landlock/test_base.c b/tools/testing/selftests/landlock/test_base.c
new file mode 100644
index 000000000000..bdf056edee03
--- /dev/null
+++ b/tools/testing/selftests/landlock/test_base.c
@@ -0,0 +1,31 @@ 
+/*
+ * 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 = prctl(PR_SET_NO_NEW_PRIVS, 1, NULL, 0, 0);
+	ASSERT_EQ(0, ret) {
+		TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS");
+	}
+	ret = seccomp(SECCOMP_APPEND_LANDLOCK_RULE, 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_fs.c b/tools/testing/selftests/landlock/test_fs.c
new file mode 100644
index 000000000000..e69eda433716
--- /dev/null
+++ b/tools/testing/selftests/landlock/test_fs.c
@@ -0,0 +1,305 @@ 
+/*
+ * Landlock tests - filesystem
+ *
+ * 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 <linux/bpf.h>
+#include <linux/filter.h>
+#include <linux/seccomp.h>
+#include <stddef.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <sys/syscall.h>
+
+#include <fcntl.h> /* open() */
+#include <sys/mount.h>
+#include <sys/stat.h> /* mkdir() */
+#include <sys/mman.h> /* mmap() */
+
+#include "test.h"
+
+#define TMP_PREFIX "tmp_"
+
+struct layout1 {
+	int file_ro;
+	int file_rw;
+	int file_wo;
+};
+
+static void setup_layout1(struct __test_metadata *_metadata,
+		struct layout1 *l1)
+{
+	int fd;
+	char buf[] = "fs_read_only";
+
+	l1->file_ro = -1;
+	l1->file_rw = -1;
+	l1->file_wo = -1;
+
+	fd = open(TMP_PREFIX "file_created",
+			O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0600);
+	ASSERT_GE(fd, 0);
+	ASSERT_EQ(sizeof(buf), write(fd, buf, sizeof(buf)));
+	ASSERT_EQ(0, close(fd));
+
+	fd = mkdir(TMP_PREFIX "dir_created", 0600);
+	ASSERT_GE(fd, 0);
+	ASSERT_EQ(0, close(fd));
+
+	l1->file_ro = open(TMP_PREFIX "file_ro",
+			O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0600);
+	ASSERT_LE(0, l1->file_ro);
+	ASSERT_EQ(sizeof(buf), write(l1->file_ro, buf, sizeof(buf)));
+	ASSERT_EQ(0, close(l1->file_ro));
+	l1->file_ro = open(TMP_PREFIX "file_ro",
+			O_RDONLY | O_CLOEXEC, 0600);
+	ASSERT_LE(0, l1->file_ro);
+
+	l1->file_rw = open(TMP_PREFIX "file_rw",
+			O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0600);
+	ASSERT_LE(0, l1->file_rw);
+	ASSERT_EQ(sizeof(buf), write(l1->file_rw, buf, sizeof(buf)));
+	ASSERT_EQ(0, lseek(l1->file_rw, 0, SEEK_SET));
+
+	l1->file_wo = open(TMP_PREFIX "file_wo",
+			O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0600);
+	ASSERT_LE(0, l1->file_wo);
+	ASSERT_EQ(sizeof(buf), write(l1->file_wo, buf, sizeof(buf)));
+	ASSERT_EQ(0, lseek(l1->file_wo, 0, SEEK_SET));
+}
+
+static void cleanup_layout1(void)
+{
+	unlink(TMP_PREFIX "file_created");
+	unlink(TMP_PREFIX "file_ro");
+	unlink(TMP_PREFIX "file_rw");
+	unlink(TMP_PREFIX "file_wo");
+	unlink(TMP_PREFIX "should_not_exist");
+	rmdir(TMP_PREFIX "dir_created");
+}
+
+FIXTURE(fs_read_only) {
+	struct layout1 l1;
+	int prog;
+};
+
+FIXTURE_SETUP(fs_read_only)
+{
+	cleanup_layout1();
+	setup_layout1(_metadata, &self->l1);
+
+	ASSERT_EQ(0, load_bpf_file("rules/fs_read_only.o")) {
+		TH_LOG("%s", bpf_log_buf);
+	}
+	self->prog = prog_fd[0];
+	ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, NULL, 0, 0)) {
+		TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS");
+	}
+}
+
+FIXTURE_TEARDOWN(fs_read_only)
+{
+	EXPECT_EQ(0, close(self->prog));
+	/* cleanup_layout1() would be denied here */
+}
+
+TEST_F(fs_read_only, load_prog) {}
+
+TEST_F(fs_read_only, read_only_file)
+{
+	int fd;
+	int step = 0;
+	char buf_write[] = "should not be written";
+	char buf_read[2];
+
+	ASSERT_EQ(-1, write(self->l1.file_ro, buf_write, sizeof(buf_write)));
+	ASSERT_EQ(EBADF, errno);
+
+	ASSERT_EQ(-1, read(self->l1.file_wo, buf_read, sizeof(buf_read)));
+	ASSERT_EQ(EBADF, errno);
+
+	ASSERT_EQ(0, seccomp(SECCOMP_APPEND_LANDLOCK_RULE, 0, &self->prog)) {
+		TH_LOG("Failed to apply rule fs_read_only: %s",
+				strerror(errno));
+	}
+
+	fd = open(".", O_TMPFILE | O_EXCL | O_RDWR | O_CLOEXEC, 0600);
+	ASSERT_STEP(fd == -1);
+	ASSERT_STEP(errno != EOPNOTSUPP)
+	ASSERT_STEP(errno == EPERM);
+
+	fd = open(TMP_PREFIX "file_created",
+			O_RDONLY | O_CLOEXEC);
+	ASSERT_STEP(fd >= 0);
+	ASSERT_STEP(!close(fd));
+
+	fd = open(TMP_PREFIX "file_created",
+			O_RDWR | O_CLOEXEC);
+	ASSERT_STEP(fd == -1);
+	ASSERT_STEP(errno == EPERM);
+
+	fd = open(TMP_PREFIX "file_created",
+			O_WRONLY | O_CLOEXEC);
+	ASSERT_STEP(fd == -1);
+	ASSERT_STEP(errno == EPERM);
+
+	fd = open(TMP_PREFIX "should_not_exist",
+			O_CREAT | O_EXCL | O_CLOEXEC, 0600);
+	ASSERT_STEP(fd == -1);
+	ASSERT_STEP(errno == EPERM);
+
+	ASSERT_STEP(-1 ==
+			write(self->l1.file_ro, buf_write, sizeof(buf_write)));
+	ASSERT_STEP(errno == EBADF);
+	ASSERT_STEP(sizeof(buf_read) ==
+			read(self->l1.file_ro, buf_read, sizeof(buf_read)));
+
+	ASSERT_STEP(-1 ==
+			write(self->l1.file_rw, buf_write, sizeof(buf_write)));
+	ASSERT_STEP(errno == EPERM);
+	ASSERT_STEP(sizeof(buf_read) ==
+			read(self->l1.file_rw, buf_read, sizeof(buf_read)));
+
+	ASSERT_STEP(-1 == write(self->l1.file_wo, buf_write, sizeof(buf_write)));
+	ASSERT_STEP(errno == EPERM);
+	ASSERT_STEP(-1 == read(self->l1.file_wo, buf_read, sizeof(buf_read)));
+	ASSERT_STEP(errno == EBADF);
+
+	ASSERT_STEP(-1 == unlink(TMP_PREFIX "file_created"));
+	ASSERT_STEP(errno == EPERM);
+	ASSERT_STEP(-1 == rmdir(TMP_PREFIX "dir_created"));
+	ASSERT_STEP(errno == EPERM);
+
+	ASSERT_STEP(0 == close(self->l1.file_ro));
+	ASSERT_STEP(0 == close(self->l1.file_rw));
+	ASSERT_STEP(0 == close(self->l1.file_wo));
+}
+
+TEST_F(fs_read_only, read_only_mount)
+{
+	int step = 0;
+
+	ASSERT_EQ(0, mount(".", TMP_PREFIX "dir_created",
+				NULL, MS_BIND, NULL));
+	ASSERT_EQ(0, umount2(TMP_PREFIX "dir_created", MNT_FORCE));
+
+	ASSERT_EQ(0, seccomp(SECCOMP_APPEND_LANDLOCK_RULE, 0, &self->prog)) {
+		TH_LOG("Failed to apply rule fs_read_only: %s",
+				strerror(errno));
+	}
+
+	ASSERT_STEP(-1 == mount(".", TMP_PREFIX "dir_created",
+				NULL, MS_BIND, NULL));
+	ASSERT_STEP(errno == EPERM);
+	ASSERT_STEP(-1 == umount("/"));
+	ASSERT_STEP(errno == EPERM);
+}
+
+TEST_F(fs_read_only, read_only_mem)
+{
+	int step = 0;
+	void *addr;
+
+	addr = mmap(NULL, 1, PROT_READ | PROT_WRITE,
+			MAP_SHARED, self->l1.file_rw, 0);
+	ASSERT_NE(NULL, addr);
+	ASSERT_EQ(0, munmap(addr, 1));
+
+	ASSERT_EQ(0, seccomp(SECCOMP_APPEND_LANDLOCK_RULE, 0, &self->prog)) {
+		TH_LOG("Failed to apply rule fs_read_only: %s",
+				strerror(errno));
+	}
+
+	addr = mmap(NULL, 1, PROT_READ, MAP_SHARED,
+			self->l1.file_rw, 0);
+	ASSERT_STEP(addr != NULL);
+	ASSERT_STEP(-1 == mprotect(addr, 1, PROT_WRITE));
+	ASSERT_STEP(errno == EPERM);
+	ASSERT_STEP(0 == munmap(addr, 1));
+
+	addr = mmap(NULL, 1, PROT_READ | PROT_WRITE, MAP_SHARED,
+			self->l1.file_rw, 0);
+	ASSERT_STEP(addr != NULL);
+	ASSERT_STEP(errno == EPERM);
+
+	addr = mmap(NULL, 1, PROT_READ | PROT_WRITE, MAP_PRIVATE,
+			self->l1.file_rw, 0);
+	ASSERT_STEP(addr != NULL);
+	ASSERT_STEP(0 == munmap(addr, 1));
+}
+
+FIXTURE(fs_no_open) {
+	struct layout1 l1;
+	int prog;
+};
+
+FIXTURE_SETUP(fs_no_open)
+{
+	cleanup_layout1();
+	setup_layout1(_metadata, &self->l1);
+
+	ASSERT_EQ(0, load_bpf_file("rules/fs_no_open.o")) {
+		TH_LOG("%s", bpf_log_buf);
+	}
+	self->prog = prog_fd[0];
+	ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, NULL, 0, 0)) {
+		TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS");
+	}
+}
+
+FIXTURE_TEARDOWN(fs_no_open)
+{
+	EXPECT_EQ(0, close(self->prog));
+	cleanup_layout1();
+}
+
+static void landlocked_deny_open(struct __test_metadata *_metadata,
+		struct layout1 *l1)
+{
+	int fd;
+	void *addr;
+
+	fd = open(".", O_DIRECTORY | O_CLOEXEC);
+	ASSERT_EQ(-1, fd);
+	ASSERT_EQ(EPERM, errno);
+
+	addr = mmap(NULL, 1, PROT_READ | PROT_WRITE,
+			MAP_SHARED, l1->file_rw, 0);
+	ASSERT_NE(NULL, addr);
+	ASSERT_EQ(0, munmap(addr, 1));
+}
+
+TEST_F(fs_no_open, deny_open_for_hierarchy) {
+	int fd;
+	int status;
+	pid_t child;
+
+	fd = open(".", O_DIRECTORY | O_CLOEXEC);
+	ASSERT_LE(0, fd);
+	ASSERT_EQ(0, close(fd));
+
+	ASSERT_EQ(0, seccomp(SECCOMP_APPEND_LANDLOCK_RULE, 0, &self->prog)) {
+		TH_LOG("Failed to apply rule fs_no_open: %s", strerror(errno));
+	}
+
+	landlocked_deny_open(_metadata, &self->l1);
+
+	child = fork();
+	ASSERT_LE(0, child);
+	if (!child) {
+		landlocked_deny_open(_metadata, &self->l1);
+		_exit(1);
+	}
+	ASSERT_EQ(child, waitpid(child, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	_exit(WEXITSTATUS(status));
+}
+
+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..0c940a7fd3d0
--- /dev/null
+++ b/tools/testing/selftests/landlock/test_ptrace.c
@@ -0,0 +1,161 @@ 
+/*
+ * 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_rule = {
+			.version = 1,
+			.event = LANDLOCK_SUBTYPE_EVENT_FS,
+		}
+	};
+	int prog;
+	char log[256] = "";
+
+	prog = bpf_load_program(BPF_PROG_TYPE_LANDLOCK,
+			(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, prctl(PR_SET_NO_NEW_PRIVS, 1, NULL, 0, 0)) {
+		TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS");
+	}
+	ASSERT_EQ(0, seccomp(SECCOMP_APPEND_LANDLOCK_RULE, 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