From: "Emil Tsalapatis" <emil@etsalapatis.com>
To: "Jiayuan Chen" <jiayuan.chen@linux.dev>, <bpf@vger.kernel.org>
Cc: "Alexei Starovoitov" <ast@kernel.org>,
"Daniel Borkmann" <daniel@iogearbox.net>,
"Andrii Nakryiko" <andrii@kernel.org>,
"Eduard Zingerman" <eddyz87@gmail.com>,
"Kumar Kartikeya Dwivedi" <memxor@gmail.com>,
"Martin KaFai Lau" <martin.lau@linux.dev>,
"Song Liu" <song@kernel.org>,
"Yonghong Song" <yonghong.song@linux.dev>,
"Jiri Olsa" <jolsa@kernel.org>,
"Emil Tsalapatis" <emil@etsalapatis.com>,
"John Fastabend" <john.fastabend@gmail.com>,
"Shuah Khan" <shuah@kernel.org>,
"Sebastian Andrzej Siewior" <bigeasy@linutronix.de>,
"Clark Williams" <clrkwllms@kernel.org>,
"Steven Rostedt" <rostedt@goodmis.org>,
<linux-kernel@vger.kernel.org>, <linux-kselftest@vger.kernel.org>,
<linux-rt-devel@lists.linux.dev>
Subject: Re: [PATCH bpf-next 3/3] selftests/bpf: Add a test for arena fault-in under memory.max
Date: Mon, 27 Jul 2026 20:54:56 -0400 [thread overview]
Message-ID: <DK9SMAEXY39E.195HV3J7GB8AI@etsalapatis.com> (raw)
In-Reply-To: <20260727062521.376231-4-jiayuan.chen@linux.dev>
On Mon Jul 27, 2026 at 2:24 AM EDT, Jiayuan Chen wrote:
> A child joins a memcg capped at 64M and faults an arena in until it runs
> out of the budget. Without the kernel fix the child dies with SIGSEGV on
> a valid arena address; with it, the child is killed by the memcg OOM
> killer.
>
> With the fix:
>
> serial_test_arena_memcg:PASS:child killed by signal
> serial_test_arena_memcg:PASS:not killed by SIGSEGV
> #5 arena_memcg:OK
>
> # dmesg
> arena_vm_fault+0x655/0xa90
> Memory cgroup out of memory: Killed process 512, file-rss:67920kB
>
> Without the fix:
>
> serial_test_arena_memcg:PASS:child killed by signal
> serial_test_arena_memcg:FAIL:not killed by SIGSEGV: actual 11
> #5 arena_memcg:FAIL
>
> # dmesg
> test_progs[508]: segfault at 100004025000 ...
>
> Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
> ---
> .../selftests/bpf/prog_tests/arena_memcg.c | 158 ++++++++++++++++++
> .../testing/selftests/bpf/progs/arena_memcg.c | 24 +++
> 2 files changed, 182 insertions(+)
> create mode 100644 tools/testing/selftests/bpf/prog_tests/arena_memcg.c
> create mode 100644 tools/testing/selftests/bpf/progs/arena_memcg.c
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/arena_memcg.c b/tools/testing/selftests/bpf/prog_tests/arena_memcg.c
> new file mode 100644
> index 000000000000..9665946fa29e
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/arena_memcg.c
> @@ -0,0 +1,158 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <test_progs.h>
> +#include <fcntl.h>
> +#include <signal.h>
> +#include <sys/mman.h>
> +#include <sys/wait.h>
> +#include <unistd.h>
> +#include <sys/user.h>
> +#ifndef PAGE_SIZE /* on some archs it comes in sys/user.h */
> +#include <unistd.h>
> +#define PAGE_SIZE getpagesize()
> +#endif
> +
> +#include "cgroup_helpers.h"
> +#include "arena_memcg.skel.h"
> +
> +#define CG_PATH "/arena_memcg"
> +
> +/* Budget the arena gets on top of whatever is already charged after load. */
> +#define ARENA_BUDGET (64 * 1024 * 1024)
> +
> +/*
> + * cgroup_helpers builds paths from getpid(), but the work dir belongs to the
> + * process that set the environment up. The child references it through that
> + * pid, so build the path explicitly.
> + */
> +static void cg_file_path(char *buf, size_t sz, pid_t owner, const char *file)
> +{
> + snprintf(buf, sz, "/mnt/cgroup-test-work-dir%d%s/%s", owner, CG_PATH, file);
These are copied over from cgroup_helpers.c, but it's not obvious they
originate from there. Maybe let's move them to cgroup_helpers.h where we
can use them everywhere for consistency.
The main issue I see is that this only triggers consistently with PREEMPT_RT, correct?
I tried with the default vmtest config we have but it does not trigger
at all. More importantly, it doesn't trigger reliably with PREEMPT_RT,
either. Does it for you? Can we cycle forks/frees multiple times to try
and trigger this more reliably?
> +}
> +
> +static long cg_read_ulong(pid_t owner, const char *file)
> +{
> + char path[PATH_MAX], buf[64];
> + long val = -1;
> + FILE *f;
> +
> + cg_file_path(path, sizeof(path), owner, file);
> + f = fopen(path, "r");
> + if (!f)
> + return -1;
> + if (fgets(buf, sizeof(buf), f))
> + val = strtol(buf, NULL, 10);
> + fclose(f);
> + return val;
> +}
> +
> +static int cg_write(pid_t owner, const char *file, const char *val)
> +{
> + char path[PATH_MAX];
> + int fd, len, ret = -1;
> +
> + cg_file_path(path, sizeof(path), owner, file);
> + fd = open(path, O_WRONLY);
> + if (fd < 0)
> + return -1;
> + len = strlen(val);
> + if (write(fd, val, len) == len)
> + ret = 0;
> + close(fd);
> + return ret;
> +}
> +
> +void serial_test_arena_memcg(void)
> +{
> + int cgroup_fd = -1, status;
> + const long ps = PAGE_SIZE;
> + pid_t owner, pid;
> +
> + if (setup_cgroup_environment())
> + return;
> + owner = getpid();
> +
> + cgroup_fd = create_and_get_cgroup(CG_PATH);
> + if (!ASSERT_OK_FD(cgroup_fd, "create_and_get_cgroup"))
> + goto out;
> +
> + /* No memory controller -> nothing to test. */
> + if (cg_read_ulong(owner, "memory.current") < 0) {
> + test__skip();
> + goto out;
> + }
> +
> + pid = fork();
> + if (!ASSERT_GE(pid, 0, "fork"))
> + goto out;
> + if (pid == 0) {
> + struct arena_memcg *cskel;
> + __u32 i, npages;
> + char buf[32];
> + char *base;
> + size_t sz;
> + long cur;
> +
> + /*
> + * Do everything from the child: the arena vma is VM_DONTCOPY so
> + * it would not survive fork(), only the child should be under the
> + * limit so that a memcg OOM cannot pick test_progs, and a map is
> + * charged to the memcg of the task that creates it - so join
> + * before load. Errors are reported to the parent through the exit
> + * code, since ASSERT_* in a forked child does not reach it.
> + */
> + snprintf(buf, sizeof(buf), "%d", getpid());
> + if (cg_write(owner, "cgroup.procs", buf))
> + _exit(2);
> +
> + cskel = arena_memcg__open_and_load();
> + if (!cskel)
> + _exit(3);
> +
> + base = bpf_map__initial_value(cskel->maps.arena, &sz);
> + if (!base)
> + _exit(4);
> + npages = bpf_map__max_entries(cskel->maps.arena);
> +
> + /*
> + * Cap only now, after load: everything but the fault-in is
> + * charged, so the arena gets a fixed budget regardless of what
> + * the load itself cost, and the load can never hit the limit.
> + */
> + cur = cg_read_ulong(owner, "memory.current");
> + if (cur < 0)
> + _exit(5);
> + snprintf(buf, sizeof(buf), "%ld", cur + ARENA_BUDGET);
> + if (cg_write(owner, "memory.max", buf))
> + _exit(6);
> +
> + for (i = 0; i < npages; i++)
> + base[(size_t)i * ps] = 1;
> + _exit(0);
> + }
> +
> + if (!ASSERT_EQ(waitpid(pid, &status, 0), pid, "waitpid"))
> + goto out;
> +
> + /* A non-zero exit means the child failed to set up; the code says where. */
> + if (WIFEXITED(status) && WEXITSTATUS(status)) {
> + ASSERT_OK(WEXITSTATUS(status), "child setup");
> + goto out;
> + }
> +
> + /*
> + * Faulting a valid arena address until memory.max is hit must not look
> + * like an invalid access. Without the fix the fault path allocated with
> + * the non-blocking allocator, turned its -ENOMEM into VM_FAULT_SIGSEGV,
> + * and the child died with SIGSEGV on a valid address; now it is handled
> + * by the memcg OOM path and the child is killed by SIGKILL instead. A
> + * clean exit means the child failed to set up (see the exit codes).
> + */
> + if (!ASSERT_TRUE(WIFSIGNALED(status), "child killed by signal"))
> + goto out;
> + ASSERT_NEQ(WTERMSIG(status), SIGSEGV, "not killed by SIGSEGV");
> +out:
> + if (cgroup_fd >= 0)
> + close(cgroup_fd);
> + cleanup_cgroup_environment();
> +}
> diff --git a/tools/testing/selftests/bpf/progs/arena_memcg.c b/tools/testing/selftests/bpf/progs/arena_memcg.c
> new file mode 100644
> index 000000000000..adecd9e8463e
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/arena_memcg.c
> @@ -0,0 +1,24 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <vmlinux.h>
> +#include <bpf/bpf_helpers.h>
> +#include "bpf_arena_common.h"
> +
> +struct {
> + __uint(type, BPF_MAP_TYPE_ARENA);
> + __uint(map_flags, BPF_F_MMAPABLE);
> + __uint(max_entries, 100000); /* number of pages */
> +#ifdef __TARGET_ARCH_arm64
> + __ulong(map_extra, 0x1ull << 32); /* start of mmap() region */
> +#else
> + __ulong(map_extra, 0x1ull << 44); /* start of mmap() region */
> +#endif
> +} arena SEC(".maps");
> +
> +SEC("syscall")
> +int noop(void *ctx)
> +{
> + return 0;
> +}
> +
> +char _license[] SEC("license") = "GPL";
next prev parent reply other threads:[~2026-07-28 0:54 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-27 6:24 [PATCH bpf-next 0/3] bpf: arena: handle memory.max on fault-in with reclaim/OOM Jiayuan Chen
2026-07-27 6:24 ` [PATCH bpf-next 1/3] bpf: Add a sleepable page allocator for map memory Jiayuan Chen
2026-07-27 6:37 ` sashiko-bot
2026-07-27 7:30 ` Jiayuan Chen
2026-07-28 0:48 ` Emil Tsalapatis
2026-07-27 6:24 ` [PATCH bpf-next 2/3] bpf: arena: allocate the fault-in page outside the lock Jiayuan Chen
2026-07-27 6:42 ` sashiko-bot
2026-07-27 8:00 ` Jiayuan Chen
2026-07-28 1:00 ` Emil Tsalapatis
2026-07-27 7:10 ` bot+bpf-ci
2026-07-28 1:22 ` Emil Tsalapatis
2026-08-03 6:43 ` Jiayuan Chen
2026-07-27 6:24 ` [PATCH bpf-next 3/3] selftests/bpf: Add a test for arena fault-in under memory.max Jiayuan Chen
2026-07-28 0:54 ` Emil Tsalapatis [this message]
2026-08-03 11:42 ` Jiayuan Chen
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=DK9SMAEXY39E.195HV3J7GB8AI@etsalapatis.com \
--to=emil@etsalapatis.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bigeasy@linutronix.de \
--cc=bpf@vger.kernel.org \
--cc=clrkwllms@kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=jiayuan.chen@linux.dev \
--cc=john.fastabend@gmail.com \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-rt-devel@lists.linux.dev \
--cc=martin.lau@linux.dev \
--cc=memxor@gmail.com \
--cc=rostedt@goodmis.org \
--cc=shuah@kernel.org \
--cc=song@kernel.org \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox