Merge branch 'bpf-fs-mount-options-parsing-follow-ups'

Andrii Nakryiko says:

====================
BPF FS mount options parsing follow ups

Original BPF token patch set ([0]) added delegate_xxx mount options which
supported only special "any" value and hexadecimal bitmask. This patch set
attempts to make specifying and inspecting these mount options more
human-friendly by supporting string constants matching corresponding bpf_cmd,
bpf_map_type, bpf_prog_type, and bpf_attach_type enumerators.

This implementation relies on BTF information to find all supported symbolic
names. If kernel wasn't built with BTF, BPF FS will still support "any" and
hex-based mask.

  [0] https://patchwork.kernel.org/project/netdevbpf/list/?series=805707&state=*

v1->v2:
  - strip BPF_, BPF_MAP_TYPE_, and BPF_PROG_TYPE_ prefixes,
    do case-insensitive comparison, normalize to lower case (Alexei).
====================

Link: https://lore.kernel.org/r/20231214225016.1209867-1-andrii@kernel.org
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Alexei Starovoitov 2023-12-14 17:30:27 -08:00
commit 0f5d5454c7
2 changed files with 240 additions and 55 deletions

View File

@ -595,6 +595,136 @@ struct bpf_prog *bpf_prog_get_type_path(const char *name, enum bpf_prog_type typ
}
EXPORT_SYMBOL(bpf_prog_get_type_path);
struct bpffs_btf_enums {
const struct btf *btf;
const struct btf_type *cmd_t;
const struct btf_type *map_t;
const struct btf_type *prog_t;
const struct btf_type *attach_t;
};
static int find_bpffs_btf_enums(struct bpffs_btf_enums *info)
{
const struct btf *btf;
const struct btf_type *t;
const char *name;
int i, n;
memset(info, 0, sizeof(*info));
btf = bpf_get_btf_vmlinux();
if (IS_ERR(btf))
return PTR_ERR(btf);
if (!btf)
return -ENOENT;
info->btf = btf;
for (i = 1, n = btf_nr_types(btf); i < n; i++) {
t = btf_type_by_id(btf, i);
if (!btf_type_is_enum(t))
continue;
name = btf_name_by_offset(btf, t->name_off);
if (!name)
continue;
if (strcmp(name, "bpf_cmd") == 0)
info->cmd_t = t;
else if (strcmp(name, "bpf_map_type") == 0)
info->map_t = t;
else if (strcmp(name, "bpf_prog_type") == 0)
info->prog_t = t;
else if (strcmp(name, "bpf_attach_type") == 0)
info->attach_t = t;
else
continue;
if (info->cmd_t && info->map_t && info->prog_t && info->attach_t)
return 0;
}
return -ESRCH;
}
static bool find_btf_enum_const(const struct btf *btf, const struct btf_type *enum_t,
const char *prefix, const char *str, int *value)
{
const struct btf_enum *e;
const char *name;
int i, n, pfx_len = strlen(prefix);
*value = 0;
if (!btf || !enum_t)
return false;
for (i = 0, n = btf_vlen(enum_t); i < n; i++) {
e = &btf_enum(enum_t)[i];
name = btf_name_by_offset(btf, e->name_off);
if (!name || strncasecmp(name, prefix, pfx_len) != 0)
continue;
/* match symbolic name case insensitive and ignoring prefix */
if (strcasecmp(name + pfx_len, str) == 0) {
*value = e->val;
return true;
}
}
return false;
}
static void seq_print_delegate_opts(struct seq_file *m,
const char *opt_name,
const struct btf *btf,
const struct btf_type *enum_t,
const char *prefix,
u64 delegate_msk, u64 any_msk)
{
const struct btf_enum *e;
bool first = true;
const char *name;
u64 msk;
int i, n, pfx_len = strlen(prefix);
delegate_msk &= any_msk; /* clear unknown bits */
if (delegate_msk == 0)
return;
seq_printf(m, ",%s", opt_name);
if (delegate_msk == any_msk) {
seq_printf(m, "=any");
return;
}
if (btf && enum_t) {
for (i = 0, n = btf_vlen(enum_t); i < n; i++) {
e = &btf_enum(enum_t)[i];
name = btf_name_by_offset(btf, e->name_off);
if (!name || strncasecmp(name, prefix, pfx_len) != 0)
continue;
msk = 1ULL << e->val;
if (delegate_msk & msk) {
/* emit lower-case name without prefix */
seq_printf(m, "%c", first ? '=' : ':');
name += pfx_len;
while (*name) {
seq_printf(m, "%c", tolower(*name));
name++;
}
delegate_msk &= ~msk;
first = false;
}
}
}
if (delegate_msk)
seq_printf(m, "%c0x%llx", first ? '=' : ':', delegate_msk);
}
/*
* Display the mount options in /proc/mounts.
*/
@ -614,29 +744,34 @@ static int bpf_show_options(struct seq_file *m, struct dentry *root)
if (mode != S_IRWXUGO)
seq_printf(m, ",mode=%o", mode);
mask = (1ULL << __MAX_BPF_CMD) - 1;
if ((opts->delegate_cmds & mask) == mask)
seq_printf(m, ",delegate_cmds=any");
else if (opts->delegate_cmds)
seq_printf(m, ",delegate_cmds=0x%llx", opts->delegate_cmds);
if (opts->delegate_cmds || opts->delegate_maps ||
opts->delegate_progs || opts->delegate_attachs) {
struct bpffs_btf_enums info;
mask = (1ULL << __MAX_BPF_MAP_TYPE) - 1;
if ((opts->delegate_maps & mask) == mask)
seq_printf(m, ",delegate_maps=any");
else if (opts->delegate_maps)
seq_printf(m, ",delegate_maps=0x%llx", opts->delegate_maps);
/* ignore errors, fallback to hex */
(void)find_bpffs_btf_enums(&info);
mask = (1ULL << __MAX_BPF_PROG_TYPE) - 1;
if ((opts->delegate_progs & mask) == mask)
seq_printf(m, ",delegate_progs=any");
else if (opts->delegate_progs)
seq_printf(m, ",delegate_progs=0x%llx", opts->delegate_progs);
mask = (1ULL << __MAX_BPF_CMD) - 1;
seq_print_delegate_opts(m, "delegate_cmds",
info.btf, info.cmd_t, "BPF_",
opts->delegate_cmds, mask);
mask = (1ULL << __MAX_BPF_MAP_TYPE) - 1;
seq_print_delegate_opts(m, "delegate_maps",
info.btf, info.map_t, "BPF_MAP_TYPE_",
opts->delegate_maps, mask);
mask = (1ULL << __MAX_BPF_PROG_TYPE) - 1;
seq_print_delegate_opts(m, "delegate_progs",
info.btf, info.prog_t, "BPF_PROG_TYPE_",
opts->delegate_progs, mask);
mask = (1ULL << __MAX_BPF_ATTACH_TYPE) - 1;
seq_print_delegate_opts(m, "delegate_attachs",
info.btf, info.attach_t, "BPF_",
opts->delegate_attachs, mask);
}
mask = (1ULL << __MAX_BPF_ATTACH_TYPE) - 1;
if ((opts->delegate_attachs & mask) == mask)
seq_printf(m, ",delegate_attachs=any");
else if (opts->delegate_attachs)
seq_printf(m, ",delegate_attachs=0x%llx", opts->delegate_attachs);
return 0;
}
@ -686,7 +821,6 @@ static int bpf_parse_param(struct fs_context *fc, struct fs_parameter *param)
kuid_t uid;
kgid_t gid;
int opt, err;
u64 msk;
opt = fs_parse(fc, bpf_fs_parameters, param, &result);
if (opt < 0) {
@ -741,24 +875,63 @@ static int bpf_parse_param(struct fs_context *fc, struct fs_parameter *param)
case OPT_DELEGATE_CMDS:
case OPT_DELEGATE_MAPS:
case OPT_DELEGATE_PROGS:
case OPT_DELEGATE_ATTACHS:
if (strcmp(param->string, "any") == 0) {
msk = ~0ULL;
} else {
err = kstrtou64(param->string, 0, &msk);
if (err)
return err;
case OPT_DELEGATE_ATTACHS: {
struct bpffs_btf_enums info;
const struct btf_type *enum_t;
const char *enum_pfx;
u64 *delegate_msk, msk = 0;
char *p;
int val;
/* ignore errors, fallback to hex */
(void)find_bpffs_btf_enums(&info);
switch (opt) {
case OPT_DELEGATE_CMDS:
delegate_msk = &opts->delegate_cmds;
enum_t = info.cmd_t;
enum_pfx = "BPF_";
break;
case OPT_DELEGATE_MAPS:
delegate_msk = &opts->delegate_maps;
enum_t = info.map_t;
enum_pfx = "BPF_MAP_TYPE_";
break;
case OPT_DELEGATE_PROGS:
delegate_msk = &opts->delegate_progs;
enum_t = info.prog_t;
enum_pfx = "BPF_PROG_TYPE_";
break;
case OPT_DELEGATE_ATTACHS:
delegate_msk = &opts->delegate_attachs;
enum_t = info.attach_t;
enum_pfx = "BPF_";
break;
default:
return -EINVAL;
}
while ((p = strsep(&param->string, ":"))) {
if (strcmp(p, "any") == 0) {
msk |= ~0ULL;
} else if (find_btf_enum_const(info.btf, enum_t, enum_pfx, p, &val)) {
msk |= 1ULL << val;
} else {
err = kstrtou64(p, 0, &msk);
if (err)
return err;
}
}
/* Setting delegation mount options requires privileges */
if (msk && !capable(CAP_SYS_ADMIN))
return -EPERM;
switch (opt) {
case OPT_DELEGATE_CMDS: opts->delegate_cmds |= msk; break;
case OPT_DELEGATE_MAPS: opts->delegate_maps |= msk; break;
case OPT_DELEGATE_PROGS: opts->delegate_progs |= msk; break;
case OPT_DELEGATE_ATTACHS: opts->delegate_attachs |= msk; break;
default: return -EINVAL;
}
*delegate_msk |= msk;
break;
}
default:
/* ignore unknown mount options */
break;
}

View File

@ -66,14 +66,22 @@ static int restore_priv_caps(__u64 old_caps)
return cap_enable_effective(old_caps, NULL);
}
static int set_delegate_mask(int fs_fd, const char *key, __u64 mask)
static int set_delegate_mask(int fs_fd, const char *key, __u64 mask, const char *mask_str)
{
char buf[32];
int err;
snprintf(buf, sizeof(buf), "0x%llx", (unsigned long long)mask);
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 == ~0ULL ? "any" : buf, 0);
mask_str, 0);
if (err < 0)
err = -errno;
return err;
@ -86,6 +94,10 @@ struct bpffs_opts {
__u64 maps;
__u64 progs;
__u64 attachs;
const char *cmds_str;
const char *maps_str;
const char *progs_str;
const char *attachs_str;
};
static int create_bpffs_fd(void)
@ -104,16 +116,16 @@ 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);
err = set_delegate_mask(fs_fd, "delegate_cmds", opts->cmds, opts->cmds_str);
if (!ASSERT_OK(err, "fs_cfg_cmds"))
return err;
err = set_delegate_mask(fs_fd, "delegate_maps", opts->maps);
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);
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);
err = set_delegate_mask(fs_fd, "delegate_attachs", opts->attachs, opts->attachs_str);
if (!ASSERT_OK(err, "fs_cfg_attachs"))
return err;
@ -295,13 +307,13 @@ static void child(int sock_fd, struct bpffs_opts *opts, child_callback_fn callba
}
/* ensure unprivileged child cannot set delegation options */
err = set_delegate_mask(fs_fd, "delegate_cmds", 0x1);
err = set_delegate_mask(fs_fd, "delegate_cmds", 0x1, NULL);
ASSERT_EQ(err, -EPERM, "delegate_cmd_eperm");
err = set_delegate_mask(fs_fd, "delegate_maps", 0x1);
err = set_delegate_mask(fs_fd, "delegate_maps", 0x1, NULL);
ASSERT_EQ(err, -EPERM, "delegate_maps_eperm");
err = set_delegate_mask(fs_fd, "delegate_progs", 0x1);
err = set_delegate_mask(fs_fd, "delegate_progs", 0x1, NULL);
ASSERT_EQ(err, -EPERM, "delegate_progs_eperm");
err = set_delegate_mask(fs_fd, "delegate_attachs", 0x1);
err = set_delegate_mask(fs_fd, "delegate_attachs", 0x1, NULL);
ASSERT_EQ(err, -EPERM, "delegate_attachs_eperm");
/* pass BPF FS context object to parent */
@ -325,22 +337,22 @@ static void child(int sock_fd, struct bpffs_opts *opts, child_callback_fn callba
}
/* ensure unprivileged child cannot reconfigure to set delegation options */
err = set_delegate_mask(fs_fd, "delegate_cmds", ~0ULL);
err = set_delegate_mask(fs_fd, "delegate_cmds", 0, "any");
if (!ASSERT_EQ(err, -EPERM, "delegate_cmd_eperm_reconfig")) {
err = -EINVAL;
goto cleanup;
}
err = set_delegate_mask(fs_fd, "delegate_maps", ~0ULL);
err = set_delegate_mask(fs_fd, "delegate_maps", 0, "any");
if (!ASSERT_EQ(err, -EPERM, "delegate_maps_eperm_reconfig")) {
err = -EINVAL;
goto cleanup;
}
err = set_delegate_mask(fs_fd, "delegate_progs", ~0ULL);
err = set_delegate_mask(fs_fd, "delegate_progs", 0, "any");
if (!ASSERT_EQ(err, -EPERM, "delegate_progs_eperm_reconfig")) {
err = -EINVAL;
goto cleanup;
}
err = set_delegate_mask(fs_fd, "delegate_attachs", ~0ULL);
err = set_delegate_mask(fs_fd, "delegate_attachs", 0, "any");
if (!ASSERT_EQ(err, -EPERM, "delegate_attachs_eperm_reconfig")) {
err = -EINVAL;
goto cleanup;
@ -933,8 +945,8 @@ void test_token(void)
{
if (test__start_subtest("map_token")) {
struct bpffs_opts opts = {
.cmds = 1ULL << BPF_MAP_CREATE,
.maps = 1ULL << BPF_MAP_TYPE_STACK,
.cmds_str = "map_create",
.maps_str = "stack",
};
subtest_userns(&opts, userns_map_create);
@ -948,9 +960,9 @@ void test_token(void)
}
if (test__start_subtest("prog_token")) {
struct bpffs_opts opts = {
.cmds = 1ULL << BPF_PROG_LOAD,
.progs = 1ULL << BPF_PROG_TYPE_XDP,
.attachs = 1ULL << BPF_XDP,
.cmds_str = "PROG_LOAD",
.progs_str = "XDP",
.attachs_str = "xdp",
};
subtest_userns(&opts, userns_prog_load);