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 (except bpf_map_info - see below).
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.
Note: since struct bpf_map_info contains no pointers, the layout is the same in native/compat64. No conversion or additional handling is therefore required.
Signed-off-by: Zachary Leaf zachary.leaf@arm.com --- include/linux/bpf_compat.h | 112 ++++++++++++++ kernel/bpf/btf.c | 94 ++++++++++-- kernel/bpf/syscall.c | 302 ++++++++++++++++++++++++++++++++++--- 3 files changed, 473 insertions(+), 35 deletions(-)
diff --git a/include/linux/bpf_compat.h b/include/linux/bpf_compat.h index 85e0198bede7..710815417a27 100644 --- a/include/linux/bpf_compat.h +++ b/include/linux/bpf_compat.h @@ -298,3 +298,115 @@ union compat_bpf_attr {
} __attribute__((aligned(8)));
+struct compat_bpf_prog_info { + __u32 type; + __u32 id; + __u8 tag[BPF_TAG_SIZE]; + __u32 jited_prog_len; + __u32 xlated_prog_len; + __aligned_u64 jited_prog_insns; + __aligned_u64 xlated_prog_insns; + __u64 load_time; /* ns since boottime */ + __u32 created_by_uid; + __u32 nr_map_ids; + __aligned_u64 map_ids; + char name[BPF_OBJ_NAME_LEN]; + __u32 ifindex; + __u32 gpl_compatible:1; + __u32:31; /* alignment pad */ + __u64 netns_dev; + __u64 netns_ino; + __u32 nr_jited_ksyms; + __u32 nr_jited_func_lens; + __aligned_u64 jited_ksyms; + __aligned_u64 jited_func_lens; + __u32 btf_id; + __u32 func_info_rec_size; + __aligned_u64 func_info; + __u32 nr_func_info; + __u32 nr_line_info; + __aligned_u64 line_info; + __aligned_u64 jited_line_info; + __u32 nr_jited_line_info; + __u32 line_info_rec_size; + __u32 jited_line_info_rec_size; + __u32 nr_prog_tags; + __aligned_u64 prog_tags; + __u64 run_time_ns; + __u64 run_cnt; + __u64 recursion_misses; + __u32 verified_insns; + __u32 attach_btf_obj_id; + __u32 attach_btf_id; +} __attribute__((aligned(8))); + +struct compat_bpf_btf_info { + __aligned_u64 btf; + __u32 btf_size; + __u32 id; + __aligned_u64 name; + __u32 name_len; + __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; + struct { + __u32 map_id; + } struct_ops; + struct { + __u32 pf; + __u32 hooknum; + __s32 priority; + __u32 flags; + } netfilter; + }; +} __attribute__((aligned(8))); + diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index e7dac3c07f08..58421266a5b8 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -7214,31 +7214,96 @@ struct btf *btf_get_by_fd(int fd) return btf; }
+static void convert_compat_btf_info_in(struct bpf_btf_info *dest, + const struct compat_bpf_btf_info *cinfo) +{ + copy_field(dest, cinfo, btf); + copy_field(dest, cinfo, btf_size); + copy_field(dest, cinfo, id); + copy_field(dest, cinfo, name); + copy_field(dest, cinfo, name_len); + copy_field(dest, cinfo, kernel_btf); +} + +static void convert_compat_btf_info_out(struct compat_bpf_btf_info *dest, + const struct bpf_btf_info *info) +{ + copy_field(dest, info, btf); + copy_field(dest, info, btf_size); + copy_field(dest, info, id); + copy_field(dest, info, name); + copy_field(dest, info, name_len); + copy_field(dest, info, kernel_btf); +} + +static int copy_bpf_btf_info_from_user(const union bpf_attr *attr, + struct bpf_btf_info *info, + u32 *info_len) +{ + struct compat_bpf_btf_info cinfo; + int err; + size_t info_size = in_compat64_syscall() ? sizeof(struct compat_bpf_btf_info) + : sizeof(struct bpf_btf_info); + void __user *uinfo = u64_to_user_ptr(attr->info.info); + + *info_len = attr->info.info_len; + err = bpf_check_uarg_tail_zero(USER_BPFPTR(uinfo), + info_size, *info_len); + if (err) + return err; + *info_len = min_t(u32, info_size, *info_len); + + memset(info, 0, sizeof(*info)); + if (in_compat64_syscall()) { + memset(&cinfo, 0, sizeof(cinfo)); + if (copy_from_user(&cinfo, uinfo, *info_len)) + return -EFAULT; + convert_compat_btf_info_in(info, &cinfo); + } else { + if (copy_from_user(info, uinfo, *info_len)) + return -EFAULT; + } + + return 0; +} + +static int copy_bpf_btf_info_to_user(const union bpf_attr *attr, + union bpf_attr __user *uattr, + struct bpf_btf_info *info, + u32 *info_len) +{ + struct compat_bpf_btf_info cinfo; + void *src_info = in_compat64_syscall() ? (struct bpf_btf_info *)&cinfo + : info; + void __user *uinfo = u64_to_user_ptr(attr->info.info); + + if (in_compat64_syscall()) { + memset(&cinfo, 0, sizeof(cinfo)); + convert_compat_btf_info_out(&cinfo, info); + } + + if (copy_to_user(uinfo, src_info, *info_len) || + bpf_put_uattr(*info_len, uattr, info.info_len)) + return -EFAULT; + + return 0; +} + int btf_get_info_by_fd(const struct btf *btf, const union bpf_attr *attr, union bpf_attr __user *uattr) { - struct bpf_btf_info __user *uinfo; struct bpf_btf_info info; - u32 info_copy, btf_copy; + u32 btf_copy; void __user *ubtf; char __user *uname; u32 uinfo_len, uname_len, name_len; - int ret = 0; - - uinfo = u64_to_user_ptr(attr->info.info); - uinfo_len = attr->info.info_len; + int ret;
- ret = bpf_check_uarg_tail_zero(USER_BPFPTR(uinfo), sizeof(*uinfo), - uinfo_len); + ret = copy_bpf_btf_info_from_user(attr, &info, &uinfo_len); if (ret) return ret;
- info_copy = min_t(u32, uinfo_len, sizeof(info)); - memset(&info, 0, sizeof(info)); - if (copy_from_user(&info, uinfo, info_copy)) - return -EFAULT; - info.id = btf->id; ubtf = u64_to_user_ptr(info.btf); btf_copy = min_t(u32, btf->data_size, info.btf_size); @@ -7272,8 +7337,7 @@ int btf_get_info_by_fd(const struct btf *btf, } }
- if (copy_to_user(uinfo, &info, info_copy) || - put_user(info_copy, &uattr->info.info_len)) + if (copy_bpf_btf_info_to_user(attr, uattr, &info, &uinfo_len)) return -EFAULT;
return ret; diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 02403abf8ed4..17d436970520 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -3950,28 +3950,163 @@ static int set_info_rec_size(struct bpf_prog_info *info) return 0; }
+static void +convert_compat_prog_info_in(struct bpf_prog_info *dest, + const struct compat_bpf_prog_info *cinfo) +{ + copy_field(dest, cinfo, type); + copy_field(dest, cinfo, id); + strncpy((char *)dest->tag, (char *)cinfo->tag, BPF_TAG_SIZE); + copy_field(dest, cinfo, jited_prog_len); + copy_field(dest, cinfo, xlated_prog_len); + copy_field(dest, cinfo, jited_prog_insns); + copy_field(dest, cinfo, xlated_prog_insns); + copy_field(dest, cinfo, load_time); + copy_field(dest, cinfo, created_by_uid); + copy_field(dest, cinfo, nr_map_ids); + copy_field(dest, cinfo, map_ids); + strncpy((char *)dest->name, (char *)cinfo->name, BPF_OBJ_NAME_LEN); + copy_field(dest, cinfo, ifindex); + copy_field(dest, cinfo, gpl_compatible); + copy_field(dest, cinfo, netns_dev); + copy_field(dest, cinfo, netns_ino); + copy_field(dest, cinfo, nr_jited_ksyms); + copy_field(dest, cinfo, nr_jited_func_lens); + copy_field(dest, cinfo, jited_ksyms); + copy_field(dest, cinfo, jited_func_lens); + copy_field(dest, cinfo, btf_id); + copy_field(dest, cinfo, func_info_rec_size); + copy_field(dest, cinfo, func_info); + copy_field(dest, cinfo, nr_func_info); + copy_field(dest, cinfo, nr_line_info); + copy_field(dest, cinfo, line_info); + copy_field(dest, cinfo, jited_line_info); + copy_field(dest, cinfo, nr_jited_line_info); + copy_field(dest, cinfo, line_info_rec_size); + copy_field(dest, cinfo, jited_line_info_rec_size); + copy_field(dest, cinfo, nr_prog_tags); + copy_field(dest, cinfo, prog_tags); + copy_field(dest, cinfo, run_time_ns); + copy_field(dest, cinfo, run_cnt); + copy_field(dest, cinfo, recursion_misses); + copy_field(dest, cinfo, verified_insns); + copy_field(dest, cinfo, attach_btf_obj_id); + copy_field(dest, cinfo, attach_btf_id); +} + +static void +convert_compat_prog_info_out(struct compat_bpf_prog_info *dest, + const struct bpf_prog_info *info) +{ + copy_field(dest, info, type); + copy_field(dest, info, id); + strncpy((char *)dest->tag, (char *)info->tag, BPF_TAG_SIZE); + copy_field(dest, info, jited_prog_len); + copy_field(dest, info, xlated_prog_len); + copy_field(dest, info, jited_prog_insns); + copy_field(dest, info, xlated_prog_insns); + copy_field(dest, info, load_time); + copy_field(dest, info, created_by_uid); + copy_field(dest, info, nr_map_ids); + copy_field(dest, info, map_ids); + strncpy((char *)dest->name, (char *)info->name, BPF_OBJ_NAME_LEN); + copy_field(dest, info, ifindex); + copy_field(dest, info, gpl_compatible); + copy_field(dest, info, netns_dev); + copy_field(dest, info, netns_ino); + copy_field(dest, info, nr_jited_ksyms); + copy_field(dest, info, nr_jited_func_lens); + copy_field(dest, info, jited_ksyms); + copy_field(dest, info, jited_func_lens); + copy_field(dest, info, btf_id); + copy_field(dest, info, func_info_rec_size); + copy_field(dest, info, func_info); + copy_field(dest, info, nr_func_info); + copy_field(dest, info, nr_line_info); + copy_field(dest, info, line_info); + copy_field(dest, info, jited_line_info); + copy_field(dest, info, nr_jited_line_info); + copy_field(dest, info, line_info_rec_size); + copy_field(dest, info, jited_line_info_rec_size); + copy_field(dest, info, nr_prog_tags); + copy_field(dest, info, prog_tags); + copy_field(dest, info, run_time_ns); + copy_field(dest, info, run_cnt); + copy_field(dest, info, recursion_misses); + copy_field(dest, info, verified_insns); + copy_field(dest, info, attach_btf_obj_id); + copy_field(dest, info, attach_btf_id); +} + +static int copy_bpf_prog_info_from_user(const union bpf_attr *attr, + struct bpf_prog_info *info, + u32 *info_len) +{ + struct compat_bpf_prog_info cinfo; + int err; + size_t info_size = in_compat64_syscall() ? sizeof(struct compat_bpf_prog_info) + : sizeof(struct bpf_prog_info); + void __user *uinfo = u64_to_user_ptr(attr->info.info); + + *info_len = attr->info.info_len; + err = bpf_check_uarg_tail_zero(USER_BPFPTR(uinfo), + info_size, *info_len); + if (err) + return err; + *info_len = min_t(u32, info_size, *info_len); + + memset(info, 0, sizeof(*info)); + if (in_compat64_syscall()) { + memset(&cinfo, 0, sizeof(cinfo)); + if (copy_from_user(&cinfo, uinfo, *info_len)) + return -EFAULT; + convert_compat_prog_info_in(info, &cinfo); + } else { + if (copy_from_user(info, uinfo, *info_len)) + return -EFAULT; + } + + return 0; +} + +static int copy_bpf_prog_info_to_user(const union bpf_attr *attr, + union bpf_attr __user *uattr, + struct bpf_prog_info *info, + u32 *info_len) +{ + struct compat_bpf_prog_info cinfo; + void *src_info = in_compat64_syscall() ? (struct bpf_prog_info *)&cinfo + : info; + void __user *uinfo = (void __user *)attr->info.info; + + if (in_compat64_syscall()) { + memset(&cinfo, 0, sizeof(cinfo)); + convert_compat_prog_info_out(&cinfo, info); + } + + if (copy_to_user(uinfo, src_info, *info_len) || + bpf_put_uattr(*info_len, uattr, info.info_len)) + return -EFAULT; + + return 0; +} + static int bpf_prog_get_info_by_fd(struct file *file, struct bpf_prog *prog, const union bpf_attr *attr, union bpf_attr __user *uattr) { - struct bpf_prog_info __user *uinfo = u64_to_user_ptr(attr->info.info); struct btf *attach_btf = bpf_prog_get_target_btf(prog); struct bpf_prog_info info; - u32 info_len = attr->info.info_len; + u32 info_len; struct bpf_prog_kstats stats; char __user *uinsns; u32 ulen; int err;
- err = bpf_check_uarg_tail_zero(USER_BPFPTR(uinfo), sizeof(info), info_len); + err = copy_bpf_prog_info_from_user(attr, &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(&info, uinfo, info_len)) - return -EFAULT;
info.type = prog->type; info.id = prog->aux->id; @@ -4232,8 +4367,7 @@ static int bpf_prog_get_info_by_fd(struct file *file, }
done: - if (copy_to_user(uinfo, &info, info_len) || - put_user(info_len, &uattr->info.info_len)) + if (copy_bpf_prog_info_to_user(attr, uattr, &info, &info_len)) return -EFAULT;
return 0; @@ -4292,24 +4426,153 @@ static int bpf_btf_get_info_by_fd(struct file *file, return btf_get_info_by_fd(btf, attr, uattr); }
+static void +convert_compat_link_info_in(struct bpf_link_info *dest, + const 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 + */ + switch (type) { + case BPF_LINK_TYPE_RAW_TRACEPOINT: + copy_field(dest, cinfo, raw_tracepoint.tp_name); + copy_field(dest, cinfo, raw_tracepoint.tp_name_len); + return; + case BPF_LINK_TYPE_ITER: + copy_field(dest, cinfo, iter.target_name); + copy_field(dest, cinfo, iter.target_name_len); + return; + default: + return; + } +} + +static void +convert_compat_link_info_out(struct compat_bpf_link_info *dest, + const struct bpf_link_info *info, + enum bpf_link_type type) +{ + copy_field(dest, info, type); + copy_field(dest, info, id); + copy_field(dest, info, prog_id); + + switch (type) { + case BPF_LINK_TYPE_RAW_TRACEPOINT: + copy_field(dest, info, raw_tracepoint.tp_name); + copy_field(dest, info, raw_tracepoint.tp_name_len); + return; + case BPF_LINK_TYPE_TRACING: + copy_field(dest, info, tracing.attach_type); + copy_field(dest, info, tracing.target_obj_id); + copy_field(dest, info, tracing.target_btf_id); + return; + case BPF_LINK_TYPE_CGROUP: + copy_field(dest, info, cgroup.cgroup_id); + copy_field(dest, info, cgroup.attach_type); + return; + case BPF_LINK_TYPE_ITER: + copy_field(dest, info, iter.target_name); + copy_field(dest, info, iter.target_name_len); + /* + * remaining struct is fixed size integers, so identical in + * any 64-bit ABI - since figuring out what members are active + * is non-trivial, memcpy to the end of the struct + */ + memcpy((u8 *)dest+offsetof(struct compat_bpf_link_info, iter.map), + (u8 *)info+offsetof(struct bpf_link_info, iter.map), + offsetofend(struct bpf_link_info, iter.cgroup) - + offsetof(struct bpf_link_info, iter.map)); + return; + case BPF_LINK_TYPE_NETNS: + copy_field(dest, info, netns.netns_ino); + copy_field(dest, info, netns.attach_type); + return; + case BPF_LINK_TYPE_XDP: + copy_field(dest, info, xdp.ifindex); + return; + case BPF_LINK_TYPE_STRUCT_OPS: + copy_field(dest, info, struct_ops.map_id); + return; + case BPF_LINK_TYPE_NETFILTER: + copy_field(dest, info, netfilter.pf); + copy_field(dest, info, netfilter.hooknum); + copy_field(dest, info, netfilter.priority); + copy_field(dest, info, netfilter.flags); + return; + default: + return; + } +} + +static int copy_bpf_link_info_from_user(const union bpf_attr *attr, + struct bpf_link_info *info, + u32 *info_len, + enum bpf_link_type type) +{ + struct compat_bpf_link_info cinfo; + int err; + size_t info_size = in_compat64_syscall() ? sizeof(struct compat_bpf_link_info) + : sizeof(struct bpf_link_info); + void __user *uinfo = u64_to_user_ptr(attr->info.info); + + *info_len = attr->info.info_len; + err = bpf_check_uarg_tail_zero(USER_BPFPTR(uinfo), + info_size, *info_len); + if (err) + return err; + *info_len = min_t(u32, info_size, *info_len); + + memset(info, 0, sizeof(*info)); + if (in_compat64_syscall()) { + memset(&cinfo, 0, sizeof(cinfo)); + if (copy_from_user(&cinfo, uinfo, *info_len)) + return -EFAULT; + convert_compat_link_info_in(info, &cinfo, type); + } else { + if (copy_from_user(info, uinfo, *info_len)) + return -EFAULT; + } + + return 0; +} + +static int copy_bpf_link_info_to_user(const union bpf_attr *attr, + union bpf_attr __user *uattr, + struct bpf_link_info *info, + u32 *info_len, + enum bpf_link_type type) +{ + struct compat_bpf_link_info cinfo; + void *src_info = in_compat64_syscall() ? (struct bpf_link_info *)&cinfo + : info; + void __user *uinfo = (void __user *)attr->info.info; + + if (in_compat64_syscall()) { + memset(&cinfo, 0, sizeof(cinfo)); + convert_compat_link_info_out(&cinfo, info, type); + } + + if (copy_to_user(uinfo, src_info, *info_len) || + bpf_put_uattr(*info_len, uattr, info.info_len)) + return -EFAULT; + + return 0; +} + 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 info; - u32 info_len = attr->info.info_len; + u32 info_len; int err;
- err = bpf_check_uarg_tail_zero(USER_BPFPTR(uinfo), sizeof(info), info_len); + err = copy_bpf_link_info_from_user(attr, &info, &info_len, link->type); if (err) return err; - info_len = min_t(u32, sizeof(info), info_len); - - memset(&info, 0, sizeof(info)); - if (copy_from_user(&info, uinfo, info_len)) - return -EFAULT;
info.type = link->type; info.id = link->id; @@ -4322,8 +4585,7 @@ static int bpf_link_get_info_by_fd(struct file *file, return err; }
- if (copy_to_user(uinfo, &info, info_len) || - put_user(info_len, &uattr->info.info_len)) + if (copy_bpf_link_info_to_user(attr, uattr, &info, &info_len, link->type)) return -EFAULT;
return 0;