union bpf_attr member info.info is a __user *ptr to either: struct bpf_btf_info struct bpf_prog_info struct bpf_map_info struct bpf_link_info
These info structs are passed in from userspace and used to store information returned from calls to BPF_OBJ_GET_INFO_BY_FD.
While inbound conversion for all other members of union bpf_attr is handled upfront, this same approach is not possible here for two main reasons:
1. Due to the extra complexity of a further userspace struct that is one of 4 types. These types themselves can be a union/multiplex of even more additional options and types. It is not straight forward or possible in many cases to determine which elements of the union are active upfront, and each has different native/compat64 layouts/offsets.
2. Due to the nature of this subcommand, the main purpose is to copy information back out to userspace, there is more work to do to convert the struct back out to compat64 compatible offsets compared to other subcommands.
Instead of an upfront conversion, convert where each is used at the end site both in/out.
Signed-off-by: Zachary Leaf zachary.leaf@arm.com --- include/linux/bpf_compat.h | 52 ++++++++++++ kernel/bpf/syscall.c | 167 ++++++++++++++++++++++++++++++++++--- 2 files changed, 209 insertions(+), 10 deletions(-)
diff --git a/include/linux/bpf_compat.h b/include/linux/bpf_compat.h index 93a2fcab5746..bdbc6a44f2bc 100644 --- a/include/linux/bpf_compat.h +++ b/include/linux/bpf_compat.h @@ -357,5 +357,57 @@ struct compat_bpf_btf_info { __u32 kernel_btf; } __attribute__((aligned(8)));
+struct compat_bpf_link_info { + __u32 type; + __u32 id; + __u32 prog_id; + union { + struct { + __aligned_u64 tp_name; /* in/out: tp_name buffer ptr */ + __u32 tp_name_len; /* in/out: tp_name buffer len */ + } raw_tracepoint; + struct { + __u32 attach_type; + __u32 target_obj_id; /* prog_id for PROG_EXT, otherwise btf object id */ + __u32 target_btf_id; /* BTF type id inside the object */ + } tracing; + struct { + __u64 cgroup_id; + __u32 attach_type; + } cgroup; + struct { + __aligned_u64 target_name; /* in/out: target_name buffer ptr */ + __u32 target_name_len; /* in/out: target_name buffer len */ + + /* If the iter specific field is 32 bits, it can be put + * in the first or second union. Otherwise it should be + * put in the second union. + */ + union { + struct { + __u32 map_id; + } map; + }; + union { + struct { + __u64 cgroup_id; + __u32 order; + } cgroup; + struct { + __u32 tid; + __u32 pid; + } task; + }; + } iter; + struct { + __u32 netns_ino; + __u32 attach_type; + } netns; + struct { + __u32 ifindex; + } xdp; + }; +} __attribute__((aligned(8))); + #endif /* CONFIG_COMPAT64 */
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 588e4d5d4077..cdba1e563688 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -4430,24 +4430,161 @@ static int bpf_btf_get_info_by_fd(struct file *file, return btf_get_info_by_fd(btf, attr, uattr); }
+#ifdef CONFIG_COMPAT64 +void convert_compat_link_info_in(struct bpf_link_info *dest, + struct compat_bpf_link_info *cinfo, + enum bpf_link_type type) +{ + /* + * Only a few fields in bpf_link_info are used as input, the + * rest do not need conversion in + */ + if (type == BPF_LINK_TYPE_RAW_TRACEPOINT) { + dest->raw_tracepoint.tp_name = + cinfo->raw_tracepoint.tp_name; + dest->raw_tracepoint.tp_name_len = + cinfo->raw_tracepoint.tp_name_len; + return; + } + if (type == BPF_LINK_TYPE_ITER) { + dest->iter.target_name = cinfo->iter.target_name; + dest->iter.target_name_len = cinfo->iter.target_name_len; + return; + } +} + +/* is_iter_XYZ_target copied from tools/bpf/bpftool/link.c */ +static bool is_iter_map_target(const char *target_name) +{ + return strcmp(target_name, "bpf_map_elem") == 0 || + strcmp(target_name, "bpf_sk_storage_map") == 0; +} + +static bool is_iter_cgroup_target(const char *target_name) +{ + return strcmp(target_name, "cgroup") == 0; +} + +static bool is_iter_task_target(const char *target_name) +{ + return strcmp(target_name, "task") == 0 || + strcmp(target_name, "task_file") == 0 || + strcmp(target_name, "task_vma") == 0; +} + +void convert_compat_link_info_out(struct compat_bpf_link_info *dest, + struct bpf_link_info *info, + enum bpf_link_type type) +{ + const char *iter_target; + + dest->type = info->type; + dest->id = info->id; + dest->prog_id = info->prog_id; + + switch (type) { + case BPF_LINK_TYPE_RAW_TRACEPOINT: + dest->raw_tracepoint.tp_name = + info->raw_tracepoint.tp_name; + dest->raw_tracepoint.tp_name_len = + info->raw_tracepoint.tp_name_len; + return; + case BPF_LINK_TYPE_TRACING: + dest->tracing.attach_type = + info->tracing.attach_type; + dest->tracing.target_obj_id = + info->tracing.target_obj_id; + dest->tracing.target_btf_id = + info->tracing.target_btf_id; + return; + case BPF_LINK_TYPE_CGROUP: + dest->cgroup.cgroup_id = info->cgroup.cgroup_id; + dest->cgroup.attach_type = info->cgroup.attach_type; + return; + case BPF_LINK_TYPE_ITER: + dest->iter.target_name = info->iter.target_name; + dest->iter.target_name_len = info->iter.target_name_len; + + iter_target = + (const char *)(uintptr_t)info->iter.target_name; + if (is_iter_map_target(iter_target)) + dest->iter.map.map_id = info->iter.map.map_id; + else if (is_iter_cgroup_target(iter_target)) { + dest->iter.cgroup.cgroup_id = + info->iter.cgroup.cgroup_id; + dest->iter.cgroup.order = + info->iter.cgroup.order; + } else if (is_iter_task_target(iter_target)) { + dest->iter.task.tid = info->iter.task.tid; + dest->iter.task.pid = info->iter.task.pid; + } + return; + case BPF_LINK_TYPE_NETNS: + dest->netns.netns_ino = info->netns.netns_ino; + dest->netns.attach_type = info->netns.attach_type; + return; + case BPF_LINK_TYPE_XDP: + dest->xdp.ifindex = info->xdp.ifindex; + return; + default: + return; + } +} +#endif /* CONFIG_COMPAT64 */ + static int bpf_link_get_info_by_fd(struct file *file, struct bpf_link *link, const union bpf_attr *attr, union bpf_attr __user *uattr) { - struct bpf_link_info __user *uinfo = u64_to_user_ptr(attr->info.info); + struct bpf_link_info __user *uinfo; struct bpf_link_info info; - u32 info_len = attr->info.info_len; + u32 info_len; int err; +#ifdef CONFIG_COMPAT64 + struct compat_bpf_link_info __user *cuinfo; + struct compat_bpf_link_info cinfo; + union compat_bpf_attr __user *cuattr = + (union compat_bpf_attr __user *)uattr; + u32 cuinfo_len; + /* + * for compat64, uattr has already been converted and copied into + * attr - however attr->info.info is a ptr to uapi struct bpf_link_info, + * which also needs converting + * + * convert bpf_link_info coming in, and later reverse this conversion + * when writing back out to userspace + */ + if (in_compat_syscall()) { + cuinfo = (struct compat_bpf_link_info __user *)attr->info.info; + cuinfo_len = attr->info.info_len;
- err = bpf_check_uarg_tail_zero(USER_BPFPTR(uinfo), sizeof(info), info_len); - if (err) - return err; - info_len = min_t(u32, sizeof(info), info_len); + err = bpf_check_uarg_tail_zero(USER_BPFPTR(cuinfo), + sizeof(cinfo), cuinfo_len); + if (err) + return err; + cuinfo_len = min_t(u32, sizeof(cinfo), cuinfo_len);
- memset(&info, 0, sizeof(info)); - if (copy_from_user(&info, uinfo, info_len)) - return -EFAULT; + if (copy_from_user_with_ptr(&cinfo, cuinfo, cuinfo_len)) + return -EFAULT; + memset(&info, 0, sizeof(info)); + convert_compat_link_info_in(&info, &cinfo, link->type); + } else +#endif + { + uinfo = u64_to_user_ptr(attr->info.info); + info_len = attr->info.info_len; + + err = bpf_check_uarg_tail_zero(USER_BPFPTR(uinfo), + sizeof(info), info_len); + if (err) + return err; + info_len = min_t(u32, sizeof(info), info_len); + + memset(&info, 0, sizeof(info)); + if (copy_from_user_with_ptr(&info, uinfo, info_len)) + return -EFAULT; + }
info.type = link->type; info.id = link->id; @@ -4459,7 +4596,17 @@ static int bpf_link_get_info_by_fd(struct file *file, return err; }
- if (copy_to_user(uinfo, &info, info_len) || +#ifdef CONFIG_COMPAT64 + if (in_compat_syscall()) { + convert_compat_link_info_out(&cinfo, &info, link->type); + if (copy_to_user_with_ptr(cuinfo, &cinfo, cuinfo_len) || + put_user(cuinfo_len, &cuattr->info.info_len)) + return -EFAULT; + + return 0; + } +#endif + if (copy_to_user_with_ptr(uinfo, &info, info_len) || put_user(info_len, &uattr->info.info_len)) return -EFAULT;