* [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
* 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 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
* [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 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
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 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.