Skip to content

bpf: Add LINK_DETACH for perf links#11291

Closed
kernel-patches-daemon-bpf[bot] wants to merge 3 commits intobpf-next_basefrom
series/1061570=>bpf-next
Closed

bpf: Add LINK_DETACH for perf links#11291
kernel-patches-daemon-bpf[bot] wants to merge 3 commits intobpf-next_basefrom
series/1061570=>bpf-next

Conversation

@kernel-patches-daemon-bpf
Copy link
Copy Markdown

Pull request for series with
subject: bpf: Add LINK_DETACH for perf links
version: 2
url: https://patchwork.kernel.org/project/netdevbpf/list/?series=1061570

@kernel-patches-daemon-bpf
Copy link
Copy Markdown
Author

Upstream branch: 05c9b2e
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=1061570
version: 2

@kernel-patches-review-bot
Copy link
Copy Markdown

commit b516258b868c21c868025562bd2d812f909cfe80
Author: Florian Lehner <dev@der-flo.net>

bpf: Add LINK_DETACH support for perf link

This patch adds a detach callback for bpf_perf_link so that
BPF_LINK_DETACH can be used to detach a BPF program from its perf
event while keeping the link fd alive.  The release path is made
idempotent with xchg(), and fill_link_info/show_fdinfo gain NULL
guards for the detached case.

> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> --- a/kernel/bpf/syscall.c
> +++ b/kernel/bpf/syscall.c
> @@ -3863,10 +3863,23 @@ struct bpf_perf_link {
>  static void bpf_perf_link_release(struct bpf_link *link)
>  {
>  	struct bpf_perf_link *perf_link = container_of(link, struct bpf_perf_link, link);
> -	struct perf_event *event = perf_link->perf_file->private_data;
> +	struct perf_event *event;
> +	struct file *perf_file;
> +
> +	/* bpf_perf_link_detach() may have already released perf_file */
> +	perf_file = xchg(&perf_link->perf_file, NULL);
> +	if (!perf_file)
> +		return;
> +
> +	event = perf_file->private_data;
>  	perf_event_free_bpf_prog(event);
> -	fput(perf_link->perf_file);
> +	fput(perf_file);
> +}

[ ... ]

> @@ -4075,9 +4088,13 @@ static int bpf_perf_link_fill_link_info(const struct bpf_link *link,
>  {
>  	struct bpf_perf_link *perf_link;
>  	const struct perf_event *event;
> +	struct file *perf_file;
> +
>  	perf_link = container_of(link, struct bpf_perf_link, link);
> -	event = perf_get_event(perf_link->perf_file);
> +	perf_file = READ_ONCE(perf_link->perf_file);
> +	if (!perf_file)
> +		return 0;
> +	event = perf_get_event(perf_file);

Could bpf_perf_link_fill_link_info() race with bpf_perf_link_release()
here?

After READ_ONCE reads a non-NULL perf_file, no reference is taken on
the file.  A concurrent BPF_LINK_DETACH on another CPU can call
bpf_perf_link_release(), which does xchg() followed by fput().  If
the perf event fd has already been closed by userspace, fput() drops
the last reference.  Once the detaching thread returns to userspace,
task_work runs __fput and frees the file struct.

At that point perf_get_event() dereferences file->f_op on freed
memory:

  Thread A (BPF_OBJ_GET_INFO_BY_FD)      Thread B (BPF_LINK_DETACH)
  --------------------------------       --------------------------
  perf_file = READ_ONCE(...)  [non-NULL]
                                         xchg(&..., NULL)
                                         fput(perf_file)
                                         return to userspace
                                         task_work -> __fput -> free
  perf_get_event(perf_file) -> UAF

The xdp and cgroup link types handle this by serializing
fill_link_info and release under a shared lock (rtnl_lock and
cgroup_lock respectively):

kernel/bpf/cgroup.c:bpf_cgroup_link_fill_link_info() {
    cgroup_lock();
    if (cg_link->cgroup)
        cg_id = cgroup_id(cg_link->cgroup);
    cgroup_unlock();
}

net/core/dev.c:bpf_xdp_link_fill_link_info() {
    rtnl_lock();
    if (xdp_link->dev)
        ifindex = xdp_link->dev->ifindex;
    rtnl_unlock();
}

The same issue applies to bpf_perf_link_show_fdinfo() below.

> @@ -4143,9 +4160,13 @@ static void bpf_perf_link_show_fdinfo(const struct bpf_link *link,
>  {
>  	struct bpf_perf_link *perf_link;
>  	const struct perf_event *event;
> +	struct file *perf_file;
> +
>  	perf_link = container_of(link, struct bpf_perf_link, link);
> -	event = perf_get_event(perf_link->perf_file);
> +	perf_file = READ_ONCE(perf_link->perf_file);
> +	if (!perf_file)
> +		return;
> +	event = perf_get_event(perf_file);

Same race as bpf_perf_link_fill_link_info() above -- a concurrent
BPF_LINK_DETACH can free perf_file between the READ_ONCE and the
perf_get_event() call.



AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

In-Reply-To-Subject: bpf: Add LINK_DETACH support for perf link
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/22690413273

@kernel-patches-daemon-bpf
Copy link
Copy Markdown
Author

@kernel-patches-daemon-bpf
Copy link
Copy Markdown
Author

Upstream branch: 4faa189
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=1061570
version: 2

@kernel-patches-daemon-bpf
Copy link
Copy Markdown
Author

Upstream branch: 748f9c6
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=1061570
version: 2

@kernel-patches-daemon-bpf
Copy link
Copy Markdown
Author

Upstream branch: 6dd780f
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=1061570
version: 2

@kernel-patches-daemon-bpf
Copy link
Copy Markdown
Author

Upstream branch: 099bded
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=1061570
version: 2

@kernel-patches-daemon-bpf
Copy link
Copy Markdown
Author

Upstream branch: bd2e02e
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=1061570
version: 2

@kernel-patches-daemon-bpf
Copy link
Copy Markdown
Author

Upstream branch: bd2e02e
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=1061570
version: 2

@kernel-patches-daemon-bpf
Copy link
Copy Markdown
Author

Upstream branch: 0c55d48
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=1061570
version: 2

@kernel-patches-daemon-bpf
Copy link
Copy Markdown
Author

Upstream branch: e95e85b
series: https://patchwork.kernel.org/project/netdevbpf/list/?series=1061570
version: 2

Add bpf_perf_link_detach() to allow detaching a BPF program from its
perf event via BPF_LINK_DETACH while keeping the link file descriptor
alive.  This mirrors the existing behavior for xdp and cgroup links
and enables temporarily disabling uprobes or other perf event-based
programs that are attached via bpf_perf_links without closing the link.

bpf_perf_link_release() is made idempotent using xchg() so that closing
the link fd after an explicit LINK_DETACH does not call
perf_event_free_bpf_prog() a second time.  bpf_perf_link_fill_link_info()
and bpf_perf_link_show_fdinfo() gain NULL guards so that querying an
already-detached link returns an empty result rather than dereferencing
a stale pointer.

Signed-off-by: Florian Lehner <dev@der-flo.net>
Add serial_test_perf_link_detach() to verify that the new LINK_DETACH
support for BPF perf links works correctly.  The test creates a link
to a BPF program for a software perf event, confirms the program is
executed, calls bpf_link__detach() to exercise the BPF_LINK_DETACH syscall
path, and then verifies the program is no longer invoked after detach.

Signed-off-by: Florian Lehner <dev@der-flo.net>
@kernel-patches-daemon-bpf
Copy link
Copy Markdown
Author

At least one diff in series https://patchwork.kernel.org/project/netdevbpf/list/?series=1061570 expired. Closing PR.

@kernel-patches-daemon-bpf kernel-patches-daemon-bpf Bot deleted the series/1061570=>bpf-next branch March 13, 2026 16:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant