From: Abhishek Khade <abhishekkhade69@gmail.com>
To: bpf@vger.kernel.org, ast@kernel.org, daniel@iogearbox.net,
andrii@kernel.org
Cc: linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
Abhishek Khade <abhishekkhade69@gmail.com>
Subject: [PATCH] bpf: Add network namespace boundary inspection kfuncs
Date: Thu, 5 Nov 2026 17:46:42 +0530 [thread overview]
Message-ID: <20261105121642.38019-1-abhishekkhade69@gmail.com> (raw)
eBPF tracing and networking programs frequently need to determine whether
an execution event originates from the initial host network namespace
(init_net) or an isolated container namespace, as well as extract the
namespace inode identifier for correlation.
Currently, dereferencing task->nsproxy->net_ns directly via BPF CO-RE
presents safety and architectural challenges:
1. nsproxy can be NULL during concurrent unshare or task exit teardown.
2. Direct comparison against &init_net exposes host kernel addresses,
complicating verification and KASLR safety.
3. Reading deep pointer chains without RCU synchronization can trigger
verifier rejections or race against namespace cleanup.
Introduce three BPF kfuncs to safely inspect network namespace boundaries:
- bpf_task_is_init_net(struct task_struct *task): Returns 1 if the task
belongs to the initial host network namespace, 0 otherwise.
- bpf_task_get_netns_inum(struct task_struct *task): Returns the inode
number of the task's network namespace, or 0 on failure.
- bpf_sock_is_init_net(struct sock *sk): Returns 1 if the socket belongs
to init_net, 0 otherwise.
These helpers are registered under generic_btf_ids with the KF_RCU flag,
allowing safe invocation within RCU read-side critical sections across
tracing, LSM, and socket programs.
Add automated selftests under tools/testing/selftests/bpf validating host
detection and container isolation via unshare(CLONE_NEWNET).
Signed-off-by: Abhishek Khade <abhishekkhade69@gmail.com>
---
kernel/bpf/helpers.c | 79 +++++++++++++
.../selftests/bpf/prog_tests/netns_kfunc.c | 104 ++++++++++++++++++
.../selftests/bpf/progs/test_netns_kfunc.c | 32 ++++++
3 files changed, 215 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/netns_kfunc.c
create mode 100644 tools/testing/selftests/bpf/progs/test_netns_kfunc.c
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 6788489a2..3584703d1 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -18,6 +18,9 @@
#include <linux/pid_namespace.h>
#include <linux/poison.h>
#include <linux/proc_ns.h>
+#include <linux/nsproxy.h>
+#include <net/net_namespace.h>
+#include <net/sock.h>
#include <linux/sched/task.h>
#include <linux/security.h>
#include <linux/btf_ids.h>
@@ -2991,6 +2994,77 @@ __bpf_kfunc long bpf_task_under_cgroup(struct task_struct *task,
return ret;
}
+#ifdef CONFIG_NET
+/**
+ * bpf_task_is_init_net - check whether task belongs to initial network namespace
+ * @task: the task to be tested
+ *
+ * Returns true if @task belongs to the initial network namespace (init_net),
+ * or false otherwise (or if @task or its nsproxy is NULL).
+ */
+__bpf_kfunc bool bpf_task_is_init_net(const struct task_struct *task)
+{
+ struct nsproxy *nsproxy;
+ struct net *net;
+ bool ret = false;
+
+ if (!task)
+ return false;
+
+ rcu_read_lock();
+ nsproxy = rcu_dereference(task->nsproxy);
+ if (nsproxy) {
+ net = READ_ONCE(nsproxy->net_ns);
+ ret = (net == &init_net);
+ }
+ rcu_read_unlock();
+
+ return ret;
+}
+
+/**
+ * bpf_task_get_netns_inum - get inode number of task's network namespace
+ * @task: the task to inspect
+ *
+ * Returns the inode number (inum) of @task's network namespace, or 0 if
+ * @task, its nsproxy, or net_ns is NULL.
+ */
+__bpf_kfunc u32 bpf_task_get_netns_inum(const struct task_struct *task)
+{
+ struct nsproxy *nsproxy;
+ struct net *net;
+ u32 inum = 0;
+
+ if (!task)
+ return 0;
+
+ rcu_read_lock();
+ nsproxy = rcu_dereference(task->nsproxy);
+ if (nsproxy) {
+ net = READ_ONCE(nsproxy->net_ns);
+ if (net)
+ inum = net->ns.inum;
+ }
+ rcu_read_unlock();
+
+ return inum;
+}
+
+/**
+ * bpf_sock_is_init_net - check whether socket belongs to initial network namespace
+ * @sk: the socket to be tested
+ *
+ * Returns true if @sk belongs to init_net, or false otherwise (or if @sk is NULL).
+ */
+__bpf_kfunc bool bpf_sock_is_init_net(const struct sock *sk)
+{
+ if (!sk)
+ return false;
+
+ return net_eq(sock_net(sk), &init_net);
+}
+#endif /* CONFIG_NET */
+
BPF_CALL_2(bpf_current_task_under_cgroup, struct bpf_map *, map, u32, idx)
{
struct bpf_array *array = container_of(map, struct bpf_array, map);
@@ -5151,6 +5225,11 @@ BTF_ID_FLAGS(func, bpf_cgroup_from_id, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_task_under_cgroup, KF_RCU)
BTF_ID_FLAGS(func, bpf_task_get_cgroup1, KF_ACQUIRE | KF_RCU | KF_RET_NULL)
#endif
+#ifdef CONFIG_NET
+BTF_ID_FLAGS(func, bpf_task_is_init_net, KF_RCU)
+BTF_ID_FLAGS(func, bpf_task_get_netns_inum, KF_RCU)
+BTF_ID_FLAGS(func, bpf_sock_is_init_net, KF_RCU)
+#endif
BTF_ID_FLAGS(func, bpf_task_from_pid, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_task_from_vpid, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_throw)
diff --git a/tools/testing/selftests/bpf/prog_tests/netns_kfunc.c b/tools/testing/selftests/bpf/prog_tests/netns_kfunc.c
new file mode 100644
index 000000000..3072f7ede
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/netns_kfunc.c
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Abhishek Khade */
+
+#define _GNU_SOURCE
+#include <sched.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <test_progs.h>
+#include "test_netns_kfunc.skel.h"
+
+void test_netns_kfunc(void)
+{
+ struct test_netns_kfunc *skel;
+ int pipefd[2];
+ pid_t pid;
+ int status = 0;
+ u32 host_inum;
+
+ skel = test_netns_kfunc__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ return;
+
+ if (!ASSERT_OK(test_netns_kfunc__attach(skel), "attach"))
+ goto cleanup;
+
+ /* Test 1: Process running in initial network namespace */
+ if (!ASSERT_OK(pipe(pipefd), "pipe_init"))
+ goto cleanup;
+
+ pid = fork();
+ if (!ASSERT_GE(pid, 0, "fork_host")) {
+ close(pipefd[0]);
+ close(pipefd[1]);
+ goto cleanup;
+ }
+
+ if (pid == 0) {
+ close(pipefd[1]);
+ if (read(pipefd[0], &status, sizeof(status)) < 0)
+ exit(1);
+ close(pipefd[0]);
+ execlp("true", "true", NULL);
+ exit(1);
+ }
+
+ skel->bss->target_pid = pid;
+ close(pipefd[0]);
+ if (write(pipefd[1], &status, sizeof(status)) < 0) {
+ close(pipefd[1]);
+ waitpid(pid, NULL, 0);
+ goto cleanup;
+ }
+ close(pipefd[1]);
+ waitpid(pid, &status, 0);
+
+ ASSERT_EQ(skel->bss->inv_is_init_net, 1, "host_task_is_init_net");
+ ASSERT_GT(skel->bss->inv_netns_inum, 0, "host_netns_inum_valid");
+ host_inum = skel->bss->inv_netns_inum;
+
+ /* Reset state */
+ skel->bss->inv_is_init_net = -1;
+ skel->bss->inv_netns_inum = 0;
+
+ /* Test 2: Process running in a new network namespace */
+ if (!ASSERT_OK(pipe(pipefd), "pipe_netns"))
+ goto cleanup;
+
+ pid = fork();
+ if (!ASSERT_GE(pid, 0, "fork_netns")) {
+ close(pipefd[0]);
+ close(pipefd[1]);
+ goto cleanup;
+ }
+
+ if (pid == 0) {
+ close(pipefd[1]);
+ if (read(pipefd[0], &status, sizeof(status)) < 0)
+ exit(1);
+ close(pipefd[0]);
+
+ if (unshare(CLONE_NEWNET) != 0)
+ exit(1);
+
+ execlp("true", "true", NULL);
+ exit(1);
+ }
+
+ skel->bss->target_pid = pid;
+ close(pipefd[0]);
+ if (write(pipefd[1], &status, sizeof(status)) < 0) {
+ close(pipefd[1]);
+ waitpid(pid, NULL, 0);
+ goto cleanup;
+ }
+ close(pipefd[1]);
+ waitpid(pid, &status, 0);
+
+ ASSERT_EQ(skel->bss->inv_is_init_net, 0, "netns_task_not_init_net");
+ ASSERT_GT(skel->bss->inv_netns_inum, 0, "child_netns_inum_valid");
+ ASSERT_NEQ(skel->bss->inv_netns_inum, host_inum, "netns_inum_different");
+
+cleanup:
+ test_netns_kfunc__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/test_netns_kfunc.c b/tools/testing/selftests/bpf/progs/test_netns_kfunc.c
new file mode 100644
index 000000000..3ad49f8ba
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_netns_kfunc.c
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Abhishek Khade */
+
+#include <vmlinux.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_helpers.h>
+
+#include "bpf_misc.h"
+
+bool bpf_task_is_init_net(const struct task_struct *task) __ksym;
+u32 bpf_task_get_netns_inum(const struct task_struct *task) __ksym;
+
+pid_t target_pid;
+int inv_is_init_net = -1;
+u32 inv_netns_inum;
+
+SEC("tp_btf/sched_process_exec")
+int BPF_PROG(on_exec, struct task_struct *task, u64 old_pid,
+ struct linux_binprm *bprm)
+{
+ pid_t pid = task->tgid;
+
+ if (pid != target_pid)
+ return 0;
+
+ inv_is_init_net = bpf_task_is_init_net(task) ? 1 : 0;
+ inv_netns_inum = bpf_task_get_netns_inum(task);
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
--
2.53.0
next reply other threads:[~2026-11-05 12:17 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-05 12:16 Abhishek Khade [this message]
2026-11-05 12:54 ` bot+bpf-ci
2026-11-05 16:39 ` Alexei Starovoitov
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=20261105121642.38019-1-abhishekkhade69@gmail.com \
--to=abhishekkhade69@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
/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
all inboxes | Powered by JetHome®