Commit 0f5d5454 authored by Alexei Starovoitov's avatar Alexei Starovoitov
Browse files

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: default avatarAlexei Starovoitov <ast@kernel.org>
parents 403f3e8f f2d0ffee
Loading
Loading
Loading
Loading
+211 −38
Original line number Diff line number Diff line
@@ -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);

	if (opts->delegate_cmds || opts->delegate_maps ||
	    opts->delegate_progs || opts->delegate_attachs) {
		struct bpffs_btf_enums info;

		/* ignore errors, fallback to hex */
		(void)find_bpffs_btf_enums(&info);

		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);
		seq_print_delegate_opts(m, "delegate_cmds",
					info.btf, info.cmd_t, "BPF_",
					opts->delegate_cmds, mask);

		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);
		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;
	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);
		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;
	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);
		seq_print_delegate_opts(m, "delegate_attachs",
					info.btf, info.attach_t, "BPF_",
					opts->delegate_attachs, mask);
	}

	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: {
		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:
		if (strcmp(param->string, "any") == 0) {
			msk = ~0ULL;
			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(param->string, 0, &msk);
				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;
	}

+32 −20
Original line number Diff line number Diff line
@@ -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;

	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);