* [PATCH bpf-next 1/2] libbpf: Don't take direct pointers into BTF data from st_ops
@ 2024-07-24 17:14 David Vernet
2024-07-24 17:14 ` [PATCH bpf-next 2/2] selftests/bpf: Add test for resizing data map with struct_ops David Vernet
2024-07-25 0:20 ` [PATCH bpf-next 1/2] libbpf: Don't take direct pointers into BTF data from st_ops patchwork-bot+netdevbpf
0 siblings, 2 replies; 5+ messages in thread
From: David Vernet @ 2024-07-24 17:14 UTC (permalink / raw)
To: bpf
Cc: ast, daniel, andrii, martin.lau, song, yonghong.song,
john.fastabend, kpsingh, sdf, haoluo, jolsa, linux-kernel,
kernel-team, tj
In struct bpf_struct_ops, we have take a pointer to a BTF type name, and
a struct btf_type. This was presumably done for convenience, but can
actually result in subtle and confusing bugs given that BTF data can be
invalidated before a program is loaded. For example, in sched_ext, we
may sometimes resize a data section after a skeleton has been opened,
but before the struct_ops scheduler map has been loaded. This may cause
the BTF data to be realloc'd, which can then cause a UAF when loading
the program because the struct_ops map has pointers directly into the
BTF data.
We're already storing the BTF type_id in struct bpf_struct_ops. Because
type_id is stable, we can therefore just update the places where we were
looking at those pointers to instead do the lookups we need from the
type_id.
Fixes: 590a00888250 ("bpf: libbpf: Add STRUCT_OPS support")
Signed-off-by: David Vernet <void@manifault.com>
---
tools/lib/bpf/libbpf.c | 23 +++++++++++++----------
1 file changed, 13 insertions(+), 10 deletions(-)
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index a3be6f8fac09..e55353887439 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -496,8 +496,6 @@ struct bpf_program {
};
struct bpf_struct_ops {
- const char *tname;
- const struct btf_type *type;
struct bpf_program **progs;
__u32 *kern_func_off;
/* e.g. struct tcp_congestion_ops in bpf_prog's btf format */
@@ -1083,11 +1081,14 @@ static int bpf_object_adjust_struct_ops_autoload(struct bpf_object *obj)
continue;
for (j = 0; j < obj->nr_maps; ++j) {
+ const struct btf_type *type;
+
map = &obj->maps[j];
if (!bpf_map__is_struct_ops(map))
continue;
- vlen = btf_vlen(map->st_ops->type);
+ type = btf__type_by_id(obj->btf, map->st_ops->type_id);
+ vlen = btf_vlen(type);
for (k = 0; k < vlen; ++k) {
slot_prog = map->st_ops->progs[k];
if (prog != slot_prog)
@@ -1121,8 +1122,8 @@ static int bpf_map__init_kern_struct_ops(struct bpf_map *map)
int err;
st_ops = map->st_ops;
- type = st_ops->type;
- tname = st_ops->tname;
+ type = btf__type_by_id(btf, st_ops->type_id);
+ tname = btf__name_by_offset(btf, type->name_off);
err = find_struct_ops_kern_types(obj, tname, &mod_btf,
&kern_type, &kern_type_id,
&kern_vtype, &kern_vtype_id,
@@ -1423,8 +1424,6 @@ static int init_struct_ops_maps(struct bpf_object *obj, const char *sec_name,
memcpy(st_ops->data,
data->d_buf + vsi->offset,
type->size);
- st_ops->tname = tname;
- st_ops->type = type;
st_ops->type_id = type_id;
pr_debug("struct_ops init: struct %s(type_id=%u) %s found at offset %u\n",
@@ -8445,11 +8444,13 @@ static int bpf_object__resolve_externs(struct bpf_object *obj,
static void bpf_map_prepare_vdata(const struct bpf_map *map)
{
+ const struct btf_type *type;
struct bpf_struct_ops *st_ops;
__u32 i;
st_ops = map->st_ops;
- for (i = 0; i < btf_vlen(st_ops->type); i++) {
+ type = btf__type_by_id(map->obj->btf, st_ops->type_id);
+ for (i = 0; i < btf_vlen(type); i++) {
struct bpf_program *prog = st_ops->progs[i];
void *kern_data;
int prog_fd;
@@ -9712,6 +9713,7 @@ static struct bpf_map *find_struct_ops_map_by_offset(struct bpf_object *obj,
static int bpf_object__collect_st_ops_relos(struct bpf_object *obj,
Elf64_Shdr *shdr, Elf_Data *data)
{
+ const struct btf_type *type;
const struct btf_member *member;
struct bpf_struct_ops *st_ops;
struct bpf_program *prog;
@@ -9771,13 +9773,14 @@ static int bpf_object__collect_st_ops_relos(struct bpf_object *obj,
}
insn_idx = sym->st_value / BPF_INSN_SZ;
- member = find_member_by_offset(st_ops->type, moff * 8);
+ type = btf__type_by_id(btf, st_ops->type_id);
+ member = find_member_by_offset(type, moff * 8);
if (!member) {
pr_warn("struct_ops reloc %s: cannot find member at moff %u\n",
map->name, moff);
return -EINVAL;
}
- member_idx = member - btf_members(st_ops->type);
+ member_idx = member - btf_members(type);
name = btf__name_by_offset(btf, member->name_off);
if (!resolve_func_ptr(btf, member->type, NULL)) {
--
2.45.2
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH bpf-next 2/2] selftests/bpf: Add test for resizing data map with struct_ops
2024-07-24 17:14 [PATCH bpf-next 1/2] libbpf: Don't take direct pointers into BTF data from st_ops David Vernet
@ 2024-07-24 17:14 ` David Vernet
2024-07-25 0:12 ` Andrii Nakryiko
2024-07-25 0:20 ` [PATCH bpf-next 1/2] libbpf: Don't take direct pointers into BTF data from st_ops patchwork-bot+netdevbpf
1 sibling, 1 reply; 5+ messages in thread
From: David Vernet @ 2024-07-24 17:14 UTC (permalink / raw)
To: bpf
Cc: ast, daniel, andrii, martin.lau, song, yonghong.song,
john.fastabend, kpsingh, sdf, haoluo, jolsa, linux-kernel,
kernel-team, tj
Tests that if you resize a map after opening a skel, that it doesn't
cause a UAF which causes a struct_ops map to fail to be able to load.
Signed-off-by: David Vernet <void@manifault.com>
---
.../bpf/prog_tests/struct_ops_resize.c | 30 +++++++++++++++++++
.../selftests/bpf/progs/struct_ops_resize.c | 24 +++++++++++++++
2 files changed, 54 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/struct_ops_resize.c
create mode 100644 tools/testing/selftests/bpf/progs/struct_ops_resize.c
diff --git a/tools/testing/selftests/bpf/prog_tests/struct_ops_resize.c b/tools/testing/selftests/bpf/prog_tests/struct_ops_resize.c
new file mode 100644
index 000000000000..7584f91c2bd1
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/struct_ops_resize.c
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include "struct_ops_resize.skel.h"
+
+static void resize_datasec(void)
+{
+ struct struct_ops_resize *skel;
+ int err;
+
+ skel = struct_ops_resize__open();
+ if (!ASSERT_OK_PTR(skel, "struct_ops_resize__open"))
+ return;
+
+ err = bpf_map__set_value_size(skel->maps.data_resizable, 1 << 15);
+ if (!ASSERT_OK(err, "bpf_map__set_value_size"))
+ goto cleanup;
+
+ err = struct_ops_resize__load(skel);
+ ASSERT_OK(err, "struct_ops_resize__load");
+
+cleanup:
+ struct_ops_resize__destroy(skel);
+}
+
+void test_struct_ops_resize(void)
+{
+ if (test__start_subtest("resize_datasec"))
+ resize_datasec();
+}
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_resize.c b/tools/testing/selftests/bpf/progs/struct_ops_resize.c
new file mode 100644
index 000000000000..d0b235f4bbaa
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_resize.c
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+char _license[] SEC("license") = "GPL";
+
+char resizable[1] SEC(".data.resizable");
+
+SEC("struct_ops/test_1")
+int BPF_PROG(test_1)
+{
+ return 0;
+}
+
+struct bpf_testmod_ops {
+ int (*test_1)(void);
+};
+
+SEC(".struct_ops.link")
+struct bpf_testmod_ops testmod = {
+ .test_1 = (void *)test_1
+};
--
2.45.2
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH bpf-next 2/2] selftests/bpf: Add test for resizing data map with struct_ops
2024-07-24 17:14 ` [PATCH bpf-next 2/2] selftests/bpf: Add test for resizing data map with struct_ops David Vernet
@ 2024-07-25 0:12 ` Andrii Nakryiko
2024-07-25 2:54 ` David Vernet
0 siblings, 1 reply; 5+ messages in thread
From: Andrii Nakryiko @ 2024-07-25 0:12 UTC (permalink / raw)
To: David Vernet
Cc: bpf, ast, daniel, andrii, martin.lau, song, yonghong.song,
john.fastabend, kpsingh, sdf, haoluo, jolsa, linux-kernel,
kernel-team, tj
On Wed, Jul 24, 2024 at 10:15 AM David Vernet <void@manifault.com> wrote:
>
> Tests that if you resize a map after opening a skel, that it doesn't
> cause a UAF which causes a struct_ops map to fail to be able to load.
>
> Signed-off-by: David Vernet <void@manifault.com>
> ---
> .../bpf/prog_tests/struct_ops_resize.c | 30 +++++++++++++++++++
> .../selftests/bpf/progs/struct_ops_resize.c | 24 +++++++++++++++
> 2 files changed, 54 insertions(+)
> create mode 100644 tools/testing/selftests/bpf/prog_tests/struct_ops_resize.c
> create mode 100644 tools/testing/selftests/bpf/progs/struct_ops_resize.c
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/struct_ops_resize.c b/tools/testing/selftests/bpf/prog_tests/struct_ops_resize.c
> new file mode 100644
> index 000000000000..7584f91c2bd1
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/struct_ops_resize.c
> @@ -0,0 +1,30 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <test_progs.h>
> +#include "struct_ops_resize.skel.h"
> +
> +static void resize_datasec(void)
> +{
> + struct struct_ops_resize *skel;
> + int err;
> +
> + skel = struct_ops_resize__open();
> + if (!ASSERT_OK_PTR(skel, "struct_ops_resize__open"))
> + return;
> +
> + err = bpf_map__set_value_size(skel->maps.data_resizable, 1 << 15);
> + if (!ASSERT_OK(err, "bpf_map__set_value_size"))
> + goto cleanup;
> +
> + err = struct_ops_resize__load(skel);
> + ASSERT_OK(err, "struct_ops_resize__load");
> +
> +cleanup:
> + struct_ops_resize__destroy(skel);
> +}
> +
> +void test_struct_ops_resize(void)
> +{
> + if (test__start_subtest("resize_datasec"))
> + resize_datasec();
It seems a bit unnecessary to add an entire new test with a subtest
just for this. Would you mind adding this testing logic into the
already existing prog_tests/global_map_resize.c set of cases?
I've applied patch #1, as it's obviously correct, so I didn't want to
delay the fix. Thanks!
> +}
> diff --git a/tools/testing/selftests/bpf/progs/struct_ops_resize.c b/tools/testing/selftests/bpf/progs/struct_ops_resize.c
> new file mode 100644
> index 000000000000..d0b235f4bbaa
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/struct_ops_resize.c
> @@ -0,0 +1,24 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <linux/bpf.h>
> +#include <bpf/bpf_helpers.h>
> +#include <bpf/bpf_tracing.h>
> +
> +char _license[] SEC("license") = "GPL";
> +
> +char resizable[1] SEC(".data.resizable");
> +
> +SEC("struct_ops/test_1")
> +int BPF_PROG(test_1)
> +{
> + return 0;
> +}
> +
> +struct bpf_testmod_ops {
> + int (*test_1)(void);
> +};
> +
> +SEC(".struct_ops.link")
> +struct bpf_testmod_ops testmod = {
> + .test_1 = (void *)test_1
> +};
> --
> 2.45.2
>
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH bpf-next 1/2] libbpf: Don't take direct pointers into BTF data from st_ops
2024-07-24 17:14 [PATCH bpf-next 1/2] libbpf: Don't take direct pointers into BTF data from st_ops David Vernet
2024-07-24 17:14 ` [PATCH bpf-next 2/2] selftests/bpf: Add test for resizing data map with struct_ops David Vernet
@ 2024-07-25 0:20 ` patchwork-bot+netdevbpf
1 sibling, 0 replies; 5+ messages in thread
From: patchwork-bot+netdevbpf @ 2024-07-25 0:20 UTC (permalink / raw)
To: David Vernet
Cc: bpf, ast, daniel, andrii, martin.lau, song, yonghong.song,
john.fastabend, kpsingh, sdf, haoluo, jolsa, linux-kernel,
kernel-team, tj
Hello:
This series was applied to bpf/bpf-next.git (master)
by Andrii Nakryiko <andrii@kernel.org>:
On Wed, 24 Jul 2024 12:14:58 -0500 you wrote:
> In struct bpf_struct_ops, we have take a pointer to a BTF type name, and
> a struct btf_type. This was presumably done for convenience, but can
> actually result in subtle and confusing bugs given that BTF data can be
> invalidated before a program is loaded. For example, in sched_ext, we
> may sometimes resize a data section after a skeleton has been opened,
> but before the struct_ops scheduler map has been loaded. This may cause
> the BTF data to be realloc'd, which can then cause a UAF when loading
> the program because the struct_ops map has pointers directly into the
> BTF data.
>
> [...]
Here is the summary with links:
- [bpf-next,1/2] libbpf: Don't take direct pointers into BTF data from st_ops
https://git.kernel.org/bpf/bpf-next/c/7244100e0389
- [bpf-next,2/2] selftests/bpf: Add test for resizing data map with struct_ops
(no matching commit)
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH bpf-next 2/2] selftests/bpf: Add test for resizing data map with struct_ops
2024-07-25 0:12 ` Andrii Nakryiko
@ 2024-07-25 2:54 ` David Vernet
0 siblings, 0 replies; 5+ messages in thread
From: David Vernet @ 2024-07-25 2:54 UTC (permalink / raw)
To: Andrii Nakryiko
Cc: bpf, ast, daniel, andrii, martin.lau, song, yonghong.song,
john.fastabend, kpsingh, sdf, haoluo, jolsa, linux-kernel,
kernel-team, tj
[-- Attachment #1: Type: text/plain, Size: 2376 bytes --]
On Wed, Jul 24, 2024 at 05:12:00PM -0700, Andrii Nakryiko wrote:
> On Wed, Jul 24, 2024 at 10:15 AM David Vernet <void@manifault.com> wrote:
> >
> > Tests that if you resize a map after opening a skel, that it doesn't
> > cause a UAF which causes a struct_ops map to fail to be able to load.
> >
> > Signed-off-by: David Vernet <void@manifault.com>
> > ---
> > .../bpf/prog_tests/struct_ops_resize.c | 30 +++++++++++++++++++
> > .../selftests/bpf/progs/struct_ops_resize.c | 24 +++++++++++++++
> > 2 files changed, 54 insertions(+)
> > create mode 100644 tools/testing/selftests/bpf/prog_tests/struct_ops_resize.c
> > create mode 100644 tools/testing/selftests/bpf/progs/struct_ops_resize.c
> >
> > diff --git a/tools/testing/selftests/bpf/prog_tests/struct_ops_resize.c b/tools/testing/selftests/bpf/prog_tests/struct_ops_resize.c
> > new file mode 100644
> > index 000000000000..7584f91c2bd1
> > --- /dev/null
> > +++ b/tools/testing/selftests/bpf/prog_tests/struct_ops_resize.c
> > @@ -0,0 +1,30 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +
> > +#include <test_progs.h>
> > +#include "struct_ops_resize.skel.h"
> > +
> > +static void resize_datasec(void)
> > +{
> > + struct struct_ops_resize *skel;
> > + int err;
> > +
> > + skel = struct_ops_resize__open();
> > + if (!ASSERT_OK_PTR(skel, "struct_ops_resize__open"))
> > + return;
> > +
> > + err = bpf_map__set_value_size(skel->maps.data_resizable, 1 << 15);
> > + if (!ASSERT_OK(err, "bpf_map__set_value_size"))
> > + goto cleanup;
> > +
> > + err = struct_ops_resize__load(skel);
> > + ASSERT_OK(err, "struct_ops_resize__load");
> > +
> > +cleanup:
> > + struct_ops_resize__destroy(skel);
> > +}
> > +
> > +void test_struct_ops_resize(void)
> > +{
> > + if (test__start_subtest("resize_datasec"))
> > + resize_datasec();
>
> It seems a bit unnecessary to add an entire new test with a subtest
> just for this. Would you mind adding this testing logic into the
> already existing prog_tests/global_map_resize.c set of cases?
Sure thing, I'll send a subsequent patch that adds the testcase to
prog_tests/global_map_resize.c.
> I've applied patch #1, as it's obviously correct, so I didn't want to
> delay the fix. Thanks!
Thanks!
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2024-07-25 2:55 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-07-24 17:14 [PATCH bpf-next 1/2] libbpf: Don't take direct pointers into BTF data from st_ops David Vernet
2024-07-24 17:14 ` [PATCH bpf-next 2/2] selftests/bpf: Add test for resizing data map with struct_ops David Vernet
2024-07-25 0:12 ` Andrii Nakryiko
2024-07-25 2:54 ` David Vernet
2024-07-25 0:20 ` [PATCH bpf-next 1/2] libbpf: Don't take direct pointers into BTF data from st_ops patchwork-bot+netdevbpf
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox