[PATCH bpf v4 2/4] bpf: Fix UAF due to missing link type check in mprog

From: Pu Lehui

Date: Mon Jul 20 2026 - 09:43:00 EST


From: Pu Lehui <pulehui@xxxxxxxxxx>

In bpf_mprog_link, the code does not check the link->type first before
dereferencing link->prog->type. This missing validation allows a user to
pass an abnormal non-netkit or non-tcx link via relative_fd. If do
BPF_LINK_UPDATE on the abnormal link, it can trigger a UAF issue.

CPU0 CPU1
netkit_link_prog_attach
bpf_mprog_attach
bpf_mprog_tuple_relative
bpf_mprog_link
link = bpf_link_get_from_fd(id_or_fd);
BPF_LINK_UPDATE
...
old_prog = xchg(&link->link.prog, new_prog);
bpf_prog_put(old_prog);
if (type && link->prog->type != type) <-- trigger UAF

Fix this by strictly validate link->type in bpf_mprog_link against the
expected link type. bpf_mprog_tuple_relative is also adjusted to accept
and pass down the expected link type.

Fixes: 053c8e1f235d ("bpf: Add generic attach/detach/query API for multi-progs")
Reported-by: Sashiko <sashiko-bot@xxxxxxxxxx>
Signed-off-by: Pu Lehui <pulehui@xxxxxxxxxx>
---
kernel/bpf/mprog.c | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/kernel/bpf/mprog.c b/kernel/bpf/mprog.c
index 1394168062e8..367f8f10da0a 100644
--- a/kernel/bpf/mprog.c
+++ b/kernel/bpf/mprog.c
@@ -6,7 +6,7 @@

static int bpf_mprog_link(struct bpf_tuple *tuple,
u32 id_or_fd, u32 flags,
- enum bpf_prog_type type)
+ enum bpf_link_type type)
{
struct bpf_link *link = ERR_PTR(-EINVAL);
bool id = flags & BPF_F_ID;
@@ -17,7 +17,7 @@ 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) {
+ if (type && link->type != type) {
bpf_link_put(link);
return -EINVAL;
}
@@ -52,21 +52,22 @@ static int bpf_mprog_prog(struct bpf_tuple *tuple,

static int bpf_mprog_tuple_relative(struct bpf_tuple *tuple,
u32 id_or_fd, u32 flags,
- enum bpf_prog_type type)
+ enum bpf_link_type ltype,
+ enum bpf_prog_type ptype)
{
bool link = flags & BPF_F_LINK;
bool id = flags & BPF_F_ID;

memset(tuple, 0, sizeof(*tuple));
if (link)
- return bpf_mprog_link(tuple, id_or_fd, flags, type);
+ return bpf_mprog_link(tuple, id_or_fd, flags, ltype);
/* If no relevant flag is set and no id_or_fd was passed, then
* tuple link/prog is just NULLed. This is the case when before/
* after selects first/last position without passing fd.
*/
if (!id && !id_or_fd)
return 0;
- return bpf_mprog_prog(tuple, id_or_fd, flags, type);
+ return bpf_mprog_prog(tuple, id_or_fd, flags, ptype);
}

static void bpf_mprog_tuple_put(struct bpf_tuple *tuple)
@@ -243,6 +244,8 @@ int bpf_mprog_attach(struct bpf_mprog_entry *entry,
return -EEXIST;
ret = bpf_mprog_tuple_relative(&rtuple, id_or_fd,
flags & ~BPF_F_REPLACE,
+ link ? link->type :
+ BPF_LINK_TYPE_UNSPEC,
prog_new->type);
if (ret)
return ret;
@@ -343,6 +346,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,
+ link ? link->type :
+ BPF_LINK_TYPE_UNSPEC,
prog ? prog->type :
BPF_PROG_TYPE_UNSPEC);
if (ret)
--
2.34.1