On 19/04/2017 01:06, Kees Cook wrote: > On Tue, Mar 28, 2017 at 4:46 PM, Mickaël Salaün wrote: >> Add a basic sandbox tool to create a process isolated from some part of >> the system. This sandbox create a read-only environment. It is only >> allowed to write to a character device such as a TTY: >> >> # :> X >> # echo $? >> 0 >> # ./samples/bpf/landlock1 /bin/sh -i >> Launching a new sandboxed process. >> # :> Y >> cannot create Y: Operation not permitted >> >> Changes since v5: >> * cosmetic fixes >> * rebase >> >> Changes since v4: >> * write Landlock rule in C and compiled it with LLVM >> * remove cgroup handling >> * remove path handling: only handle a read-only environment >> * remove errno return codes >> >> Changes since v3: >> * remove seccomp and origin field: completely free from seccomp programs >> * handle more FS-related hooks >> * handle inode hooks and directory traversal >> * add faked but consistent view thanks to ENOENT >> * add /lib64 in the example >> * fix spelling >> * rename some types and definitions (e.g. SECCOMP_ADD_LANDLOCK_RULE) >> >> Changes since v2: >> * use BPF_PROG_ATTACH for cgroup handling >> >> 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 >> --- >> samples/bpf/Makefile | 4 ++ >> samples/bpf/bpf_load.c | 31 +++++++++++-- >> samples/bpf/landlock1_kern.c | 46 +++++++++++++++++++ >> samples/bpf/landlock1_user.c | 102 +++++++++++++++++++++++++++++++++++++++++++ >> 4 files changed, 179 insertions(+), 4 deletions(-) >> create mode 100644 samples/bpf/landlock1_kern.c >> create mode 100644 samples/bpf/landlock1_user.c >> >> diff --git a/samples/bpf/Makefile b/samples/bpf/Makefile >> index d42b495b0992..4743674a3fa3 100644 >> --- a/samples/bpf/Makefile >> +++ b/samples/bpf/Makefile >> @@ -36,6 +36,7 @@ hostprogs-y += lwt_len_hist >> hostprogs-y += xdp_tx_iptunnel >> hostprogs-y += test_map_in_map >> hostprogs-y += per_socket_stats_example >> +hostprogs-y += landlock1 >> >> # Libbpf dependencies >> LIBBPF := ../../tools/lib/bpf/bpf.o >> @@ -76,6 +77,7 @@ lwt_len_hist-objs := bpf_load.o $(LIBBPF) lwt_len_hist_user.o >> xdp_tx_iptunnel-objs := bpf_load.o $(LIBBPF) xdp_tx_iptunnel_user.o >> test_map_in_map-objs := bpf_load.o $(LIBBPF) test_map_in_map_user.o >> per_socket_stats_example-objs := $(LIBBPF) cookie_uid_helper_example.o >> +landlock1-objs := bpf_load.o $(LIBBPF) landlock1_user.o >> >> # Tell kbuild to always build the programs >> always := $(hostprogs-y) >> @@ -111,6 +113,7 @@ always += lwt_len_hist_kern.o >> always += xdp_tx_iptunnel_kern.o >> always += test_map_in_map_kern.o >> always += cookie_uid_helper_example.o >> +always += landlock1_kern.o >> >> HOSTCFLAGS += -I$(objtree)/usr/include >> HOSTCFLAGS += -I$(srctree)/tools/lib/ >> @@ -146,6 +149,7 @@ HOSTLOADLIBES_tc_l2_redirect += -l elf >> HOSTLOADLIBES_lwt_len_hist += -l elf >> HOSTLOADLIBES_xdp_tx_iptunnel += -lelf >> HOSTLOADLIBES_test_map_in_map += -lelf >> +HOSTLOADLIBES_landlock1 += -lelf >> >> # 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 >> diff --git a/samples/bpf/bpf_load.c b/samples/bpf/bpf_load.c >> index 4a3460d7c01f..3713e5e2e998 100644 >> --- a/samples/bpf/bpf_load.c >> +++ b/samples/bpf/bpf_load.c >> @@ -29,6 +29,8 @@ >> >> static char license[128]; >> static int kern_version; >> +static union bpf_prog_subtype subtype = {}; >> +static bool has_subtype; >> static bool processed_sec[128]; >> char bpf_log_buf[BPF_LOG_BUF_SIZE]; >> int map_fd[MAX_MAPS]; >> @@ -68,6 +70,7 @@ static int load_and_attach(const char *event, struct bpf_insn *prog, int size) >> bool is_perf_event = strncmp(event, "perf_event", 10) == 0; >> bool is_cgroup_skb = strncmp(event, "cgroup/skb", 10) == 0; >> bool is_cgroup_sk = strncmp(event, "cgroup/sock", 11) == 0; >> + bool is_landlock = strncmp(event, "landlock", 8) == 0; >> size_t insns_cnt = size / sizeof(struct bpf_insn); >> enum bpf_prog_type prog_type; >> char buf[256]; >> @@ -94,6 +97,13 @@ static int load_and_attach(const char *event, struct bpf_insn *prog, int size) >> prog_type = BPF_PROG_TYPE_CGROUP_SKB; >> } else if (is_cgroup_sk) { >> prog_type = BPF_PROG_TYPE_CGROUP_SOCK; >> + } else if (is_landlock) { >> + prog_type = BPF_PROG_TYPE_LANDLOCK; >> + if (!has_subtype) { >> + printf("No subtype\n"); >> + return -1; >> + } >> + st = &subtype; >> } else { >> printf("Unknown event '%s'\n", event); >> return -1; >> @@ -108,7 +118,8 @@ static int load_and_attach(const char *event, struct bpf_insn *prog, int size) >> >> prog_fd[prog_cnt++] = fd; >> >> - if (is_xdp || is_perf_event || is_cgroup_skb || is_cgroup_sk) >> + if (is_xdp || is_perf_event || is_cgroup_skb || is_cgroup_sk || >> + is_landlock) >> return 0; >> >> if (is_socket) { >> @@ -294,6 +305,7 @@ int load_bpf_file(char *path) >> kern_version = 0; >> memset(license, 0, sizeof(license)); >> memset(processed_sec, 0, sizeof(processed_sec)); >> + has_subtype = false; >> >> if (elf_version(EV_CURRENT) == EV_NONE) >> return 1; >> @@ -339,6 +351,16 @@ int load_bpf_file(char *path) >> processed_sec[i] = true; >> if (load_maps(data->d_buf, data->d_size)) >> return 1; >> + } else if (strcmp(shname, "subtype") == 0) { >> + processed_sec[i] = true; >> + if (data->d_size != sizeof(union bpf_prog_subtype)) { >> + printf("invalid size of subtype section %zd\n", >> + data->d_size); >> + return 1; >> + } >> + memcpy(&subtype, data->d_buf, >> + sizeof(union bpf_prog_subtype)); >> + has_subtype = true; >> } else if (shdr.sh_type == SHT_SYMTAB) { >> symbols = data; >> } >> @@ -376,14 +398,14 @@ int load_bpf_file(char *path) >> memcmp(shname_prog, "xdp", 3) == 0 || >> memcmp(shname_prog, "perf_event", 10) == 0 || >> memcmp(shname_prog, "socket", 6) == 0 || >> - memcmp(shname_prog, "cgroup/", 7) == 0) >> + memcmp(shname_prog, "cgroup/", 7) == 0 || >> + memcmp(shname_prog, "landlock", 8) == 0) >> load_and_attach(shname_prog, insns, data_prog->d_size); >> } >> } >> >> /* load programs that don't use maps */ >> for (i = 1; i < ehdr.e_shnum; i++) { >> - >> if (processed_sec[i]) >> continue; >> >> @@ -396,7 +418,8 @@ int load_bpf_file(char *path) >> memcmp(shname, "xdp", 3) == 0 || >> memcmp(shname, "perf_event", 10) == 0 || >> memcmp(shname, "socket", 6) == 0 || >> - memcmp(shname, "cgroup/", 7) == 0) >> + memcmp(shname, "cgroup/", 7) == 0 || >> + memcmp(shname, "landlock", 8) == 0) >> load_and_attach(shname, data->d_buf, data->d_size); >> } >> >> diff --git a/samples/bpf/landlock1_kern.c b/samples/bpf/landlock1_kern.c >> new file mode 100644 >> index 000000000000..b8a9b0ca84c9 >> --- /dev/null >> +++ b/samples/bpf/landlock1_kern.c >> @@ -0,0 +1,46 @@ >> +/* >> + * Landlock rule - partial 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. >> + */ >> + >> +#define KBUILD_MODNAME "foo" >> +#include >> +#include /* S_ISCHR() */ >> +#include "bpf_helpers.h" >> + >> +SEC("landlock1") >> +static int landlock_fs_prog1(struct landlock_context *ctx) > > Since this is in samples, I think this needs a lot more comments to > describe each pieces, how it fits together, etc. This is where people > are going to come to learn how to use landlock, so making it as clear > as possible is important. This is especially true for each step of the > rule logic. (e.g. some will wonder why is S_ISCHR excluded, etc.) Right, I already extended a bit this example with a S_ISFIFO() and some comments. There is also the Documentation/.../user.rst which should help understand how it works. > >> +{ >> + char fmt_error[] = "landlock1: error: get_mode:%lld\n"; >> + char fmt_name[] = "landlock1: syscall:%d\n"; >> + long long ret; >> + >> + if (!(ctx->arg2 & LANDLOCK_ACTION_FS_WRITE)) >> + return 0; >> + ret = bpf_handle_fs_get_mode((void *)ctx->arg1); >> + if (ret < 0) { >> + bpf_trace_printk(fmt_error, sizeof(fmt_error), ret); >> + return 1; >> + } >> + if (S_ISCHR(ret)) >> + return 0; >> + bpf_trace_printk(fmt_name, sizeof(fmt_name), ctx->syscall_nr); >> + return 1; >> +} >> + >> +SEC("subtype") >> +static union bpf_prog_subtype _subtype = { > > Can this be const? Yes > >> + .landlock_rule = { >> + .version = 1, >> + .event = LANDLOCK_SUBTYPE_EVENT_FS, >> + .ability = LANDLOCK_SUBTYPE_ABILITY_DEBUG, >> + } >> +}; >> + >> +SEC("license") >> +static const char _license[] = "GPL"; >> diff --git a/samples/bpf/landlock1_user.c b/samples/bpf/landlock1_user.c >> new file mode 100644 >> index 000000000000..6f79eb0ee6db >> --- /dev/null >> +++ b/samples/bpf/landlock1_user.c >> @@ -0,0 +1,102 @@ >> +/* >> + * Landlock sandbox - partial 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 "bpf_load.h" >> +#include "libbpf.h" >> + >> +#define _GNU_SOURCE >> +#include >> +#include /* open() */ >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> +#include >> + >> +#ifndef seccomp >> +static int seccomp(unsigned int op, unsigned int flags, void *args) >> +{ >> + errno = 0; >> + return syscall(__NR_seccomp, op, flags, args); >> +} >> +#endif >> + >> +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) >> +#define MAX_ERRNO 4095 > > Is MAX_ERRNO actually needed? Not anymore. > >> + >> + >> +struct landlock_rule { >> + enum landlock_subtype_event event; >> + struct bpf_insn *bpf; >> + size_t size; >> +}; >> + >> +static int apply_sandbox(int prog_fd) >> +{ >> + int ret = 0; >> + >> + /* set up the test sandbox */ >> + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { >> + perror("prctl(no_new_priv)"); >> + return 1; >> + } >> + if (seccomp(SECCOMP_APPEND_LANDLOCK_RULE, 0, &prog_fd)) { >> + perror("seccomp(set_hook)"); >> + ret = 1; >> + } >> + close(prog_fd); >> + >> + return ret; >> +} >> + >> +int main(int argc, char * const argv[], char * const *envp) >> +{ >> + char filename[256]; >> + char *cmd_path; >> + char * const *cmd_argv; >> + >> + if (argc < 2) { >> + fprintf(stderr, "usage: %s [args]...\n\n", argv[0]); >> + fprintf(stderr, "Launch a command in a read-only environment " >> + "(except for character devices).\n"); >> + fprintf(stderr, "Display debug with: " >> + "cat /sys/kernel/debug/tracing/trace_pipe &\n"); >> + return 1; >> + } >> + >> + snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]); >> + if (load_bpf_file(filename)) { >> + printf("%s", bpf_log_buf); >> + return 1; >> + } >> + if (!prog_fd[0]) { >> + if (errno) { >> + printf("load_bpf_file: %s\n", strerror(errno)); >> + } else { >> + printf("load_bpf_file: Error\n"); >> + } >> + return 1; >> + } >> + >> + if (apply_sandbox(prog_fd[0])) >> + return 1; >> + cmd_path = argv[1]; >> + cmd_argv = argv + 1; >> + fprintf(stderr, "Launching a new sandboxed process.\n"); >> + execve(cmd_path, cmd_argv, envp); >> + perror("execve"); >> + return 1; >> +} > > I like this example. It's a very powerful rule to for a program to > enforce on itself. :) > > -Kees > Thanks!