Hi,
Currently the test of BPF STRUCT_OPS depends on the specific bpf
implementation (e.g, 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.
Instead of loading a userspace-implemeted bpf_dummy_ops map into
kernel and calling the specific function by writing to sysfs provided
by bpf_testmode.ko, only loading bpf_dummy_ops related prog into
kernel and calling these prog by bpf_prog_test_run(). The latter
is more flexible and has no dependency on extra kernel module.
Now only the return value handling related test cases are supported,
if more is needed, we can add afterwards.
Comments are always welcome.
Regards,
Hou
Changelog:
* RFC: https://www.spinics.net/lists/bpf/msg46117.html
Hou Tao (5):
bpf: add dummy BPF STRUCT_OPS for test purpose
bpf: factor out a helper to prepare trampoline for struct_ops prog
bpf: do .test_run in dummy BPF STRUCT_OPS
bpf: hook .test_run for struct_ops program
selftests/bpf: test return value handling for struct_ops prog
include/linux/bpf.h | 5 +
include/linux/bpf_dummy_ops.h | 25 ++
kernel/bpf/bpf_struct_ops.c | 43 +++-
kernel/bpf/bpf_struct_ops_types.h | 2 +
net/bpf/Makefile | 3 +
net/bpf/bpf_dummy_struct_ops.c | 220 ++++++++++++++++++
.../selftests/bpf/prog_tests/dummy_st_ops.c | 81 +++++++
.../selftests/bpf/progs/dummy_st_ops.c | 33 +++
8 files changed, 403 insertions(+), 9 deletions(-)
create mode 100644 include/linux/bpf_dummy_ops.h
create mode 100644 net/bpf/bpf_dummy_struct_ops.c
create mode 100644 tools/testing/selftests/bpf/prog_tests/dummy_st_ops.c
create mode 100644 tools/testing/selftests/bpf/progs/dummy_st_ops.c
--
2.29.2
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).
Signed-off-by: Hou Tao <redacted>
---
include/linux/bpf_dummy_ops.h | 14 ++++++++++
kernel/bpf/bpf_struct_ops_types.h | 2 ++
net/bpf/Makefile | 3 +++
net/bpf/bpf_dummy_struct_ops.c | 44 +++++++++++++++++++++++++++++++
4 files changed, 63 insertions(+)
create mode 100644 include/linux/bpf_dummy_ops.h
create mode 100644 net/bpf/bpf_dummy_struct_ops.c
@@ -2,6 +2,8 @@/* internal file - do not include directly */#ifdef CONFIG_BPF_JIT+#include<linux/bpf_dummy_ops.h>+BPF_STRUCT_OPS_TYPE(bpf_dummy_ops)#ifdef CONFIG_INET#include<net/tcp.h>BPF_STRUCT_OPS_TYPE(tcp_congestion_ops)
Factor out a helper bpf_prepare_st_ops_prog() to prepare trampoline
for BPF_PROG_TYPE_STRUCT_OPS prog. It will be used by .test_run
callback in following patch.
Signed-off-by: Hou Tao <redacted>
---
include/linux/bpf.h | 5 +++++
kernel/bpf/bpf_struct_ops.c | 26 +++++++++++++++++---------
2 files changed, 22 insertions(+), 9 deletions(-)
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 <redacted>
---
include/linux/bpf_dummy_ops.h | 13 ++-
net/bpf/bpf_dummy_struct_ops.c | 176 +++++++++++++++++++++++++++++++++
2 files changed, 188 insertions(+), 1 deletion(-)
@@ -10,12 +10,188 @@externstructbpf_struct_opsbpf_bpf_dummy_ops;+staticconststructbtf_type*dummy_ops_state;++staticstructbpf_dummy_ops_state*+init_dummy_ops_state(constunionbpf_attr*kattr)+{+__u32size_in;+structbpf_dummy_ops_state*state;+void__user*data_in;++size_in=kattr->test.data_size_in;+if(!size_in)+returnNULL;++if(size_in!=sizeof(*state))+returnERR_PTR(-EINVAL);++state=kzalloc(sizeof(*state),GFP_KERNEL);+if(!state)+returnERR_PTR(-ENOMEM);++data_in=u64_to_user_ptr(kattr->test.data_in);+if(copy_from_user(state,data_in,size_in)){+kfree(state);+returnERR_PTR(-EFAULT);+}++returnstate;+}++staticintcopy_dummy_ops_state(structbpf_dummy_ops_state*state,+constunionbpf_attr*kattr,+unionbpf_attr__user*uattr)+{+interr=0;+void__user*data_out;++if(!state)+return0;++data_out=u64_to_user_ptr(kattr->test.data_out);+if(copy_to_user(data_out,state,sizeof(*state))){+err=-EFAULT;+gotoout;+}+if(put_user(sizeof(*state),&uattr->test.data_size_out)){+err=-EFAULT;+gotoout;+}+out:+returnerr;+}++staticinlinevoidexit_dummy_ops_state(structbpf_dummy_ops_state*state)+{+kfree(state);+}++intbpf_dummy_st_ops_test_run(structbpf_prog*prog,+constunionbpf_attr*kattr,+unionbpf_attr__user*uattr)+{+conststructbpf_struct_ops*st_ops=&bpf_bpf_dummy_ops;+structbpf_dummy_ops_state*state=NULL;+structbpf_tramp_progs*tprogs=NULL;+void*image=NULL;+interr;+intprog_ret;++/* Now only support to call init(...) */+if(prog->expected_attach_type!=0){+err=-EOPNOTSUPP;+gotoout;+}++/* 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;+gotoout;+}++tprogs=kcalloc(BPF_TRAMP_MAX,sizeof(*tprogs),GFP_KERNEL);+if(!tprogs){+err=-ENOMEM;+gotoout;+}++image=bpf_jit_alloc_exec(PAGE_SIZE);+if(!image){+err=-ENOMEM;+gotoout;+}+set_vm_flush_reset_perms(image);++err=bpf_prepare_st_ops_prog(tprogs,prog,&st_ops->func_models[0],+image,image+PAGE_SIZE);+if(err<0)+gotoout;++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);+if(err)+gotoout;+if(put_user(prog_ret,&uattr->test.retval))+err=-EFAULT;+out:+exit_dummy_ops_state(state);+bpf_jit_free_exec(image);+kfree(tprogs);+returnerr;+}+staticintbpf_dummy_init(structbtf*btf){+s32type_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);+return0;}+staticboolbpf_dummy_ops_is_valid_access(intoff,intsize,+enumbpf_access_typetype,+conststructbpf_prog*prog,+structbpf_insn_access_aux*info)+{+/* init(state) only has one argument */+if(off||type!=BPF_READ)+returnfalse;++returnbtf_ctx_access(off,size,type,prog,info);+}++staticintbpf_dummy_ops_btf_struct_access(structbpf_verifier_log*log,+conststructbtf*btf,+conststructbtf_type*t,intoff,+intsize,enumbpf_access_typeatype,+u32*next_btf_id)+{+size_tend;++if(atype==BPF_READ)+returnbtf_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;+}++switch(off){+caseoffsetof(structbpf_dummy_ops_state,val):+end=offsetofend(structbpf_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;+}++returnNOT_INIT;+}+staticconststructbpf_verifier_opsbpf_dummy_verifier_ops={+.is_valid_access=bpf_dummy_ops_is_valid_access,+.btf_struct_access=bpf_dummy_ops_btf_struct_access,};staticintbpf_dummy_init_member(conststructbtf_type*t,
Running a BPF_PROG_TYPE_STRUCT_OPS prog for dummy_st_ops::init()
through bpf_prog_test_run(). Three test cases are added:
(1) attach dummy_st_ops should fail
(2) function return value of bpf_dummy_ops::init() is expected
(3) pointer argument of bpf_dummy_ops::init() works as expected
Signed-off-by: Hou Tao <redacted>
---
.../selftests/bpf/prog_tests/dummy_st_ops.c | 81 +++++++++++++++++++
.../selftests/bpf/progs/dummy_st_ops.c | 33 ++++++++
2 files changed, 114 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/dummy_st_ops.c
create mode 100644 tools/testing/selftests/bpf/progs/dummy_st_ops.c
bpf_struct_ops_test_run() will be used to run struct_ops program
from bpf_dummy_ops and now its main purpose is to test the handling
of return value.
Signed-off-by: Hou Tao <redacted>
---
kernel/bpf/bpf_struct_ops.c | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
On Mon, Sep 27, 2021 at 7:38 PM Hou Tao [off-list ref] wrote:
quoted hunk
Running a BPF_PROG_TYPE_STRUCT_OPS prog for dummy_st_ops::init()
through bpf_prog_test_run(). Three test cases are added:
(1) attach dummy_st_ops should fail
(2) function return value of bpf_dummy_ops::init() is expected
(3) pointer argument of bpf_dummy_ops::init() works as expected
Signed-off-by: Hou Tao <redacted>
---
.../selftests/bpf/prog_tests/dummy_st_ops.c | 81 +++++++++++++++++++
.../selftests/bpf/progs/dummy_st_ops.c | 33 ++++++++
2 files changed, 114 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/dummy_st_ops.c
create mode 100644 tools/testing/selftests/bpf/progs/dummy_st_ops.c
From: Martin KaFai Lau <hidden> Date: 2021-09-29 17:56:49
On Tue, Sep 28, 2021 at 10:52:25AM +0800, Hou Tao wrote:
Factor out a helper bpf_prepare_st_ops_prog() to prepare trampoline
for BPF_PROG_TYPE_STRUCT_OPS prog. It will be used by .test_run
callback in following patch.
Thanks for the patches.
This preparation change should be the first patch instead.
The existing struct_ops functions in the kernel now have naming like
bpf_struct_ops_.*(). How about renaming it to
bpf_struct_ops_prepare_trampoline()?
This change can't apply to bpf-next now because
commit 356ed64991c6 ("bpf: Handle return value of BPF_PROG_TYPE_STRUCT_OPS prog")
is not pulled into bpf-next yet. Please mention the dependency
in the cover letter if it is still the case in v2.
From: Martin KaFai Lau <hidden> Date: 2021-09-29 18:56:05
On Tue, Sep 28, 2021 at 10:52:26AM +0800, Hou Tao wrote:
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.
Patch 1 and patch 3 in this set should be combined.
The changes here seem not worth a new header file.
Let see if they can be simplified and move the only needed things to bpf.h.
quoted hunk
@@ -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;+};
This struct can be moved to net/bpf/bpf_dummy_struct_ops.c.
+
+typedef int (*bpf_dummy_ops_init_t)(struct bpf_dummy_ops_state *cb);
If I read it correctly, the typedef is only useful in casting later.
It would need another typedef in the future if new test function is added.
Lets try to remove it (more on this later).
struct bpf_dummy_ops {
bpf_dummy_ops_init_t init;
"init" is a little confusing since it is not doing initialization.
It is for testing purpose. How about renaming it to test1, test2, test3...:
int (*test1)(struct bpf_dummy_ops_state *cb);
Also, it should at least add another function to test more
arguments which is another limitation of testing with
tcp_congestion_ops.
};
The whole struct bpf_dummy_ops can be moved to include/linux/bpf.h also
next to where other bpf_struct_ops_*() functions are residing.
+extern int bpf_dummy_st_ops_test_run(struct bpf_prog *prog,
+ const union bpf_attr *kattr,
+ union bpf_attr __user *uattr);
Same here. It can be moved to include/linux/bpf.h and remove the
"extern" also.
These are the args for the test functions? Using ctx_in/ctx_size_in
and ctx_out/ctx_size_out instead should be more consistent
with other bpf_prog_test_run* in test_run.c.
+ if (!size_in)
+ return NULL;
+
+ if (size_in != sizeof(*state))
+ return ERR_PTR(-EINVAL);
+
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (!state)
+ 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;
static is good enough. no need to inline. Allow the compiler to decide.
+{
+ kfree(state);
Probably just remove this helper function and directly call kfree instead.
Could you help to check if bpf_ctx_init and bpf_ctx_finish can be directly
reused instead? I haven't looked at them closely to compare yet.
+}
+
+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;
+ struct bpf_tramp_progs *tprogs = NULL;
+ void *image = NULL;
+ 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;
+ }
+
+ tprogs = kcalloc(BPF_TRAMP_MAX, sizeof(*tprogs), GFP_KERNEL);
+ if (!tprogs) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ image = bpf_jit_alloc_exec(PAGE_SIZE);
+ if (!image) {
+ err = -ENOMEM;
+ goto out;
+ }
+ set_vm_flush_reset_perms(image);
+
+ 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 = ((bpf_dummy_ops_init_t)image)(state);
I would do something like this to avoid creating the
bpf_dummy_ops_init_t typedef.
struct bpf_dummy_ops ops;
ops.init = (void *)image;
prog_ret = ops.init(state);
The idea is to only allow writing to dummy_ops_state?
How about something like this (uncompiled code):
int ret;
if (atype != BPF_READ && t != dummy_ops_state)
return -EACCES;
ret = btf_struct_access(log, btf, t, off, size, atype,
next_btf_id);
if (ret < 0)
return ret;
return atype == BPF_READ ? ret : NOT_INIT;
Then the following switch and offset logic can go away.
+
+ 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,
--
2.29.2
On Tue, Sep 28, 2021 at 10:52:25AM +0800, Hou Tao wrote:
quoted
Factor out a helper bpf_prepare_st_ops_prog() to prepare trampoline
for BPF_PROG_TYPE_STRUCT_OPS prog. It will be used by .test_run
callback in following patch.
Thanks for the patches.
Thanks for you review.
This preparation change should be the first patch instead.
The existing struct_ops functions in the kernel now have naming like
bpf_struct_ops_.*(). How about renaming it to
bpf_struct_ops_prepare_trampoline()?
bpf_struct_ops_prepare_trampoline() may be a little long, and it will make
the indentations of its parameters look ugly, so how about
bpf_struct_ops_prep_prog() ?
This change can't apply to bpf-next now because
commit 356ed64991c6 ("bpf: Handle return value of BPF_PROG_TYPE_STRUCT_OPS prog")
is not pulled into bpf-next yet. Please mention the dependency
in the cover letter if it is still the case in v2.
On Tue, Sep 28, 2021 at 10:52:26AM +0800, Hou Tao wrote:
quoted
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.
Patch 1 and patch 3 in this set should be combined.
Will do. The purpose of splitting into two patches is that if only the return
value test is needed, patch 3 can be dropped. But now we will add more
tests, so i think combine two patches into one is OK.
The changes here seem not worth a new header file.
Let see if they can be simplified and move the only needed things to bpf.h.
quoted
@@ -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;+};
This struct can be moved to net/bpf/bpf_dummy_struct_ops.c.
quoted
+
+typedef int (*bpf_dummy_ops_init_t)(struct bpf_dummy_ops_state *cb);
If I read it correctly, the typedef is only useful in casting later.
It would need another typedef in the future if new test function is added.
Lets try to remove it (more on this later).
quoted
struct bpf_dummy_ops {
bpf_dummy_ops_init_t init;
"init" is a little confusing since it is not doing initialization.
It is for testing purpose. How about renaming it to test1, test2, test3...:
int (*test1)(struct bpf_dummy_ops_state *cb);
Also, it should at least add another function to test more
arguments which is another limitation of testing with
tcp_congestion_ops.
quoted
};
The whole struct bpf_dummy_ops can be moved to include/linux/bpf.h also
next to where other bpf_struct_ops_*() functions are residing.
Will do. Thanks for your suggestions.
quoted
+extern int bpf_dummy_st_ops_test_run(struct bpf_prog *prog,
+ const union bpf_attr *kattr,
+ union bpf_attr __user *uattr);
Same here. It can be moved to include/linux/bpf.h and remove the
"extern" also.
These are the args for the test functions? Using ctx_in/ctx_size_in
and ctx_out/ctx_size_out instead should be more consistent
with other bpf_prog_test_run* in test_run.c.
Yes, there are args. I had think about using ctx_in/ctx_out, but I didn't
because I thought the program which using ctx_in/ctx_out only has
one argument (namely bpf_context *), but the bpf_dummy_ops::init
may have multiple arguments. Anyway I will check it again and use
ctx_in/ctx_out if possible.
quoted
+ if (!size_in)
+ return NULL;
+
+ if (size_in != sizeof(*state))
+ return ERR_PTR(-EINVAL);
+
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (!state)
+ 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;
static is good enough. no need to inline. Allow the compiler to decide.
quoted
+{
+ kfree(state);
Probably just remove this helper function and directly call kfree instead.
Could you help to check if bpf_ctx_init and bpf_ctx_finish can be directly
reused instead? I haven't looked at them closely to compare yet.
Will do.
quoted
+}
+
+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;
+ struct bpf_tramp_progs *tprogs = NULL;
+ void *image = NULL;
+ 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;
+ }
+
+ tprogs = kcalloc(BPF_TRAMP_MAX, sizeof(*tprogs), GFP_KERNEL);
+ if (!tprogs) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ image = bpf_jit_alloc_exec(PAGE_SIZE);
+ if (!image) {
+ err = -ENOMEM;
+ goto out;
+ }
+ set_vm_flush_reset_perms(image);
+
+ 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 = ((bpf_dummy_ops_init_t)image)(state);
I would do something like this to avoid creating the
bpf_dummy_ops_init_t typedef.
struct bpf_dummy_ops ops;
ops.init = (void *)image;
prog_ret = ops.init(state);
The idea is to only allow writing to dummy_ops_state?
How about something like this (uncompiled code):
int ret;
if (atype != BPF_READ && t != dummy_ops_state)
return -EACCES;
ret = btf_struct_access(log, btf, t, off, size, atype,
next_btf_id);
if (ret < 0)
return ret;
return atype == BPF_READ ? ret : NOT_INIT;
Then the following switch and offset logic can go away.
Good idea. Will do that in v2.
quoted
+
+ 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,
--
2.29.2
On Mon, Sep 27, 2021 at 7:38 PM Hou Tao [off-list ref] wrote:
quoted
Running a BPF_PROG_TYPE_STRUCT_OPS prog for dummy_st_ops::init()
through bpf_prog_test_run(). Three test cases are added:
(1) attach dummy_st_ops should fail
(2) function return value of bpf_dummy_ops::init() is expected
(3) pointer argument of bpf_dummy_ops::init() works as expected
Signed-off-by: Hou Tao <redacted>
---
.../selftests/bpf/prog_tests/dummy_st_ops.c | 81 +++++++++++++++++++
.../selftests/bpf/progs/dummy_st_ops.c | 33 ++++++++
2 files changed, 114 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/dummy_st_ops.c
create mode 100644 tools/testing/selftests/bpf/progs/dummy_st_ops.c
The existing struct_ops functions in the kernel now have naming like
bpf_struct_ops_.*(). How about renaming it to
bpf_struct_ops_prepare_trampoline()?
bpf_struct_ops_prepare_trampoline() may be a little long, and it will make
the indentations of its parameters look ugly, so how about
bpf_struct_ops_prep_prog() ?
hmm... naming is hard...
but it is preparing the trampoline instead of preparing the
prog, and most other bpf funcs are using 'prepare' instead of 'prep'.
My preference is a better naming on what the func does and a
consistent naming with others. The indentation looks fine also.
It is not too bad ;)
bpf_struct_ops_prepare_prog()
arch_prepare_bpf_trampoline()
bpf_struct_ops_prepare_trampoline()
The params indentation looks fine and within 80 cols:
int bpf_struct_ops_prepare_trampoline(struct bpf_tramp_progs *tprogs,
struct bpf_prog *prog,
const struct btf_func_model *model,
void *image, void *image_end0
{
}
These are the args for the test functions? Using ctx_in/ctx_size_in
and ctx_out/ctx_size_out instead should be more consistent
with other bpf_prog_test_run* in test_run.c.
Yes, there are args. I had think about using ctx_in/ctx_out, but I didn't
because I thought the program which using ctx_in/ctx_out only has
one argument (namely bpf_context *), but the bpf_dummy_ops::init
may have multiple arguments. Anyway I will check it again and use
ctx_in/ctx_out if possible.
got it.
ctx_in could have multiple args.
I was more thinking on the muliple arg test also. Potentially some of them
are just integers, e.g.
int test2(struct bpf_dummy_ops_state *state, char a, short b, int c, long d)
{
}
All args can be put in ctx_in like bpf_prog_test_run_raw_tp().
Take a look at raw_tp_test_run.c. Although it is not strictly
necessary to use u64 for all args in the struct_ops test
because the struct_ops test still wants to prepare the
trampoline to catch the return value issue...etc, passing
an array of u64 args in ctx_in should make it easier to program
the userspace and optimizing the ctx_in based on the sizeof each
arg seems not gaining much as a test also.
For "struct bpf_dummy_ops_state *state", instead of making an
exception to pass ptr arg in data_in, the user ptr can be directly
passed as a u64 stored in ctx_in also, then there is no need to use
data_in or data_size_in. If it is needed, the userspace's
sizeof(struct bpf_dummy_ops_state) can be found from the
prog->aux->btf.
There is no need to use data_out/data_out_size also, just directly
copy it back to the same user ptr stored in ctx_in.