* [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; 7+ 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] 7+ 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 17:36 ` Song Liu
2026-10-20 18:24 ` Alexei Starovoitov
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, 2 replies; 7+ 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] 7+ 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
2026-10-20 17:37 ` Song Liu
1 sibling, 1 reply; 7+ 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] 7+ messages in thread
* Re: [PATCH bpf-next 1/2] bpf: fix crash in bpf_path_d_path() for exiting tasks
2026-10-20 10:50 ` [PATCH bpf-next 1/2] " Matt Bobrowski
@ 2026-10-20 17:36 ` Song Liu
2026-10-20 18:24 ` Alexei Starovoitov
1 sibling, 0 replies; 7+ messages in thread
From: Song Liu @ 2026-10-20 17:36 UTC (permalink / raw)
To: Matt Bobrowski
Cc: bpf, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai,
Christian Brauner, Alexander Viro, Jan Kara, linux-fsdevel,
stable
On Tue, Oct 20, 2026 at 3:50 AM Matt Bobrowski <matt@bobrowski.net> wrote:
>
[...]
>
> 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>
Acked-by: Song Liu <song@kernel.org>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [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 2/2] selftests/bpf: test bpf_path_d_path() on a file released at task exit Matt Bobrowski
@ 2026-10-20 17:37 ` Song Liu
0 siblings, 0 replies; 7+ messages in thread
From: Song Liu @ 2026-10-20 17:37 UTC (permalink / raw)
To: Matt Bobrowski
Cc: bpf, Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai,
Christian Brauner, Alexander Viro, Jan Kara, linux-fsdevel,
stable
On Tue, Oct 20, 2026 at 3:50 AM Matt Bobrowski <matt@bobrowski.net> wrote:
>
> 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>
Acked-by: Song Liu <song@kernel.org>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH bpf-next 1/2] bpf: fix crash in bpf_path_d_path() for exiting tasks
2026-10-20 10:50 ` [PATCH bpf-next 1/2] " Matt Bobrowski
2026-10-20 17:36 ` Song Liu
@ 2026-10-20 18:24 ` Alexei Starovoitov
2026-10-20 19:02 ` Al Viro
1 sibling, 1 reply; 7+ messages in thread
From: Alexei Starovoitov @ 2026-10-20 18:24 UTC (permalink / raw)
To: Matt Bobrowski, bpf
Cc: 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,
stable
On Tue, Oct 20, 2026 at 03:50 AM Matt Bobrowski <matt@bobrowski.net> wrote:
> 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.
The crash is gone, but so is the path. file_release progs will get
-EINVAL for every file that a task still has open when it exits.
It also breaks what works today. The d_dname branch in d_path() returns
before it looks at current->fs, so at task exit a pipe or a socket
resolves to "pipe:[123]" now and to -EINVAL with this patch.
And it's not specific to bpf. The comment in fs/cachefiles/namei.c says
that current->fs == NULL in do_exit() breaks d_path() called from ext4
open.
can we fix it in d_path() instead ?
With an empty root prepend_path() goes up to the global root, the same
way d_absolute_path() does. Then the check in bpf_path_d_path() is not
needed and the selftest can expect the path. A chrooted task gets the
path from the global root, but it doesn't have a root anymore.
Something like this (compile tested only):
diff --git a/fs/d_path.c b/fs/d_path.c
index c253090..ea49d33 100644
--- a/fs/d_path.c
+++ b/fs/d_path.c
@@ -265,7 +265,7 @@ static void get_fs_root_rcu(struct fs_struct *fs, struct path *root)
char *d_path(const struct path *path, char *buf, int buflen)
{
DECLARE_BUFFER(b, buf, buflen);
- struct path root;
+ struct path root = {};
/*
* We have various synthetic filesystems that never get mounted. On
@@ -284,7 +284,9 @@ char *d_path(const struct path *path, char *buf, int buflen)
return path->dentry->d_op->d_dname(path->dentry, buf, buflen);
rcu_read_lock();
- get_fs_root_rcu(current->fs, &root);
+ /* Past exit_fs() there is no root to stop at, go up to the global one. */
+ if (current->fs)
+ get_fs_root_rcu(current->fs, &root);
if (unlikely(d_unlinked(path->dentry)))
prepend(&b, " (deleted)", 11);
else
pw-bot: cr
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH bpf-next 1/2] bpf: fix crash in bpf_path_d_path() for exiting tasks
2026-10-20 18:24 ` Alexei Starovoitov
@ 2026-10-20 19:02 ` Al Viro
0 siblings, 0 replies; 7+ messages in thread
From: Al Viro @ 2026-10-20 19:02 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: Matt Bobrowski, bpf, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, Christian Brauner, Jan Kara, linux-fsdevel, stable
On Tue, Oct 20, 2026 at 06:24:42PM +0000, Alexei Starovoitov wrote:
> And it's not specific to bpf. The comment in fs/cachefiles/namei.c says
> that current->fs == NULL in do_exit() breaks d_path() called from ext4
> open.
>
> can we fix it in d_path() instead ?
Once more, with feeling: if you want a pathname relative to your chroot,
use d_path(); otherwise, use d_absolute_path(). If you don't know what
the difference is, just use "I don't give a fuck" and save the cycles
(and avoid potential infoleaks, while we are at it).
Folks, it's _not_ just about an oops - specific callers need to decide
which string do they want.
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-10-20 19:02 UTC | newest]
Thread overview: 7+ 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 17:36 ` Song Liu
2026-10-20 18:24 ` Alexei Starovoitov
2026-10-20 19:02 ` Al Viro
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
2026-10-20 17:37 ` Song Liu
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox