| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (C) 2021. Huawei Technologies Co., Ltd | |
| */ | |
| #include <linux/kernel.h> | |
| #include <linux/bpf_verifier.h> | |
| #include <linux/bpf.h> | |
| #include <linux/btf.h> | |
| static struct bpf_struct_ops bpf_bpf_dummy_ops; | |
| /* A common type for test_N with return value in bpf_dummy_ops */ | |
| typedef int (*dummy_ops_test_ret_fn)(struct bpf_dummy_ops_state *state, ...); | |
| static int dummy_ops_test_ret_function(struct bpf_dummy_ops_state *state, ...) | |
| { | |
| return 0; | |
| } | |
| struct bpf_dummy_ops_test_args { | |
| u64 args[MAX_BPF_FUNC_ARGS]; | |
| struct bpf_dummy_ops_state state; | |
| }; | |
| static struct btf *bpf_dummy_ops_btf; | |
| static struct bpf_dummy_ops_test_args * | |
| dummy_ops_init_args(const union bpf_attr *kattr, unsigned int nr) | |
| { | |
| __u32 size_in; | |
| struct bpf_dummy_ops_test_args *args; | |
| void __user *ctx_in; | |
| void __user *u_state; | |
| size_in = kattr->test.ctx_size_in; | |
| if (size_in != sizeof(u64) * nr) | |
| return ERR_PTR(-EINVAL); | |
| args = kzalloc_obj(*args); | |
| if (!args) | |
| return ERR_PTR(-ENOMEM); | |
| ctx_in = u64_to_user_ptr(kattr->test.ctx_in); | |
| if (copy_from_user(args->args, ctx_in, size_in)) | |
| goto out; | |
| /* args[0] is 0 means state argument of test_N will be NULL */ | |
| u_state = u64_to_user_ptr(args->args[0]); | |
| if (u_state && copy_from_user(&args->state, u_state, | |
| sizeof(args->state))) | |
| goto out; | |
| return args; | |
| out: | |
| kfree(args); | |
| return ERR_PTR(-EFAULT); | |
| } | |
| static int dummy_ops_copy_args(struct bpf_dummy_ops_test_args *args) | |
| { | |
| void __user *u_state; | |
| u_state = u64_to_user_ptr(args->args[0]); | |
| if (u_state && copy_to_user(u_state, &args->state, sizeof(args->state))) | |
| return -EFAULT; | |
| return 0; | |
| } | |
| static int dummy_ops_call_op(void *image, struct bpf_dummy_ops_test_args *args) | |
| { | |
| dummy_ops_test_ret_fn test = (void *)image + cfi_get_offset(); | |
| struct bpf_dummy_ops_state *state = NULL; | |
| /* state needs to be NULL if args[0] is 0 */ | |
| if (args->args[0]) | |
| state = &args->state; | |
| return test(state, args->args[1], args->args[2], | |
| args->args[3], args->args[4]); | |
| } | |
| static const struct bpf_ctx_arg_aux *find_ctx_arg_info(struct bpf_prog_aux *aux, int offset) | |
| { | |
| int i; | |
| for (i = 0; i < aux->ctx_arg_info_size; i++) | |
| if (aux->ctx_arg_info[i].offset == offset) | |
| return &aux->ctx_arg_info[i]; | |
| return NULL; | |
| } | |
| /* There is only one check at the moment: | |
| * - zero should not be passed for pointer parameters not marked as nullable. | |
| */ | |
| static int check_test_run_args(struct bpf_prog *prog, struct bpf_dummy_ops_test_args *args) | |
| { | |
| const struct btf_type *func_proto = prog->aux->attach_func_proto; | |
| for (u32 arg_no = 0; arg_no < btf_type_vlen(func_proto) ; ++arg_no) { | |
| const struct btf_param *param = &btf_params(func_proto)[arg_no]; | |
| const struct bpf_ctx_arg_aux *info; | |
| const struct btf_type *t; | |
| int offset; | |
| if (args->args[arg_no] != 0) | |
| continue; | |
| /* Program is validated already, so there is no need | |
| * to check if t is NULL. | |
| */ | |
| t = btf_type_skip_modifiers(bpf_dummy_ops_btf, param->type, NULL); | |
| if (!btf_type_is_ptr(t)) | |
| continue; | |
| offset = btf_ctx_arg_offset(bpf_dummy_ops_btf, func_proto, arg_no); | |
| info = find_ctx_arg_info(prog->aux, offset); | |
| if (info && type_may_be_null(info->reg_type)) | |
| continue; | |
| return -EINVAL; | |
| } | |
| return 0; | |
| } | |
| extern const struct bpf_link_ops bpf_struct_ops_link_lops; | |
| int bpf_struct_ops_test_run(struct bpf_prog *prog, const union bpf_attr *kattr, | |
| union bpf_attr __user *uattr) | |
| { | |
| const struct bpf_struct_ops *st_ops = &bpf_bpf_dummy_ops; | |
| const struct btf_type *func_proto; | |
| struct bpf_dummy_ops_test_args *args; | |
| struct bpf_tramp_nodes *tnodes = NULL; | |
| struct bpf_tramp_link *link = NULL; | |
| void *image = NULL; | |
| unsigned int op_idx; | |
| u32 image_off = 0; | |
| int prog_ret; | |
| s32 type_id; | |
| int err; | |
| type_id = btf_find_by_name_kind(bpf_dummy_ops_btf, | |
| bpf_bpf_dummy_ops.name, | |
| BTF_KIND_STRUCT); | |
| if (type_id < 0) | |
| return -EINVAL; | |
| if (prog->aux->attach_btf_id != type_id) | |
| return -EOPNOTSUPP; | |
| func_proto = prog->aux->attach_func_proto; | |
| args = dummy_ops_init_args(kattr, btf_type_vlen(func_proto)); | |
| if (IS_ERR(args)) | |
| return PTR_ERR(args); | |
| err = check_test_run_args(prog, args); | |
| if (err) | |
| goto out; | |
| tnodes = kzalloc_objs(*tnodes, BPF_TRAMP_MAX); | |
| if (!tnodes) { | |
| err = -ENOMEM; | |
| goto out; | |
| } | |
| link = kzalloc_obj(*link, GFP_USER); | |
| if (!link) { | |
| err = -ENOMEM; | |
| goto out; | |
| } | |
| /* prog doesn't take the ownership of the reference from caller */ | |
| bpf_prog_inc(prog); | |
| bpf_tramp_link_init(link, BPF_LINK_TYPE_STRUCT_OPS, &bpf_struct_ops_link_lops, | |
| prog, prog->expected_attach_type, 0); | |
| op_idx = prog->expected_attach_type; | |
| err = bpf_struct_ops_prepare_trampoline(tnodes, &link->node, | |
| &st_ops->func_models[op_idx], | |
| &dummy_ops_test_ret_function, | |
| &image, &image_off, | |
| true); | |
| if (err < 0) | |
| goto out; | |
| err = arch_protect_bpf_trampoline(image, PAGE_SIZE); | |
| if (err) | |
| goto out; | |
| prog_ret = dummy_ops_call_op(image, args); | |
| err = dummy_ops_copy_args(args); | |
| if (err) | |
| goto out; | |
| if (put_user(prog_ret, &uattr->test.retval)) | |
| err = -EFAULT; | |
| out: | |
| kfree(args); | |
| bpf_struct_ops_image_free(image); | |
| if (link) | |
| bpf_link_put(&link->link); | |
| kfree(tnodes); | |
| return err; | |
| } | |
| static int bpf_dummy_init(struct btf *btf) | |
| { | |
| bpf_dummy_ops_btf = btf; | |
| return 0; | |
| } | |
| static bool bpf_dummy_ops_is_valid_access(int off, int size, | |
| enum bpf_access_type type, | |
| const struct bpf_prog *prog, | |
| struct bpf_insn_access_aux *info) | |
| { | |
| return bpf_tracing_btf_ctx_access(off, size, type, prog, info); | |
| } | |
| static int bpf_dummy_ops_check_member(const struct btf_type *t, | |
| const struct btf_member *member, | |
| const struct bpf_prog *prog) | |
| { | |
| u32 moff = __btf_member_bit_offset(t, member) / 8; | |
| switch (moff) { | |
| case offsetof(struct bpf_dummy_ops, test_sleepable): | |
| break; | |
| default: | |
| if (prog->sleepable) | |
| return -EINVAL; | |
| } | |
| return 0; | |
| } | |
| static int bpf_dummy_ops_btf_struct_access(struct bpf_verifier_log *log, | |
| const struct bpf_reg_state *reg, | |
| int off, int size) | |
| { | |
| const struct btf_type *state; | |
| const struct btf_type *t; | |
| s32 type_id; | |
| type_id = btf_find_by_name_kind(reg->btf, "bpf_dummy_ops_state", | |
| BTF_KIND_STRUCT); | |
| if (type_id < 0) | |
| return -EINVAL; | |
| t = btf_type_by_id(reg->btf, reg->btf_id); | |
| state = btf_type_by_id(reg->btf, type_id); | |
| if (t != state) { | |
| bpf_log(log, "only access to bpf_dummy_ops_state is supported\n"); | |
| return -EACCES; | |
| } | |
| if (off + size > sizeof(struct bpf_dummy_ops_state)) { | |
| bpf_log(log, "write access at off %d with size %d\n", off, size); | |
| return -EACCES; | |
| } | |
| return NOT_INIT; | |
| } | |
| static const struct bpf_verifier_ops bpf_dummy_verifier_ops = { | |
| .is_valid_access = bpf_dummy_ops_is_valid_access, | |
| .btf_struct_access = bpf_dummy_ops_btf_struct_access, | |
| }; | |
| static int bpf_dummy_init_member(const struct btf_type *t, | |
| const struct btf_member *member, | |
| void *kdata, const void *udata) | |
| { | |
| return -EOPNOTSUPP; | |
| } | |
| static int bpf_dummy_reg(void *kdata, struct bpf_link *link) | |
| { | |
| return -EOPNOTSUPP; | |
| } | |
| static void bpf_dummy_unreg(void *kdata, struct bpf_link *link) | |
| { | |
| } | |
| static int bpf_dummy_ops__test_1(struct bpf_dummy_ops_state *cb__nullable) | |
| { | |
| return 0; | |
| } | |
| static int bpf_dummy_test_2(struct bpf_dummy_ops_state *cb, int a1, unsigned short a2, | |
| char a3, unsigned long a4) | |
| { | |
| return 0; | |
| } | |
| static int bpf_dummy_test_sleepable(struct bpf_dummy_ops_state *cb) | |
| { | |
| return 0; | |
| } | |
| static struct bpf_dummy_ops __bpf_bpf_dummy_ops = { | |
| .test_1 = bpf_dummy_ops__test_1, | |
| .test_2 = bpf_dummy_test_2, | |
| .test_sleepable = bpf_dummy_test_sleepable, | |
| }; | |
| static struct bpf_struct_ops bpf_bpf_dummy_ops = { | |
| .verifier_ops = &bpf_dummy_verifier_ops, | |
| .init = bpf_dummy_init, | |
| .check_member = bpf_dummy_ops_check_member, | |
| .init_member = bpf_dummy_init_member, | |
| .reg = bpf_dummy_reg, | |
| .unreg = bpf_dummy_unreg, | |
| .name = "bpf_dummy_ops", | |
| .cfi_stubs = &__bpf_bpf_dummy_ops, | |
| .owner = THIS_MODULE, | |
| }; | |
| static int __init bpf_dummy_struct_ops_init(void) | |
| { | |
| return register_bpf_struct_ops(&bpf_bpf_dummy_ops, bpf_dummy_ops); | |
| } | |
| late_initcall(bpf_dummy_struct_ops_init); |