diff mbox series

[v2,bpf-next,6/6] selftests/bpf: Cover BPF_F_REPLACE in test_cgroup_attach

Message ID bc55a274ea572d237bd091819f38502fa837abb5.1576193131.git.rdna@fb.com
State Changes Requested
Delegated to: BPF Maintainers
Headers show
Series bpf: Support replacing cgroup-bpf program in MULTI mode | expand

Commit Message

Andrey Ignatov Dec. 12, 2019, 11:30 p.m. UTC
Test replacement of a cgroup-bpf program attached with BPF_F_ALLOW_MULTI
and possible failure modes: invalid combination of flags, invalid
replace_bpf_fd, replacing a non-attachd to specified cgroup program.

Example of program replacing:

  # gdb -q ./test_cgroup_attach
  Reading symbols from /data/users/rdna/bin/test_cgroup_attach...done.
  ...
  Breakpoint 1, test_multiprog () at test_cgroup_attach.c:443
  443     test_cgroup_attach.c: No such file or directory.
  (gdb)
  [2]+  Stopped                 gdb -q ./test_cgroup_attach
  # bpftool c s /mnt/cgroup2/cgroup-test-work-dir/cg1
  ID       AttachType      AttachFlags     Name
  35       egress          multi
  36       egress          multi
  # fg gdb -q ./test_cgroup_attach
  c
  Continuing.
  Detaching after fork from child process 361.

  Breakpoint 2, test_multiprog () at test_cgroup_attach.c:454
  454     in test_cgroup_attach.c
  (gdb)
  [2]+  Stopped                 gdb -q ./test_cgroup_attach
  # bpftool c s /mnt/cgroup2/cgroup-test-work-dir/cg1
  ID       AttachType      AttachFlags     Name
  41       egress          multi
  36       egress          multi

Signed-off-by: Andrey Ignatov <rdna@fb.com>
---
 .../selftests/bpf/test_cgroup_attach.c        | 62 +++++++++++++++++--
 1 file changed, 57 insertions(+), 5 deletions(-)

Comments

Andrii Nakryiko Dec. 13, 2019, 7:01 a.m. UTC | #1
On Thu, Dec 12, 2019 at 3:34 PM Andrey Ignatov <rdna@fb.com> wrote:
>
> Test replacement of a cgroup-bpf program attached with BPF_F_ALLOW_MULTI
> and possible failure modes: invalid combination of flags, invalid
> replace_bpf_fd, replacing a non-attachd to specified cgroup program.
>
> Example of program replacing:
>
>   # gdb -q ./test_cgroup_attach
>   Reading symbols from /data/users/rdna/bin/test_cgroup_attach...done.
>   ...
>   Breakpoint 1, test_multiprog () at test_cgroup_attach.c:443
>   443     test_cgroup_attach.c: No such file or directory.
>   (gdb)
>   [2]+  Stopped                 gdb -q ./test_cgroup_attach
>   # bpftool c s /mnt/cgroup2/cgroup-test-work-dir/cg1
>   ID       AttachType      AttachFlags     Name
>   35       egress          multi
>   36       egress          multi
>   # fg gdb -q ./test_cgroup_attach
>   c
>   Continuing.
>   Detaching after fork from child process 361.
>
>   Breakpoint 2, test_multiprog () at test_cgroup_attach.c:454
>   454     in test_cgroup_attach.c
>   (gdb)
>   [2]+  Stopped                 gdb -q ./test_cgroup_attach
>   # bpftool c s /mnt/cgroup2/cgroup-test-work-dir/cg1
>   ID       AttachType      AttachFlags     Name
>   41       egress          multi
>   36       egress          multi
>
> Signed-off-by: Andrey Ignatov <rdna@fb.com>
> ---
>  .../selftests/bpf/test_cgroup_attach.c        | 62 +++++++++++++++++--
>  1 file changed, 57 insertions(+), 5 deletions(-)
>

I second Alexei's sentiment. Having this as part of test_progs would
certainly be better in terms of ensuring this doesn't accidentally
breaks.

[...]

>
> +       /* invalid input */
> +
> +       DECLARE_LIBBPF_OPTS(bpf_prog_attach_opts, attach_opts,
> +               .target_fd              = cg1,
> +               .prog_fd                = allow_prog[6],
> +               .replace_prog_fd        = allow_prog[0],
> +               .type                   = BPF_CGROUP_INET_EGRESS,
> +               .flags                  = BPF_F_ALLOW_MULTI | BPF_F_REPLACE,
> +       );

This might cause compiler warnings (depending on compiler settings, of
course). DECLARE_LIBBPF_OPTS does declare variable, so this is a
situation of mixing code and variable declarations, which under C89
(or whatever it's named, the older standard that kernel is trying to
stick to for the most part) is not allowed.

> +
> +       attach_opts.flags = BPF_F_ALLOW_OVERRIDE | BPF_F_REPLACE;
> +       if (!bpf_prog_attach_xattr(&attach_opts)) {
> +               log_err("Unexpected success with OVERRIDE | REPLACE");
> +               goto err;
> +       }
> +       assert(errno == EINVAL);
> +
Andrey Ignatov Dec. 18, 2019, 4:57 p.m. UTC | #2
Andrii Nakryiko <andrii.nakryiko@gmail.com> [Thu, 2019-12-12 23:01 -0800]:
> On Thu, Dec 12, 2019 at 3:34 PM Andrey Ignatov <rdna@fb.com> wrote:
> >
> > Test replacement of a cgroup-bpf program attached with BPF_F_ALLOW_MULTI
> > and possible failure modes: invalid combination of flags, invalid
> > replace_bpf_fd, replacing a non-attachd to specified cgroup program.
> >
> > Example of program replacing:
> >
> >   # gdb -q ./test_cgroup_attach
> >   Reading symbols from /data/users/rdna/bin/test_cgroup_attach...done.
> >   ...
> >   Breakpoint 1, test_multiprog () at test_cgroup_attach.c:443
> >   443     test_cgroup_attach.c: No such file or directory.
> >   (gdb)
> >   [2]+  Stopped                 gdb -q ./test_cgroup_attach
> >   # bpftool c s /mnt/cgroup2/cgroup-test-work-dir/cg1
> >   ID       AttachType      AttachFlags     Name
> >   35       egress          multi
> >   36       egress          multi
> >   # fg gdb -q ./test_cgroup_attach
> >   c
> >   Continuing.
> >   Detaching after fork from child process 361.
> >
> >   Breakpoint 2, test_multiprog () at test_cgroup_attach.c:454
> >   454     in test_cgroup_attach.c
> >   (gdb)
> >   [2]+  Stopped                 gdb -q ./test_cgroup_attach
> >   # bpftool c s /mnt/cgroup2/cgroup-test-work-dir/cg1
> >   ID       AttachType      AttachFlags     Name
> >   41       egress          multi
> >   36       egress          multi
> >
> > Signed-off-by: Andrey Ignatov <rdna@fb.com>
> > ---
> >  .../selftests/bpf/test_cgroup_attach.c        | 62 +++++++++++++++++--
> >  1 file changed, 57 insertions(+), 5 deletions(-)
> >
> 
> I second Alexei's sentiment. Having this as part of test_progs would
> certainly be better in terms of ensuring this doesn't accidentally
> breaks.

OK, I converted both existing test_cgroup_attach and my test for
BPF_F_REPLACE to test_progs and will send v3 with this change.


> [...]
> 
> >
> > +       /* invalid input */
> > +
> > +       DECLARE_LIBBPF_OPTS(bpf_prog_attach_opts, attach_opts,
> > +               .target_fd              = cg1,
> > +               .prog_fd                = allow_prog[6],
> > +               .replace_prog_fd        = allow_prog[0],
> > +               .type                   = BPF_CGROUP_INET_EGRESS,
> > +               .flags                  = BPF_F_ALLOW_MULTI | BPF_F_REPLACE,
> > +       );
> 
> This might cause compiler warnings (depending on compiler settings, of
> course). DECLARE_LIBBPF_OPTS does declare variable, so this is a
> situation of mixing code and variable declarations, which under C89
> (or whatever it's named, the older standard that kernel is trying to
> stick to for the most part) is not allowed.

Yeah, I know about such a warning and expected it but didn't get it with
the current setup (what surprised me btw) and decided to keep it.

The main reason I kept it is it's not actually clear how to separate
declaration and initialization of opts structure when
DECLARE_LIBBPF_OPTS is used since the macro does both things at once. In
selftests I can just switch to direct initialization (w/o the macro)
since libbpf and selftests are in sync, but for real use-cases there
should be smth else (e.g. INIT_LIBBPF_OPTS macro that does only
initialization of already declared struct).


> > +
> > +       attach_opts.flags = BPF_F_ALLOW_OVERRIDE | BPF_F_REPLACE;
> > +       if (!bpf_prog_attach_xattr(&attach_opts)) {
> > +               log_err("Unexpected success with OVERRIDE | REPLACE");
> > +               goto err;
> > +       }
> > +       assert(errno == EINVAL);
> > +
Andrii Nakryiko Dec. 18, 2019, 5:24 p.m. UTC | #3
On Wed, Dec 18, 2019 at 8:58 AM Andrey Ignatov <rdna@fb.com> wrote:
>
> Andrii Nakryiko <andrii.nakryiko@gmail.com> [Thu, 2019-12-12 23:01 -0800]:
> > On Thu, Dec 12, 2019 at 3:34 PM Andrey Ignatov <rdna@fb.com> wrote:
> > >
> > > Test replacement of a cgroup-bpf program attached with BPF_F_ALLOW_MULTI
> > > and possible failure modes: invalid combination of flags, invalid
> > > replace_bpf_fd, replacing a non-attachd to specified cgroup program.
> > >
> > > Example of program replacing:
> > >
> > >   # gdb -q ./test_cgroup_attach
> > >   Reading symbols from /data/users/rdna/bin/test_cgroup_attach...done.
> > >   ...
> > >   Breakpoint 1, test_multiprog () at test_cgroup_attach.c:443
> > >   443     test_cgroup_attach.c: No such file or directory.
> > >   (gdb)
> > >   [2]+  Stopped                 gdb -q ./test_cgroup_attach
> > >   # bpftool c s /mnt/cgroup2/cgroup-test-work-dir/cg1
> > >   ID       AttachType      AttachFlags     Name
> > >   35       egress          multi
> > >   36       egress          multi
> > >   # fg gdb -q ./test_cgroup_attach
> > >   c
> > >   Continuing.
> > >   Detaching after fork from child process 361.
> > >
> > >   Breakpoint 2, test_multiprog () at test_cgroup_attach.c:454
> > >   454     in test_cgroup_attach.c
> > >   (gdb)
> > >   [2]+  Stopped                 gdb -q ./test_cgroup_attach
> > >   # bpftool c s /mnt/cgroup2/cgroup-test-work-dir/cg1
> > >   ID       AttachType      AttachFlags     Name
> > >   41       egress          multi
> > >   36       egress          multi
> > >
> > > Signed-off-by: Andrey Ignatov <rdna@fb.com>
> > > ---
> > >  .../selftests/bpf/test_cgroup_attach.c        | 62 +++++++++++++++++--
> > >  1 file changed, 57 insertions(+), 5 deletions(-)
> > >
> >
> > I second Alexei's sentiment. Having this as part of test_progs would
> > certainly be better in terms of ensuring this doesn't accidentally
> > breaks.
>
> OK, I converted both existing test_cgroup_attach and my test for
> BPF_F_REPLACE to test_progs and will send v3 with this change.
>

Great, thanks!

>
> > [...]
> >
> > >
> > > +       /* invalid input */
> > > +
> > > +       DECLARE_LIBBPF_OPTS(bpf_prog_attach_opts, attach_opts,
> > > +               .target_fd              = cg1,
> > > +               .prog_fd                = allow_prog[6],
> > > +               .replace_prog_fd        = allow_prog[0],
> > > +               .type                   = BPF_CGROUP_INET_EGRESS,
> > > +               .flags                  = BPF_F_ALLOW_MULTI | BPF_F_REPLACE,
> > > +       );
> >
> > This might cause compiler warnings (depending on compiler settings, of
> > course). DECLARE_LIBBPF_OPTS does declare variable, so this is a
> > situation of mixing code and variable declarations, which under C89
> > (or whatever it's named, the older standard that kernel is trying to
> > stick to for the most part) is not allowed.
>
> Yeah, I know about such a warning and expected it but didn't get it with
> the current setup (what surprised me btw) and decided to keep it.

yeah, selftests compiler flags must not be as strict as kernel's, I guess?...

>
> The main reason I kept it is it's not actually clear how to separate
> declaration and initialization of opts structure when
> DECLARE_LIBBPF_OPTS is used since the macro does both things at once. In
> selftests I can just switch to direct initialization (w/o the macro)
> since libbpf and selftests are in sync, but for real use-cases there
> should be smth else (e.g. INIT_LIBBPF_OPTS macro that does only
> initialization of already declared struct).

For compiler, DECLARE_LIBBPF_OPTS is purely declaration, in the same
sense as struct declaration+initialization is still just declaration.
If you need to postpone some of the field initialization, then you can
do that by assinging that field explicitly:

DECLARE_LIBBPF_OPTS(bpf_prog_attach_opts, attach_opts,
    .target_fd = cgl,
);


... some code here ...
attach_opts.prog_fd = allow_prog[6];

It is just a struct, DECLARE_LIBBPF_OPTS just hides memset to 0 and
setting .sz correctly, nothing more.

>
>
> > > +
> > > +       attach_opts.flags = BPF_F_ALLOW_OVERRIDE | BPF_F_REPLACE;
> > > +       if (!bpf_prog_attach_xattr(&attach_opts)) {
> > > +               log_err("Unexpected success with OVERRIDE | REPLACE");
> > > +               goto err;
> > > +       }
> > > +       assert(errno == EINVAL);
> > > +
>
> --
> Andrey Ignatov
Andrey Ignatov Dec. 18, 2019, 5:37 p.m. UTC | #4
Andrii Nakryiko <andrii.nakryiko@gmail.com> [Wed, 2019-12-18 09:25 -0800]:
> On Wed, Dec 18, 2019 at 8:58 AM Andrey Ignatov <rdna@fb.com> wrote:
> >
> > Andrii Nakryiko <andrii.nakryiko@gmail.com> [Thu, 2019-12-12 23:01 -0800]:
> > > On Thu, Dec 12, 2019 at 3:34 PM Andrey Ignatov <rdna@fb.com> wrote:
> > > >
> > > > Test replacement of a cgroup-bpf program attached with BPF_F_ALLOW_MULTI
> > > > and possible failure modes: invalid combination of flags, invalid
> > > > replace_bpf_fd, replacing a non-attachd to specified cgroup program.
> > > >
> > > > Example of program replacing:
> > > >
> > > >   # gdb -q ./test_cgroup_attach
> > > >   Reading symbols from /data/users/rdna/bin/test_cgroup_attach...done.
> > > >   ...
> > > >   Breakpoint 1, test_multiprog () at test_cgroup_attach.c:443
> > > >   443     test_cgroup_attach.c: No such file or directory.
> > > >   (gdb)
> > > >   [2]+  Stopped                 gdb -q ./test_cgroup_attach
> > > >   # bpftool c s /mnt/cgroup2/cgroup-test-work-dir/cg1
> > > >   ID       AttachType      AttachFlags     Name
> > > >   35       egress          multi
> > > >   36       egress          multi
> > > >   # fg gdb -q ./test_cgroup_attach
> > > >   c
> > > >   Continuing.
> > > >   Detaching after fork from child process 361.
> > > >
> > > >   Breakpoint 2, test_multiprog () at test_cgroup_attach.c:454
> > > >   454     in test_cgroup_attach.c
> > > >   (gdb)
> > > >   [2]+  Stopped                 gdb -q ./test_cgroup_attach
> > > >   # bpftool c s /mnt/cgroup2/cgroup-test-work-dir/cg1
> > > >   ID       AttachType      AttachFlags     Name
> > > >   41       egress          multi
> > > >   36       egress          multi
> > > >
> > > > Signed-off-by: Andrey Ignatov <rdna@fb.com>
> > > > ---
> > > >  .../selftests/bpf/test_cgroup_attach.c        | 62 +++++++++++++++++--
> > > >  1 file changed, 57 insertions(+), 5 deletions(-)
> > > >
> > >
> > > I second Alexei's sentiment. Having this as part of test_progs would
> > > certainly be better in terms of ensuring this doesn't accidentally
> > > breaks.
> >
> > OK, I converted both existing test_cgroup_attach and my test for
> > BPF_F_REPLACE to test_progs and will send v3 with this change.
> >
> 
> Great, thanks!
> 
> >
> > > [...]
> > >
> > > >
> > > > +       /* invalid input */
> > > > +
> > > > +       DECLARE_LIBBPF_OPTS(bpf_prog_attach_opts, attach_opts,
> > > > +               .target_fd              = cg1,
> > > > +               .prog_fd                = allow_prog[6],
> > > > +               .replace_prog_fd        = allow_prog[0],
> > > > +               .type                   = BPF_CGROUP_INET_EGRESS,
> > > > +               .flags                  = BPF_F_ALLOW_MULTI | BPF_F_REPLACE,
> > > > +       );
> > >
> > > This might cause compiler warnings (depending on compiler settings, of
> > > course). DECLARE_LIBBPF_OPTS does declare variable, so this is a
> > > situation of mixing code and variable declarations, which under C89
> > > (or whatever it's named, the older standard that kernel is trying to
> > > stick to for the most part) is not allowed.
> >
> > Yeah, I know about such a warning and expected it but didn't get it with
> > the current setup (what surprised me btw) and decided to keep it.
> 
> yeah, selftests compiler flags must not be as strict as kernel's, I guess?...

That I don't know :) I thought they should be similar but apparently
it's not the case.

> > The main reason I kept it is it's not actually clear how to separate
> > declaration and initialization of opts structure when
> > DECLARE_LIBBPF_OPTS is used since the macro does both things at once. In
> > selftests I can just switch to direct initialization (w/o the macro)
> > since libbpf and selftests are in sync, but for real use-cases there
> > should be smth else (e.g. INIT_LIBBPF_OPTS macro that does only
> > initialization of already declared struct).
> 
> For compiler, DECLARE_LIBBPF_OPTS is purely declaration, in the same
> sense as struct declaration+initialization is still just declaration.
> If you need to postpone some of the field initialization, then you can
> do that by assinging that field explicitly:
> 
> DECLARE_LIBBPF_OPTS(bpf_prog_attach_opts, attach_opts,
>     .target_fd = cgl,
> );
> 
> 
> ... some code here ...
> attach_opts.prog_fd = allow_prog[6];
> 
> It is just a struct, DECLARE_LIBBPF_OPTS just hides memset to 0 and
> setting .sz correctly, nothing more.

Lol that makes sense of course. I'm not sure why I decided that all
fields should be specified in DECLARE_LIBBPF_OPTS() at once .. Thanks!

> > > > +
> > > > +       attach_opts.flags = BPF_F_ALLOW_OVERRIDE | BPF_F_REPLACE;
> > > > +       if (!bpf_prog_attach_xattr(&attach_opts)) {
> > > > +               log_err("Unexpected success with OVERRIDE | REPLACE");
> > > > +               goto err;
> > > > +       }
> > > > +       assert(errno == EINVAL);
> > > > +
> >
> > --
> > Andrey Ignatov
diff mbox series

Patch

diff --git a/tools/testing/selftests/bpf/test_cgroup_attach.c b/tools/testing/selftests/bpf/test_cgroup_attach.c
index 7671909ee1cb..6c7971ffe683 100644
--- a/tools/testing/selftests/bpf/test_cgroup_attach.c
+++ b/tools/testing/selftests/bpf/test_cgroup_attach.c
@@ -250,7 +250,7 @@  static int prog_load_cnt(int verdict, int val)
 		BPF_LD_MAP_FD(BPF_REG_1, map_fd),
 		BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),
 		BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2),
-		BPF_MOV64_IMM(BPF_REG_1, val), /* r1 = 1 */
+		BPF_MOV64_IMM(BPF_REG_1, val), /* r1 = val */
 		BPF_RAW_INSN(BPF_STX | BPF_XADD | BPF_DW, BPF_REG_0, BPF_REG_1, 0, 0), /* xadd r0 += r1 */
 
 		BPF_LD_MAP_FD(BPF_REG_1, cgroup_storage_fd),
@@ -290,11 +290,11 @@  static int test_multiprog(void)
 {
 	__u32 prog_ids[4], prog_cnt = 0, attach_flags, saved_prog_id;
 	int cg1 = 0, cg2 = 0, cg3 = 0, cg4 = 0, cg5 = 0, key = 0;
-	int drop_prog, allow_prog[6] = {}, rc = 0;
+	int drop_prog, allow_prog[7] = {}, rc = 0;
 	unsigned long long value;
 	int i = 0;
 
-	for (i = 0; i < 6; i++) {
+	for (i = 0; i < ARRAY_SIZE(allow_prog); i++) {
 		allow_prog[i] = prog_load_cnt(1, 1 << i);
 		if (!allow_prog[i])
 			goto err;
@@ -400,6 +400,58 @@  static int test_multiprog(void)
 	assert(bpf_map_lookup_elem(map_fd, &key, &value) == 0);
 	assert(value == 1 + 2 + 8 + 16);
 
+	/* invalid input */
+
+	DECLARE_LIBBPF_OPTS(bpf_prog_attach_opts, attach_opts,
+		.target_fd		= cg1,
+		.prog_fd		= allow_prog[6],
+		.replace_prog_fd	= allow_prog[0],
+		.type			= BPF_CGROUP_INET_EGRESS,
+		.flags			= BPF_F_ALLOW_MULTI | BPF_F_REPLACE,
+	);
+
+	attach_opts.flags = BPF_F_ALLOW_OVERRIDE | BPF_F_REPLACE;
+	if (!bpf_prog_attach_xattr(&attach_opts)) {
+		log_err("Unexpected success with OVERRIDE | REPLACE");
+		goto err;
+	}
+	assert(errno == EINVAL);
+
+	attach_opts.flags = BPF_F_REPLACE;
+	if (!bpf_prog_attach_xattr(&attach_opts)) {
+		log_err("Unexpected success with REPLACE alone");
+		goto err;
+	}
+	assert(errno == EINVAL);
+	attach_opts.flags = BPF_F_ALLOW_MULTI | BPF_F_REPLACE;
+
+	attach_opts.replace_prog_fd = -1;
+	if (!bpf_prog_attach_xattr(&attach_opts)) {
+		log_err("Unexpected success with bad replace fd");
+		goto err;
+	}
+	assert(errno == EBADF);
+
+	/* replacing a program that is not attached to cgroup should fail  */
+	attach_opts.replace_prog_fd = allow_prog[3];
+	if (!bpf_prog_attach_xattr(&attach_opts)) {
+		log_err("Unexpected success: replace not-attached prog on cg1");
+		goto err;
+	}
+	assert(errno == ENOENT);
+	attach_opts.replace_prog_fd = allow_prog[0];
+
+	/* replace 1st from the top program */
+	if (bpf_prog_attach_xattr(&attach_opts)) {
+		log_err("Replace prog1 with prog7 on cg1");
+		goto err;
+	}
+	value = 0;
+	assert(bpf_map_update_elem(map_fd, &key, &value, 0) == 0);
+	assert(system(PING_CMD) == 0);
+	assert(bpf_map_lookup_elem(map_fd, &key, &value) == 0);
+	assert(value == 64 + 2 + 8 + 16);
+
 	/* detach 3rd from bottom program and ping again */
 	errno = 0;
 	if (!bpf_prog_detach2(0, cg3, BPF_CGROUP_INET_EGRESS)) {
@@ -414,7 +466,7 @@  static int test_multiprog(void)
 	assert(bpf_map_update_elem(map_fd, &key, &value, 0) == 0);
 	assert(system(PING_CMD) == 0);
 	assert(bpf_map_lookup_elem(map_fd, &key, &value) == 0);
-	assert(value == 1 + 2 + 16);
+	assert(value == 64 + 2 + 16);
 
 	/* detach 2nd from bottom program and ping again */
 	if (bpf_prog_detach2(-1, cg4, BPF_CGROUP_INET_EGRESS)) {
@@ -425,7 +477,7 @@  static int test_multiprog(void)
 	assert(bpf_map_update_elem(map_fd, &key, &value, 0) == 0);
 	assert(system(PING_CMD) == 0);
 	assert(bpf_map_lookup_elem(map_fd, &key, &value) == 0);
-	assert(value == 1 + 2 + 4);
+	assert(value == 64 + 2 + 4);
 
 	prog_cnt = 4;
 	assert(bpf_prog_query(cg5, BPF_CGROUP_INET_EGRESS, BPF_F_QUERY_EFFECTIVE,