Sorry for the delay on this. Discussed in [1]. It's a helper for
proving to the verifier that your access in the array is valid. Happy
to change names or whatever. =)
Also added a libbpf helper function for mmapping an mmappable map.
We've been using both in our ghost-BPF schedulers[2].
[1] https://lore.kernel.org/bpf/[email protected]/T/
[2] https://github.com/google/ghost-userspace/blob/main/third_party/bpf/common.bpf.h#L218
Barret Rhoden (2):
libbpf: add helpers for mmapping maps
selftests/bpf: add inline assembly helpers to access array elements
tools/bpf/bpftool/gen.c | 16 +-
tools/lib/bpf/libbpf.c | 23 +++
tools/lib/bpf/libbpf.h | 6 +
tools/lib/bpf/libbpf.map | 4 +
tools/testing/selftests/bpf/Makefile | 2 +-
.../bpf/prog_tests/test_array_elem.c | 112 ++++++++++
.../selftests/bpf/progs/array_elem_test.c | 195 ++++++++++++++++++
tools/testing/selftests/bpf/progs/bpf_misc.h | 43 ++++
8 files changed, 387 insertions(+), 14 deletions(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/test_array_elem.c
create mode 100644 tools/testing/selftests/bpf/progs/array_elem_test.c
--
2.43.0.472.g3155946c3a-goog
bpf_map__mmap_size() was internal to bpftool. Use that to make wrappers
for mmap and munmap.
Signed-off-by: Barret Rhoden <[email protected]>
---
tools/bpf/bpftool/gen.c | 16 +++-------------
tools/lib/bpf/libbpf.c | 23 +++++++++++++++++++++++
tools/lib/bpf/libbpf.h | 6 ++++++
tools/lib/bpf/libbpf.map | 4 ++++
4 files changed, 36 insertions(+), 13 deletions(-)
diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c
index ee3ce2b8000d..a328e960c141 100644
--- a/tools/bpf/bpftool/gen.c
+++ b/tools/bpf/bpftool/gen.c
@@ -453,16 +453,6 @@ static void print_hex(const char *data, int data_sz)
}
}
-static size_t bpf_map_mmap_sz(const struct bpf_map *map)
-{
- long page_sz = sysconf(_SC_PAGE_SIZE);
- size_t map_sz;
-
- map_sz = (size_t)roundup(bpf_map__value_size(map), 8) * bpf_map__max_entries(map);
- map_sz = roundup(map_sz, page_sz);
- return map_sz;
-}
-
/* Emit type size asserts for all top-level fields in memory-mapped internal maps. */
static void codegen_asserts(struct bpf_object *obj, const char *obj_name)
{
@@ -641,7 +631,7 @@ static void codegen_destroy(struct bpf_object *obj, const char *obj_name)
if (bpf_map__is_internal(map) &&
(bpf_map__map_flags(map) & BPF_F_MMAPABLE))
printf("\tskel_free_map_data(skel->%1$s, skel->maps.%1$s.initial_value, %2$zd);\n",
- ident, bpf_map_mmap_sz(map));
+ ident, bpf_map__mmap_size(map));
codegen("\
\n\
skel_closenz(skel->maps.%1$s.map_fd); \n\
@@ -723,7 +713,7 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
goto cleanup; \n\
skel->maps.%1$s.initial_value = (__u64) (long) skel->%1$s;\n\
} \n\
- ", ident, bpf_map_mmap_sz(map));
+ ", ident, bpf_map__mmap_size(map));
}
codegen("\
\n\
@@ -780,7 +770,7 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
if (!skel->%1$s) \n\
return -ENOMEM; \n\
",
- ident, bpf_map_mmap_sz(map), mmap_flags);
+ ident, bpf_map__mmap_size(map), mmap_flags);
}
codegen("\
\n\
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index ebcfb2147fbd..171a977cb5fd 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -9830,6 +9830,29 @@ void *bpf_map__initial_value(struct bpf_map *map, size_t *psize)
return map->mmaped;
}
+size_t bpf_map__mmap_size(const struct bpf_map *map)
+{
+ long page_sz = sysconf(_SC_PAGE_SIZE);
+ size_t map_sz;
+
+ map_sz = (size_t)roundup(bpf_map__value_size(map), 8) *
+ bpf_map__max_entries(map);
+ map_sz = roundup(map_sz, page_sz);
+ return map_sz;
+}
+
+void *bpf_map__mmap(const struct bpf_map *map)
+{
+ return mmap(NULL, bpf_map__mmap_size(map),
+ PROT_READ | PROT_WRITE, MAP_SHARED,
+ bpf_map__fd(map), 0);
+}
+
+int bpf_map__munmap(const struct bpf_map *map, void *addr)
+{
+ return munmap(addr, bpf_map__mmap_size(map));
+}
+
bool bpf_map__is_internal(const struct bpf_map *map)
{
return map->libbpf_type != LIBBPF_MAP_UNSPEC;
diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
index 6cd9c501624f..148f4c783ca7 100644
--- a/tools/lib/bpf/libbpf.h
+++ b/tools/lib/bpf/libbpf.h
@@ -996,6 +996,12 @@ LIBBPF_API int bpf_map__set_map_extra(struct bpf_map *map, __u64 map_extra);
LIBBPF_API int bpf_map__set_initial_value(struct bpf_map *map,
const void *data, size_t size);
LIBBPF_API void *bpf_map__initial_value(struct bpf_map *map, size_t *psize);
+/* get the mmappable size of the map */
+LIBBPF_API size_t bpf_map__mmap_size(const struct bpf_map *map);
+/* mmap the map */
+LIBBPF_API void *bpf_map__mmap(const struct bpf_map *map);
+/* munmap the map at addr */
+LIBBPF_API int bpf_map__munmap(const struct bpf_map *map, void *addr);
/**
* @brief **bpf_map__is_internal()** tells the caller whether or not the
diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
index 91c5aef7dae7..9e44de4fbf39 100644
--- a/tools/lib/bpf/libbpf.map
+++ b/tools/lib/bpf/libbpf.map
@@ -411,4 +411,8 @@ LIBBPF_1.3.0 {
} LIBBPF_1.2.0;
LIBBPF_1.4.0 {
+ global:
+ bpf_map__mmap_size;
+ bpf_map__mmap;
+ bpf_map__munmap;
} LIBBPF_1.3.0;
--
2.43.0.472.g3155946c3a-goog
When accessing an array, even if you insert your own bounds check,
sometimes the compiler will remove the check, or modify it such that the
verifier no longer knows your access is within bounds.
The compiler is even free to make a copy of a register, check the copy,
and use the original to access the array. The verifier knows the *copy*
is within bounds, but not the original register!
Signed-off-by: Barret Rhoden <[email protected]>
---
tools/testing/selftests/bpf/Makefile | 2 +-
.../bpf/prog_tests/test_array_elem.c | 112 ++++++++++
.../selftests/bpf/progs/array_elem_test.c | 195 ++++++++++++++++++
tools/testing/selftests/bpf/progs/bpf_misc.h | 43 ++++
4 files changed, 351 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/test_array_elem.c
create mode 100644 tools/testing/selftests/bpf/progs/array_elem_test.c
diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 617ae55c3bb5..651d4663cc78 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -34,7 +34,7 @@ LIBELF_CFLAGS := $(shell $(PKG_CONFIG) libelf --cflags 2>/dev/null)
LIBELF_LIBS := $(shell $(PKG_CONFIG) libelf --libs 2>/dev/null || echo -lelf)
CFLAGS += -g $(OPT_FLAGS) -rdynamic \
- -Wall -Werror \
+ -dicks -Wall -Werror \
$(GENFLAGS) $(SAN_CFLAGS) $(LIBELF_CFLAGS) \
-I$(CURDIR) -I$(INCLUDE_DIR) -I$(GENDIR) -I$(LIBDIR) \
-I$(TOOLSINCDIR) -I$(APIDIR) -I$(OUTPUT)
diff --git a/tools/testing/selftests/bpf/prog_tests/test_array_elem.c b/tools/testing/selftests/bpf/prog_tests/test_array_elem.c
new file mode 100644
index 000000000000..c953636f07c9
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/test_array_elem.c
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2024 Google LLC. */
+#include <test_progs.h>
+#include "array_elem_test.skel.h"
+
+#define NR_MAP_ELEMS 100
+
+/*
+ * Helper to load and run a program.
+ * Call must define skel, map_elems, and bss_elems.
+ * Destroy the skel when you're done.
+ */
+#define load_and_run(PROG) ({ \
+ int err; \
+ skel = array_elem_test__open(); \
+ if (!ASSERT_OK_PTR(skel, "array_elem_test open")) \
+ return; \
+ bpf_program__set_autoload(skel->progs.x_ ## PROG, true); \
+ err = array_elem_test__load(skel); \
+ if (!ASSERT_EQ(err, 0, "array_elem_test load")) { \
+ array_elem_test__destroy(skel); \
+ return; \
+ } \
+ err = array_elem_test__attach(skel); \
+ if (!ASSERT_EQ(err, 0, "array_elem_test attach")) { \
+ array_elem_test__destroy(skel); \
+ return; \
+ } \
+ for (int i = 0; i < NR_MAP_ELEMS; i++) \
+ skel->bss->lookup_indexes[i] = i; \
+ map_elems = bpf_map__mmap(skel->maps.arraymap); \
+ ASSERT_OK_PTR(map_elems, "mmap"); \
+ bss_elems = skel->bss->bss_elems; \
+ skel->bss->target_pid = getpid(); \
+ usleep(1); \
+})
+
+static void test_access_all(void)
+{
+ struct array_elem_test *skel;
+ int *map_elems;
+ int *bss_elems;
+
+ load_and_run(access_all);
+
+ for (int i = 0; i < NR_MAP_ELEMS; i++)
+ ASSERT_EQ(map_elems[i], i, "array_elem map value not written");
+
+ for (int i = 0; i < NR_MAP_ELEMS; i++)
+ ASSERT_EQ(bss_elems[i], i, "array_elem bss value not written");
+
+ array_elem_test__destroy(skel);
+}
+
+static void test_oob_access(void)
+{
+ struct array_elem_test *skel;
+ int *map_elems;
+ int *bss_elems;
+
+ load_and_run(oob_access);
+
+ for (int i = 0; i < NR_MAP_ELEMS; i++)
+ ASSERT_EQ(map_elems[i], 0, "array_elem map value was written");
+
+ for (int i = 0; i < NR_MAP_ELEMS; i++)
+ ASSERT_EQ(bss_elems[i], 0, "array_elem bss value was written");
+
+ array_elem_test__destroy(skel);
+}
+
+static void test_access_array_map_infer_sz(void)
+{
+ struct array_elem_test *skel;
+ int *map_elems;
+ int *bss_elems __maybe_unused;
+
+ load_and_run(access_array_map_infer_sz);
+
+ for (int i = 0; i < NR_MAP_ELEMS; i++)
+ ASSERT_EQ(map_elems[i], i, "array_elem map value not written");
+
+ array_elem_test__destroy(skel);
+}
+
+
+/* Test that attempting to load a bad program fails. */
+#define test_bad(PROG) ({ \
+ struct array_elem_test *skel; \
+ int err; \
+ skel = array_elem_test__open(); \
+ if (!ASSERT_OK_PTR(skel, "array_elem_test open")) \
+ return; \
+ bpf_program__set_autoload(skel->progs.x_bad_ ## PROG, true); \
+ err = array_elem_test__load(skel); \
+ ASSERT_ERR(err, "array_elem_test load " # PROG); \
+ array_elem_test__destroy(skel); \
+})
+
+void test_test_array_elem(void)
+{
+ if (test__start_subtest("array_elem_access_all"))
+ test_access_all();
+ if (test__start_subtest("array_elem_oob_access"))
+ test_oob_access();
+ if (test__start_subtest("array_elem_access_array_map_infer_sz"))
+ test_access_array_map_infer_sz();
+ if (test__start_subtest("array_elem_bad_map_array_access"))
+ test_bad(map_array_access);
+ if (test__start_subtest("array_elem_bad_bss_array_access"))
+ test_bad(bss_array_access);
+}
diff --git a/tools/testing/selftests/bpf/progs/array_elem_test.c b/tools/testing/selftests/bpf/progs/array_elem_test.c
new file mode 100644
index 000000000000..9d48afc933f0
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/array_elem_test.c
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2024 Google LLC. */
+#include <stdbool.h>
+#include <linux/types.h>
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+int target_pid = 0;
+
+#define NR_MAP_ELEMS 100
+
+/*
+ * We want to test valid accesses into an array, but we also need to fool the
+ * verifier. If we just do for (i = 0; i < 100; i++), the verifier knows the
+ * value of i and can tell we're inside the array.
+ *
+ * This "lookup" array is just the values 0, 1, 2..., such that
+ * lookup_indexes[i] == i. (set by userspace). But the verifier doesn't know
+ * that.
+ */
+unsigned int lookup_indexes[NR_MAP_ELEMS];
+
+/* Arrays can be in the BSS or inside a map element. Make sure both work. */
+int bss_elems[NR_MAP_ELEMS];
+
+struct map_array {
+ int elems[NR_MAP_ELEMS];
+};
+
+/*
+ * This is an ARRAY_MAP of a single struct, and that struct is an array of
+ * elements. Userspace can mmap the map as if it was just a basic array of
+ * elements. Though if you make an ARRAY_MAP where the *values* are ints, don't
+ * forget that bpf map elements are rounded up to 8 bytes.
+ *
+ * Once you get the pointer to the base of the inner array, you can access all
+ * of the elements without another bpf_map_lookup_elem(), which is useful if you
+ * are operating on multiple elements while holding a spinlock.
+ */
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, int);
+ __type(value, struct map_array);
+ __uint(map_flags, BPF_F_MMAPABLE);
+} arraymap SEC(".maps");
+
+static struct map_array *get_map_array(void)
+{
+ int zero = 0;
+
+ return bpf_map_lookup_elem(&arraymap, &zero);
+}
+
+static int *get_map_elems(void)
+{
+ struct map_array *arr = get_map_array();
+
+ if (!arr)
+ return NULL;
+ return arr->elems;
+}
+
+/*
+ * Test that we can access all elements, and that we are accessing the element
+ * we think we are accessing.
+ */
+static void access_all(void)
+{
+ int *map_elems = get_map_elems();
+ int *x;
+
+ for (int i = 0; i < NR_MAP_ELEMS; i++) {
+ x = bpf_array_elem(map_elems, NR_MAP_ELEMS, lookup_indexes[i]);
+ if (x)
+ *x = i;
+ }
+
+ for (int i = 0; i < NR_MAP_ELEMS; i++) {
+ x = bpf_array_sz_elem(bss_elems, lookup_indexes[i]);
+ if (x)
+ *x = i;
+ }
+}
+
+SEC("?tp/syscalls/sys_enter_nanosleep")
+int x_access_all(void *ctx)
+{
+ if ((bpf_get_current_pid_tgid() >> 32) != target_pid)
+ return 0;
+ access_all();
+ return 0;
+}
+
+/*
+ * Helper for various OOB tests. An out-of-bound access should be handled like
+ * a lookup failure. Specifically, the verifier should ensure we do not access
+ * outside the array. Userspace will check that we didn't access somewhere
+ * inside the array.
+ */
+static void set_elem_to_1(long idx)
+{
+ int *map_elems = get_map_elems();
+ int *x;
+
+ x = bpf_array_elem(map_elems, NR_MAP_ELEMS, idx);
+ if (x)
+ *x = 1;
+ x = bpf_array_sz_elem(bss_elems, idx);
+ if (x)
+ *x = 1;
+}
+
+/*
+ * Test various out-of-bounds accesses.
+ */
+static void oob_access(void)
+{
+ set_elem_to_1(NR_MAP_ELEMS + 5);
+ set_elem_to_1(NR_MAP_ELEMS);
+ set_elem_to_1(-1);
+ set_elem_to_1(~0UL);
+}
+
+SEC("?tp/syscalls/sys_enter_nanosleep")
+int x_oob_access(void *ctx)
+{
+ if ((bpf_get_current_pid_tgid() >> 32) != target_pid)
+ return 0;
+ oob_access();
+ return 0;
+}
+
+/*
+ * Test that we can use the ARRAY_SIZE-style helper with an array in a map.
+ *
+ * Note that you cannot infer the size of the array from just a pointer; you
+ * have to use the actual elems[100]. i.e. this will fail and should fail to
+ * compile (-Wsizeof-pointer-div):
+ *
+ * int *map_elems = get_map_elems();
+ * x = bpf_array_sz_elem(map_elems, lookup_indexes[i]);
+ */
+static void access_array_map_infer_sz(void)
+{
+ struct map_array *arr = get_map_array();
+ int *x;
+
+ for (int i = 0; i < NR_MAP_ELEMS; i++) {
+ x = bpf_array_sz_elem(arr->elems, lookup_indexes[i]);
+ if (x)
+ *x = i;
+ }
+}
+
+SEC("?tp/syscalls/sys_enter_nanosleep")
+int x_access_array_map_infer_sz(void *ctx)
+{
+ if ((bpf_get_current_pid_tgid() >> 32) != target_pid)
+ return 0;
+ access_array_map_infer_sz();
+ return 0;
+}
+
+
+
+SEC("?tp/syscalls/sys_enter_nanosleep")
+int x_bad_map_array_access(void *ctx)
+{
+ int *map_elems = get_map_elems();
+
+ /*
+ * Need to check to promote map_elems from MAP_OR_NULL to MAP so that we
+ * fail to load below for the right reason.
+ */
+ if (!map_elems)
+ return 0;
+ /* Fail to load: we don't prove our access is inside map_elems[] */
+ for (int i = 0; i < NR_MAP_ELEMS; i++)
+ map_elems[lookup_indexes[i]] = i;
+ return 0;
+}
+
+SEC("?tp/syscalls/sys_enter_nanosleep")
+int x_bad_bss_array_access(void *ctx)
+{
+ /* Fail to load: we don't prove our access is inside bss_elems[] */
+ for (int i = 0; i < NR_MAP_ELEMS; i++)
+ bss_elems[lookup_indexes[i]] = i;
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/bpf_misc.h b/tools/testing/selftests/bpf/progs/bpf_misc.h
index 2fd59970c43a..002bab44cde2 100644
--- a/tools/testing/selftests/bpf/progs/bpf_misc.h
+++ b/tools/testing/selftests/bpf/progs/bpf_misc.h
@@ -135,4 +135,47 @@
/* make it look to compiler like value is read and written */
#define __sink(expr) asm volatile("" : "+g"(expr))
+/*
+ * Access an array element within a bound, such that the verifier knows the
+ * access is safe.
+ *
+ * This macro asm is the equivalent of:
+ *
+ * if (!arr)
+ * return NULL;
+ * if (idx >= arr_sz)
+ * return NULL;
+ * return &arr[idx];
+ *
+ * The index (___idx below) needs to be a u64, at least for certain versions of
+ * the BPF ISA, since there aren't u32 conditional jumps.
+ */
+#define bpf_array_elem(arr, arr_sz, idx) ({ \
+ typeof(&(arr)[0]) ___arr = arr; \
+ __u64 ___idx = idx; \
+ if (___arr) { \
+ asm volatile("if %[__idx] >= %[__bound] goto 1f; \
+ %[__idx] *= %[__size]; \
+ %[__arr] += %[__idx]; \
+ goto 2f; \
+ 1:; \
+ %[__arr] = 0; \
+ 2: \
+ " \
+ : [__arr]"+r"(___arr), [__idx]"+r"(___idx) \
+ : [__bound]"r"((arr_sz)), \
+ [__size]"i"(sizeof(typeof((arr)[0]))) \
+ : "cc"); \
+ } \
+ ___arr; \
+})
+
+/*
+ * Convenience wrapper for bpf_array_elem(), where we compute the size of the
+ * array. Be sure to use an actual array, and not a pointer, just like with the
+ * ARRAY_SIZE macro.
+ */
+#define bpf_array_sz_elem(arr, idx) \
+ bpf_array_elem(arr, sizeof(arr) / sizeof((arr)[0]), idx)
+
#endif
--
2.43.0.472.g3155946c3a-goog
Barret Rhoden wrote:
> bpf_map__mmap_size() was internal to bpftool. Use that to make wrappers
> for mmap and munmap.
>
> Signed-off-by: Barret Rhoden <[email protected]>
> ---
> tools/bpf/bpftool/gen.c | 16 +++-------------
> tools/lib/bpf/libbpf.c | 23 +++++++++++++++++++++++
> tools/lib/bpf/libbpf.h | 6 ++++++
> tools/lib/bpf/libbpf.map | 4 ++++
> 4 files changed, 36 insertions(+), 13 deletions(-)
>
> diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c
> index ee3ce2b8000d..a328e960c141 100644
> --- a/tools/bpf/bpftool/gen.c
> +++ b/tools/bpf/bpftool/gen.c
> @@ -453,16 +453,6 @@ static void print_hex(const char *data, int data_sz)
> }
> }
>
> -static size_t bpf_map_mmap_sz(const struct bpf_map *map)
> -{
> - long page_sz = sysconf(_SC_PAGE_SIZE);
> - size_t map_sz;
> -
> - map_sz = (size_t)roundup(bpf_map__value_size(map), 8) * bpf_map__max_entries(map);
> - map_sz = roundup(map_sz, page_sz);
> - return map_sz;
> -}
> -
> /* Emit type size asserts for all top-level fields in memory-mapped internal maps. */
> static void codegen_asserts(struct bpf_object *obj, const char *obj_name)
> {
> @@ -641,7 +631,7 @@ static void codegen_destroy(struct bpf_object *obj, const char *obj_name)
> if (bpf_map__is_internal(map) &&
> (bpf_map__map_flags(map) & BPF_F_MMAPABLE))
> printf("\tskel_free_map_data(skel->%1$s, skel->maps.%1$s.initial_value, %2$zd);\n",
> - ident, bpf_map_mmap_sz(map));
> + ident, bpf_map__mmap_size(map));
> codegen("\
> \n\
> skel_closenz(skel->maps.%1$s.map_fd); \n\
> @@ -723,7 +713,7 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
> goto cleanup; \n\
> skel->maps.%1$s.initial_value = (__u64) (long) skel->%1$s;\n\
> } \n\
> - ", ident, bpf_map_mmap_sz(map));
> + ", ident, bpf_map__mmap_size(map));
> }
> codegen("\
> \n\
> @@ -780,7 +770,7 @@ static int gen_trace(struct bpf_object *obj, const char *obj_name, const char *h
> if (!skel->%1$s) \n\
> return -ENOMEM; \n\
> ",
> - ident, bpf_map_mmap_sz(map), mmap_flags);
> + ident, bpf_map__mmap_size(map), mmap_flags);
> }
> codegen("\
> \n\
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index ebcfb2147fbd..171a977cb5fd 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
> @@ -9830,6 +9830,29 @@ void *bpf_map__initial_value(struct bpf_map *map, size_t *psize)
> return map->mmaped;
> }
It seems libbpf.c has its own bpf_map_mmap_sz() as well.
static size_t bpf_map_mmap_sz(unsigned int value_sz, unsigned int max_entries)
{
const long page_sz = sysconf(_SC_PAGE_SIZE);
size_t map_sz;
map_sz = (size_t)roundup(value_sz, 8) * max_entries;
map_sz = roundup(map_sz, page_sz);
return map_sz;
}
Can we consolidate these a bit. Seems we don't want/need to
have both bpf_map__mmap_size and bpf_map_mmap_sz() floating
around.
Should bpf_map__mmap_size just calls bpf_map_mmap_sz with
the correct sz and max_entries?
>
> +size_t bpf_map__mmap_size(const struct bpf_map *map)
> +{
> + long page_sz = sysconf(_SC_PAGE_SIZE);
> + size_t map_sz;
> +
> + map_sz = (size_t)roundup(bpf_map__value_size(map), 8) *
> + bpf_map__max_entries(map);
> + map_sz = roundup(map_sz, page_sz);
> + return map_sz;
> +}
> +
> +void *bpf_map__mmap(const struct bpf_map *map)
> +{
> + return mmap(NULL, bpf_map__mmap_size(map),
> + PROT_READ | PROT_WRITE, MAP_SHARED,
> + bpf_map__fd(map), 0);
> +}
> +
> +int bpf_map__munmap(const struct bpf_map *map, void *addr)
> +{
> + return munmap(addr, bpf_map__mmap_size(map));
> +}
> +
> bool bpf_map__is_internal(const struct bpf_map *map)
> {
> return map->libbpf_type != LIBBPF_MAP_UNSPEC;
> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
> index 6cd9c501624f..148f4c783ca7 100644
> --- a/tools/lib/bpf/libbpf.h
> +++ b/tools/lib/bpf/libbpf.h
> @@ -996,6 +996,12 @@ LIBBPF_API int bpf_map__set_map_extra(struct bpf_map *map, __u64 map_extra);
> LIBBPF_API int bpf_map__set_initial_value(struct bpf_map *map,
> const void *data, size_t size);
> LIBBPF_API void *bpf_map__initial_value(struct bpf_map *map, size_t *psize);
> +/* get the mmappable size of the map */
> +LIBBPF_API size_t bpf_map__mmap_size(const struct bpf_map *map);
> +/* mmap the map */
> +LIBBPF_API void *bpf_map__mmap(const struct bpf_map *map);
> +/* munmap the map at addr */
> +LIBBPF_API int bpf_map__munmap(const struct bpf_map *map, void *addr);
>
> /**
> * @brief **bpf_map__is_internal()** tells the caller whether or not the
> diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
> index 91c5aef7dae7..9e44de4fbf39 100644
> --- a/tools/lib/bpf/libbpf.map
> +++ b/tools/lib/bpf/libbpf.map
> @@ -411,4 +411,8 @@ LIBBPF_1.3.0 {
> } LIBBPF_1.2.0;
>
> LIBBPF_1.4.0 {
> + global:
> + bpf_map__mmap_size;
> + bpf_map__mmap;
> + bpf_map__munmap;
> } LIBBPF_1.3.0;
> --
> 2.43.0.472.g3155946c3a-goog
>
>
Barret Rhoden wrote:
> When accessing an array, even if you insert your own bounds check,
> sometimes the compiler will remove the check, or modify it such that the
> verifier no longer knows your access is within bounds.
>
> The compiler is even free to make a copy of a register, check the copy,
> and use the original to access the array. The verifier knows the *copy*
> is within bounds, but not the original register!
>
> Signed-off-by: Barret Rhoden <[email protected]>
> ---
> tools/testing/selftests/bpf/Makefile | 2 +-
> .../bpf/prog_tests/test_array_elem.c | 112 ++++++++++
> .../selftests/bpf/progs/array_elem_test.c | 195 ++++++++++++++++++
> tools/testing/selftests/bpf/progs/bpf_misc.h | 43 ++++
> 4 files changed, 351 insertions(+), 1 deletion(-)
> create mode 100644 tools/testing/selftests/bpf/prog_tests/test_array_elem.c
> create mode 100644 tools/testing/selftests/bpf/progs/array_elem_test.c
>
> diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
> index 617ae55c3bb5..651d4663cc78 100644
> --- a/tools/testing/selftests/bpf/Makefile
> +++ b/tools/testing/selftests/bpf/Makefile
> @@ -34,7 +34,7 @@ LIBELF_CFLAGS := $(shell $(PKG_CONFIG) libelf --cflags 2>/dev/null)
> LIBELF_LIBS := $(shell $(PKG_CONFIG) libelf --libs 2>/dev/null || echo -lelf)
>
> CFLAGS += -g $(OPT_FLAGS) -rdynamic \
> - -Wall -Werror \
> + -dicks -Wall -Werror \
> $(GENFLAGS) $(SAN_CFLAGS) $(LIBELF_CFLAGS) \
> -I$(CURDIR) -I$(INCLUDE_DIR) -I$(GENDIR) -I$(LIBDIR) \
> -I$(TOOLSINCDIR) -I$(APIDIR) -I$(OUTPUT)
> diff --git a/tools/testing/selftests/bpf/prog_tests/test_array_elem.c b/tools/testing/selftests/bpf/prog_tests/test_array_elem.c
> new file mode 100644
> index 000000000000..c953636f07c9
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/test_array_elem.c
> @@ -0,0 +1,112 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) 2024 Google LLC. */
> +#include <test_progs.h>
> +#include "array_elem_test.skel.h"
> +
> +#define NR_MAP_ELEMS 100
[...]
> diff --git a/tools/testing/selftests/bpf/progs/bpf_misc.h b/tools/testing/selftests/bpf/progs/bpf_misc.h
> index 2fd59970c43a..002bab44cde2 100644
> --- a/tools/testing/selftests/bpf/progs/bpf_misc.h
> +++ b/tools/testing/selftests/bpf/progs/bpf_misc.h
> @@ -135,4 +135,47 @@
> /* make it look to compiler like value is read and written */
> #define __sink(expr) asm volatile("" : "+g"(expr))
>
> +/*
> + * Access an array element within a bound, such that the verifier knows the
> + * access is safe.
> + *
> + * This macro asm is the equivalent of:
> + *
> + * if (!arr)
> + * return NULL;
> + * if (idx >= arr_sz)
> + * return NULL;
> + * return &arr[idx];
> + *
> + * The index (___idx below) needs to be a u64, at least for certain versions of
> + * the BPF ISA, since there aren't u32 conditional jumps.
> + */
This is nice, but in practice what we've been doing is making
our maps power of 2 and then just masking them as needed. I think
this is more efficient if you care about performance.
FWIW I'm not opposed to having this though.
> +#define bpf_array_elem(arr, arr_sz, idx) ({ \
> + typeof(&(arr)[0]) ___arr = arr; \
> + __u64 ___idx = idx; \
> + if (___arr) { \
> + asm volatile("if %[__idx] >= %[__bound] goto 1f; \
> + %[__idx] *= %[__size]; \
> + %[__arr] += %[__idx]; \
> + goto 2f; \
+1 for using asm goto :)
> + 1:; \
> + %[__arr] = 0; \
> + 2: \
> + " \
> + : [__arr]"+r"(___arr), [__idx]"+r"(___idx) \
> + : [__bound]"r"((arr_sz)), \
> + [__size]"i"(sizeof(typeof((arr)[0]))) \
> + : "cc"); \
> + } \
> + ___arr; \
> +})
> +
> +/*
> + * Convenience wrapper for bpf_array_elem(), where we compute the size of the
> + * array. Be sure to use an actual array, and not a pointer, just like with the
> + * ARRAY_SIZE macro.
> + */
> +#define bpf_array_sz_elem(arr, idx) \
> + bpf_array_elem(arr, sizeof(arr) / sizeof((arr)[0]), idx)
> +
> #endif
> --
> 2.43.0.472.g3155946c3a-goog
>
>
On 1/3/24 11:57, John Fastabend wrote:
> Should bpf_map__mmap_size just calls bpf_map_mmap_sz with
> the correct sz and max_entries?
will do, thanks!
On Wed, Jan 3, 2024 at 7:33 AM Barret Rhoden <[email protected]> wrote:
>
> When accessing an array, even if you insert your own bounds check,
> sometimes the compiler will remove the check, or modify it such that the
> verifier no longer knows your access is within bounds.
>
> The compiler is even free to make a copy of a register, check the copy,
> and use the original to access the array. The verifier knows the *copy*
> is within bounds, but not the original register!
>
> Signed-off-by: Barret Rhoden <[email protected]>
> ---
> tools/testing/selftests/bpf/Makefile | 2 +-
> .../bpf/prog_tests/test_array_elem.c | 112 ++++++++++
> .../selftests/bpf/progs/array_elem_test.c | 195 ++++++++++++++++++
> tools/testing/selftests/bpf/progs/bpf_misc.h | 43 ++++
> 4 files changed, 351 insertions(+), 1 deletion(-)
> create mode 100644 tools/testing/selftests/bpf/prog_tests/test_array_elem.c
> create mode 100644 tools/testing/selftests/bpf/progs/array_elem_test.c
>
I'm curious how bpf_cmp_likely/bpf_cmp_unlikely (just applied to
bpf-next) compares to this?
> diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
> index 617ae55c3bb5..651d4663cc78 100644
> --- a/tools/testing/selftests/bpf/Makefile
> +++ b/tools/testing/selftests/bpf/Makefile
> @@ -34,7 +34,7 @@ LIBELF_CFLAGS := $(shell $(PKG_CONFIG) libelf --cflags 2>/dev/null)
> LIBELF_LIBS := $(shell $(PKG_CONFIG) libelf --libs 2>/dev/null || echo -lelf)
>
> CFLAGS += -g $(OPT_FLAGS) -rdynamic \
> - -Wall -Werror \
> + -dicks -Wall -Werror \
what does this magic argument do? )
> $(GENFLAGS) $(SAN_CFLAGS) $(LIBELF_CFLAGS) \
> -I$(CURDIR) -I$(INCLUDE_DIR) -I$(GENDIR) -I$(LIBDIR) \
> -I$(TOOLSINCDIR) -I$(APIDIR) -I$(OUTPUT)
> diff --git a/tools/testing/selftests/bpf/prog_tests/test_array_elem.c b/tools/testing/selftests/bpf/prog_tests/test_array_elem.c
> new file mode 100644
> index 000000000000..c953636f07c9
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/test_array_elem.c
> @@ -0,0 +1,112 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (c) 2024 Google LLC. */
> +#include <test_progs.h>
> +#include "array_elem_test.skel.h"
> +
> +#define NR_MAP_ELEMS 100
> +
> +/*
> + * Helper to load and run a program.
> + * Call must define skel, map_elems, and bss_elems.
> + * Destroy the skel when you're done.
> + */
> +#define load_and_run(PROG) ({
does this have to be a macro? Can you write it as a function?
\
> + int err; \
> + skel = array_elem_test__open(); \
> + if (!ASSERT_OK_PTR(skel, "array_elem_test open")) \
> + return; \
> + bpf_program__set_autoload(skel->progs.x_ ## PROG, true); \
> + err = array_elem_test__load(skel); \
> + if (!ASSERT_EQ(err, 0, "array_elem_test load")) { \
> + array_elem_test__destroy(skel); \
> + return; \
> + } \
> + err = array_elem_test__attach(skel); \
> + if (!ASSERT_EQ(err, 0, "array_elem_test attach")) { \
> + array_elem_test__destroy(skel); \
> + return; \
> + } \
> + for (int i = 0; i < NR_MAP_ELEMS; i++) \
> + skel->bss->lookup_indexes[i] = i; \
> + map_elems = bpf_map__mmap(skel->maps.arraymap); \
> + ASSERT_OK_PTR(map_elems, "mmap"); \
> + bss_elems = skel->bss->bss_elems; \
> + skel->bss->target_pid = getpid(); \
> + usleep(1); \
> +})
> +
[...]
On 1/3/24 14:51, Andrii Nakryiko wrote:
> I'm curious how bpf_cmp_likely/bpf_cmp_unlikely (just applied to
> bpf-next) compares to this?
these work great!
e.g.
if (bpf_cmp_likely(idx, <, NR_MAP_ELEMS))
map_elems[idx] = i;
works fine. since that's essentially the code that bpf_array_elem() was
trying to replace, i'd rather just use the new bpf_cmp helpers than have
the special array_elem helpers.
thanks,
barret
On Wed, Jan 3, 2024 at 12:06 PM Barret Rhoden <[email protected]> wrote:
>
> On 1/3/24 14:51, Andrii Nakryiko wrote:
> > I'm curious how bpf_cmp_likely/bpf_cmp_unlikely (just applied to
> > bpf-next) compares to this?
>
> these work great!
>
> e.g.
>
> if (bpf_cmp_likely(idx, <, NR_MAP_ELEMS))
> map_elems[idx] = i;
>
> works fine. since that's essentially the code that bpf_array_elem() was
> trying to replace, i'd rather just use the new bpf_cmp helpers than have
> the special array_elem helpers.
ok, cool, thanks for checking! The less special macros, the better.
>
> thanks,
>
> barret
>
>
On 1/3/24 16:21, Andrii Nakryiko wrote:
> On Wed, Jan 3, 2024 at 12:06 PM Barret Rhoden<[email protected]> wrote:
>> On 1/3/24 14:51, Andrii Nakryiko wrote:
>>> I'm curious how bpf_cmp_likely/bpf_cmp_unlikely (just applied to
>>> bpf-next) compares to this?
>> these work great!
>>
>> e.g.
>>
>> if (bpf_cmp_likely(idx, <, NR_MAP_ELEMS))
>> map_elems[idx] = i;
>>
>> works fine. since that's essentially the code that bpf_array_elem() was
>> trying to replace, i'd rather just use the new bpf_cmp helpers than have
>> the special array_elem helpers.
> ok, cool, thanks for checking! The less special macros, the better.
sorry - turns out it only worked in testing. in my actual program, i
still run into issues. the comparison is done, which is what bpf_cmp
enforces. but the compiler is discarding the comparison. i have more
info in the other thread, but figured i'd mention it here too. =(
On 1/3/24 14:51, Andrii Nakryiko wrote:
>> +
>> +/*
>> + * Helper to load and run a program.
>> + * Call must define skel, map_elems, and bss_elems.
>> + * Destroy the skel when you're done.
>> + */
>> +#define load_and_run(PROG) ({
> does this have to be a macro? Can you write it as a function?
can do. (if we keep these patches).
i used a macro for the ## PROG below, but i can do something with ints
and switches to turn on the autoload for a single prog. or just
copy-paste the boilerplate.
>> + int err; \
>> + skel = array_elem_test__open(); \
>> + if (!ASSERT_OK_PTR(skel, "array_elem_test open")) \
>> + return; \
>> + bpf_program__set_autoload(skel->progs.x_ ## PROG, true); \
thanks,
barret
On Thu, Jan 4, 2024 at 1:37 PM Barret Rhoden <[email protected]> wrote:
>
> On 1/3/24 14:51, Andrii Nakryiko wrote:
> >> +
> >> +/*
> >> + * Helper to load and run a program.
> >> + * Call must define skel, map_elems, and bss_elems.
> >> + * Destroy the skel when you're done.
> >> + */
> >> +#define load_and_run(PROG) ({
> > does this have to be a macro? Can you write it as a function?
>
> can do. (if we keep these patches).
>
> i used a macro for the ## PROG below, but i can do something with ints
> and switches to turn on the autoload for a single prog. or just
> copy-paste the boilerplate.
why can't you pass the `struct bpf_program *prog` parameter?
>
> >> + int err; \
> >> + skel = array_elem_test__open(); \
> >> + if (!ASSERT_OK_PTR(skel, "array_elem_test open")) \
> >> + return; \
> >> + bpf_program__set_autoload(skel->progs.x_ ## PROG, true); \
>
> thanks,
>
> barret
>
>