From c08b50dd174a1dadb67f215239664c9096a5c4bd Mon Sep 17 00:00:00 2001 From: Alexei Starovoitov Date: Mon, 2 Nov 2026 06:42:31 +0000 Subject: [PATCH] selftests/bpf: Run arena_mmap serially and test MAP_FIXED addresses Each arena reserves 4 GiB of vmalloc space. The KASAN shadow of arenas from parallel tests can exhaust the memory of a CI VM. Thus commit a14f9ba080c1 ("selftests/bpf: Run arena tests serially") made arena_* and libarena* serial. Do the same for arena_mmap, which the commit below added later. Add four subtests for "bpf: Don't move a MAP_FIXED address in arena_get_unmapped_area()". Test that mmap() with MAP_FIXED or MAP_FIXED_NOREPLACE fails with EINVAL if the range crosses a 4 GiB boundary. Test that mmap() with MAP_FIXED also fails for a range that wraps and for a map_extra range that ends above TASK_SIZE. Today arena_map_alloc() accepts such a map_extra. map_extra_last_pages also passes if the kernel refuses the map, because no task can map this range. The arch code gets a range before the arena code tests it for a crossing. For the last two ranges LoongArch returns -EINVAL, and powerpc with the hash MMU returns -ENOMEM. There the two subtests do not reach that test. On the other architectures with arenas, the arena code returns -EINVAL or __get_unmapped_area() returns -ENOMEM. Thus the two subtests accept both errnos. All these errors occur before mmap() changes a mapping. Two other changes of that patch have no subtest. With MAP_FIXED the arch code gets len and not 2 * len. A 64-bit task on x86, arm64 or riscv does not see this. LoongArch, s390 and powerpc with the hash MMU compare the end of the range with different limits. In arena_mmap the unsigned ret changes only the errno of map_extra_last_pages, which accepts both. Fixes: 14d3e5bf9ffe ("selftests/bpf: Test the length and the address of an arena mmap()") Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20261102064231.2153879-3-alexei.starovoitov@gmail.com --- .../selftests/bpf/prog_tests/arena_mmap.c | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/arena_mmap.c b/tools/testing/selftests/bpf/prog_tests/arena_mmap.c index 2babc9ecf182..f55d950720f1 100644 --- a/tools/testing/selftests/bpf/prog_tests/arena_mmap.c +++ b/tools/testing/selftests/bpf/prog_tests/arena_mmap.c @@ -5,9 +5,12 @@ #define ARENA_PAGES 8 -static void test_refused(int pages, int flags, int err) +/* err == 0: the range ends above TASK_SIZE. EINVAL or ENOMEM, this depends on the arch */ +static void test_refused(__u64 map_extra, __u64 addr, int pages, int flags, int err) { - LIBBPF_OPTS(bpf_map_create_opts, opts, .map_flags = BPF_F_MMAPABLE); + LIBBPF_OPTS(bpf_map_create_opts, opts, + .map_flags = BPF_F_MMAPABLE, + .map_extra = map_extra); void *area; int fd; @@ -16,22 +19,35 @@ static void test_refused(int pages, int flags, int err) test__skip(); return; } + /* No task can map this range, so the kernel can also refuse the map. */ + if (fd < 0 && map_extra) + return; if (!ASSERT_GE(fd, 0, "bpf_map_create")) return; - area = mmap(NULL, pages * getpagesize(), PROT_READ | PROT_WRITE, - MAP_SHARED | flags, fd, 0); - if (ASSERT_EQ(area, MAP_FAILED, "mmap")) - ASSERT_EQ(errno, err, "errno"); - else + area = mmap((void *)(unsigned long)addr, pages * getpagesize(), + PROT_READ | PROT_WRITE, MAP_SHARED | flags, fd, 0); + if (!ASSERT_EQ(area, MAP_FAILED, "mmap")) munmap(area, pages * getpagesize()); + else if (err || errno != ENOMEM) + ASSERT_EQ(errno, err ?: EINVAL, "errno"); close(fd); } -void test_arena_mmap(void) +void serial_test_arena_mmap(void) { + __u64 page = getpagesize(), last = -(ARENA_PAGES * page); + if (test__start_subtest("long")) - test_refused(ARENA_PAGES + 1, 0, E2BIG); + test_refused(0, 0, ARENA_PAGES + 1, 0, E2BIG); if (test__start_subtest("address_0")) - test_refused(ARENA_PAGES, MAP_FIXED, EINVAL); + test_refused(0, 0, ARENA_PAGES, MAP_FIXED, EINVAL); + if (test__start_subtest("crosses_4g")) + test_refused(0, (1ULL << 32) - page, ARENA_PAGES, MAP_FIXED, EINVAL); + if (test__start_subtest("crosses_4g_noreplace")) + test_refused(0, (1ULL << 32) - page, ARENA_PAGES, MAP_FIXED_NOREPLACE, EINVAL); + if (test__start_subtest("wraps")) + test_refused(0, -page, ARENA_PAGES, MAP_FIXED, 0); + if (test__start_subtest("map_extra_last_pages")) + test_refused(last, last, ARENA_PAGES, MAP_FIXED, 0); }