]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
selftests/bpf: Add test for cgroup storage OOB read
authorLang Xu <xulang@uniontech.com>
Thu, 2 Apr 2026 07:42:36 +0000 (15:42 +0800)
committerAlexei Starovoitov <ast@kernel.org>
Sun, 12 Apr 2026 20:36:32 +0000 (13:36 -0700)
Add a test case to reproduce the out-of-bounds read issue when copying
from a cgroup storage map to a pcpu map with a value_size not rounded
up to 8 bytes.

The test creates:
1. A CGROUP_STORAGE map with 4-byte value (not 8-byte aligned)
2. A LRU_PERCPU_HASH map with 4-byte value (same size)

When a socket is created in the cgroup, the BPF program triggers
bpf_map_update_elem() which calls copy_map_value_long(). This function
rounds up the copy size to 8 bytes, but the cgroup storage buffer is
only 4 bytes, causing an OOB read (before the fix).

Signed-off-by: Lang Xu <xulang@uniontech.com>
Link: https://lore.kernel.org/r/D63BF0DBFF1EA122+20260402074236.2187154-2-xulang@uniontech.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
tools/testing/selftests/bpf/prog_tests/cgroup_storage.c
tools/testing/selftests/bpf/progs/cgroup_storage.c

index cf395715ced47037d9730dca9e2aba098a55582a..5b56dc893e7325eac4d94a05bb21c74256ca11c5 100644 (file)
@@ -1,5 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 
+#include <unistd.h>
+#include <sys/socket.h>
 #include <test_progs.h>
 #include "cgroup_helpers.h"
 #include "network_helpers.h"
@@ -94,3 +96,43 @@ cleanup_cgroup:
        close(cgroup_fd);
        cleanup_cgroup_environment();
 }
+
+void test_cgroup_storage_oob(void)
+{
+       struct cgroup_storage *skel;
+       int cgroup_fd, sock_fd;
+
+       cgroup_fd = cgroup_setup_and_join(TEST_CGROUP);
+       if (!ASSERT_OK_FD(cgroup_fd, "create cgroup"))
+               return;
+
+       /* Load and attach BPF program */
+       skel = cgroup_storage__open_and_load();
+       if (!ASSERT_OK_PTR(skel, "cgroup_storage__open_and_load"))
+               goto cleanup_cgroup;
+
+       skel->links.trigger_oob = bpf_program__attach_cgroup(skel->progs.trigger_oob,
+                                                             cgroup_fd);
+       if (!ASSERT_OK_PTR(skel->links.trigger_oob, "attach_cgroup"))
+               goto cleanup_skel;
+
+       /* Create a socket to trigger cgroup/sock_create hook.
+        * This will execute our BPF program and trigger the OOB read
+        * if the bug is present (before the fix).
+        */
+       sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
+       if (!ASSERT_OK_FD(sock_fd, "create socket"))
+               goto cleanup_skel;
+
+       close(sock_fd);
+
+       /* If we reach here without a kernel panic or KASAN report,
+        * the test passes (the fix is working).
+        */
+
+cleanup_skel:
+       cgroup_storage__destroy(skel);
+cleanup_cgroup:
+       close(cgroup_fd);
+       cleanup_cgroup_environment();
+}
index db1e4d2d328101aabe61472140982c820e1fb555..59da1d95e5b966ddbf79adb9097024a401e34167 100644 (file)
@@ -21,4 +21,47 @@ int bpf_prog(struct __sk_buff *skb)
        return (*counter & 1);
 }
 
+/* Maps for OOB test */
+struct {
+       __uint(type, BPF_MAP_TYPE_CGROUP_STORAGE);
+       __type(key, struct bpf_cgroup_storage_key);
+       __type(value, __u32);  /* 4-byte value - not 8-byte aligned */
+} cgroup_storage_oob SEC(".maps");
+
+struct {
+       __uint(type, BPF_MAP_TYPE_LRU_PERCPU_HASH);
+       __uint(max_entries, 1);
+       __type(key, __u32);
+       __type(value, __u32);  /* 4-byte value - same as cgroup storage */
+} lru_map SEC(".maps");
+
+SEC("cgroup/sock_create")
+int trigger_oob(struct bpf_sock *sk)
+{
+       __u32 key = 0;
+       __u32 *cgroup_val;
+       __u32 value = 0x12345678;
+
+       /* Get cgroup storage value */
+       cgroup_val = bpf_get_local_storage(&cgroup_storage_oob, 0);
+       if (!cgroup_val)
+               return 0;
+
+       /* Initialize cgroup storage */
+       *cgroup_val = value;
+
+       /* This triggers the OOB read:
+        * bpf_map_update_elem() -> htab_map_update_elem() ->
+        * pcpu_init_value() -> copy_map_value_long() ->
+        * bpf_obj_memcpy(..., long_memcpy=true) ->
+        * bpf_long_memcpy(dst, src, round_up(4, 8))
+        *
+        * The copy size is rounded up to 8 bytes, but cgroup_val
+        * points to a 4-byte buffer, causing a 4-byte OOB read.
+        */
+       bpf_map_update_elem(&lru_map, &key, cgroup_val, BPF_ANY);
+
+       return 1;
+}
+
 char _license[] SEC("license") = "GPL";