Inter-revision diff: patch 3

Comparing v1 (message) to v4 (message)

--- 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
 
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help