[PATCH bpf v4] bpf: Fix UAF when reading prog type in bpf_mprog_link

From: Pu Lehui

Date: Tue Jul 28 2026 - 03:43:34 EST


From: Pu Lehui <pulehui@xxxxxxxxxx>

In bpf_mprog_link, the code currently allows a user to pass an abnormal
non-netkit or non-tcx link via relative_fd. If a concurrent
BPF_LINK_UPDATE is performed on this abnormal link before dereferencing
link->prog->type in bpf_mprog_link(), it can trigger a UAF issue.

CPU0 CPU1
netkit_link_prog_attach
bpf_mprog_attach
bpf_mprog_tuple_relative
bpf_mprog_link
/* non-netkit or non-tcx link */
link = bpf_link_get_from_fd(id_or_fd);
BPF_LINK_UPDATE on relative link
...
old_prog = xchg(&link->link.prog, new_prog);
bpf_prog_put(old_prog);
if (type && link->prog->type != type) <-- trigger UAF

The reason for the UAF is that each subsystem provides its own
protection for link->prog. Since there is no cross subsystem protection
(if not considering the RCU of prog tear down), dereferencing the prog
of an anchor link that does not belong to the current subsystem is not
safe: it may have been freed.

To resolve this, we access link->prog under RCU protection to safely
fetch the pointer and guarantee its lifetime during the type check.
Meanwhile, add a comment explaining that when ptype == UNSPEC in
bpf_mprog_detach, it acts as a wildcard.

Fixes: 053c8e1f235d ("bpf: Add generic attach/detach/query API for multi-progs")
Reported-by: Sashiko <sashiko-bot@xxxxxxxxxx>
Reviewed-by: Emil Tsalapatis <emil@xxxxxxxxxxxxxxx>
Signed-off-by: Pu Lehui <pulehui@xxxxxxxxxx>
---
v4:
- Access prog->type under rcu protection to simplify the repair logic,
and let unconditional detachment make sense when the bare prog or
link->prog being detached is NULL.
- Add Reviewed-by tag by Emil.
- Separate from patchset [0]. (Andrii)

Link: https://lore.kernel.org/bpf/f87b53c0-8f00-45a6-82db-8242fa9b143f@xxxxxxxxxxxxxxx [0]

v3: https://lore.kernel.org/bpf/20260722072326.1545677-3-pulehui@xxxxxxxxxxxxxxx
- BPF_F_LINK flag set means the relative id_or_fd is a link, not the object being
attached. We can not get the link while attach a bare prog with relative link.
So passing expected link type from callers of bpf_mprog_attach/detach. (Sashiko)
- Add comment to explain that why ptype == UNSPEC. (Emil)

v2: https://lore.kernel.org/bpf/20260721041048.1394085-3-pulehui@xxxxxxxxxxxxxxx
- Improve commit msg for patch 2. (Amery)

v1: https://lore.kernel.org/bpf/20260720134547.1289964-3-pulehui@xxxxxxxxxxxxxxx

kernel/bpf/mprog.c | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/kernel/bpf/mprog.c b/kernel/bpf/mprog.c
index 1394168062e8..af3e6c1c6a1f 100644
--- a/kernel/bpf/mprog.c
+++ b/kernel/bpf/mprog.c
@@ -10,6 +10,8 @@ static int bpf_mprog_link(struct bpf_tuple *tuple,
{
struct bpf_link *link = ERR_PTR(-EINVAL);
bool id = flags & BPF_F_ID;
+ bool type_mismatch = false;
+ struct bpf_prog *prog;

if (id)
link = bpf_link_by_id(id_or_fd);
@@ -17,13 +19,20 @@ static int bpf_mprog_link(struct bpf_tuple *tuple,
link = bpf_link_get_from_fd(id_or_fd);
if (IS_ERR(link))
return PTR_ERR(link);
- if (type && link->prog->type != type) {
+
+ rcu_read_lock();
+ prog = READ_ONCE(link->prog);
+ if (!prog || (type && prog->type != type))
+ type_mismatch = true;
+ rcu_read_unlock();
+
+ if (type_mismatch) {
bpf_link_put(link);
return -EINVAL;
}

tuple->link = link;
- tuple->prog = link->prog;
+ tuple->prog = prog;
return 0;
}

@@ -343,8 +352,8 @@ int bpf_mprog_detach(struct bpf_mprog_entry *entry,
if (!bpf_mprog_total(entry))
return -ENOENT;
ret = bpf_mprog_tuple_relative(&rtuple, id_or_fd, flags,
- prog ? prog->type :
- BPF_PROG_TYPE_UNSPEC);
+ /* Use UNSPEC as wildcard when prog is NULL */
+ prog ? prog->type : BPF_PROG_TYPE_UNSPEC);
if (ret)
return ret;
if (dtuple.prog) {
--
2.34.1