From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932906AbdC1Xss (ORCPT ); Tue, 28 Mar 2017 19:48:48 -0400 Received: from smtp-sh.infomaniak.ch ([128.65.195.4]:48575 "EHLO smtp-sh.infomaniak.ch" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S932778AbdC1Xsj (ORCPT ); Tue, 28 Mar 2017 19:48:39 -0400 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= To: linux-kernel@vger.kernel.org Cc: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= , Alexei Starovoitov , Andy Lutomirski , Arnaldo Carvalho de Melo , Casey Schaufler , Daniel Borkmann , David Drysdale , "David S . Miller" , "Eric W . Biederman" , James Morris , Jann Horn , Jonathan Corbet , Matthew Garrett , Michael Kerrisk , Kees Cook , Paul Moore , Sargun Dhillon , "Serge E . Hallyn" , Shuah Khan , Tejun Heo , Thomas Graf , Will Drewry , kernel-hardening@lists.openwall.com, linux-api@vger.kernel.org, linux-security-module@vger.kernel.org, netdev@vger.kernel.org Subject: [PATCH net-next v6 10/11] bpf,landlock: Add tests for Landlock Date: Wed, 29 Mar 2017 01:46:49 +0200 Message-Id: <20170328234650.19695-11-mic@digikod.net> X-Mailer: git-send-email 2.11.0 In-Reply-To: <20170328234650.19695-1-mic@digikod.net> References: <20170328234650.19695-1-mic@digikod.net> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Antivirus: Dr.Web (R) for Unix mail servers drweb plugin ver.6.0.2.8 X-Antivirus-Code: 0x100000 Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org 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 Cc: Alexei Starovoitov Cc: Andy Lutomirski Cc: Daniel Borkmann Cc: David S. Miller Cc: James Morris Cc: Kees Cook Cc: Serge E. Hallyn Cc: Shuah Khan Cc: Will Drewry --- 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 + +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 + * + * 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 +#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 + * + * 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 +#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 + * + * 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 +#include +#include + +#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 + * + * 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 + +#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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include /* open() */ +#include +#include /* mkdir() */ +#include /* 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 + * + * 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 /* raise */ +#include +#include /* waitpid */ +#include /* waitpid */ +#include /* 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 From mboxrd@z Thu Jan 1 00:00:00 1970 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Subject: [PATCH net-next v6 10/11] bpf,landlock: Add tests for Landlock Date: Wed, 29 Mar 2017 01:46:49 +0200 Message-ID: <20170328234650.19695-11-mic@digikod.net> References: <20170328234650.19695-1-mic@digikod.net> Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cc: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= , Alexei Starovoitov , Andy Lutomirski , Arnaldo Carvalho de Melo , Casey Schaufler , Daniel Borkmann , David Drysdale , "David S . Miller" , "Eric W . Biederman" , James Morris , Jann Horn , Jonathan Corbet , Matthew Garrett , Michael Kerrisk , Kees Cook , Paul Moore , Sargun Dhillon , "Serge E . Hallyn" , Shuah Khan , Tejun Heo In-Reply-To: <20170328234650.19695-1-mic@digikod.net> Sender: linux-kernel-owner@vger.kernel.org List-Id: netdev.vger.kernel.org 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 Cc: Alexei Starovoitov Cc: Andy Lutomirski Cc: Daniel Borkmann Cc: David S. Miller Cc: James Morris Cc: Kees Cook Cc: Serge E. Hallyn Cc: Shuah Khan Cc: Will Drewry --- 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 + +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 + * + * 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 +#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 + * + * 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 +#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 + * + * 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 +#include +#include + +#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 + * + * 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 + +#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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include /* open() */ +#include +#include /* mkdir() */ +#include /* 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 + * + * 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 /* raise */ +#include +#include /* waitpid */ +#include /* waitpid */ +#include /* 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 From mboxrd@z Thu Jan 1 00:00:00 1970 From: mic@digikod.net (=?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?=) Date: Wed, 29 Mar 2017 01:46:49 +0200 Subject: [PATCH net-next v6 10/11] bpf,landlock: Add tests for Landlock In-Reply-To: <20170328234650.19695-1-mic@digikod.net> References: <20170328234650.19695-1-mic@digikod.net> Message-ID: <20170328234650.19695-11-mic@digikod.net> To: linux-security-module@vger.kernel.org List-Id: linux-security-module.vger.kernel.org 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 Cc: Alexei Starovoitov Cc: Andy Lutomirski Cc: Daniel Borkmann Cc: David S. Miller Cc: James Morris Cc: Kees Cook Cc: Serge E. Hallyn Cc: Shuah Khan Cc: Will Drewry --- 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 + +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 + * + * 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 +#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 + * + * 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 +#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 + * + * 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 +#include +#include + +#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 + * + * 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 + +#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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include /* open() */ +#include +#include /* mkdir() */ +#include /* 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 + * + * 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 /* raise */ +#include +#include /* waitpid */ +#include /* waitpid */ +#include /* 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 -- To unsubscribe from this list: send the line "unsubscribe linux-security-module" in the body of a message to majordomo at vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html From mboxrd@z Thu Jan 1 00:00:00 1970 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Wed, 29 Mar 2017 01:46:49 +0200 Message-Id: <20170328234650.19695-11-mic@digikod.net> In-Reply-To: <20170328234650.19695-1-mic@digikod.net> References: <20170328234650.19695-1-mic@digikod.net> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Subject: [kernel-hardening] [PATCH net-next v6 10/11] bpf,landlock: Add tests for Landlock To: linux-kernel@vger.kernel.org Cc: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= , Alexei Starovoitov , Andy Lutomirski , Arnaldo Carvalho de Melo , Casey Schaufler , Daniel Borkmann , David Drysdale , "David S . Miller" , "Eric W . Biederman" , James Morris , Jann Horn , Jonathan Corbet , Matthew Garrett , Michael Kerrisk , Kees Cook , Paul Moore , Sargun Dhillon , "Serge E . Hallyn" , Shuah Khan , Tejun Heo , Thomas Graf , Will Drewry , kernel-hardening@lists.openwall.com, linux-api@vger.kernel.org, linux-security-module@vger.kernel.org, netdev@vger.kernel.org List-ID: 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 Cc: Alexei Starovoitov Cc: Andy Lutomirski Cc: Daniel Borkmann Cc: David S. Miller Cc: James Morris Cc: Kees Cook Cc: Serge E. Hallyn Cc: Shuah Khan Cc: Will Drewry --- 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 + +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 + * + * 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 +#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 + * + * 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 +#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 + * + * 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 +#include +#include + +#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 + * + * 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 + +#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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include /* open() */ +#include +#include /* mkdir() */ +#include /* 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 + * + * 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 /* raise */ +#include +#include /* waitpid */ +#include /* waitpid */ +#include /* 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