Inter-revision diff: patch 3

Comparing v4 (message) to v1 (message)

--- v4
+++ v1
@@ -1,202 +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>
-Acked-by: Martin KaFai Lau <kafai@fb.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    | 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 */
+ 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..fbc896323bec
---- /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,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)
+@@ -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;
-+
-+	size_in = kattr->test.ctx_size_in;
-+	if (size_in != sizeof(u64) * nr)
++	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);
 +
-+	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) {
@@ -211,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,
@@ -253,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
 
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help