
Research repository for CVE-2025-38502, a Linux kernel BPF cgroup local storage out-of-bounds access via tail calls enabling local privilege escalation.

Abraxas Labs · abraxaslabs.tech · github.com/abraxas · @abraxas_null
Linux kernel BPF cgroup local storage out-of-bounds access via tail calls
| CVE | CVE-2025-38502 |
| CWE | CWE-125 — Out-of-bounds Read |
| Vendor | Linux kernel |
| Component | kernel/bpf/core.c, include/linux/bpf.h (cgroup local storage + tail calls) |
| Impact | Local kernel memory corruption; privilege escalation is in scope on unpatched kernels |
| Attack vector | Local (AV:L) |
| Privileges | Low (PR:L) — a process that can load BPF programs of type CGROUP_SKB (or equivalent cgroup-attached programs) |
| User interaction | None |
| CVSS 3.1 (kernel.org CNA) | 7.8 HIGH — CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| CVSS 3.1 (NVD) | 7.1 HIGH — CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H |
| Public | 16 August 2025 |
| Upstream fix | abad3d0 in 6.17-rc1; backported to 6.16.1, 6.12.46, 6.6.105, 6.1.151, 5.15.192 |
Research / educational use only. Do not run, deploy, or use material in this repository against any host unless you have explicit written permission from both the party hosting this repository and the owner of the target systems. Found in the wild.
The source filename CVE-2025-3850-Linux-LPE-Abaraxas-Labs.c truncates the identifier. The published record is CVE-2025-38502. There is no Linux CVE CVE-2025-3850.
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.
This is a local kernel heap out-of-bounds access. Severity scoring varies by vendor because they disagree on whether the primitive is “read-only DoS” or full memory corruption:
| Source | Score | Integrity | Notes |
|---|---|---|---|
| kernel.org CNA / cve.org | 7.8 HIGH | High | C:H/I:H/A:H — treats the bug as full local impact |
| NVD | 7.1 HIGH | None | C:H/I:N/A:H — confidentiality + availability |
| Ubuntu | Medium (7.1) | — | USN-7909 |
| Red Hat | 4.0 LOW | None | C:N/I:N/A:L — rated as limited availability |
| Amazon Linux | 4.0 Medium | None | same vector as Red Hat |
| SUSE | 6.1 Moderate | None | some SLE 15 streams marked WONTFIX |
What that means in practice:
struct bpf_array sprayed into the same slab/order) can be corrupted.map->ops, hijack a helper, commit_creds / namespace switch). That is why this tree labels the issue LPE. Red Hat's lower score reflects their product-specific assessment, not the absence of the bug.The bug does not require a network-facing service. It is local. It does not require a TTY, a setuid helper, or user interaction.
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.
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.
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.
BPF_PROG_TEST_RUN on a BPF_PROG_TYPE_CGROUP_SKB program allocates cgroup storage for the duration of the test. That allocation sits on the kernel heap next to whatever else was recently freed in the same size class — including struct bpf_array maps whose value_size was chosen to land in the same kmalloc order. An OOB from the storage buffer can therefore reach bpf_map fields (ops, RCU list, value[]) of a neighbouring array map.
That heap-layout detail is why a “mere OOB read” advisory and an LPE write-up can describe the same CVE.
Introduced: Linux 5.9 (7d9c3427894fe70d1347b4820476bf37736d2ff0)
Unaffected: all kernels before 5.9
| Series | Affected | First fixed |
|---|---|---|
| 5.9 – 5.15 | 5.9 through 5.15.191 | 5.15.192 (c1c74584…) |
| 5.16 – 6.1 | 5.16 through 6.1.150 | 6.1.151 (66da7cee…) |
| 6.2 – 6.6 | 6.2 through 6.6.104 | 6.6.105 (7acfa07c…) |
| 6.7 – 6.12 | 6.7 through 6.12.45 | 6.12.46 (41688d1f…) |
| 6.13 – 6.16 | 6.13 through 6.16.0 | 6.16.1 (19341d5c…) |
| mainline | until the fix landed | 6.17-rc1 (abad3d0b…) |
One-liner: