* [PATCH bpf-next 0/2] bpf: fix crash in bpf_path_d_path() for exiting tasks
@ 2026-10-20 10:50 Matt Bobrowski
2026-10-20 10:50 ` [PATCH bpf-next 1/2] " Matt Bobrowski
2026-10-20 10:50 ` [PATCH bpf-next 2/2] selftests/bpf: test bpf_path_d_path() on a file released at task exit Matt Bobrowski
0 siblings, 2 replies; 3+ messages in thread
From: Matt Bobrowski @ 2026-10-20 10:50 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, Christian Brauner, Alexander Viro, Jan Kara,
linux-fsdevel, Matt Bobrowski
bpf_path_d_path() can run on a file released from exit_task_work(),
after exit_fs() has cleared the task's fs, as a BPF LSM program attached
to file_release does whenever a task exits holding the last reference to
an open file. d_path() then dereferences the NULL fs.
Patch 1 returns -EINVAL from bpf_path_d_path() when current->fs is NULL.
Patch 2 adds a selftest that crashes the kernel without it.
Matt Bobrowski (2):
bpf: fix crash in bpf_path_d_path() for exiting tasks
selftests/bpf: test bpf_path_d_path() on a file released at task exit
fs/bpf_fs_kfuncs.c | 8 +++
.../selftests/bpf/prog_tests/fs_kfuncs.c | 51 +++++++++++++++++++
.../selftests/bpf/progs/test_path_d_path.c | 35 +++++++++++++
3 files changed, 94 insertions(+)
create mode 100644 tools/testing/selftests/bpf/progs/test_path_d_path.c
base-commit: 0007654a7bf13ef2a19afd398bbc6507087f19fd
--
2.47.3
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH bpf-next 1/2] bpf: fix crash in bpf_path_d_path() for exiting tasks
2026-10-20 10:50 [PATCH bpf-next 0/2] bpf: fix crash in bpf_path_d_path() for exiting tasks Matt Bobrowski
@ 2026-10-20 10:50 ` Matt Bobrowski
2026-10-20 10:50 ` [PATCH bpf-next 2/2] selftests/bpf: test bpf_path_d_path() on a file released at task exit Matt Bobrowski
1 sibling, 0 replies; 3+ messages in thread
From: Matt Bobrowski @ 2026-10-20 10:50 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, Christian Brauner, Alexander Viro, Jan Kara,
linux-fsdevel, Matt Bobrowski, stable
bpf_path_d_path() passes its path to d_path(), which resolves it
against current->fs through get_fs_root_rcu() without checking that
the calling task still has one. An exiting task does not. do_exit()
calls exit_fs(), which clears tsk->fs, before exit_task_work(), and a
file whose last reference the task drops in exit_files() has its final
__fput() deferred to that task work.
A BPF LSM program attached to security_file_release, which may pass
file->f_path to bpf_path_d_path() as a trusted argument, dereferences a
NULL fs whenever a task exits with a file still open:
Call trace:
lock_acquire+0x8c/0x218
d_path+0x270/0x378
bpf_path_d_path+0x28/0x80
bpf_prog_9801cf2b982f05b2_file_release+0x1d0/0xc60
bpf_trampoline_6442515074+0x7c/0x100
bpf_lsm_file_release+0x8/0x20
security_file_release+0x58/0x90
__fput+0xcc/0x320
____fput+0x1c/0x30
task_work_run+0x88/0x100
do_exit+0x210/0x3e8
do_group_exit+0x3c/0xa0
__arm64_sys_exit_group+0x20/0x30
With lockdep enabled the fault is taken in lock_acquire(), from the
seqcount read in get_fs_root_rcu(); without it, on the read of
fs->seq itself.
Fix this by returning -EINVAL from bpf_path_d_path() when current->fs
is NULL. Only the task itself clears its fs, in exit_fs(), so the check
cannot race with that.
Fixes: d08e2045ebf0 ("bpf: introduce new VFS based BPF kfuncs")
Cc: stable@vger.kernel.org
Signed-off-by: Matt Bobrowski <matt@bobrowski.net>
---
fs/bpf_fs_kfuncs.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c
index 08f0847c4970..6665a78218e1 100644
--- a/fs/bpf_fs_kfuncs.c
+++ b/fs/bpf_fs_kfuncs.c
@@ -87,6 +87,14 @@ __bpf_kfunc int bpf_path_d_path(const struct path *path, char *buf, size_t buf__
if (!buf__sz)
return -EINVAL;
+ /*
+ * An exiting task releases its last files from exit_task_work(), after
+ * exit_fs() has already dropped its ->fs, which d_path() uses to
+ * resolve against.
+ */
+ if (!current->fs)
+ return -EINVAL;
+
ret = d_path(path, buf, buf__sz);
if (IS_ERR(ret))
return PTR_ERR(ret);
--
2.47.3
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH bpf-next 2/2] selftests/bpf: test bpf_path_d_path() on a file released at task exit
2026-10-20 10:50 [PATCH bpf-next 0/2] bpf: fix crash in bpf_path_d_path() for exiting tasks Matt Bobrowski
2026-10-20 10:50 ` [PATCH bpf-next 1/2] " Matt Bobrowski
@ 2026-10-20 10:50 ` Matt Bobrowski
1 sibling, 0 replies; 3+ messages in thread
From: Matt Bobrowski @ 2026-10-20 10:50 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, Christian Brauner, Alexander Viro, Jan Kara,
linux-fsdevel, Matt Bobrowski, stable
Add a fs_kfuncs subtest for the crash fixed by the previous commit. A
child opens a file and exits without closing it, so exit_files() drops
the last reference and the file is released from exit_task_work(),
after exit_fs() has cleared the task's fs. A file_release program calls
bpf_path_d_path() on that release, matched by the file's inode number,
and records what it returned.
Without the fix the subtest crashes the kernel in d_path(). With it,
bpf_path_d_path() returns -EINVAL.
Fixes: d08e2045ebf0 ("bpf: introduce new VFS based BPF kfuncs")
Cc: stable@vger.kernel.org
Signed-off-by: Matt Bobrowski <matt@bobrowski.net>
---
.../selftests/bpf/prog_tests/fs_kfuncs.c | 51 +++++++++++++++++++
.../selftests/bpf/progs/test_path_d_path.c | 35 +++++++++++++
2 files changed, 86 insertions(+)
create mode 100644 tools/testing/selftests/bpf/progs/test_path_d_path.c
diff --git a/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c b/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c
index 43a26ec69a8e..510cf11c8bd8 100644
--- a/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c
+++ b/tools/testing/selftests/bpf/prog_tests/fs_kfuncs.c
@@ -4,12 +4,15 @@
#include <stdlib.h>
#include <sys/types.h>
#include <sys/xattr.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
#include <linux/fsverity.h>
#include <unistd.h>
#include <test_progs.h>
#include "test_get_xattr.skel.h"
#include "test_set_remove_xattr.skel.h"
#include "test_fsverity.skel.h"
+#include "test_path_d_path.skel.h"
static const char testfile[] = "/tmp/test_progs_fs_kfuncs";
@@ -268,6 +271,51 @@ static void test_fsverity(void)
remove(testfile);
}
+static void test_path_d_path_on_exit(void)
+{
+ struct test_path_d_path *skel = NULL;
+ struct stat st;
+ int fd, status;
+ pid_t pid;
+
+ fd = open(testfile, O_CREAT | O_RDONLY, 0644);
+ if (!ASSERT_GE(fd, 0, "create_file"))
+ return;
+ close(fd);
+
+ if (!ASSERT_OK(stat(testfile, &st), "stat"))
+ goto out;
+
+ skel = test_path_d_path__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "test_path_d_path__open_and_load"))
+ goto out;
+
+ skel->bss->monitored_ino = st.st_ino;
+
+ if (!ASSERT_OK(test_path_d_path__attach(skel), "test_path_d_path__attach"))
+ goto out;
+
+ /*
+ * The child exits with testfile still open, so exit_files() drops
+ * the last reference and the file is released from exit_task_work(),
+ * after exit_fs().
+ */
+ pid = fork();
+ if (!ASSERT_GE(pid, 0, "fork"))
+ goto out;
+ if (pid == 0)
+ _exit(open(testfile, O_RDONLY) < 0);
+
+ if (!ASSERT_EQ(waitpid(pid, &status, 0), pid, "waitpid"))
+ goto out;
+ ASSERT_TRUE(WIFEXITED(status) && !WEXITSTATUS(status), "child_open");
+ ASSERT_EQ(skel->bss->called, 1, "called");
+ ASSERT_EQ(skel->bss->ret, -EINVAL, "bpf_path_d_path");
+out:
+ test_path_d_path__destroy(skel);
+ remove(testfile);
+}
+
void test_fs_kfuncs(void)
{
/* Matches xattr_names in progs/test_get_xattr.c */
@@ -288,4 +336,7 @@ void test_fs_kfuncs(void)
if (test__start_subtest("fsverity"))
test_fsverity();
+
+ if (test__start_subtest("path_d_path_on_exit"))
+ test_path_d_path_on_exit();
}
diff --git a/tools/testing/selftests/bpf/progs/test_path_d_path.c b/tools/testing/selftests/bpf/progs/test_path_d_path.c
new file mode 100644
index 000000000000..4ea50a067819
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_path_d_path.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <errno.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+#include "bpf_experimental.h"
+
+#define PF_EXITING 0x00000004
+
+char _license[] SEC("license") = "GPL";
+
+__u64 monitored_ino;
+int called;
+int ret;
+
+/*
+ * A task that exits with a file still open releases it from
+ * exit_task_work(), after exit_fs() has already cleared its fs.
+ */
+SEC("lsm/file_release")
+int BPF_PROG(path_d_path_on_exit, struct file *file)
+{
+ struct task_struct *task = bpf_get_current_task_btf();
+ char buf[64];
+
+ if (file->f_inode->i_ino != monitored_ino ||
+ !(task->flags & PF_EXITING))
+ return 0;
+
+ ret = bpf_path_d_path(&file->f_path, buf, sizeof(buf));
+ called = 1;
+ return 0;
+}
--
2.47.3
^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-20 10:50 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-20 10:50 [PATCH bpf-next 0/2] bpf: fix crash in bpf_path_d_path() for exiting tasks Matt Bobrowski
2026-10-20 10:50 ` [PATCH bpf-next 1/2] " Matt Bobrowski
2026-10-20 10:50 ` [PATCH bpf-next 2/2] selftests/bpf: test bpf_path_d_path() on a file released at task exit Matt Bobrowski
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.