libbpf: Selftests for resizing datasec maps
authorJP Kobryn <inwardvessel@gmail.com>
Wed, 24 May 2023 00:45:37 +0000 (17:45 -0700)
committerAndrii Nakryiko <andrii@kernel.org>
Wed, 24 May 2023 18:44:17 +0000 (11:44 -0700)
This patch adds test coverage for resizing datasec maps. The first two
subtests resize the bss and custom data sections. In both cases, an
initial array (of length one) has its element set to one. After resizing
the rest of the array is filled with ones as well. A BPF program is then
run to sum the respective arrays and back on the userspace side the sum
is checked to be equal to the number of elements.
The third subtest attempts to perform resizing under conditions that
will result in either the resize failing or the BTF info being cleared.

Signed-off-by: JP Kobryn <inwardvessel@gmail.com>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Acked-by: Stanislav Fomichev <sdf@google.com>
Link: https://lore.kernel.org/bpf/20230524004537.18614-3-inwardvessel@gmail.com
tools/testing/selftests/bpf/prog_tests/global_map_resize.c [new file with mode: 0644]
tools/testing/selftests/bpf/progs/test_global_map_resize.c [new file with mode: 0644]

diff --git a/tools/testing/selftests/bpf/prog_tests/global_map_resize.c b/tools/testing/selftests/bpf/prog_tests/global_map_resize.c
new file mode 100644 (file)
index 0000000..fd41425
--- /dev/null
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */
+#include <errno.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+#include "test_global_map_resize.skel.h"
+#include "test_progs.h"
+
+static void run_prog_bss_array_sum(void)
+{
+       (void)syscall(__NR_getpid);
+}
+
+static void run_prog_data_array_sum(void)
+{
+       (void)syscall(__NR_getuid);
+}
+
+static void global_map_resize_bss_subtest(void)
+{
+       int err;
+       struct test_global_map_resize *skel;
+       struct bpf_map *map;
+       const __u32 desired_sz = sizeof(skel->bss->sum) + sysconf(_SC_PAGE_SIZE) * 2;
+       size_t array_len, actual_sz;
+
+       skel = test_global_map_resize__open();
+       if (!ASSERT_OK_PTR(skel, "test_global_map_resize__open"))
+               goto teardown;
+
+       /* set some initial value before resizing.
+        * it is expected this non-zero value will be preserved
+        * while resizing.
+        */
+       skel->bss->array[0] = 1;
+
+       /* resize map value and verify the new size */
+       map = skel->maps.bss;
+       err = bpf_map__set_value_size(map, desired_sz);
+       if (!ASSERT_OK(err, "bpf_map__set_value_size"))
+               goto teardown;
+       if (!ASSERT_EQ(bpf_map__value_size(map), desired_sz, "resize"))
+               goto teardown;
+
+       /* set the expected number of elements based on the resized array */
+       array_len = (desired_sz - sizeof(skel->bss->sum)) / sizeof(skel->bss->array[0]);
+       if (!ASSERT_GT(array_len, 1, "array_len"))
+               goto teardown;
+
+       skel->bss = bpf_map__initial_value(skel->maps.bss, &actual_sz);
+       if (!ASSERT_OK_PTR(skel->bss, "bpf_map__initial_value (ptr)"))
+               goto teardown;
+       if (!ASSERT_EQ(actual_sz, desired_sz, "bpf_map__initial_value (size)"))
+               goto teardown;
+
+       /* fill the newly resized array with ones,
+        * skipping the first element which was previously set
+        */
+       for (int i = 1; i < array_len; i++)
+               skel->bss->array[i] = 1;
+
+       /* set global const values before loading */
+       skel->rodata->pid = getpid();
+       skel->rodata->bss_array_len = array_len;
+       skel->rodata->data_array_len = 1;
+
+       err = test_global_map_resize__load(skel);
+       if (!ASSERT_OK(err, "test_global_map_resize__load"))
+               goto teardown;
+       err = test_global_map_resize__attach(skel);
+       if (!ASSERT_OK(err, "test_global_map_resize__attach"))
+               goto teardown;
+
+       /* run the bpf program which will sum the contents of the array.
+        * since the array was filled with ones,verify the sum equals array_len
+        */
+       run_prog_bss_array_sum();
+       if (!ASSERT_EQ(skel->bss->sum, array_len, "sum"))
+               goto teardown;
+
+teardown:
+       test_global_map_resize__destroy(skel);
+}
+
+static void global_map_resize_data_subtest(void)
+{
+       int err;
+       struct test_global_map_resize *skel;
+       struct bpf_map *map;
+       const __u32 desired_sz = sysconf(_SC_PAGE_SIZE) * 2;
+       size_t array_len, actual_sz;
+
+       skel = test_global_map_resize__open();
+       if (!ASSERT_OK_PTR(skel, "test_global_map_resize__open"))
+               goto teardown;
+
+       /* set some initial value before resizing.
+        * it is expected this non-zero value will be preserved
+        * while resizing.
+        */
+       skel->data_custom->my_array[0] = 1;
+
+       /* resize map value and verify the new size */
+       map = skel->maps.data_custom;
+       err = bpf_map__set_value_size(map, desired_sz);
+       if (!ASSERT_OK(err, "bpf_map__set_value_size"))
+               goto teardown;
+       if (!ASSERT_EQ(bpf_map__value_size(map), desired_sz, "resize"))
+               goto teardown;
+
+       /* set the expected number of elements based on the resized array */
+       array_len = (desired_sz - sizeof(skel->bss->sum)) / sizeof(skel->data_custom->my_array[0]);
+       if (!ASSERT_GT(array_len, 1, "array_len"))
+               goto teardown;
+
+       skel->data_custom = bpf_map__initial_value(skel->maps.data_custom, &actual_sz);
+       if (!ASSERT_OK_PTR(skel->data_custom, "bpf_map__initial_value (ptr)"))
+               goto teardown;
+       if (!ASSERT_EQ(actual_sz, desired_sz, "bpf_map__initial_value (size)"))
+               goto teardown;
+
+       /* fill the newly resized array with ones,
+        * skipping the first element which was previously set
+        */
+       for (int i = 1; i < array_len; i++)
+               skel->data_custom->my_array[i] = 1;
+
+       /* set global const values before loading */
+       skel->rodata->pid = getpid();
+       skel->rodata->bss_array_len = 1;
+       skel->rodata->data_array_len = array_len;
+
+       err = test_global_map_resize__load(skel);
+       if (!ASSERT_OK(err, "test_global_map_resize__load"))
+               goto teardown;
+       err = test_global_map_resize__attach(skel);
+       if (!ASSERT_OK(err, "test_global_map_resize__attach"))
+               goto teardown;
+
+       /* run the bpf program which will sum the contents of the array.
+        * since the array was filled with ones,verify the sum equals array_len
+        */
+       run_prog_data_array_sum();
+       if (!ASSERT_EQ(skel->bss->sum, array_len, "sum"))
+               goto teardown;
+
+teardown:
+       test_global_map_resize__destroy(skel);
+}
+
+static void global_map_resize_invalid_subtest(void)
+{
+       int err;
+       struct test_global_map_resize *skel;
+       struct bpf_map *map;
+       __u32 element_sz, desired_sz;
+
+       skel = test_global_map_resize__open();
+       if (!ASSERT_OK_PTR(skel, "test_global_map_resize__open"))
+               return;
+
+        /* attempt to resize a global datasec map to size
+         * which does NOT align with array
+         */
+       map = skel->maps.data_custom;
+       if (!ASSERT_NEQ(bpf_map__btf_value_type_id(map), 0, ".data.custom initial btf"))
+               goto teardown;
+       /* set desired size a fraction of element size beyond an aligned size */
+       element_sz = sizeof(skel->data_custom->my_array[0]);
+       desired_sz = element_sz + element_sz / 2;
+       /* confirm desired size does NOT align with array */
+       if (!ASSERT_NEQ(desired_sz % element_sz, 0, "my_array alignment"))
+               goto teardown;
+       err = bpf_map__set_value_size(map, desired_sz);
+       /* confirm resize is OK but BTF info is cleared */
+       if (!ASSERT_OK(err, ".data.custom bpf_map__set_value_size") ||
+           !ASSERT_EQ(bpf_map__btf_key_type_id(map), 0, ".data.custom clear btf key") ||
+           !ASSERT_EQ(bpf_map__btf_value_type_id(map), 0, ".data.custom clear btf val"))
+               goto teardown;
+
+       /* attempt to resize a global datasec map whose only var is NOT an array */
+       map = skel->maps.data_non_array;
+       if (!ASSERT_NEQ(bpf_map__btf_value_type_id(map), 0, ".data.non_array initial btf"))
+               goto teardown;
+       /* set desired size to arbitrary value */
+       desired_sz = 1024;
+       err = bpf_map__set_value_size(map, desired_sz);
+       /* confirm resize is OK but BTF info is cleared */
+       if (!ASSERT_OK(err, ".data.non_array bpf_map__set_value_size") ||
+           !ASSERT_EQ(bpf_map__btf_key_type_id(map), 0, ".data.non_array clear btf key") ||
+           !ASSERT_EQ(bpf_map__btf_value_type_id(map), 0, ".data.non_array clear btf val"))
+               goto teardown;
+
+       /* attempt to resize a global datasec map
+        * whose last var is NOT an array
+        */
+       map = skel->maps.data_array_not_last;
+       if (!ASSERT_NEQ(bpf_map__btf_value_type_id(map), 0, ".data.array_not_last initial btf"))
+               goto teardown;
+       /* set desired size to a multiple of element size */
+       element_sz = sizeof(skel->data_array_not_last->my_array_first[0]);
+       desired_sz = element_sz * 8;
+       /* confirm desired size aligns with array */
+       if (!ASSERT_EQ(desired_sz % element_sz, 0, "my_array_first alignment"))
+               goto teardown;
+       err = bpf_map__set_value_size(map, desired_sz);
+       /* confirm resize is OK but BTF info is cleared */
+       if (!ASSERT_OK(err, ".data.array_not_last bpf_map__set_value_size") ||
+           !ASSERT_EQ(bpf_map__btf_key_type_id(map), 0, ".data.array_not_last clear btf key") ||
+           !ASSERT_EQ(bpf_map__btf_value_type_id(map), 0, ".data.array_not_last clear btf val"))
+               goto teardown;
+
+teardown:
+       test_global_map_resize__destroy(skel);
+}
+
+void test_global_map_resize(void)
+{
+       if (test__start_subtest("global_map_resize_bss"))
+               global_map_resize_bss_subtest();
+
+       if (test__start_subtest("global_map_resize_data"))
+               global_map_resize_data_subtest();
+
+       if (test__start_subtest("global_map_resize_invalid"))
+               global_map_resize_invalid_subtest();
+}
diff --git a/tools/testing/selftests/bpf/progs/test_global_map_resize.c b/tools/testing/selftests/bpf/progs/test_global_map_resize.c
new file mode 100644 (file)
index 0000000..2588f23
--- /dev/null
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+
+char _license[] SEC("license") = "GPL";
+
+/* rodata section */
+const volatile pid_t pid;
+const volatile size_t bss_array_len;
+const volatile size_t data_array_len;
+
+/* bss section */
+int sum = 0;
+int array[1];
+
+/* custom data secton */
+int my_array[1] SEC(".data.custom");
+
+/* custom data section which should NOT be resizable,
+ * since it contains a single var which is not an array
+ */
+int my_int SEC(".data.non_array");
+
+/* custom data section which should NOT be resizable,
+ * since its last var is not an array
+ */
+int my_array_first[1] SEC(".data.array_not_last");
+int my_int_last SEC(".data.array_not_last");
+
+SEC("tp/syscalls/sys_enter_getpid")
+int bss_array_sum(void *ctx)
+{
+       if (pid != (bpf_get_current_pid_tgid() >> 32))
+               return 0;
+
+       sum = 0;
+
+       for (size_t i = 0; i < bss_array_len; ++i)
+               sum += array[i];
+
+       return 0;
+}
+
+SEC("tp/syscalls/sys_enter_getuid")
+int data_array_sum(void *ctx)
+{
+       if (pid != (bpf_get_current_pid_tgid() >> 32))
+               return 0;
+
+       sum = 0;
+
+       for (size_t i = 0; i < data_array_len; ++i)
+               sum += my_array[i];
+
+       return 0;
+}