--- v3
+++ v1
@@ -1,204 +1,138 @@
-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.
+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.
Signed-off-by: Hou Tao <houtao1@huawei.com>
---
- 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 | 203 ++++++++++++++++++++++++++++++
- 5 files changed, 228 insertions(+)
- create mode 100644 net/bpf/bpf_dummy_struct_ops.c
-
-diff --git a/include/linux/bpf.h b/include/linux/bpf.h
-index 0dd2de9eeed3..1297af15ec1c 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 */
+ 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>
++
+struct bpf_dummy_ops_state {
+ int val;
+};
+
-+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 = {
++typedef int (*bpf_dummy_ops_init_t)(struct bpf_dummy_ops_state *cb);
+
+ struct bpf_dummy_ops {
+ bpf_dummy_ops_init_t init;
};
- 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
++extern int bpf_dummy_st_ops_test_run(struct bpf_prog *prog,
++ const union bpf_attr *kattr,
++ union bpf_attr __user *uattr);
++
+ #endif
diff --git a/net/bpf/bpf_dummy_struct_ops.c b/net/bpf/bpf_dummy_struct_ops.c
-new file mode 100644
-index 000000000000..ac27836e55df
---- /dev/null
+index 1249e4bb4ccb..da77736cd093 100644
+--- a/net/bpf/bpf_dummy_struct_ops.c
+++ b/net/bpf/bpf_dummy_struct_ops.c
-@@ -0,0 +1,203 @@
-+// 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)
+@@ -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)
+{
+ __u32 size_in;
-+ struct bpf_dummy_ops_test_args *args;
-+ void __user *ctx_in;
-+ void __user *u_state;
-+
-+ if (!nr || nr > MAX_BPF_FUNC_ARGS)
++ 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))
+ return ERR_PTR(-EINVAL);
+
-+ size_in = kattr->test.ctx_size_in;
-+ if (size_in != sizeof(u64) * nr)
-+ return ERR_PTR(-EINVAL);
-+
-+ args = kzalloc(sizeof(*args), GFP_KERNEL);
-+ if (!args)
++ state = kzalloc(sizeof(*state), GFP_KERNEL);
++ if (!state)
+ 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;
++ 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;
++ }
+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;
++ 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)
++{
++ const struct bpf_struct_ops *st_ops = &bpf_bpf_dummy_ops;
+ 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;
-+ const struct btf_type *func_proto;
-+ struct bpf_dummy_ops_test_args *args;
-+ struct bpf_tramp_progs *tprogs;
++ struct bpf_tramp_progs *tprogs = NULL;
+ void *image = NULL;
-+ unsigned int op_idx;
++ int err;
+ int prog_ret;
-+ int err;
-+
-+ 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);
++
++ /* 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;
++ }
+
+ tprogs = kcalloc(BPF_TRAMP_MAX, sizeof(*tprogs), GFP_KERNEL);
+ if (!tprogs) {
@@ -213,40 +147,51 @@
+ }
+ set_vm_flush_reset_perms(image);
+
-+ op_idx = prog->expected_attach_type;
-+ err = bpf_struct_ops_prepare_trampoline(tprogs, prog,
-+ &st_ops->func_models[op_idx],
-+ image, image + PAGE_SIZE);
++ err = bpf_prepare_st_ops_prog(tprogs, prog, &st_ops->func_models[0],
++ image, image + PAGE_SIZE);
+ if (err < 0)
+ goto out;
+
+ set_memory_ro((long)image, 1);
+ set_memory_x((long)image, 1);
-+ prog_ret = dummy_ops_call_op(image, args);
-+
-+ err = dummy_ops_copy_args(args);
++ prog_ret = ((bpf_dummy_ops_init_t)image)(state);
++
++ err = copy_dummy_ops_state(state, kattr, uattr);
+ if (err)
+ goto out;
+ if (put_user(prog_ret, &uattr->test.retval))
+ err = -EFAULT;
+out:
-+ kfree(args);
++ exit_dummy_ops_state(state);
+ bpf_jit_free_exec(image);
+ kfree(tprogs);
+ return err;
+}
+
-+static int bpf_dummy_init(struct btf *btf)
-+{
-+ return 0;
-+}
-+
+ 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 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);
++ /* init(state) only has one argument */
++ if (off || type != BPF_READ)
++ return false;
++
++ return btf_ctx_access(off, size, type, prog, info);
+}
+
+static int bpf_dummy_ops_btf_struct_access(struct bpf_verifier_log *log,
@@ -255,57 +200,43 @@
+ int size, enum bpf_access_type atype,
+ u32 *next_btf_id)
+{
-+ 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");
++ 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");
+ return -EACCES;
+ }
+
-+ 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 = {
++ 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 = {
+ .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)
-+{
-+ 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",
-+};
+ };
+
+ static int bpf_dummy_init_member(const struct btf_type *t,
--
2.29.2