selinux.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Eric Suen <ericsu@linux.microsoft.com>
To: Stephen Smalley <stephen.smalley.work@gmail.com>
Cc: selinux@vger.kernel.org, paul@paul-moore.com,
	omosnace@redhat.com, danieldurning.work@gmail.com
Subject: Re: [PATCH testsuite v4] tests/bpf: Add tests for SELinux BPF token access control
Date: Thu, 4 Sep 2025 14:23:53 -0700	[thread overview]
Message-ID: <a873a1de-ddc5-4226-b68f-a7b494b7d520@linux.microsoft.com> (raw)
In-Reply-To: <CAEjxPJ5Zq9cnYJEnHsawZuQyTJ=QyhMHdBnuj1fwLtNwrih+VA@mail.gmail.com>

On 9/3/2025 6:12 AM, Stephen Smalley wrote:
> On Wed, Sep 3, 2025 at 9:01 AM Stephen Smalley
> <stephen.smalley.work@gmail.com> wrote:
>> On Tue, Sep 2, 2025 at 10:09 AM Stephen Smalley
>> <stephen.smalley.work@gmail.com> wrote:
>>> On Fri, Aug 29, 2025 at 2:17 PM Eric Suen <ericsu@linux.microsoft.com> wrote:
>>>> 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/
>>> No need to re-spin just for this, but I would have also provided the
>>> link to the libsepol patch on which this depends.
>>> Just for future reference if you need to re-spin this for other
>>> reasons or for future patches.
>>>
>>>> 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'
>>>>
>>>> Changes in v4:
>>>> - Updated 'token_test.c' to write error messages only when DEBUG
>>>>    is defined
>>> Likewise, no need to re-spin just for this, but for future patches,
>>> please put the Changelog below the "---" line just prior to the
>>> diffstat output.
>>> This allows the reviewer to see it for review purposes but omits it
>>> from the final commit message since it ceases to be relevant once a
>>> patch is merged.
>> One item that will need to be fixed before this is merged is adding
>> conditional guards to the policy/Makefile and tests/bpf/test so the
>> policy rules with the new permissions are omitted if the policy
>> headers do not yet define them and the tests are skipped if the loaded
>> policy doesn't include the definitions. You can see existing examples
>> of this elsewhere in the testsuite.
> Likely the easiest way to handle this for the policy rules would be to
> split the new rules into a separate .te file, e.g. test_bpf_token.te,
> and then the policy/Makefile can just omit it entirely if the
> requisite permission definitions and policy capability are not defined
> (see the nlmsg test dependencies in policy/Makefile for an example but
> note that your change does not depend on a specific policy version
> number so those checks can be omitted). For the test itself, you can
> follow the example of tests/nnp_nosuid/test, which checks to see if a
> policy capability is defined and if so, enables additional tests to
> run, and do likewise for your additions to tests/bpf/test.

This makes a lot of sense. Let me look into this. Thanks a lot for the 
review and comments, Stephen.

>>>> Signed-off-by: Eric Suen <ericsu@linux.microsoft.com>
>>>> Tested-by: Daniel Durning <danieldurning.work@gmail.com>
>>>> ---
>>>>   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 | 559 +++++++++++++++++++++++++++++++++++++++++
>>>>   6 files changed, 674 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..64f5222
>>>> --- /dev/null
>>>> +++ b/tests/bpf/token_test.c
>>>> @@ -0,0 +1,559 @@
>>>> +// 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 <linux/unistd.h>
>>>> +#include "sys/wait.h"
>>>> +#include "sys/socket.h"
>>>> +#include "fcntl.h"
>>>> +#include "sched.h"
>>>> +#include <bpf/btf.h>
>>>> +
>>>> +#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]))
>>>> +
>>>> +#ifdef DEBUG
>>>> +#define _CHECK(condition, format...) ({    \
>>>> +       int __ret = !!(condition); \
>>>> +       int __save_errno = errno;   \
>>>> +       if (__ret) {    \
>>>> +               fprintf(stderr, ##format);   \
>>>> +       }   \
>>>> +       errno = __save_errno;   \
>>>> +       __ret;  \
>>>> +})
>>>> +#else
>>>> +#define _CHECK(condition, format...) ({    \
>>>> +       int __ret = !!(condition); \
>>>> +       __ret;  \
>>>> +})
>>>> +#endif
>>>> +
>>>> +#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);
>>>> +
>>>> +       if (err)
>>>> +               fprintf(stderr, "Failed to create BPF map with BPF token enabled: %s\n",
>>>> +                       strerror(errno));
>>>> +
>>>> +       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);
>>>> +
>>>> +       if (err)
>>>> +               fprintf(stderr, "Failed to load BPF prog with token enabled: %s\n",
>>>> +                       strerror(errno));
>>>> +
>>>> +       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.51.0.windows.1
>>>>


      reply	other threads:[~2025-09-04 21:23 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-08-29 18:17 [PATCH testsuite v4] tests/bpf: Add tests for SELinux BPF token access control Eric Suen
2025-09-02 14:09 ` Stephen Smalley
2025-09-03 13:01   ` Stephen Smalley
2025-09-03 13:12     ` Stephen Smalley
2025-09-04 21:23       ` Eric Suen [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=a873a1de-ddc5-4226-b68f-a7b494b7d520@linux.microsoft.com \
    --to=ericsu@linux.microsoft.com \
    --cc=danieldurning.work@gmail.com \
    --cc=omosnace@redhat.com \
    --cc=paul@paul-moore.com \
    --cc=selinux@vger.kernel.org \
    --cc=stephen.smalley.work@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).