CVE-2025-38502: Linux kernel, local BPF cgroup storage out-of-bounds
cgroup BPF local storage is taken from the program that was attached, not the program a tail call entered. A callee compiled against a larger map walks off the caller's smaller buffer. Local. Needs permission to load cgroup BPF.
- Name
- CVE-2025-38502: Linux kernel, local BPF cgroup storage out-of-bounds
- Type
- N-day analysis
- CVE
- CVE-2025-38502
- CVE Risk
- high
- Disclosure Status
- public
- Vendor
- Linux kernel
- Affected
- Linux 5.9 through the fix in 6.17-rc1 (abad3d0) and the listed stable backports (6.16.1, 6.12.46, 6.6.105, 6.1.151, 5.15.192). Some distro 5.10 builds were still open.
- Published
- 28 Oct 2026
- Updated
- 28 Oct 2026
- Tags
- n-day, linux, bpf, cwe-125
Linux kernel, local BPF cgroup storage out-of-bounds
I am @abraxas_null. The proof of concept is on GitHub: abraxas/CVE-2025-38502-Linux-LPE (client). Authorized lab only. This one is local, not HTTP.
Lonial reported that cgroup BPF local storage can be accessed out of bounds across a tail call.
The eBPF verifier type-checks each program in isolation. At runtime, bpf_get_local_storage() does not look up the currently executing program's map. It reads the cgroup-storage pointer out of current->bpf_ctx → bpf_cg_run_ctx → prog_item->cgroup_storage[]. That slot is filled from the originally attached program, not from the program that was tail-called into.
If program A (small BPF_MAP_TYPE_CGROUP_STORAGE value size) tail-calls program B (large value size), B's bpf_get_local_storage() still returns A's smaller buffer. Accesses the verifier allowed against B's map then walk off the end of A's allocation.
The defect was introduced in Linux 5.9 by 7d9c342 (bpf: Make cgroup storages shared between programs on the same cgroup). It was fixed by extending bpf_map_owner with a storage_cookie[] so tail-call combinations are only accepted when the callee uses the same cgroup-storage maps as the caller, or uses none.
CVE-2025-38502 (NVD). CWE-125. Vendor: Linux kernel. Linux 5.9 through the fix in 6.17-rc1 (abad3d0) and the listed stable backports (6.16.1, 6.12.46, 6.6.105, 6.1.151, 5.15.192). Some distro 5.10 builds were still open.
What an attacker can do
A local user who can load cgroup BPF programs tail-calls from a small cgroup-storage map into a program the verifier checked against a larger map. The callee still receives the caller's smaller buffer, so those accesses walk off the allocation. That is local kernel memory corruption on an unpatched kernel. It is not a remote bug. A process that cannot load BPF cannot reach it.
Where the check is missing
Verifier vs runtime
Two cgroup BPF programs, each with its own BPF_MAP_TYPE_CGROUP_STORAGE (shared flavor, BPF_CGROUP_STORAGE_SHARED):
| Program | Role | Storage value size |
|---|---|---|
| A | attached / tail-call caller | small (e.g. fits a given kmalloc order) |
| B | tail-call target | large (verifier allows accesses up to this size) |
The verifier checks A against A's map and B against B's map. Both pass.
At run time the helper does:
ctx = container_of(current->bpf_ctx, struct bpf_cg_run_ctx, run_ctx);
storage = ctx->prog_item->cgroup_storage[stype];
if (stype == BPF_CGROUP_STORAGE_SHARED)
ptr = &READ_ONCE(storage->buf)->data[0];
else
ptr = this_cpu_ptr(storage->percpu_buf);prog_item is the array entry for the program that started the cgroup run, not the program currently executing after bpf_tail_call. B therefore operates on A's storage object.
Why the sizes matter
bpf_cgroup_storage_alloc() sizes the backing buffer from the map's value_size. A's buffer is too small for B's verified accesses. The result is a classic type-confusion of map identity across a control-transfer - the same family of bugs as other BPF "helper sees a different map than the verifier did" issues.
Shared storage on a cgroup
Commit 7d9c342 made cgroup storages shared between programs attached to the same cgroup. That sharing is what makes the run-context slot a single pointer rather than a per-program lookup, and is why kernels before 5.9 are not affected.
The lab
There is no lab/ tree in this repository. The proof of concept is the C client linked above. It is a local kernel reproduction, not a loopback HTTP lab. Run it only on a machine you own.