[PATCH net-next v6 10/11] bpf,landlock: Add tests for Landlock
Mickaël Salaün
mic at digikod.net
Tue Apr 18 23:53:36 UTC 2017
On 19/04/2017 01:16, Kees Cook wrote:
> On Tue, Mar 28, 2017 at 4:46 PM, Mickaël Salaün <mic at 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 at digikod.net>
>> Cc: Alexei Starovoitov <ast at kernel.org>
>> Cc: Andy Lutomirski <luto at amacapital.net>
>> Cc: Daniel Borkmann <daniel at iogearbox.net>
>> Cc: David S. Miller <davem at davemloft.net>
>> Cc: James Morris <james.l.morris at oracle.com>
>> Cc: Kees Cook <keescook at chromium.org>
>> Cc: Serge E. Hallyn <serge at hallyn.com>
>> Cc: Shuah Khan <shuah at kernel.org>
>> Cc: Will Drewry <wad at 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 at 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 at 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 at 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 at 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 at 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 at 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
>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 488 bytes
Desc: OpenPGP digital signature
URL: <http://kernsec.org/pipermail/linux-security-module-archive/attachments/20170419/697d4cae/attachment.sig>
More information about the Linux-security-module-archive
mailing list