From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 739FD246BB6 for ; Thu, 21 Aug 2025 19:03:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=13.77.154.182 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755802996; cv=none; b=cDiVNp/e0+7pwYri9zqJ/Mvm/GaqxIFj85h9Pdw0N1aua8yEz74d4Iuh7TODnJBmTa0DkaSYHVeVDhVyWE3nb0Jb1tRPhDZyy6VXAPRDoriD08ILySZ51iCPiDGLGdhBO757JAdsdysaSoWDKAPubewhk152df+NRUN3yU+3KKQ= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755802996; c=relaxed/simple; bh=Y4hJvQypPSYwgRz9SRlaX6U75Oo0jrX626gDgOk+4J8=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=DprKqRON0kYNZ4OZ29BycFe5KpK05ynSdr8vChPDxHRA2hljsN15IDLxFChD+sp3Z4IjP0lAbblTGN2GMyfc4FXTHDkp7yV01Rr6lJCinpQ3CftqkrBXlL85ou+OdiIn5T/iobkwHC7mo2vNQKuTwIcW57KUal2l57vLvQQm+nM= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.microsoft.com; spf=pass smtp.mailfrom=linux.microsoft.com; dkim=pass (1024-bit key) header.d=linux.microsoft.com header.i=@linux.microsoft.com header.b=SaFA6VrR; arc=none smtp.client-ip=13.77.154.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.microsoft.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.microsoft.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.microsoft.com header.i=@linux.microsoft.com header.b="SaFA6VrR" Received: from ericsu-dev1 (unknown [131.107.1.202]) by linux.microsoft.com (Postfix) with ESMTPSA id E1A182115A5E; Thu, 21 Aug 2025 12:03:12 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com E1A182115A5E DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1755802992; bh=EVnGWA4OXnW+5/g08WoSsQqxmp2ZEWpAu4Y+zNsbAi0=; h=From:To:Cc:Subject:Date:From; b=SaFA6VrR1kvME1bzcSr3Rv3i+eAJUbeD4VufzWqszi3auKGCldJt00cl7Bt2b2uXF fbyLITvSi9MS3KAatWqxBvsoBpLKrFwBuqkBhnS6DqeKIy4vvgOr2WWw9W/uvnibuj Q30vnTvrXF4YhoOl/WuRVa7CTegcjLLPvZNStb/o= From: Eric Suen To: selinux@vger.kernel.org Cc: paul@paul-moore.com, stephen.smalley.work@gmail.com, omosnace@redhat.com, danieldurning.work@gmail.com Subject: [PATCH testsuite v3] tests/bpf: Add tests for SELinux BPF token access control Date: Thu, 21 Aug 2025 12:03:12 -0700 Message-ID: <20250821190312.1361-1-ericsu@linux.microsoft.com> X-Mailer: git-send-email 2.50.1.windows.1 Precedence: bulk X-Mailing-List: selinux@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit This patch adds new tests to verify the SELinux support for BPF token access control, as introduced in the corresponding kernel patch: https://lore.kernel.org/selinux/20250816201420.197-1-ericsu@linux.microsoft.com/ Four new tests are added to cover both positive and negative scenarios, ensuring that the SELinux policy enforcement on BPF token usage behaves as expected. - Successful map_create and prog_load when SELinux permissions are granted. - Enforcement of SELinux policy restrictions when access is denied. For testing purposes, you can update the base policy by manually modifying your base module and tweaking /usr/share/selinux/devel as follows: sudo semodule -c -E base sudo cp base.cil base.cil.orig sudo sed -i "s/map_create/map_create map_create_as/" base.cil sudo sed -i "s/prog_load/prog_load prog_load_as/" base.cil sudo semodule -i base.cil echo "(policycap bpf_token_perms)" > bpf_token_perms.cil sudo semodule -i bpf_token_perms.cil sudo cp /usr/share/selinux/devel/include/support/all_perms.spt \ /usr/share/selinux/devel/include/support/all_perms.spt.orig sudo sed -i "s/map_create/map_create map_create_as/" \ /usr/share/selinux/devel/include/support/all_perms.spt sudo sed -i "s/prog_load/prog_load prog_load_as/" \ /usr/share/selinux/devel/include/support/all_perms.spt When finished testing, you can semodule -r base bpf_token_perms to undo the two module changes and restore your all_perms.spt file from the saved .orig file. Changes in v2: - Removed allow rule for 'kernel_t' in test_bpf.te which was added due to a bug in the kernel - Cleaned up other unnecessary rules in test_bpf.te - Added token_test.c which was missing from previous patch Changes in v3: - Added original license in 'token_test.c' - Updated patch description to - replace 'base.sil' with 'base.cil' - Remove extra quotation mark in 'sudo 'sed -i "s/"map_create' Signed-off-by: Eric Suen Tested-by: Daniel Durning --- policy/test_bpf.te | 39 +++ tests/bpf/Makefile | 5 +- tests/bpf/bpf_common.h | 10 + tests/bpf/bpf_test.c | 59 +++-- tests/bpf/test | 21 +- tests/bpf/token_test.c | 543 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 658 insertions(+), 19 deletions(-) create mode 100644 tests/bpf/token_test.c diff --git a/policy/test_bpf.te b/policy/test_bpf.te index 5eab0bd..45da852 100644 --- a/policy/test_bpf.te +++ b/policy/test_bpf.te @@ -57,3 +57,42 @@ typeattribute test_bpf_deny_prog_run_t bpfdomain; allow test_bpf_deny_prog_run_t self:process { setrlimit }; allow test_bpf_deny_prog_run_t self:capability { sys_resource sys_admin }; allow test_bpf_deny_prog_run_t self:bpf { map_create map_read map_write prog_load }; + +################### Allow map_create_as and prog_load_as ################### +fs_list_bpf_dirs(test_bpf_t) +allow test_bpf_t bpf_t:filesystem mount; +allow test_bpf_t root_t:dir mounton; +allow test_bpf_t self:bpf { map_create_as prog_load_as }; +allow test_bpf_t self:cap2_userns { bpf perfmon }; +allow test_bpf_t self:cap_userns { net_admin setgid setuid sys_admin }; +allow test_bpf_t self:user_namespace create; + +############################ Deny map_create_as ############################ +type test_bpf_deny_map_create_as_t; +testsuite_domain_type(test_bpf_deny_map_create_as_t) +typeattribute test_bpf_deny_map_create_as_t bpfdomain; +allow test_bpf_deny_map_create_as_t self:process { setrlimit }; +allow test_bpf_deny_map_create_as_t self:capability { sys_resource sys_admin }; + +fs_list_bpf_dirs(test_bpf_deny_map_create_as_t) +allow test_bpf_deny_map_create_as_t bpf_t:filesystem mount; +allow test_bpf_deny_map_create_as_t root_t:dir mounton; +allow test_bpf_deny_map_create_as_t self:bpf { map_create map_read map_write prog_load prog_load_as }; +allow test_bpf_deny_map_create_as_t self:cap2_userns { bpf }; +allow test_bpf_deny_map_create_as_t self:cap_userns { setgid setuid sys_admin }; +allow test_bpf_deny_map_create_as_t self:user_namespace create; + +############################ Deny prog_load_as ############################# +type test_bpf_deny_prog_load_as_t; +testsuite_domain_type(test_bpf_deny_prog_load_as_t) +typeattribute test_bpf_deny_prog_load_as_t bpfdomain; +allow test_bpf_deny_prog_load_as_t self:process { setrlimit }; +allow test_bpf_deny_prog_load_as_t self:capability { sys_resource sys_admin }; + +fs_list_bpf_dirs(test_bpf_deny_prog_load_as_t) +allow test_bpf_deny_prog_load_as_t bpf_t:filesystem mount; +allow test_bpf_deny_prog_load_as_t root_t:dir mounton; +allow test_bpf_deny_prog_load_as_t self:bpf { map_create map_create_as map_read map_write prog_load }; +allow test_bpf_deny_prog_load_as_t self:cap2_userns { bpf perfmon }; +allow test_bpf_deny_prog_load_as_t self:cap_userns { net_admin setgid setuid sys_admin }; +allow test_bpf_deny_prog_load_as_t self:user_namespace create; diff --git a/tests/bpf/Makefile b/tests/bpf/Makefile index 1ae8ce9..cacefbe 100644 --- a/tests/bpf/Makefile +++ b/tests/bpf/Makefile @@ -1,5 +1,5 @@ TARGETS = bpf_test -DEPS = bpf_common.c bpf_common.h +SRCS = bpf_test.c bpf_common.c token_test.c LDLIBS += -lselinux -lbpf # export so that BPF_ENABLED entries get built correctly on local build @@ -14,4 +14,5 @@ clean: rm -f $(TARGETS) test_sock flag *_flag @set -e; for i in $(BPF_ENABLED); do $(MAKE) -C $$i clean ; done -$(TARGETS): $(DEPS) +$(TARGETS): $(SRCS) + $(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) \ No newline at end of file diff --git a/tests/bpf/bpf_common.h b/tests/bpf/bpf_common.h index 44ac28f..adba522 100644 --- a/tests/bpf/bpf_common.h +++ b/tests/bpf/bpf_common.h @@ -12,6 +12,8 @@ extern int create_bpf_map(void); extern int create_bpf_prog(void); extern void bpf_setrlimit(void); +extern int test_bpf_map_create(void); +extern int test_bpf_prog_load(void); /* edited eBPF instruction library */ /* Short form of mov, dst_reg = imm32 */ @@ -32,3 +34,11 @@ extern void bpf_setrlimit(void); .off = 0, \ .imm = 0 }) +/* Raw code statement block */ +#define BPF_RAW_INSN(CODE, DST, SRC, OFF, IMM) \ + ((struct bpf_insn) { \ + .code = CODE, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = OFF, \ + .imm = IMM }) diff --git a/tests/bpf/bpf_test.c b/tests/bpf/bpf_test.c index 3c6a29c..a8dc383 100644 --- a/tests/bpf/bpf_test.c +++ b/tests/bpf/bpf_test.c @@ -1,28 +1,38 @@ #include "bpf_common.h" +#define write_verbose(verbose, fmt, ...) \ + do { \ + if (verbose) \ + printf(fmt "\n", ##__VA_ARGS__); \ + } while (0) + static void usage(char *progname) { fprintf(stderr, - "usage: %s -m|-p [-v]\n" + "usage: %s -m|-p|-c|-l [-v]\n" "Where:\n\t" "-m Create BPF map fd\n\t" "-p Create BPF prog fd\n\t" + "-c Test BPF token map create\n\t" + "-l Test BPF token program load\n\t" "-v Print information.\n", progname); exit(-1); } int main(int argc, char *argv[]) { - int opt, result, fd; - bool verbose = false; + int opt, result, ret; + bool verbose = false, is_fd = true; char *context; enum { MAP_FD = 1, - PROG_FD + PROG_FD, + MAP_CREATE, + PROG_LOAD, } bpf_fd_type; - while ((opt = getopt(argc, argv, "mpv")) != -1) { + while ((opt = getopt(argc, argv, "mpclv")) != -1) { switch (opt) { case 'm': bpf_fd_type = MAP_FD; @@ -30,6 +40,12 @@ int main(int argc, char *argv[]) case 'p': bpf_fd_type = PROG_FD; break; + case 'c': + bpf_fd_type = MAP_CREATE; + break; + case 'l': + bpf_fd_type = PROG_LOAD; + break; case 'v': verbose = true; break; @@ -44,8 +60,7 @@ int main(int argc, char *argv[]) exit(-1); } - if (verbose) - printf("Process context:\n\t%s\n", context); + write_verbose(verbose, "Process context:\n\n%s", context); free(context); @@ -54,24 +69,36 @@ int main(int argc, char *argv[]) switch (bpf_fd_type) { case MAP_FD: - if (verbose) - printf("Creating BPF map\n"); + write_verbose(verbose, "Creating BPF map"); - fd = create_bpf_map(); + ret = create_bpf_map(); break; case PROG_FD: - if (verbose) - printf("Creating BPF prog\n"); + write_verbose(verbose, "Creating BPF prog"); + + ret = create_bpf_prog(); + break; + case MAP_CREATE: + is_fd = false; + write_verbose(verbose, "Testing BPF map create"); + + ret = test_bpf_map_create(); + break; + case PROG_LOAD: + is_fd = false; + write_verbose(verbose, "Testing BPF prog load"); - fd = create_bpf_prog(); + ret = test_bpf_prog_load(); break; default: usage(argv[0]); } - if (fd < 0) - return fd; + if (ret < 0) + return ret; + + if (is_fd) + close(ret); - close(fd); return 0; } diff --git a/tests/bpf/test b/tests/bpf/test index a3fd856..14bda32 100755 --- a/tests/bpf/test +++ b/tests/bpf/test @@ -9,8 +9,10 @@ BEGIN { $test_bpf_count = 7; $test_fdreceive_count = 4; + $test_bpf_token_count = 4; - $test_count = $test_bpf_count + $test_fdreceive_count; + $test_count = + $test_bpf_count + $test_fdreceive_count + $test_bpf_token_count; # allow info to be shown during tests $v = $ARGV[0]; @@ -67,6 +69,13 @@ ok( $result eq 0 ); $result = system "runcon -t test_bpf_t $basedir/bpf_test -p $v"; ok( $result eq 0 ); +# BPF token - BPF_MAP_CREATE_AS, BPF_PROG_LOAD_AS +$result = system "runcon -t test_bpf_t $basedir/bpf_test -c $v"; +ok( $result eq 0 ); + +$result = system "runcon -t test_bpf_t $basedir/bpf_test -l $v"; +ok( $result eq 0 ); + # Deny map_create permission $result = system "runcon -t test_bpf_deny_map_create_t $basedir/bpf_test -m $v 2>&1"; @@ -92,6 +101,16 @@ $result = system "runcon -t test_bpf_deny_prog_run_t $basedir/bpf_test -p $v 2>&1"; ok($result); +# BPF token - deny BPF_MAP_CREATE_AS +$result = + system "runcon -t test_bpf_deny_map_create_as_t $basedir/bpf_test -c $v 2>&1"; +ok($result); + +# BPF token - deny BPF_PROG_LOAD_AS +$result = + system "runcon -t test_bpf_deny_prog_load_as_t $basedir/bpf_test -l $v 2>&1"; +ok($result); + # ################ BPF Tests for fdreceive ####################### # diff --git a/tests/bpf/token_test.c b/tests/bpf/token_test.c new file mode 100644 index 0000000..8e2659b --- /dev/null +++ b/tests/bpf/token_test.c @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Code derived from: linux/source/tools/testing/selftests/bpf/prog_tests/token.c + * Copyright (c) 2023 Meta Platforms, Inc. and affiliates. + */ + +#include "bpf_common.h" +#include "signal.h" +#include "linux/mount.h" +#include +#include "sys/wait.h" +#include "sys/socket.h" +#include "fcntl.h" +#include "sched.h" +#include + +#define bit(n) (1ULL << (n)) + +#define zclose(fd) do { if (fd >= 0) close(fd); fd = -1; } while (0) +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +#define _CHECK(condition, format...) ({ \ + int __ret = !!(condition); \ + int __save_errno = errno; \ + if (__ret) { \ + fprintf(stderr, ##format); \ + } \ + errno = __save_errno; \ + __ret; \ +}) + +#define ASSERT_OK(res, name) ({ \ + long long ___res = (res); \ + bool ___ok = ___res == 0; \ + _CHECK(!___ok, \ + "%s failed. unexpected error: %lld (errno %d)\n", \ + name, ___res, errno); \ + ___ok; \ +}) + +#define ASSERT_GT(actual, expected, name) ({ \ + typeof(actual) ___act = (actual); \ + typeof(expected) ___exp = (expected); \ + bool ___ok = ___act > ___exp; \ + _CHECK(!___ok, \ + "unexpected %s: actual %lld <= expected %lld (errno %d)\n", \ + (name), (long long)(___act), (long long)(___exp), errno); \ + ___ok; \ +}) + +#define ASSERT_GE(actual, expected, name) ({ \ + typeof(actual) ___act = (actual); \ + typeof(expected) ___exp = (expected); \ + bool ___ok = ___act >= ___exp; \ + _CHECK(!___ok, \ + "unexpected %s: actual %lld < expected %lld (errno %d)\n", \ + (name), (long long)(___act), (long long)(___exp), errno); \ + ___ok; \ +}) + +#define ASSERT_EQ(actual, expected, name) ({ \ + typeof(actual) ___act = (actual); \ + typeof(expected) ___exp = (expected); \ + bool ___ok = ___act == ___exp; \ + _CHECK(!___ok, \ + "unexpected %s: actual %lld != expected %lld (errno %d)\n", \ + (name), (long long)(___act), (long long)(___exp), errno); \ + ___ok; \ +}) + +#define ASSERT_OK_PTR(ptr, name) ({ \ + const void *___res = (ptr); \ + int ___err = libbpf_get_error(___res); \ + bool ___ok = ___err == 0; \ + _CHECK(!___ok, \ + "%s unexpected error: %d\n", name, ___err); \ + ___ok; \ +}) + +struct bpffs_opts { + __u64 cmds; + __u64 maps; + __u64 progs; + __u64 attachs; + const char *cmds_str; + const char *maps_str; + const char *progs_str; + const char *attachs_str; +}; + +typedef int (*child_callback_fn)(int bpffs_fd); + +static inline int sys_mount(const char *dev_name, const char *dir_name, + const char *type, unsigned long flags, + const void *data) +{ + return syscall(__NR_mount, dev_name, dir_name, type, flags, data); +} + +static inline int sys_fsopen(const char *fsname, unsigned int flags) +{ + return syscall(__NR_fsopen, fsname, flags); +} + +static inline int sys_fsconfig(int fs_fd, unsigned int cmd, const char *key, + const void *val, int aux) +{ + return syscall(__NR_fsconfig, fs_fd, cmd, key, val, aux); +} + +static inline int sys_fsmount(int fs_fd, unsigned int flags, + unsigned int ms_flags) +{ + return syscall(__NR_fsmount, fs_fd, flags, ms_flags); +} + +static int set_delegate_mask(int fs_fd, const char *key, __u64 mask, + const char *mask_str) +{ + char buf[32]; + int err; + + if (!mask_str) { + if (mask == ~0ULL) { + mask_str = "any"; + } else { + snprintf(buf, sizeof(buf), "0x%llx", (unsigned long long)mask); + mask_str = buf; + } + } + + err = sys_fsconfig(fs_fd, FSCONFIG_SET_STRING, key, + mask_str, 0); + if (err < 0) + err = -errno; + return err; +} + +static int create_bpffs_fd(void) +{ + int fs_fd; + + /* create VFS context */ + fs_fd = sys_fsopen("bpf", 0); + ASSERT_GE(fs_fd, 0, "fs_fd"); + + return fs_fd; +} + +static int materialize_bpffs_fd(int fs_fd, struct bpffs_opts *opts) +{ + int mnt_fd, err; + + /* set up token delegation mount options */ + err = set_delegate_mask(fs_fd, "delegate_cmds", opts->cmds, opts->cmds_str); + if (!ASSERT_OK(err, "fs_cfg_cmd")) + return err; + err = set_delegate_mask(fs_fd, "delegate_maps", opts->maps, opts->maps_str); + if (!ASSERT_OK(err, "fs_cfg_maps")) + return err; + err = set_delegate_mask(fs_fd, "delegate_progs", opts->progs, opts->progs_str); + if (!ASSERT_OK(err, "fs_cfg_progs")) + return err; + err = set_delegate_mask(fs_fd, "delegate_attachs", opts->attachs, + opts->attachs_str); + if (!ASSERT_OK(err, "fs_cfg_attachs")) + return err; + + /* instantiate FS object */ + err = sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0); + if (err < 0) + return -errno; + + /* create O_PATH fd for detached mount */ + mnt_fd = sys_fsmount(fs_fd, 0, 0); + if (err < 0) + return -errno; + + return mnt_fd; +} + +static ssize_t write_nointr(int fd, const void *buf, size_t count) +{ + ssize_t ret; + + do { + ret = write(fd, buf, count); + } while (ret < 0 && errno == EINTR); + + return ret; +} + +static int write_file(const char *path, const void *buf, size_t count) +{ + int fd; + ssize_t ret; + + fd = open(path, O_WRONLY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW); + if (fd < 0) + return -1; + + ret = write_nointr(fd, buf, count); + close(fd); + if (ret < 0 || (size_t)ret != count) + return -1; + + return 0; +} + +static int create_and_enter_userns(void) +{ + uid_t uid; + gid_t gid; + char map[100]; + + uid = getuid(); + gid = getgid(); + + if (unshare(CLONE_NEWUSER)) + return -1; + + if (write_file("/proc/self/setgroups", "deny", sizeof("deny") - 1) && + errno != ENOENT) + return -1; + + snprintf(map, sizeof(map), "0 %d 1", uid); + if (write_file("/proc/self/uid_map", map, strlen(map))) + return -1; + + + snprintf(map, sizeof(map), "0 %d 1", gid); + if (write_file("/proc/self/gid_map", map, strlen(map))) + return -1; + + if (setgid(0)) + return -1; + + if (setuid(0)) + return -1; + + return 0; +} + +static int sendfd(int sockfd, int fd) +{ + struct msghdr msg = {}; + struct cmsghdr *cmsg; + int fds[1] = { fd }, err; + char iobuf[1]; + struct iovec io = { + .iov_base = iobuf, + .iov_len = sizeof(iobuf), + }; + union { + char buf[CMSG_SPACE(sizeof(fds))]; + struct cmsghdr align; + } u; + + msg.msg_iov = &io; + msg.msg_iovlen = 1; + msg.msg_control = u.buf; + msg.msg_controllen = sizeof(u.buf); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(fds)); + memcpy(CMSG_DATA(cmsg), fds, sizeof(fds)); + + err = sendmsg(sockfd, &msg, 0); + if (err < 0) + err = -errno; + if (!ASSERT_EQ(err, 1, "sendmsg")) + return -EINVAL; + + return 0; +} + +static int recvfd(int sockfd, int *fd) +{ + struct msghdr msg = {}; + struct cmsghdr *cmsg; + int fds[1], err; + char iobuf[1]; + struct iovec io = { + .iov_base = iobuf, + .iov_len = sizeof(iobuf), + }; + union { + char buf[CMSG_SPACE(sizeof(fds))]; + struct cmsghdr align; + } u; + + msg.msg_iov = &io; + msg.msg_iovlen = 1; + msg.msg_control = u.buf; + msg.msg_controllen = sizeof(u.buf); + + err = recvmsg(sockfd, &msg, 0); + if (err < 0) + err = -errno; + if (!ASSERT_EQ(err, 1, "recvmsg")) + return -EINVAL; + + cmsg = CMSG_FIRSTHDR(&msg); + if (!ASSERT_OK_PTR(cmsg, "cmsg_null") || + !ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(fds)), "cmsg_len") || + !ASSERT_EQ(cmsg->cmsg_level, SOL_SOCKET, "cmsg_level") || + !ASSERT_EQ(cmsg->cmsg_type, SCM_RIGHTS, "cmsg_type")) + return -EINVAL; + + memcpy(fds, CMSG_DATA(cmsg), sizeof(fds)); + *fd = fds[0]; + + return 0; +} + +static int wait_for_pid(pid_t pid) +{ + int status, ret; + +again: + ret = waitpid(pid, &status, 0); + if (ret == -1) { + if (errno == EINTR) + goto again; + + return -1; + } + + if (!WIFEXITED(status)) + return -1; + + return WEXITSTATUS(status); +} + +static int child(int sock_fd, struct bpffs_opts *bpffs_opts, + child_callback_fn callback) +{ + int mnt_fd = -1, fs_fd = -1, err = 0, bpffs_fd = -1, token_fd = -1; + + err = create_and_enter_userns(); + if (!ASSERT_OK(err, "create_and_enter_userns")) + goto cleanup; + + err = unshare(CLONE_NEWNS); + if (!ASSERT_OK(err, "create_mountns")) + goto cleanup; + + err = sys_mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, 0); + if (!ASSERT_OK(err, "remount_root")) + goto cleanup; + + fs_fd = create_bpffs_fd(); + if (!ASSERT_GE(fs_fd, 0, "create_bpffs_fd")) { + err = -EINVAL; + goto cleanup; + } + + err = sendfd(sock_fd, fs_fd); + if (!ASSERT_OK(err, "send_fs_fd")) + goto cleanup; + zclose(fs_fd); + + err = recvfd(sock_fd, &mnt_fd); + if (!ASSERT_OK(err, "recv_mnt_fd")) + goto cleanup; + + bpffs_fd = openat(mnt_fd, ".", 0, O_RDWR); + if (!ASSERT_GE(bpffs_fd, 0, "bpffs_open")) { + err = -EINVAL; + goto cleanup; + } + + err = callback(bpffs_fd); + if (!ASSERT_OK(err, "test_callback")) + goto cleanup; + + err = 0; + +cleanup: + zclose(sock_fd); + zclose(mnt_fd); + zclose(fs_fd); + zclose(bpffs_fd); + zclose(token_fd); + + exit(-err); +} + +static int parent(int child_pid, struct bpffs_opts *bpffs_opts, int sock_fd) +{ + int fs_fd = -1, mnt_fd = -1, token_fd = -1, err; + + err = recvfd(sock_fd, &fs_fd); + if (!ASSERT_OK(err, "recv_bpffs_fd")) + goto cleanup; + + mnt_fd = materialize_bpffs_fd(fs_fd, bpffs_opts); + if (!ASSERT_GE(mnt_fd, 0, "materialize_bpffs_fd")) { + err = -EINVAL; + goto cleanup; + } + zclose(fs_fd); + + err = sendfd(sock_fd, mnt_fd); + if (!ASSERT_OK(err, "send_mnt_fd")) + goto cleanup; + zclose(mnt_fd); + + err = wait_for_pid(child_pid); + if (!ASSERT_OK(err, "waitpid_child")) { + err = -EINVAL; + goto cleanup; + } + +cleanup: + zclose(sock_fd); + zclose(fs_fd); + zclose(mnt_fd); + zclose(token_fd); + + if (child_pid > 0) + (void)kill(child_pid, SIGKILL); + + return err; +} + +static int subtest(struct bpffs_opts *bpffs_opts, child_callback_fn child_cb) +{ + int sock_fds[2] = { -1, -1 }; + int child_pid = 0, err; + + err = socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fds); + if (!ASSERT_OK(err, "socketpair")) + goto cleanup; + + child_pid = fork(); + if (!ASSERT_GE(child_pid, 0, "fork")) + goto cleanup; + + if (child_pid == 0) { + zclose(sock_fds[0]); + return child(sock_fds[1], bpffs_opts, child_cb); + } else { + zclose(sock_fds[1]); + return parent(child_pid, bpffs_opts, sock_fds[0]); + } + +cleanup: + zclose(sock_fds[0]); + zclose(sock_fds[1]); + if (child_pid > 0) + (void)kill(child_pid, SIGKILL); + + return -err; +} + +static int userns_map_create(int mnt_fd) +{ + LIBBPF_OPTS(bpf_map_create_opts, map_opts); + int err = 0, token_fd = -1, map_fd = -1; + + /* create BPF token from BPF FS mount */ + token_fd = bpf_token_create(mnt_fd, NULL); + if (!ASSERT_GT(token_fd, 0, "userns_map_create/token_create")) { + err = -EINVAL; + goto cleanup; + } + + map_opts.map_flags = BPF_F_TOKEN_FD; + map_opts.token_fd = token_fd; + map_fd = bpf_map_create(BPF_MAP_TYPE_STACK, "userns_map_create", 0, 8, 1, + &map_opts); + if (!ASSERT_GT(map_fd, 0, "userns_map_create/bpf_map_create")) { + err = -EINVAL; + goto cleanup; + } + +cleanup: + zclose(token_fd); + zclose(map_fd); + + return err; +} + +static int userns_prog_load(int mnt_fd) +{ + LIBBPF_OPTS(bpf_prog_load_opts, prog_opts); + int err, token_fd = -1, prog_fd = -1; + struct bpf_insn insns[] = { + /* bpf_jiffies64() requires CAP_BPF */ + BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_jiffies64), + /* bpf_get_current_task() requires CAP_PERFMON */ + BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_get_current_task), + /* r0 = 0; exit; */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }; + size_t insn_cnt = ARRAY_SIZE(insns); + + token_fd = bpf_token_create(mnt_fd, NULL); + if (!ASSERT_GT(token_fd, 0, "userns_prog_load/token_create")) { + err = -EINVAL; + goto cleanup; + } + + prog_opts.prog_flags = BPF_F_TOKEN_FD; + prog_opts.token_fd = token_fd; + prog_opts.expected_attach_type = BPF_XDP; + prog_fd = bpf_prog_load(BPF_PROG_TYPE_XDP, "token_prog", "GPL", + insns, insn_cnt, &prog_opts); + if (!ASSERT_GT(prog_fd, 0, "userns_prog_load/bpf_prog_load")) { + err = -EPERM; + goto cleanup; + } + + err = 0; + +cleanup: + zclose(prog_fd); + zclose(token_fd); + return err; +} + +int test_bpf_map_create(void) +{ + struct bpffs_opts opts = { + .cmds_str = "map_create", + .maps_str = "stack" + }; + + return subtest(&opts, userns_map_create); +} + +int test_bpf_prog_load(void) +{ + struct bpffs_opts opts = { + .cmds_str = "prog_load", + .progs_str = "XDP", + .attachs_str = "xdp", + }; + + return subtest(&opts, userns_prog_load); +} -- 2.50.1.windows.1