bpf: Allow bpf_d_path() from filp_close_sync()

The d_path selftest closes its descriptors with close_range() so that
its fentry program on filp_close() runs.

Commit 056069eb2b ("fs: make close_range() synchronous") switched
close_range() to filp_close_sync(), so the program still attaches but
is never called:

  test_d_path_basic:FAIL:close trampoline for filp_close was not called

With that series, close(), close_range() and the closes on exec and
exit all go through filp_close_sync(). Few paths still reach
filp_close(), dup2() being one of them, so a program on filp_close()
no longer sees most file closes.

Allow bpf_d_path() from filp_close_sync(), but only from fentry.
filp_close_sync() drops the last file reference inline, so by the time
an fexit program runs, file->f_path may already be freed, with no RCU
delay for NORCU dentries such as pipes and sockets.

Attach the selftest program to filp_close_sync() and keep triggering
it with close_range().

Add a test rejecting bpf_d_path() from fexit/filp_close_sync.

Fixes: 056069eb2b ("fs: make close_range() synchronous")
Signed-off-by: Ihor Solodrai <ihor.solodrai@linux.dev>
Link: https://patch.msgid.link/20261021201400.5409-1-ihor.solodrai@linux.dev
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
Ihor Solodrai 2026-10-21 13:13:59 -07:00 committed by Christian Brauner
parent 81dfa03589
commit 3a00ecc890
No known key found for this signature in database
GPG Key ID: 91C61BC06578DCA2
4 changed files with 39 additions and 6 deletions

View File

@ -974,6 +974,10 @@ BTF_ID(func, vfs_getattr)
BTF_ID(func, filp_close)
BTF_SET_END(btf_allowlist_d_path)
BTF_SET_START(btf_allowlist_d_path_fentry)
BTF_ID(func, filp_close_sync)
BTF_SET_END(btf_allowlist_d_path_fentry)
static bool bpf_d_path_allowed(const struct bpf_prog *prog)
{
if (prog->type == BPF_PROG_TYPE_TRACING &&
@ -983,8 +987,15 @@ static bool bpf_d_path_allowed(const struct bpf_prog *prog)
if (prog->type == BPF_PROG_TYPE_LSM)
return bpf_lsm_is_sleepable_hook(prog->aux->attach_btf_id);
return btf_id_set_contains(&btf_allowlist_d_path,
prog->aux->attach_btf_id);
if (btf_id_set_contains(&btf_allowlist_d_path,
prog->aux->attach_btf_id))
return true;
if (btf_id_set_contains(&btf_allowlist_d_path_fentry,
prog->aux->attach_btf_id))
return prog->expected_attach_type == BPF_TRACE_FENTRY;
return false;
}
BTF_ID_LIST_SINGLE(bpf_d_path_btf_ids, struct, path)

View File

@ -109,8 +109,9 @@ static int trigger_fstat_events(pid_t pid)
fstat(indicatorfd, &fileStat);
out_close:
/* sys_close no longer triggers filp_close, but we can
* call sys_close_range instead which still does
/*
* filp_close_sync() may be inlined into close(2), so use
* close_range(2), which calls it directly.
*/
syscall_close(pipefd[0]);
syscall_close(pipefd[1]);
@ -165,7 +166,7 @@ static void test_d_path_basic(void)
if (CHECK(!bss->called_close,
"close",
"trampoline for filp_close was not called\n"))
"trampoline for filp_close_sync was not called\n"))
goto cleanup;
for (int i = 0; i < MAX_FILES; i++) {

View File

@ -41,7 +41,7 @@ int BPF_PROG(prog_stat, struct path *path, struct kstat *stat,
return 0;
}
SEC("fentry/filp_close")
SEC("fentry/filp_close_sync")
int BPF_PROG(prog_close, struct file *file, void *id)
{
pid_t pid = bpf_get_current_pid_tgid() >> 32;

View File

@ -45,4 +45,25 @@ __naked void d_path_reject(void)
: __clobber_all);
}
SEC("fexit/filp_close_sync")
__description("d_path fexit reject")
__failure __msg("helper call is not allowed in probe")
__naked void d_path_fexit_reject(void)
{
asm volatile (" \
r1 = *(u64 *)(r1 + 0); \
r2 = r10; \
r2 += -8; \
r6 = 0; \
*(u64*)(r2 + 0) = r6; \
r3 = 8 ll; \
call %[bpf_d_path]; \
r0 = 0; \
exit; \
"
:
: __imm(bpf_d_path)
: __clobber_all);
}
char _license[] SEC("license") = "GPL";