Message ID | 20221107074222.1323017-4-houtao@huaweicloud.com (mailing list archive) |
---|---|
State | Changes Requested |
Delegated to: | BPF |
Headers | show |
Series | Pin the start cgroup for cgroup iterator | expand |
On 11/6/22 11:42 PM, Hou Tao wrote: > From: Hou Tao <houtao1@huawei.com> > > The test closes both iterator link fd and cgroup fd, and removes the > cgroup file to make a dead cgroup before reading cgroup iterator fd. It > also uses kern_sync_rcu() and usleep() to wait for the release of > start cgroup. If the start cgroup is not pinned by cgroup iterator, > reading iterator fd will trigger use-after-free. > > Signed-off-by: Hou Tao <houtao1@huawei.com> LGTM with a few nits below. Acked-by: Yonghong Song <yhs@fb.com> > --- > .../selftests/bpf/prog_tests/cgroup_iter.c | 78 +++++++++++++++++++ > 1 file changed, 78 insertions(+) > > diff --git a/tools/testing/selftests/bpf/prog_tests/cgroup_iter.c b/tools/testing/selftests/bpf/prog_tests/cgroup_iter.c > index c4a2adb38da1..d64ed1cf1554 100644 > --- a/tools/testing/selftests/bpf/prog_tests/cgroup_iter.c > +++ b/tools/testing/selftests/bpf/prog_tests/cgroup_iter.c > @@ -189,6 +189,82 @@ static void test_walk_self_only(struct cgroup_iter *skel) > BPF_CGROUP_ITER_SELF_ONLY, "self_only"); > } > > +static void test_walk_dead_self_only(struct cgroup_iter *skel) > +{ > + DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts); > + char expected_output[128], buf[128]; > + const char *cgrp_name = "/dead"; > + union bpf_iter_link_info linfo; > + int len, cgrp_fd, iter_fd; > + struct bpf_link *link; > + size_t left; > + char *p; > + > + cgrp_fd = create_and_get_cgroup(cgrp_name); > + if (!ASSERT_GE(cgrp_fd, 0, "create cgrp")) > + return; > + > + /* The cgroup is already dead during iteration, so it only has epilogue > + * in the output. > + */ Let us reword the comment like The cgroup will be dead during read() iteration, and it only has epilogue in the output. > + snprintf(expected_output, sizeof(expected_output), EPILOGUE); > + > + memset(&linfo, 0, sizeof(linfo)); > + linfo.cgroup.cgroup_fd = cgrp_fd; > + linfo.cgroup.order = BPF_CGROUP_ITER_SELF_ONLY; > + opts.link_info = &linfo; > + opts.link_info_len = sizeof(linfo); > + > + link = bpf_program__attach_iter(skel->progs.cgroup_id_printer, &opts); > + if (!ASSERT_OK_PTR(link, "attach_iter")) > + goto close_cg; > + > + iter_fd = bpf_iter_create(bpf_link__fd(link)); > + if (!ASSERT_GE(iter_fd, 0, "iter_create")) > + goto free_link; > + > + /* Close link fd and cgroup fd */ > + bpf_link__destroy(link); > + link = NULL; > + close(cgrp_fd); > + cgrp_fd = -1; We can remove 'link = NULL' and 'cgroup_fd = -1' and add 'return' after 'close(iter_fd)', which seems better to understand the code. > + > + /* Remove cgroup to mark it as dead */ > + remove_cgroup(cgrp_name); > + > + /* Two kern_sync_rcu() and usleep() pair are used to wait for the > + * releases of cgroup css, and the last kern_sync_rcu() and usleep() > + * pair is used to wait for the free of cgroup itself. > + */ > + kern_sync_rcu(); > + usleep(8000); > + kern_sync_rcu(); > + usleep(8000); > + kern_sync_rcu(); > + usleep(1000); > + > + memset(buf, 0, sizeof(buf)); > + left = ARRAY_SIZE(buf); > + p = buf; > + while ((len = read(iter_fd, p, left)) > 0) { > + p += len; > + left -= len; > + } > + > + ASSERT_STREQ(buf, expected_output, "dead cgroup output"); > + > + /* read() after iter finishes should be ok. */ > + if (len == 0) > + ASSERT_OK(read(iter_fd, buf, sizeof(buf)), "second_read"); > + > + close(iter_fd); > +free_link: > + bpf_link__destroy(link); > +close_cg: > + if (cgrp_fd >= 0) > + close(cgrp_fd); > +} > + > void test_cgroup_iter(void) > { > struct cgroup_iter *skel = NULL; > @@ -217,6 +293,8 @@ void test_cgroup_iter(void) > test_early_termination(skel); > if (test__start_subtest("cgroup_iter__self_only")) > test_walk_self_only(skel); > + if (test__start_subtest("cgroup_iter_dead_self_only")) Let us follow the convention in this file with cgroup_iter__dead_self_only > + test_walk_dead_self_only(skel); > out: > cgroup_iter__destroy(skel); > cleanup_cgroups();
Hi, On 11/8/2022 6:44 AM, Yonghong Song wrote: > > > On 11/6/22 11:42 PM, Hou Tao wrote: >> From: Hou Tao <houtao1@huawei.com> >> >> The test closes both iterator link fd and cgroup fd, and removes the >> cgroup file to make a dead cgroup before reading cgroup iterator fd. It >> also uses kern_sync_rcu() and usleep() to wait for the release of >> start cgroup. If the start cgroup is not pinned by cgroup iterator, >> reading iterator fd will trigger use-after-free. >> >> Signed-off-by: Hou Tao <houtao1@huawei.com> > > LGTM with a few nits below. > > Acked-by: Yonghong Song <yhs@fb.com> SNIP > >> + cgrp_fd = create_and_get_cgroup(cgrp_name); >> + if (!ASSERT_GE(cgrp_fd, 0, "create cgrp")) >> + return; >> + >> + /* The cgroup is already dead during iteration, so it only has epilogue >> + * in the output. >> + */ > > Let us reword the comment like > The cgroup will be dead during read() iteration, and it only has > epilogue in the output. Will do in v2. > >> + snprintf(expected_output, sizeof(expected_output), EPILOGUE); >> + >> + memset(&linfo, 0, sizeof(linfo)); >> + linfo.cgroup.cgroup_fd = cgrp_fd; >> + linfo.cgroup.order = BPF_CGROUP_ITER_SELF_ONLY; >> + opts.link_info = &linfo; >> + opts.link_info_len = sizeof(linfo); >> + SNIP >> void test_cgroup_iter(void) >> { >> struct cgroup_iter *skel = NULL; >> @@ -217,6 +293,8 @@ void test_cgroup_iter(void) >> test_early_termination(skel); >> if (test__start_subtest("cgroup_iter__self_only")) >> test_walk_self_only(skel); >> + if (test__start_subtest("cgroup_iter_dead_self_only")) > > Let us follow the convention in this file with > cgroup_iter__dead_self_only My bad. Will fixes in v2. > >> + test_walk_dead_self_only(skel); >> out: >> cgroup_iter__destroy(skel); >> cleanup_cgroups();
diff --git a/tools/testing/selftests/bpf/prog_tests/cgroup_iter.c b/tools/testing/selftests/bpf/prog_tests/cgroup_iter.c index c4a2adb38da1..d64ed1cf1554 100644 --- a/tools/testing/selftests/bpf/prog_tests/cgroup_iter.c +++ b/tools/testing/selftests/bpf/prog_tests/cgroup_iter.c @@ -189,6 +189,82 @@ static void test_walk_self_only(struct cgroup_iter *skel) BPF_CGROUP_ITER_SELF_ONLY, "self_only"); } +static void test_walk_dead_self_only(struct cgroup_iter *skel) +{ + DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts); + char expected_output[128], buf[128]; + const char *cgrp_name = "/dead"; + union bpf_iter_link_info linfo; + int len, cgrp_fd, iter_fd; + struct bpf_link *link; + size_t left; + char *p; + + cgrp_fd = create_and_get_cgroup(cgrp_name); + if (!ASSERT_GE(cgrp_fd, 0, "create cgrp")) + return; + + /* The cgroup is already dead during iteration, so it only has epilogue + * in the output. + */ + snprintf(expected_output, sizeof(expected_output), EPILOGUE); + + memset(&linfo, 0, sizeof(linfo)); + linfo.cgroup.cgroup_fd = cgrp_fd; + linfo.cgroup.order = BPF_CGROUP_ITER_SELF_ONLY; + opts.link_info = &linfo; + opts.link_info_len = sizeof(linfo); + + link = bpf_program__attach_iter(skel->progs.cgroup_id_printer, &opts); + if (!ASSERT_OK_PTR(link, "attach_iter")) + goto close_cg; + + iter_fd = bpf_iter_create(bpf_link__fd(link)); + if (!ASSERT_GE(iter_fd, 0, "iter_create")) + goto free_link; + + /* Close link fd and cgroup fd */ + bpf_link__destroy(link); + link = NULL; + close(cgrp_fd); + cgrp_fd = -1; + + /* Remove cgroup to mark it as dead */ + remove_cgroup(cgrp_name); + + /* Two kern_sync_rcu() and usleep() pair are used to wait for the + * releases of cgroup css, and the last kern_sync_rcu() and usleep() + * pair is used to wait for the free of cgroup itself. + */ + kern_sync_rcu(); + usleep(8000); + kern_sync_rcu(); + usleep(8000); + kern_sync_rcu(); + usleep(1000); + + memset(buf, 0, sizeof(buf)); + left = ARRAY_SIZE(buf); + p = buf; + while ((len = read(iter_fd, p, left)) > 0) { + p += len; + left -= len; + } + + ASSERT_STREQ(buf, expected_output, "dead cgroup output"); + + /* read() after iter finishes should be ok. */ + if (len == 0) + ASSERT_OK(read(iter_fd, buf, sizeof(buf)), "second_read"); + + close(iter_fd); +free_link: + bpf_link__destroy(link); +close_cg: + if (cgrp_fd >= 0) + close(cgrp_fd); +} + void test_cgroup_iter(void) { struct cgroup_iter *skel = NULL; @@ -217,6 +293,8 @@ void test_cgroup_iter(void) test_early_termination(skel); if (test__start_subtest("cgroup_iter__self_only")) test_walk_self_only(skel); + if (test__start_subtest("cgroup_iter_dead_self_only")) + test_walk_dead_self_only(skel); out: cgroup_iter__destroy(skel); cleanup_cgroups();