public inbox for bpf@vger.kernel.org
 help / color / mirror / Atom feed
From: Kuniyuki Iwashima <kuniyu@google.com>
To: 2022090917019@std.uestc.edu.cn
Cc: M202472210@hust.edu.cn, bpf@vger.kernel.org,
	daniel@iogearbox.net,  dddddd@hust.edu.cn, dzm91@hust.edu.cn,
	edumazet@google.com,  hust-os-kernel-patches@googlegroups.com,
	jiayuan.chen@linux.dev,  kuniyu@google.com
Subject: Uninitialized Stack Variable / NULL Pointer Dereference in __sys_socket_create via BPF LSM hooks
Date: Tue, 21 Apr 2026 08:47:31 +0000	[thread overview]
Message-ID: <20260421084806.1785541-1-kuniyu@google.com> (raw)
In-Reply-To: <567d3206-74a5-44e5-99c6-779c425f399e@std.uestc.edu.cn>

From: Quan Sun <2022090917019@std.uestc.edu.cn>
Date: Tue, 21 Apr 2026 14:32:35 +0800
> Our fuzzing found an Uninitialized Stack Variable vulnerability in the 
> Linux Socket Subsystem. The issue is triggered when a 
> `BPF_PROG_TYPE_LSM` attached to the `bpf_lsm_socket_create` cgroup hook 
> uses the `bpf_set_retval()` helper to return a value greater than 0 
> (e.g., `1`). This bypasses the actual `socket_create` execution but 
> returns a success status back to `__sys_socket_create`, which then 
> accesses the uninitialized stack variable `sock` leading to a NULL 
> pointer dereference or potential privilege escalation.
> 
> Reported-by: Quan Sun <2022090917019@std.uestc.edu.cn>
> Reported-by: Yinhao Hu <dddddd@hust.edu.cn>
> Reported-by: Kaiyan Mei <M202472210@hust.edu.cn>
> Reviewed-by: Dongliang Mu <dzm91@hust.edu.cn>
> 
> ## Root Cause
> 
> This vulnerability is caused by treating a manipulated return value from 
> an LSM hook as complete success in `__sys_socket_create` without 
> ensuring that the underlying object has been fully initialized.
> 
> 1. `__sys_socket_create()` in `net/socket.c` reserves a pointer variable 
> `struct socket *sock;` on the kernel stack without zero-initializing it.
> 2. It calls `sock_create(family, type, protocol, &sock);` to allocate 
> and initialize the socket object.
> 3. `sock_create` proceeds to allocate the socket internally and invokes 
> the `security_socket_create` LSM hook (which triggers 
> `bpf_lsm_socket_create`).
> 4. If a BPF program is attached to the cgroup LSM hook for 
> `bpf_lsm_socket_create`, it can call `bpf_set_retval(1)` and return `1`.
> 5. Because the BPF hook completes without an error code (< 0), 
> `sock_create` interprets this bypass as success and promptly returns `1` 
> (or a positive bypass value) back to `__sys_socket_create()`. However, 
> the critical variable `sock` is never populated.
> 6. The caller `__sys_socket_create()` checks if `sock_create()`'s return 
> value is `< 0`. Since `1` is not less than `0`, it assumes the `sock` 
> pointer is valid.
> 7. Subsequent socket operations inside `__sys_socket_create()`, such as 
> `sock_map_fd(sock, ...)`, attempt to dereference the uninitialized 
> `sock` stack pointer.
> 
> #### Execution Flow Visualization
> 
> ```text
> Vulnerability Execution Flow
> |
> |--- 1. `__sys_socket_create(...)`
> |    |\
> |    | `-- struct socket *sock; (Uninitialized pointer on stack)
> |    |
> |--- 2. `sock_create(...)` -> `security_socket_create(...)`
> |    |\
> |    | `-- BPF LSM CGROUP hook for `bpf_lsm_socket_create` triggered.
> |    |     |
> |    |     `-- bpf_set_retval(1); return 1;
> |    |
> |--- 3. Context switches back to `sock_create`
> |    |\
> |    | `-- LSM hook returns `1`. `sock_create` skips allocation and 
> returns `1`.
> |    |
> |--- 4. Context switches back to `__sys_socket_create`
> |    |\
> |    | `-- Check return value (1 < 0 is False). Treated as SUCCESS.
> |    |     |
> |    |     `-- `sock_map_fd(sock, ...)`
> |    |         |
> |    |         `-- Dereferences `sock` leading to KERNEL PANIC or Hijack.
> ```
> 
> ## Reproduction Steps
> 
> 1. Load a `BPF_PROG_TYPE_LSM` BPF program that:
>     - Sets the target BTF ID to `bpf_lsm_socket_create`.
>     - Calls the `bpf_set_retval(1)` helper inside the hook.
> 2. Attach the loaded program to a chosen cgroup directory using 
> `BPF_LSM_CGROUP`.
> 3. Add the current process to the targeted cgroup.
> 4. Trigger the creation of a socket by invoking the `socket(AF_INET, 
> SOCK_STREAM, 0)` system call from within the cgroup.
> 5. The `sock_create` function skips allocation, returns 1, and 
> `__sys_socket_create` dereferences the uninitialized stack pointer.

Thanks for the report.

Maybe we could fix like below.
I guess all (most?) places suppose LSM to return 0 or
a negative value, and the btf_id_set_contains() check
would be unnecessary ?

---8<---
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index c5c925f00202..c4b9e9d4de92 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -87,6 +87,13 @@ BTF_ID(func, bpf_lsm_socket_socketpair)
 #endif
 BTF_SET_END(bpf_lsm_unlocked_sockopt_hooks)
 
+BTF_SET_START(bpf_lsm_negative_set_retval_hooks)
+#ifdef CONFIG_SECURITY_NETWORK
+BTF_ID(func, bpf_lsm_socket_create)
+#endif
+BTF_SET_END(bpf_lsm_negative_set_retval_hooks)
+
+
 #ifdef CONFIG_CGROUP_BPF
 void bpf_lsm_find_cgroup_shim(const struct bpf_prog *prog,
 			     bpf_func_t *bpf_func)
@@ -221,12 +228,37 @@ static const struct bpf_func_proto bpf_get_attach_cookie_proto = {
 	.arg1_type	= ARG_PTR_TO_CTX,
 };
 
+BPF_CALL_1(bpf_lsm_set_retval, int, retval)
+{
+	struct bpf_cg_run_ctx *ctx;
+
+	if (retval > 0)
+		return -EINVAL;
+
+	ctx = container_of(current->bpf_ctx, struct bpf_cg_run_ctx, run_ctx);
+	ctx->retval = retval;
+
+	return 0;
+}
+
+const struct bpf_func_proto bpf_lsm_set_retval_proto = {
+	.func		= bpf_lsm_set_retval,
+	.gpl_only	= false,
+	.ret_type	= RET_INTEGER,
+	.arg1_type	= ARG_ANYTHING,
+};
+
 static const struct bpf_func_proto *
 bpf_lsm_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
 {
 	const struct bpf_func_proto *func_proto;
 
 	if (prog->expected_attach_type == BPF_LSM_CGROUP) {
+		if (func_id == BPF_FUNC_set_retval &&
+		    btf_id_set_contains(&bpf_lsm_negative_set_retval_hooks,
+					prog->aux->attach_btf_id))
+			return &bpf_lsm_set_retval_proto;
+
 		func_proto = cgroup_common_func_proto(func_id, prog);
 		if (func_proto)
 			return func_proto;
---8<---

  reply	other threads:[~2026-04-21  8:48 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-21  6:32 Uninitialized Stack Variable / NULL Pointer Dereference in __sys_socket_create via BPF LSM hooks Quan Sun
2026-04-21  8:47 ` Kuniyuki Iwashima [this message]
2026-04-21  9:17   ` Matt Bobrowski
2026-04-21  9:59     ` Kuniyuki Iwashima
2026-04-21 13:05       ` Xu Kuohai
2026-04-21 20:20         ` Matt Bobrowski
2026-04-21  8:50 ` Matt Bobrowski
2026-04-21  9:11 ` 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=20260421084806.1785541-1-kuniyu@google.com \
    --to=kuniyu@google.com \
    --cc=2022090917019@std.uestc.edu.cn \
    --cc=M202472210@hust.edu.cn \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=dddddd@hust.edu.cn \
    --cc=dzm91@hust.edu.cn \
    --cc=edumazet@google.com \
    --cc=hust-os-kernel-patches@googlegroups.com \
    --cc=jiayuan.chen@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