--- v1
+++ v4
@@ -1,138 +1,202 @@
-Now only program for bpf_dummy_ops::init() is supported. The following
-two cases are exercised in bpf_dummy_st_ops_test_run():
-
-(1) test and check the value returned from state arg in init(state)
-The content of state is copied from data_in before calling init() and
-copied back to data_out after calling, so test program could use
-data_in to pass the input state and use data_out to get the
-output state.
-
-(2) test and check the return value of init(NULL)
-data_in_size is set as 0, so the state will be NULL and there will be
-no copy-in & copy-out.
+Currently the test of BPF STRUCT_OPS depends on the specific bpf
+implementation of tcp_congestion_ops, but it can not cover all
+basic functionalities (e.g, return value handling), so introduce
+a dummy BPF STRUCT_OPS for test purpose.
+
+Loading a bpf_dummy_ops implementation from userspace is prohibited,
+and its only purpose is to run BPF_PROG_TYPE_STRUCT_OPS program
+through bpf(BPF_PROG_TEST_RUN). Now programs for test_1() & test_2()
+are supported. The following three cases are exercised in
+bpf_dummy_struct_ops_test_run():
+
+(1) test and check the value returned from state arg in test_1(state)
+The content of state is copied from userspace pointer and copied back
+after calling test_1(state). The user pointer is saved in an u64 array
+and the array address is passed through ctx_in.
+
+(2) test and check the return value of test_1(NULL)
+Just simulate the case in which an invalid input argument is passed in.
+
+(3) test multiple arguments passing in test_2(state, ...)
+5 arguments are passed through ctx_in in form of u64 array. The first
+element of array is userspace pointer of state and others 4 arguments
+follow.
Signed-off-by: Hou Tao <houtao1@huawei.com>
+Acked-by: Martin KaFai Lau <kafai@fb.com>
---
- include/linux/bpf_dummy_ops.h | 13 ++-
- net/bpf/bpf_dummy_struct_ops.c | 176 +++++++++++++++++++++++++++++++++
- 2 files changed, 188 insertions(+), 1 deletion(-)
-
-diff --git a/include/linux/bpf_dummy_ops.h b/include/linux/bpf_dummy_ops.h
-index a594ae830eba..5049484e6693 100644
---- a/include/linux/bpf_dummy_ops.h
-+++ b/include/linux/bpf_dummy_ops.h
-@@ -5,10 +5,21 @@
- #ifndef _BPF_DUMMY_OPS_H
- #define _BPF_DUMMY_OPS_H
-
--typedef int (*bpf_dummy_ops_init_t)(void);
-+#include <linux/bpf.h>
-+#include <linux/filter.h>
-+
+ include/linux/bpf.h | 16 +++
+ kernel/bpf/bpf_struct_ops.c | 3 +
+ kernel/bpf/bpf_struct_ops_types.h | 3 +
+ net/bpf/Makefile | 3 +
+ net/bpf/bpf_dummy_struct_ops.c | 200 ++++++++++++++++++++++++++++++
+ 5 files changed, 225 insertions(+)
+ create mode 100644 net/bpf/bpf_dummy_struct_ops.c
+
+diff --git a/include/linux/bpf.h b/include/linux/bpf.h
+index d986e2cc2498..51a85b6e987e 100644
+--- a/include/linux/bpf.h
++++ b/include/linux/bpf.h
+@@ -1017,6 +1017,22 @@ static inline void bpf_module_put(const void *data, struct module *owner)
+ else
+ module_put(owner);
+ }
++
++#ifdef CONFIG_NET
++/* Define it here to avoid the use of forward declaration */
+struct bpf_dummy_ops_state {
+ int val;
+};
+
-+typedef int (*bpf_dummy_ops_init_t)(struct bpf_dummy_ops_state *cb);
-
- struct bpf_dummy_ops {
- bpf_dummy_ops_init_t init;
++struct bpf_dummy_ops {
++ int (*test_1)(struct bpf_dummy_ops_state *cb);
++ int (*test_2)(struct bpf_dummy_ops_state *cb, int a1, unsigned short a2,
++ char a3, unsigned long a4);
++};
++
++int bpf_struct_ops_test_run(struct bpf_prog *prog, const union bpf_attr *kattr,
++ union bpf_attr __user *uattr);
++#endif
+ #else
+ static inline const struct bpf_struct_ops *bpf_struct_ops_find(u32 type_id)
+ {
+diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
+index 44be101f2562..8ecfe4752769 100644
+--- a/kernel/bpf/bpf_struct_ops.c
++++ b/kernel/bpf/bpf_struct_ops.c
+@@ -93,6 +93,9 @@ const struct bpf_verifier_ops bpf_struct_ops_verifier_ops = {
};
-+extern int bpf_dummy_st_ops_test_run(struct bpf_prog *prog,
-+ const union bpf_attr *kattr,
-+ union bpf_attr __user *uattr);
-+
- #endif
+ const struct bpf_prog_ops bpf_struct_ops_prog_ops = {
++#ifdef CONFIG_NET
++ .test_run = bpf_struct_ops_test_run,
++#endif
+ };
+
+ static const struct btf_type *module_type;
+diff --git a/kernel/bpf/bpf_struct_ops_types.h b/kernel/bpf/bpf_struct_ops_types.h
+index 066d83ea1c99..5678a9ddf817 100644
+--- a/kernel/bpf/bpf_struct_ops_types.h
++++ b/kernel/bpf/bpf_struct_ops_types.h
+@@ -2,6 +2,9 @@
+ /* internal file - do not include directly */
+
+ #ifdef CONFIG_BPF_JIT
++#ifdef CONFIG_NET
++BPF_STRUCT_OPS_TYPE(bpf_dummy_ops)
++#endif
+ #ifdef CONFIG_INET
+ #include <net/tcp.h>
+ BPF_STRUCT_OPS_TYPE(tcp_congestion_ops)
+diff --git a/net/bpf/Makefile b/net/bpf/Makefile
+index 1c0a98d8c28f..1ebe270bde23 100644
+--- a/net/bpf/Makefile
++++ b/net/bpf/Makefile
+@@ -1,2 +1,5 @@
+ # SPDX-License-Identifier: GPL-2.0-only
+ obj-$(CONFIG_BPF_SYSCALL) := test_run.o
++ifeq ($(CONFIG_BPF_JIT),y)
++obj-$(CONFIG_BPF_SYSCALL) += bpf_dummy_struct_ops.o
++endif
diff --git a/net/bpf/bpf_dummy_struct_ops.c b/net/bpf/bpf_dummy_struct_ops.c
-index 1249e4bb4ccb..da77736cd093 100644
---- a/net/bpf/bpf_dummy_struct_ops.c
+new file mode 100644
+index 000000000000..fbc896323bec
+--- /dev/null
+++ b/net/bpf/bpf_dummy_struct_ops.c
-@@ -10,12 +10,188 @@
-
- extern struct bpf_struct_ops bpf_bpf_dummy_ops;
-
-+static const struct btf_type *dummy_ops_state;
-+
-+static struct bpf_dummy_ops_state *
-+init_dummy_ops_state(const union bpf_attr *kattr)
+@@ -0,0 +1,200 @@
++// 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>
++
++extern 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, ...);
++
++struct bpf_dummy_ops_test_args {
++ u64 args[MAX_BPF_FUNC_ARGS];
++ struct bpf_dummy_ops_state state;
++};
++
++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_state *state;
-+ void __user *data_in;
-+
-+ size_in = kattr->test.data_size_in;
-+ if (!size_in)
-+ return NULL;
-+
-+ if (size_in != sizeof(*state))
++ 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);
+
-+ state = kzalloc(sizeof(*state), GFP_KERNEL);
-+ if (!state)
++ args = kzalloc(sizeof(*args), GFP_KERNEL);
++ if (!args)
+ return ERR_PTR(-ENOMEM);
+
-+ data_in = u64_to_user_ptr(kattr->test.data_in);
-+ if (copy_from_user(state, data_in, size_in)) {
-+ kfree(state);
-+ return ERR_PTR(-EFAULT);
-+ }
-+
-+ return state;
-+}
-+
-+static int copy_dummy_ops_state(struct bpf_dummy_ops_state *state,
-+ const union bpf_attr *kattr,
-+ union bpf_attr __user *uattr)
-+{
-+ int err = 0;
-+ void __user *data_out;
-+
-+ if (!state)
-+ return 0;
-+
-+ data_out = u64_to_user_ptr(kattr->test.data_out);
-+ if (copy_to_user(data_out, state, sizeof(*state))) {
-+ err = -EFAULT;
-+ goto out;
-+ }
-+ if (put_user(sizeof(*state), &uattr->test.data_size_out)) {
-+ err = -EFAULT;
-+ goto out;
-+ }
++ 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:
-+ return err;
-+}
-+
-+static inline void exit_dummy_ops_state(struct bpf_dummy_ops_state *state)
-+{
-+ kfree(state);
-+}
-+
-+int bpf_dummy_st_ops_test_run(struct bpf_prog *prog,
-+ const union bpf_attr *kattr,
-+ union bpf_attr __user *uattr)
++ 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;
++ 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]);
++}
++
++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;
-+ struct bpf_dummy_ops_state *state = NULL;
-+ struct bpf_tramp_progs *tprogs = NULL;
++ const struct btf_type *func_proto;
++ struct bpf_dummy_ops_test_args *args;
++ struct bpf_tramp_progs *tprogs;
+ void *image = NULL;
++ unsigned int op_idx;
++ int prog_ret;
+ int err;
-+ int prog_ret;
-+
-+ /* Now only support to call init(...) */
-+ if (prog->expected_attach_type != 0) {
-+ err = -EOPNOTSUPP;
-+ goto out;
-+ }
-+
-+ /* state will be NULL when data_size_in == 0 */
-+ state = init_dummy_ops_state(kattr);
-+ if (IS_ERR(state)) {
-+ err = PTR_ERR(state);
-+ state = NULL;
-+ goto out;
-+ }
++
++ if (prog->aux->attach_btf_id != st_ops->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);
+
+ tprogs = kcalloc(BPF_TRAMP_MAX, sizeof(*tprogs), GFP_KERNEL);
+ if (!tprogs) {
@@ -147,51 +211,40 @@
+ }
+ set_vm_flush_reset_perms(image);
+
-+ err = bpf_prepare_st_ops_prog(tprogs, prog, &st_ops->func_models[0],
-+ image, image + PAGE_SIZE);
++ op_idx = prog->expected_attach_type;
++ err = bpf_struct_ops_prepare_trampoline(tprogs, prog,
++ &st_ops->func_models[op_idx],
++ image, image + PAGE_SIZE);
+ if (err < 0)
+ goto out;
+
+ set_memory_ro((long)image, 1);
+ set_memory_x((long)image, 1);
-+ prog_ret = ((bpf_dummy_ops_init_t)image)(state);
-+
-+ err = copy_dummy_ops_state(state, kattr, uattr);
++ 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:
-+ exit_dummy_ops_state(state);
++ kfree(args);
+ bpf_jit_free_exec(image);
+ kfree(tprogs);
+ return err;
+}
+
- static int bpf_dummy_init(struct btf *btf)
- {
-+ s32 type_id;
-+
-+ type_id = btf_find_by_name_kind(btf, "bpf_dummy_ops_state",
-+ BTF_KIND_STRUCT);
-+ if (type_id < 0)
-+ return -EINVAL;
-+
-+ dummy_ops_state = btf_type_by_id(btf, type_id);
-+
- return 0;
- }
-
++static int bpf_dummy_init(struct 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)
+{
-+ /* init(state) only has one argument */
-+ if (off || type != BPF_READ)
-+ return false;
-+
-+ return btf_ctx_access(off, size, type, prog, info);
++ return bpf_tracing_btf_ctx_access(off, size, type, prog, info);
+}
+
+static int bpf_dummy_ops_btf_struct_access(struct bpf_verifier_log *log,
@@ -200,43 +253,57 @@
+ int size, enum bpf_access_type atype,
+ u32 *next_btf_id)
+{
-+ size_t end;
-+
-+ if (atype == BPF_READ)
-+ return btf_struct_access(log, btf, t, off, size, atype,
-+ next_btf_id);
-+
-+ if (t != dummy_ops_state) {
-+ bpf_log(log, "only read is supported\n");
++ const struct btf_type *state;
++ s32 type_id;
++ int err;
++
++ type_id = btf_find_by_name_kind(btf, "bpf_dummy_ops_state",
++ BTF_KIND_STRUCT);
++ if (type_id < 0)
++ return -EINVAL;
++
++ state = btf_type_by_id(btf, type_id);
++ if (t != state) {
++ bpf_log(log, "only access to bpf_dummy_ops_state is supported\n");
+ return -EACCES;
+ }
+
-+ switch (off) {
-+ case offsetof(struct bpf_dummy_ops_state, val):
-+ end = offsetofend(struct bpf_dummy_ops_state, val);
-+ break;
-+ default:
-+ bpf_log(log, "no write support to bpf_dummy_ops_state at off %d\n",
-+ off);
-+ return -EACCES;
-+ }
-+
-+ if (off + size > end) {
-+ bpf_log(log,
-+ "write access at off %d with size %d beyond the member of bpf_dummy_ops_state ended at %zu\n",
-+ off, size, end);
-+ return -EACCES;
-+ }
-+
-+ return NOT_INIT;
-+}
-+
- static const struct bpf_verifier_ops bpf_dummy_verifier_ops = {
++ err = btf_struct_access(log, btf, t, off, size, atype, next_btf_id);
++ if (err < 0)
++ return err;
++
++ return atype == BPF_READ ? err : 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,
++};
++
++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)
++{
++ return -EOPNOTSUPP;
++}
++
++static void bpf_dummy_unreg(void *kdata)
++{
++}
++
++struct bpf_struct_ops bpf_bpf_dummy_ops = {
++ .verifier_ops = &bpf_dummy_verifier_ops,
++ .init = bpf_dummy_init,
++ .init_member = bpf_dummy_init_member,
++ .reg = bpf_dummy_reg,
++ .unreg = bpf_dummy_unreg,
++ .name = "bpf_dummy_ops",
++};
--
2.29.2