Add tests for LSM interactions (both bpf_token_capable and bpf_token_cmd LSM hooks) with BPF token in bpf() subsystem. Now child process passes back token FD for parent to be able to do tests with token originating in "wrong" userns. But we also create token in initns and check that token LSMs don't accidentally reject BPF operations when capable() checks pass without BPF token. Signed-off-by: Andrii Nakryiko <andrii@xxxxxxxxxx> --- .../testing/selftests/bpf/prog_tests/token.c | 89 +++++++++++++++---- tools/testing/selftests/bpf/progs/token_lsm.c | 32 +++++++ 2 files changed, 104 insertions(+), 17 deletions(-) create mode 100644 tools/testing/selftests/bpf/progs/token_lsm.c diff --git a/tools/testing/selftests/bpf/prog_tests/token.c b/tools/testing/selftests/bpf/prog_tests/token.c index 1f6aa685e6f7..fc4a175d8d76 100644 --- a/tools/testing/selftests/bpf/prog_tests/token.c +++ b/tools/testing/selftests/bpf/prog_tests/token.c @@ -18,6 +18,7 @@ #include "priv_map.skel.h" #include "priv_prog.skel.h" #include "dummy_st_ops_success.skel.h" +#include "token_lsm.skel.h" static inline int sys_mount(const char *dev_name, const char *dir_name, const char *type, unsigned long flags, @@ -279,12 +280,23 @@ static int create_and_enter_userns(void) return 0; } -typedef int (*child_callback_fn)(int); +typedef int (*child_callback_fn)(int bpffs_fd, struct token_lsm *lsm_skel); static void child(int sock_fd, struct bpffs_opts *opts, child_callback_fn callback) { - LIBBPF_OPTS(bpf_map_create_opts, map_opts); - int mnt_fd = -1, fs_fd = -1, err = 0, bpffs_fd = -1; + int mnt_fd = -1, fs_fd = -1, err = 0, bpffs_fd = -1, token_fd = -1; + struct token_lsm *lsm_skel = NULL; + + /* load and attach LSM "policy" before we go into unpriv userns */ + lsm_skel = token_lsm__open_and_load(); + if (!ASSERT_OK_PTR(lsm_skel, "lsm_skel_load")) { + err = -EINVAL; + goto cleanup; + } + lsm_skel->bss->my_pid = getpid(); + err = token_lsm__attach(lsm_skel); + if (!ASSERT_OK(err, "lsm_skel_attach")) + goto cleanup; /* setup userns with root mappings */ err = create_and_enter_userns(); @@ -365,8 +377,19 @@ static void child(int sock_fd, struct bpffs_opts *opts, child_callback_fn callba goto cleanup; } + /* create BPF token FD and pass it to parent for some extra checks */ + token_fd = bpf_token_create(bpffs_fd, NULL); + if (!ASSERT_GT(token_fd, 0, "child_token_create")) { + err = -EINVAL; + goto cleanup; + } + err = sendfd(sock_fd, token_fd); + if (!ASSERT_OK(err, "send_token_fd")) + goto cleanup; + zclose(token_fd); + /* do custom test logic with customly set up BPF FS instance */ - err = callback(bpffs_fd); + err = callback(bpffs_fd, lsm_skel); if (!ASSERT_OK(err, "test_callback")) goto cleanup; @@ -376,6 +399,10 @@ static void child(int sock_fd, struct bpffs_opts *opts, child_callback_fn callba zclose(mnt_fd); zclose(fs_fd); zclose(bpffs_fd); + zclose(token_fd); + + lsm_skel->bss->my_pid = 0; + token_lsm__destroy(lsm_skel); exit(-err); } @@ -401,7 +428,7 @@ static int wait_for_pid(pid_t pid) static void parent(int child_pid, struct bpffs_opts *bpffs_opts, int sock_fd) { - int fs_fd = -1, mnt_fd = -1, err; + int fs_fd = -1, mnt_fd = -1, token_fd = -1, err; err = recvfd(sock_fd, &fs_fd); if (!ASSERT_OK(err, "recv_bpffs_fd")) @@ -420,6 +447,11 @@ static void parent(int child_pid, struct bpffs_opts *bpffs_opts, int sock_fd) goto cleanup; zclose(mnt_fd); + /* receive BPF token FD back from child for some extra tests */ + err = recvfd(sock_fd, &token_fd); + if (!ASSERT_OK(err, "recv_token_fd")) + goto cleanup; + err = wait_for_pid(child_pid); ASSERT_OK(err, "waitpid_child"); @@ -427,12 +459,14 @@ static void parent(int child_pid, struct bpffs_opts *bpffs_opts, int sock_fd) zclose(sock_fd); zclose(fs_fd); zclose(mnt_fd); + zclose(token_fd); if (child_pid > 0) (void)kill(child_pid, SIGKILL); } -static void subtest_userns(struct bpffs_opts *bpffs_opts, child_callback_fn cb) +static void subtest_userns(struct bpffs_opts *bpffs_opts, + child_callback_fn child_cb) { int sock_fds[2] = { -1, -1 }; int child_pid = 0, err; @@ -447,7 +481,7 @@ static void subtest_userns(struct bpffs_opts *bpffs_opts, child_callback_fn cb) if (child_pid == 0) { zclose(sock_fds[0]); - return child(sock_fds[1], bpffs_opts, cb); + return child(sock_fds[1], bpffs_opts, child_cb); } else { zclose(sock_fds[1]); @@ -461,7 +495,7 @@ static void subtest_userns(struct bpffs_opts *bpffs_opts, child_callback_fn cb) (void)kill(child_pid, SIGKILL); } -static int userns_map_create(int mnt_fd) +static int userns_map_create(int mnt_fd, struct token_lsm *lsm_skel) { LIBBPF_OPTS(bpf_map_create_opts, map_opts); int err, token_fd = -1, map_fd = -1; @@ -529,7 +563,7 @@ static int userns_map_create(int mnt_fd) return err; } -static int userns_btf_load(int mnt_fd) +static int userns_btf_load(int mnt_fd, struct token_lsm *lsm_skel) { LIBBPF_OPTS(bpf_btf_load_opts, btf_opts); int err, token_fd = -1, btf_fd = -1; @@ -598,7 +632,7 @@ static int userns_btf_load(int mnt_fd) return err; } -static int userns_prog_load(int mnt_fd) +static int userns_prog_load(int mnt_fd, struct token_lsm *lsm_skel) { LIBBPF_OPTS(bpf_prog_load_opts, prog_opts); int err, token_fd = -1, prog_fd = -1; @@ -677,7 +711,7 @@ static int userns_prog_load(int mnt_fd) return err; } -static int userns_obj_priv_map(int mnt_fd) +static int userns_obj_priv_map(int mnt_fd, struct token_lsm *lsm_skel) { LIBBPF_OPTS(bpf_object_open_opts, opts); char buf[256]; @@ -705,7 +739,7 @@ static int userns_obj_priv_map(int mnt_fd) return 0; } -static int userns_obj_priv_prog(int mnt_fd) +static int userns_obj_priv_prog(int mnt_fd, struct token_lsm *lsm_skel) { LIBBPF_OPTS(bpf_object_open_opts, opts); char buf[256]; @@ -724,12 +758,33 @@ static int userns_obj_priv_prog(int mnt_fd) skel = priv_prog__open_opts(&opts); if (!ASSERT_OK_PTR(skel, "obj_token_path_open")) return -EINVAL; - err = priv_prog__load(skel); priv_prog__destroy(skel); if (!ASSERT_OK(err, "obj_token_path_load")) return -EINVAL; + /* provide BPF token, but reject bpf_token_capable() with LSM */ + lsm_skel->bss->reject_capable = true; + lsm_skel->bss->reject_cmd = false; + skel = priv_prog__open_opts(&opts); + if (!ASSERT_OK_PTR(skel, "obj_token_lsm_reject_cap_open")) + return -EINVAL; + err = priv_prog__load(skel); + priv_prog__destroy(skel); + if (!ASSERT_ERR(err, "obj_token_lsm_reject_cap_load")) + return -EINVAL; + + /* provide BPF token, but reject bpf_token_cmd() with LSM */ + lsm_skel->bss->reject_capable = false; + lsm_skel->bss->reject_cmd = true; + skel = priv_prog__open_opts(&opts); + if (!ASSERT_OK_PTR(skel, "obj_token_lsm_reject_cmd_open")) + return -EINVAL; + err = priv_prog__load(skel); + priv_prog__destroy(skel); + if (!ASSERT_ERR(err, "obj_token_lsm_reject_cmd_load")) + return -EINVAL; + return 0; } @@ -763,12 +818,12 @@ static int validate_struct_ops_load(int mnt_fd, bool expect_success) return 0; } -static int userns_obj_priv_btf_fail(int mnt_fd) +static int userns_obj_priv_btf_fail(int mnt_fd, struct token_lsm *lsm_skel) { return validate_struct_ops_load(mnt_fd, false /* should fail */); } -static int userns_obj_priv_btf_success(int mnt_fd) +static int userns_obj_priv_btf_success(int mnt_fd, struct token_lsm *lsm_skel) { return validate_struct_ops_load(mnt_fd, true /* should succeed */); } @@ -776,7 +831,7 @@ static int userns_obj_priv_btf_success(int mnt_fd) #define TOKEN_ENVVAR "LIBBPF_BPF_TOKEN_PATH" #define TOKEN_BPFFS_CUSTOM "/bpf-token-fs" -static int userns_obj_priv_implicit_token(int mnt_fd) +static int userns_obj_priv_implicit_token(int mnt_fd, struct token_lsm *lsm_skel) { LIBBPF_OPTS(bpf_object_open_opts, opts); struct dummy_st_ops_success *skel; @@ -835,7 +890,7 @@ static int userns_obj_priv_implicit_token(int mnt_fd) return 0; } -static int userns_obj_priv_implicit_token_envvar(int mnt_fd) +static int userns_obj_priv_implicit_token_envvar(int mnt_fd, struct token_lsm *lsm_skel) { LIBBPF_OPTS(bpf_object_open_opts, opts); struct dummy_st_ops_success *skel; diff --git a/tools/testing/selftests/bpf/progs/token_lsm.c b/tools/testing/selftests/bpf/progs/token_lsm.c new file mode 100644 index 000000000000..e4d59b6ba743 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/token_lsm.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */ + +#include "vmlinux.h" +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_tracing.h> + +char _license[] SEC("license") = "GPL"; + +int my_pid; +bool reject_capable; +bool reject_cmd; + +SEC("lsm/bpf_token_capable") +int BPF_PROG(token_capable, struct bpf_token *token, int cap) +{ + if (my_pid == 0 || my_pid != (bpf_get_current_pid_tgid() >> 32)) + return 0; + if (reject_capable) + return -1; + return 0; +} + +SEC("lsm/bpf_token_cmd") +int BPF_PROG(token_cmd, struct bpf_token *token, enum bpf_cmd cmd) +{ + if (my_pid == 0 || my_pid != (bpf_get_current_pid_tgid() >> 32)) + return 0; + if (reject_cmd) + return -1; + return 0; +} -- 2.34.1