* [PATCH bpf v6 0/4] Fixes for bpf link update
@ 2026-07-22 7:23 Pu Lehui
2026-07-22 7:23 ` [PATCH bpf v6 1/4] bpf: Fix potential UAF in bpf_netns_link_update_prog Pu Lehui
` (4 more replies)
0 siblings, 5 replies; 15+ messages in thread
From: Pu Lehui @ 2026-07-22 7:23 UTC (permalink / raw)
To: bpf, linux-kernel, Amery Hung, Emil Tsalapatis, Mykyta Yatsenko
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Pu Lehui, Pu Lehui
From: Pu Lehui <pulehui@huawei.com>
This patch series addresses a few concurrency UAF issues within bpf link update.
v6:
- 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 in bpf_mprog_detach. (Emil)
v5: https://lore.kernel.org/bpf/20260721041048.1394085-1-pulehui@huaweicloud.com
- Remove rcu_read_lock_trace as rcu_read_unlock is sufficient. (Mykyta)
- Improve commit msg for patch 2. (Amery)
- Add Reviewed-by tag by Amery.
v4: https://lore.kernel.org/bpf/20260720134547.1289964-1-pulehui@huaweicloud.com
- Add new fix for UAF due to missing link type check in mprog. (Sashiko)
- Add new fix for UAF in bpf_netns_link_update_prog. (Sashiko)
- Include the storage and flags rollbacks to patch4 for sake of code rigor,
as it's hard to make update_effective_progs fail in __cgroup_bpf_attach.
v3: https://lore.kernel.org/bpf/20260720033055.1215477-1-pulehui@huaweicloud.com
- Add new fix for UAF issue when reading bpf link info. (Sashiko)
- Add new fix for storage not restored when update_effective_progs
failed. (Sashiko)
v2: https://lore.kernel.org/bpf/20260717073343.958862-1-pulehui@huaweicloud.com
- Fix invalid access for in-place update when storage changed. (Sashiko)
v1: https://lore.kernel.org/bpf/20260714014659.401063-1-pulehui@huaweicloud.com
Pu Lehui (4):
bpf: Fix potential UAF in bpf_netns_link_update_prog
bpf: Fix UAF due to missing link type check in mprog
bpf: Fix potential UAF when reading bpf link info
bpf, cgroup: Fix storage null-ptr-deref after replacing prog
drivers/net/netkit.c | 13 ++++++-----
include/linux/bpf_mprog.h | 6 ++++--
kernel/bpf/cgroup.c | 44 +++++++++++++++++++++++++++++++++++++-
kernel/bpf/mprog.c | 23 ++++++++++++--------
kernel/bpf/net_namespace.c | 14 +++++++-----
kernel/bpf/syscall.c | 21 ++++++++++++++----
kernel/bpf/tcx.c | 13 ++++++-----
7 files changed, 99 insertions(+), 35 deletions(-)
--
2.34.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH bpf v6 1/4] bpf: Fix potential UAF in bpf_netns_link_update_prog
2026-07-22 7:23 [PATCH bpf v6 0/4] Fixes for bpf link update Pu Lehui
@ 2026-07-22 7:23 ` Pu Lehui
2026-07-27 23:01 ` Andrii Nakryiko
2026-07-22 7:23 ` [PATCH bpf v6 2/4] bpf: Fix UAF due to missing link type check in mprog Pu Lehui
` (3 subsequent siblings)
4 siblings, 1 reply; 15+ messages in thread
From: Pu Lehui @ 2026-07-22 7:23 UTC (permalink / raw)
To: bpf, linux-kernel, Amery Hung, Emil Tsalapatis, Mykyta Yatsenko
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Pu Lehui, Pu Lehui
From: Pu Lehui <pulehui@huawei.com>
In bpf_netns_link_update_prog, the checks for old_prog and prog type
are currently performed locklessly before acquiring netns_bpf_mutex.
This creates a race condition that can lead to a UAF issue.
If two threads concurrently execute BPF_LINK_UPDATE on the same netns
link, the following execution path can trigger a UAF:
CPU0 CPU1
bpf_netns_link_update_prog
if (old_prog && old_prog != link->prog)
return -EPERM;
bpf_netns_link_update_prog
if (old_prog && old_prog != link->prog)
...
old_prog = xchg(&link->prog, new_prog);
bpf_prog_put(old_prog);
if (new_prog->type != link->prog->type) <-- trigger UAF
Fix this by moving the old_prog and prog->type checks inside the
netns_bpf_mutex critical section.
Fixes: 7f045a49fee0 ("bpf: Add link-based BPF program attachment to network namespace")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Reviewed-by: Amery Hung <ameryhung@gmail.com>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
Signed-off-by: Pu Lehui <pulehui@huawei.com>
---
kernel/bpf/net_namespace.c | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/kernel/bpf/net_namespace.c b/kernel/bpf/net_namespace.c
index 25f30f9edaef..9fc62db1441c 100644
--- a/kernel/bpf/net_namespace.c
+++ b/kernel/bpf/net_namespace.c
@@ -171,13 +171,17 @@ static int bpf_netns_link_update_prog(struct bpf_link *link,
struct net *net;
int idx, ret;
- if (old_prog && old_prog != link->prog)
- return -EPERM;
- if (new_prog->type != link->prog->type)
- return -EINVAL;
-
mutex_lock(&netns_bpf_mutex);
+ if (old_prog && old_prog != link->prog) {
+ ret = -EPERM;
+ goto out_unlock;
+ }
+ if (new_prog->type != link->prog->type) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
net = net_link->net;
if (!net || !check_net(net)) {
/* Link auto-detached or netns dying */
--
2.34.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH bpf v6 2/4] bpf: Fix UAF due to missing link type check in mprog
2026-07-22 7:23 [PATCH bpf v6 0/4] Fixes for bpf link update Pu Lehui
2026-07-22 7:23 ` [PATCH bpf v6 1/4] bpf: Fix potential UAF in bpf_netns_link_update_prog Pu Lehui
@ 2026-07-22 7:23 ` Pu Lehui
2026-07-22 16:25 ` Emil Tsalapatis
2026-07-22 7:23 ` [PATCH bpf v6 3/4] bpf: Fix potential UAF when reading bpf link info Pu Lehui
` (2 subsequent siblings)
4 siblings, 1 reply; 15+ messages in thread
From: Pu Lehui @ 2026-07-22 7:23 UTC (permalink / raw)
To: bpf, linux-kernel, Amery Hung, Emil Tsalapatis, Mykyta Yatsenko
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Pu Lehui, Pu Lehui
From: Pu Lehui <pulehui@huawei.com>
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 doing
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
/* 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. Therefore, we need to validate link->type
to reject foreign anchors.
Fix this by strictly validating link->type in bpf_mprog_link against the
expected link type. mprog APIs is also adjusted to accept and pass down
the expected link type. 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@kernel.org>
Reviewed-by: Amery Hung <ameryhung@gmail.com>
Signed-off-by: Pu Lehui <pulehui@huawei.com>
---
drivers/net/netkit.c | 13 ++++++-------
include/linux/bpf_mprog.h | 6 ++++--
kernel/bpf/mprog.c | 23 ++++++++++++++---------
kernel/bpf/tcx.c | 13 ++++++-------
4 files changed, 30 insertions(+), 25 deletions(-)
diff --git a/drivers/net/netkit.c b/drivers/net/netkit.c
index a3931cd82132..99ddf2befb23 100644
--- a/drivers/net/netkit.c
+++ b/drivers/net/netkit.c
@@ -768,7 +768,7 @@ int netkit_prog_attach(const union bpf_attr *attr, struct bpf_prog *prog)
}
ret = bpf_mprog_attach(entry, &entry_new, prog, NULL, replace_prog,
attr->attach_flags, attr->relative_fd,
- attr->expected_revision);
+ attr->expected_revision, BPF_LINK_TYPE_NETKIT);
if (!ret) {
if (entry != entry_new) {
netkit_entry_update(dev, entry_new);
@@ -802,7 +802,7 @@ int netkit_prog_detach(const union bpf_attr *attr, struct bpf_prog *prog)
goto out;
}
ret = bpf_mprog_detach(entry, &entry_new, prog, NULL, attr->attach_flags,
- attr->relative_fd, attr->expected_revision);
+ attr->relative_fd, attr->expected_revision, BPF_LINK_TYPE_NETKIT);
if (!ret) {
if (!bpf_mprog_total(entry_new))
entry_new = NULL;
@@ -850,7 +850,7 @@ static int netkit_link_prog_attach(struct bpf_link *link, u32 flags,
ASSERT_RTNL();
entry = netkit_entry_fetch(dev, true);
ret = bpf_mprog_attach(entry, &entry_new, link->prog, link, NULL, flags,
- id_or_fd, revision);
+ id_or_fd, revision, BPF_LINK_TYPE_NETKIT);
if (!ret) {
if (entry != entry_new) {
netkit_entry_update(dev, entry_new);
@@ -877,7 +877,7 @@ static void netkit_link_release(struct bpf_link *link)
ret = -ENOENT;
goto out;
}
- ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0, 0, 0);
+ ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0, 0, 0, BPF_LINK_TYPE_NETKIT);
if (!ret) {
if (!bpf_mprog_total(entry_new))
entry_new = NULL;
@@ -919,9 +919,8 @@ static int netkit_link_update(struct bpf_link *link, struct bpf_prog *nprog,
ret = -ENOENT;
goto out;
}
- ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog,
- BPF_F_REPLACE | BPF_F_ID,
- link->prog->aux->id, 0);
+ ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog, BPF_F_REPLACE | BPF_F_ID,
+ link->prog->aux->id, 0, BPF_LINK_TYPE_NETKIT);
if (!ret) {
WARN_ON_ONCE(entry != entry_new);
oprog = xchg(&link->prog, nprog);
diff --git a/include/linux/bpf_mprog.h b/include/linux/bpf_mprog.h
index 0b9f4caeeb0a..1fbe1a923968 100644
--- a/include/linux/bpf_mprog.h
+++ b/include/linux/bpf_mprog.h
@@ -321,12 +321,14 @@ int bpf_mprog_attach(struct bpf_mprog_entry *entry,
struct bpf_mprog_entry **entry_new,
struct bpf_prog *prog_new, struct bpf_link *link,
struct bpf_prog *prog_old,
- u32 flags, u32 id_or_fd, u64 revision);
+ u32 flags, u32 id_or_fd, u64 revision,
+ enum bpf_link_type expected_link_type);
int bpf_mprog_detach(struct bpf_mprog_entry *entry,
struct bpf_mprog_entry **entry_new,
struct bpf_prog *prog, struct bpf_link *link,
- u32 flags, u32 id_or_fd, u64 revision);
+ u32 flags, u32 id_or_fd, u64 revision,
+ enum bpf_link_type expected_link_type);
int bpf_mprog_query(const union bpf_attr *attr, union bpf_attr __user *uattr,
struct bpf_mprog_entry *entry);
diff --git a/kernel/bpf/mprog.c b/kernel/bpf/mprog.c
index 1394168062e8..b4a1b35ff569 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)
@@ -226,7 +227,8 @@ int bpf_mprog_attach(struct bpf_mprog_entry *entry,
struct bpf_mprog_entry **entry_new,
struct bpf_prog *prog_new, struct bpf_link *link,
struct bpf_prog *prog_old,
- u32 flags, u32 id_or_fd, u64 revision)
+ u32 flags, u32 id_or_fd, u64 revision,
+ enum bpf_link_type expected_link_type)
{
struct bpf_tuple rtuple, ntuple = {
.prog = prog_new,
@@ -243,6 +245,7 @@ int bpf_mprog_attach(struct bpf_mprog_entry *entry,
return -EEXIST;
ret = bpf_mprog_tuple_relative(&rtuple, id_or_fd,
flags & ~BPF_F_REPLACE,
+ expected_link_type,
prog_new->type);
if (ret)
return ret;
@@ -328,7 +331,8 @@ static int bpf_mprog_fetch(struct bpf_mprog_entry *entry,
int bpf_mprog_detach(struct bpf_mprog_entry *entry,
struct bpf_mprog_entry **entry_new,
struct bpf_prog *prog, struct bpf_link *link,
- u32 flags, u32 id_or_fd, u64 revision)
+ u32 flags, u32 id_or_fd, u64 revision,
+ enum bpf_link_type expected_link_type)
{
struct bpf_tuple rtuple, dtuple = {
.prog = prog,
@@ -343,8 +347,9 @@ 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);
+ expected_link_type,
+ /* Use UNSPEC as wildcard when prog is NULL */
+ prog ? prog->type : BPF_PROG_TYPE_UNSPEC);
if (ret)
return ret;
if (dtuple.prog) {
diff --git a/kernel/bpf/tcx.c b/kernel/bpf/tcx.c
index 02db0113b8e7..f208cef13a98 100644
--- a/kernel/bpf/tcx.c
+++ b/kernel/bpf/tcx.c
@@ -38,7 +38,7 @@ int tcx_prog_attach(const union bpf_attr *attr, struct bpf_prog *prog)
}
ret = bpf_mprog_attach(entry, &entry_new, prog, NULL, replace_prog,
attr->attach_flags, attr->relative_fd,
- attr->expected_revision);
+ attr->expected_revision, BPF_LINK_TYPE_TCX);
if (!ret) {
if (entry != entry_new) {
tcx_entry_update(dev, entry_new, ingress);
@@ -76,7 +76,7 @@ int tcx_prog_detach(const union bpf_attr *attr, struct bpf_prog *prog)
goto out;
}
ret = bpf_mprog_detach(entry, &entry_new, prog, NULL, attr->attach_flags,
- attr->relative_fd, attr->expected_revision);
+ attr->relative_fd, attr->expected_revision, BPF_LINK_TYPE_TCX);
if (!ret) {
if (!tcx_entry_is_active(entry_new))
entry_new = NULL;
@@ -152,7 +152,7 @@ static int tcx_link_prog_attach(struct bpf_link *link, u32 flags, u32 id_or_fd,
if (!entry)
return -ENOMEM;
ret = bpf_mprog_attach(entry, &entry_new, link->prog, link, NULL, flags,
- id_or_fd, revision);
+ id_or_fd, revision, BPF_LINK_TYPE_TCX);
if (!ret) {
if (entry != entry_new) {
tcx_entry_update(dev, entry_new, ingress);
@@ -183,7 +183,7 @@ static void tcx_link_release(struct bpf_link *link)
ret = -ENOENT;
goto out;
}
- ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0, 0, 0);
+ ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0, 0, 0, BPF_LINK_TYPE_TCX);
if (!ret) {
if (!tcx_entry_is_active(entry_new))
entry_new = NULL;
@@ -229,9 +229,8 @@ static int tcx_link_update(struct bpf_link *link, struct bpf_prog *nprog,
ret = -ENOENT;
goto out;
}
- ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog,
- BPF_F_REPLACE | BPF_F_ID,
- link->prog->aux->id, 0);
+ ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog, BPF_F_REPLACE | BPF_F_ID,
+ link->prog->aux->id, 0, BPF_LINK_TYPE_TCX);
if (!ret) {
WARN_ON_ONCE(entry != entry_new);
oprog = xchg(&link->prog, nprog);
--
2.34.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH bpf v6 3/4] bpf: Fix potential UAF when reading bpf link info
2026-07-22 7:23 [PATCH bpf v6 0/4] Fixes for bpf link update Pu Lehui
2026-07-22 7:23 ` [PATCH bpf v6 1/4] bpf: Fix potential UAF in bpf_netns_link_update_prog Pu Lehui
2026-07-22 7:23 ` [PATCH bpf v6 2/4] bpf: Fix UAF due to missing link type check in mprog Pu Lehui
@ 2026-07-22 7:23 ` Pu Lehui
2026-07-27 23:15 ` Andrii Nakryiko
2026-07-22 7:23 ` [PATCH bpf v6 4/4] bpf, cgroup: Fix storage null-ptr-deref after replacing prog Pu Lehui
2026-07-27 23:17 ` [PATCH bpf v6 0/4] Fixes for bpf link update Andrii Nakryiko
4 siblings, 1 reply; 15+ messages in thread
From: Pu Lehui @ 2026-07-22 7:23 UTC (permalink / raw)
To: bpf, linux-kernel, Amery Hung, Emil Tsalapatis, Mykyta Yatsenko
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Pu Lehui, Pu Lehui
From: Pu Lehui <pulehui@huawei.com>
In bpf_link_show_fdinfo and bpf_link_get_info_by_fd, link->prog is
accessed without holding any locks. If the prog is concurrently replaced
via bpf_link_update, the old prog can be freed, leading to a potential
UAF issue.
Before dereferencing the prog, both normal RCU and RCU Tasks Trace read
locks would normally be required, as BPF_LINK_TYPE_ITER supports both
non-sleepable and sleepable progs. However, as commit 57b23c0f612d
("bpf: Retire rcu_trace_implies_rcu_gp()") clarifies, an RCU Tasks Trace
grace period implies an RCU grace period, so holding only
rcu_read_lock() is already sufficient.
Fixes: 0c991ebc8c69 ("bpf: Implement bpf_prog replacement for an active bpf_cgroup_link")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
Reviewed-by: Amery Hung <ameryhung@gmail.com>
Signed-off-by: Pu Lehui <pulehui@huawei.com>
---
kernel/bpf/syscall.c | 21 +++++++++++++++++----
1 file changed, 17 insertions(+), 4 deletions(-)
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 6db306d23b47..cad986807d53 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -3471,9 +3471,10 @@ static const char *bpf_link_type_strs[] = {
static void bpf_link_show_fdinfo(struct seq_file *m, struct file *filp)
{
const struct bpf_link *link = filp->private_data;
- const struct bpf_prog *prog = link->prog;
+ const struct bpf_prog *prog;
enum bpf_link_type type = link->type;
char prog_tag[sizeof(prog->tag) * 2 + 1] = { };
+ u32 prog_id;
if (type < ARRAY_SIZE(bpf_link_type_strs) && bpf_link_type_strs[type]) {
if (link->type == BPF_LINK_TYPE_KPROBE_MULTI)
@@ -3490,13 +3491,20 @@ static void bpf_link_show_fdinfo(struct seq_file *m, struct file *filp)
}
seq_printf(m, "link_id:\t%u\n", link->id);
+ rcu_read_lock();
+ prog = READ_ONCE(link->prog);
if (prog) {
bin2hex(prog_tag, prog->tag, sizeof(prog->tag));
+ prog_id = prog->aux->id;
+ }
+ rcu_read_unlock();
+
+ if (prog) {
seq_printf(m,
"prog_tag:\t%s\n"
"prog_id:\t%u\n",
prog_tag,
- prog->aux->id);
+ prog_id);
}
if (link->ops->show_fdinfo)
link->ops->show_fdinfo(link, m);
@@ -5535,6 +5543,7 @@ static int bpf_link_get_info_by_fd(struct file *file,
{
struct bpf_link_info __user *uinfo = u64_to_user_ptr(attr->info.info);
struct bpf_link_info info;
+ const struct bpf_prog *prog;
u32 info_len = attr->info.info_len;
int err;
@@ -5549,8 +5558,12 @@ static int bpf_link_get_info_by_fd(struct file *file,
info.type = link->type;
info.id = link->id;
- if (link->prog)
- info.prog_id = link->prog->aux->id;
+
+ rcu_read_lock();
+ prog = READ_ONCE(link->prog);
+ if (prog)
+ info.prog_id = prog->aux->id;
+ rcu_read_unlock();
if (link->ops->fill_link_info) {
err = link->ops->fill_link_info(link, &info);
--
2.34.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH bpf v6 4/4] bpf, cgroup: Fix storage null-ptr-deref after replacing prog
2026-07-22 7:23 [PATCH bpf v6 0/4] Fixes for bpf link update Pu Lehui
` (2 preceding siblings ...)
2026-07-22 7:23 ` [PATCH bpf v6 3/4] bpf: Fix potential UAF when reading bpf link info Pu Lehui
@ 2026-07-22 7:23 ` Pu Lehui
2026-07-27 23:17 ` [PATCH bpf v6 0/4] Fixes for bpf link update Andrii Nakryiko
4 siblings, 0 replies; 15+ messages in thread
From: Pu Lehui @ 2026-07-22 7:23 UTC (permalink / raw)
To: bpf, linux-kernel, Amery Hung, Emil Tsalapatis, Mykyta Yatsenko
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Pu Lehui, Pu Lehui
From: Pu Lehui <pulehui@huawei.com>
Syzkaller reported a storage null-ptr-deref issue after replacing prog.
This occurs in the following scenario:
1. prog A, an empty prog, is attached to a cgrp.
2. prog B uses BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE and calls the
bpf_get_local_storage helper.
3. link_update is called to replace prog A with prog B.
The reason is that __cgroup_bpf_replace fails to alloc and assign the
required cgrp storage for the incoming replacement prog. Consequently,
the new prog inherits an uninit storage, leading to null-ptr-deref panic
when kick the new prog.
Fix this by properly allocating the storage and comparing the old and
new storage pointers. If the storage changed, fallback to
update_effective_progs which performs a RCU-safe update of the entire
array. If the storage remains unchanged, we can safely retain the
fast-path in-place update.
Additionally, handle the error path in __cgroup_bpf_attach strictly.
Although it is rare for update_effective_progs to fail in this context,
proper rollbacks for storage and flags are added for code rigor.
Fixes: 0c991ebc8c69 ("bpf: Implement bpf_prog replacement for an active bpf_cgroup_link")
Reviewed-by: Amery Hung <ameryhung@gmail.com>
Signed-off-by: Pu Lehui <pulehui@huawei.com>
---
kernel/bpf/cgroup.c | 44 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 43 insertions(+), 1 deletion(-)
diff --git a/kernel/bpf/cgroup.c b/kernel/bpf/cgroup.c
index 4355ccb78a9c..56d538f05520 100644
--- a/kernel/bpf/cgroup.c
+++ b/kernel/bpf/cgroup.c
@@ -813,10 +813,12 @@ static int __cgroup_bpf_attach(struct cgroup *cgrp,
struct bpf_prog *old_prog = NULL;
struct bpf_cgroup_storage *storage[MAX_BPF_CGROUP_STORAGE_TYPE] = {};
struct bpf_cgroup_storage *new_storage[MAX_BPF_CGROUP_STORAGE_TYPE] = {};
+ struct bpf_cgroup_storage *old_storage[MAX_BPF_CGROUP_STORAGE_TYPE] = {};
struct bpf_prog *new_prog = prog ? : link->link.prog;
enum cgroup_bpf_attach_type atype;
struct bpf_prog_list *pl;
struct hlist_head *progs;
+ u8 old_flags;
int err;
if (((flags & BPF_F_ALLOW_OVERRIDE) && (flags & BPF_F_ALLOW_MULTI)) ||
@@ -883,7 +885,10 @@ static int __cgroup_bpf_attach(struct cgroup *cgrp,
pl->prog = prog;
pl->link = link;
pl->flags = flags;
+ if (old_prog)
+ bpf_cgroup_storages_assign(old_storage, pl->storage);
bpf_cgroup_storages_assign(pl->storage, storage);
+ old_flags = cgrp->bpf.flags[atype];
cgrp->bpf.flags[atype] = saved_flags;
if (type == BPF_LSM_CGROUP) {
@@ -915,12 +920,14 @@ static int __cgroup_bpf_attach(struct cgroup *cgrp,
if (old_prog) {
pl->prog = old_prog;
pl->link = NULL;
+ bpf_cgroup_storages_assign(pl->storage, old_storage);
}
bpf_cgroup_storages_free(new_storage);
if (!old_prog) {
hlist_del(&pl->node);
kfree(pl);
}
+ cgrp->bpf.flags[atype] = old_flags;
return err;
}
@@ -1032,11 +1039,17 @@ static int __cgroup_bpf_replace(struct cgroup *cgrp,
struct bpf_cgroup_link *link,
struct bpf_prog *new_prog)
{
+ struct bpf_cgroup_storage *new_storage[MAX_BPF_CGROUP_STORAGE_TYPE] = {};
+ struct bpf_cgroup_storage *old_storage[MAX_BPF_CGROUP_STORAGE_TYPE] = {};
+ struct bpf_cgroup_storage *storage[MAX_BPF_CGROUP_STORAGE_TYPE] = {};
+ enum bpf_cgroup_storage_type stype;
enum cgroup_bpf_attach_type atype;
+ bool storage_changed = false;
struct bpf_prog *old_prog;
struct bpf_prog_list *pl;
struct hlist_head *progs;
bool found = false;
+ int err;
atype = bpf_cgroup_atype_find(link->link.attach_type, new_prog->aux->attach_btf_id);
if (atype < 0)
@@ -1056,10 +1069,39 @@ static int __cgroup_bpf_replace(struct cgroup *cgrp,
if (!found)
return -ENOENT;
+ if (bpf_cgroup_storages_alloc(storage, new_storage, link->link.attach_type,
+ new_prog, cgrp))
+ return -ENOMEM;
+
+ for_each_cgroup_storage_type(stype) {
+ if (storage[stype] != pl->storage[stype]) {
+ storage_changed = true;
+ break;
+ }
+ }
+
cgrp->bpf.revisions[atype] += 1;
old_prog = xchg(&link->link.prog, new_prog);
- replace_effective_prog(cgrp, atype, pl);
+
+ if (!storage_changed) {
+ replace_effective_prog(cgrp, atype, pl);
+ bpf_prog_put(old_prog);
+ return 0;
+ }
+
+ bpf_cgroup_storages_assign(old_storage, pl->storage);
+ bpf_cgroup_storages_assign(pl->storage, storage);
+ err = update_effective_progs(cgrp, atype);
+ if (err) {
+ xchg(&link->link.prog, old_prog);
+ bpf_cgroup_storages_assign(pl->storage, old_storage);
+ bpf_cgroup_storages_free(new_storage);
+ cgrp->bpf.revisions[atype] -= 1;
+ return err;
+ }
+
bpf_prog_put(old_prog);
+ bpf_cgroup_storages_link(new_storage, cgrp, link->link.attach_type);
return 0;
}
--
2.34.1
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH bpf v6 2/4] bpf: Fix UAF due to missing link type check in mprog
2026-07-22 7:23 ` [PATCH bpf v6 2/4] bpf: Fix UAF due to missing link type check in mprog Pu Lehui
@ 2026-07-22 16:25 ` Emil Tsalapatis
2026-07-23 3:14 ` Pu Lehui
0 siblings, 1 reply; 15+ messages in thread
From: Emil Tsalapatis @ 2026-07-22 16:25 UTC (permalink / raw)
To: Pu Lehui, bpf, linux-kernel, Amery Hung, Emil Tsalapatis,
Mykyta Yatsenko
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Pu Lehui
On Wed Jul 22, 2026 at 3:23 AM EDT, Pu Lehui wrote:
> From: Pu Lehui <pulehui@huawei.com>
>
> 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 doing
> 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
> /* 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. Therefore, we need to validate link->type
> to reject foreign anchors.
>
> Fix this by strictly validating link->type in bpf_mprog_link against the
> expected link type. mprog APIs is also adjusted to accept and pass down
> the expected link type. 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@kernel.org>
> Reviewed-by: Amery Hung <ameryhung@gmail.com>
> Signed-off-by: Pu Lehui <pulehui@huawei.com>
Unless I'm missing something, at the very least there's no need for the
extra argument in detach() since we pass UNSPEC to bpf_mprog_link() and
never use it otherwise.
Even for the attach case, aren't we required to provide a new link
for attachment, and so already have the ltype available to test
against?
> ---
> drivers/net/netkit.c | 13 ++++++-------
> include/linux/bpf_mprog.h | 6 ++++--
> kernel/bpf/mprog.c | 23 ++++++++++++++---------
> kernel/bpf/tcx.c | 13 ++++++-------
> 4 files changed, 30 insertions(+), 25 deletions(-)
>
> diff --git a/drivers/net/netkit.c b/drivers/net/netkit.c
> index a3931cd82132..99ddf2befb23 100644
> --- a/drivers/net/netkit.c
> +++ b/drivers/net/netkit.c
> @@ -768,7 +768,7 @@ int netkit_prog_attach(const union bpf_attr *attr, struct bpf_prog *prog)
> }
> ret = bpf_mprog_attach(entry, &entry_new, prog, NULL, replace_prog,
> attr->attach_flags, attr->relative_fd,
> - attr->expected_revision);
> + attr->expected_revision, BPF_LINK_TYPE_NETKIT);
> if (!ret) {
> if (entry != entry_new) {
> netkit_entry_update(dev, entry_new);
> @@ -802,7 +802,7 @@ int netkit_prog_detach(const union bpf_attr *attr, struct bpf_prog *prog)
> goto out;
> }
> ret = bpf_mprog_detach(entry, &entry_new, prog, NULL, attr->attach_flags,
> - attr->relative_fd, attr->expected_revision);
> + attr->relative_fd, attr->expected_revision, BPF_LINK_TYPE_NETKIT);
> if (!ret) {
> if (!bpf_mprog_total(entry_new))
> entry_new = NULL;
> @@ -850,7 +850,7 @@ static int netkit_link_prog_attach(struct bpf_link *link, u32 flags,
> ASSERT_RTNL();
> entry = netkit_entry_fetch(dev, true);
> ret = bpf_mprog_attach(entry, &entry_new, link->prog, link, NULL, flags,
> - id_or_fd, revision);
> + id_or_fd, revision, BPF_LINK_TYPE_NETKIT);
> if (!ret) {
> if (entry != entry_new) {
> netkit_entry_update(dev, entry_new);
> @@ -877,7 +877,7 @@ static void netkit_link_release(struct bpf_link *link)
> ret = -ENOENT;
> goto out;
> }
> - ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0, 0, 0);
> + ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0, 0, 0, BPF_LINK_TYPE_NETKIT);
> if (!ret) {
> if (!bpf_mprog_total(entry_new))
> entry_new = NULL;
> @@ -919,9 +919,8 @@ static int netkit_link_update(struct bpf_link *link, struct bpf_prog *nprog,
> ret = -ENOENT;
> goto out;
> }
> - ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog,
> - BPF_F_REPLACE | BPF_F_ID,
> - link->prog->aux->id, 0);
> + ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog, BPF_F_REPLACE | BPF_F_ID,
> + link->prog->aux->id, 0, BPF_LINK_TYPE_NETKIT);
> if (!ret) {
> WARN_ON_ONCE(entry != entry_new);
> oprog = xchg(&link->prog, nprog);
> diff --git a/include/linux/bpf_mprog.h b/include/linux/bpf_mprog.h
> index 0b9f4caeeb0a..1fbe1a923968 100644
> --- a/include/linux/bpf_mprog.h
> +++ b/include/linux/bpf_mprog.h
> @@ -321,12 +321,14 @@ int bpf_mprog_attach(struct bpf_mprog_entry *entry,
> struct bpf_mprog_entry **entry_new,
> struct bpf_prog *prog_new, struct bpf_link *link,
> struct bpf_prog *prog_old,
> - u32 flags, u32 id_or_fd, u64 revision);
> + u32 flags, u32 id_or_fd, u64 revision,
> + enum bpf_link_type expected_link_type);
>
> int bpf_mprog_detach(struct bpf_mprog_entry *entry,
> struct bpf_mprog_entry **entry_new,
> struct bpf_prog *prog, struct bpf_link *link,
> - u32 flags, u32 id_or_fd, u64 revision);
> + u32 flags, u32 id_or_fd, u64 revision,
> + enum bpf_link_type expected_link_type);
>
> int bpf_mprog_query(const union bpf_attr *attr, union bpf_attr __user *uattr,
> struct bpf_mprog_entry *entry);
> diff --git a/kernel/bpf/mprog.c b/kernel/bpf/mprog.c
> index 1394168062e8..b4a1b35ff569 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)
> @@ -226,7 +227,8 @@ int bpf_mprog_attach(struct bpf_mprog_entry *entry,
> struct bpf_mprog_entry **entry_new,
> struct bpf_prog *prog_new, struct bpf_link *link,
> struct bpf_prog *prog_old,
> - u32 flags, u32 id_or_fd, u64 revision)
> + u32 flags, u32 id_or_fd, u64 revision,
> + enum bpf_link_type expected_link_type)
> {
> struct bpf_tuple rtuple, ntuple = {
> .prog = prog_new,
> @@ -243,6 +245,7 @@ int bpf_mprog_attach(struct bpf_mprog_entry *entry,
> return -EEXIST;
> ret = bpf_mprog_tuple_relative(&rtuple, id_or_fd,
> flags & ~BPF_F_REPLACE,
> + expected_link_type,
> prog_new->type);
> if (ret)
> return ret;
> @@ -328,7 +331,8 @@ static int bpf_mprog_fetch(struct bpf_mprog_entry *entry,
> int bpf_mprog_detach(struct bpf_mprog_entry *entry,
> struct bpf_mprog_entry **entry_new,
> struct bpf_prog *prog, struct bpf_link *link,
> - u32 flags, u32 id_or_fd, u64 revision)
> + u32 flags, u32 id_or_fd, u64 revision,
> + enum bpf_link_type expected_link_type)
> {
> struct bpf_tuple rtuple, dtuple = {
> .prog = prog,
> @@ -343,8 +347,9 @@ 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);
> + expected_link_type,
> + /* Use UNSPEC as wildcard when prog is NULL */
> + prog ? prog->type : BPF_PROG_TYPE_UNSPEC);
> if (ret)
> return ret;
> if (dtuple.prog) {
> diff --git a/kernel/bpf/tcx.c b/kernel/bpf/tcx.c
> index 02db0113b8e7..f208cef13a98 100644
> --- a/kernel/bpf/tcx.c
> +++ b/kernel/bpf/tcx.c
> @@ -38,7 +38,7 @@ int tcx_prog_attach(const union bpf_attr *attr, struct bpf_prog *prog)
> }
> ret = bpf_mprog_attach(entry, &entry_new, prog, NULL, replace_prog,
> attr->attach_flags, attr->relative_fd,
> - attr->expected_revision);
> + attr->expected_revision, BPF_LINK_TYPE_TCX);
> if (!ret) {
> if (entry != entry_new) {
> tcx_entry_update(dev, entry_new, ingress);
> @@ -76,7 +76,7 @@ int tcx_prog_detach(const union bpf_attr *attr, struct bpf_prog *prog)
> goto out;
> }
> ret = bpf_mprog_detach(entry, &entry_new, prog, NULL, attr->attach_flags,
> - attr->relative_fd, attr->expected_revision);
> + attr->relative_fd, attr->expected_revision, BPF_LINK_TYPE_TCX);
> if (!ret) {
> if (!tcx_entry_is_active(entry_new))
> entry_new = NULL;
> @@ -152,7 +152,7 @@ static int tcx_link_prog_attach(struct bpf_link *link, u32 flags, u32 id_or_fd,
> if (!entry)
> return -ENOMEM;
> ret = bpf_mprog_attach(entry, &entry_new, link->prog, link, NULL, flags,
> - id_or_fd, revision);
> + id_or_fd, revision, BPF_LINK_TYPE_TCX);
> if (!ret) {
> if (entry != entry_new) {
> tcx_entry_update(dev, entry_new, ingress);
> @@ -183,7 +183,7 @@ static void tcx_link_release(struct bpf_link *link)
> ret = -ENOENT;
> goto out;
> }
> - ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0, 0, 0);
> + ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0, 0, 0, BPF_LINK_TYPE_TCX);
> if (!ret) {
> if (!tcx_entry_is_active(entry_new))
> entry_new = NULL;
> @@ -229,9 +229,8 @@ static int tcx_link_update(struct bpf_link *link, struct bpf_prog *nprog,
> ret = -ENOENT;
> goto out;
> }
> - ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog,
> - BPF_F_REPLACE | BPF_F_ID,
> - link->prog->aux->id, 0);
> + ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog, BPF_F_REPLACE | BPF_F_ID,
> + link->prog->aux->id, 0, BPF_LINK_TYPE_TCX);
> if (!ret) {
> WARN_ON_ONCE(entry != entry_new);
> oprog = xchg(&link->prog, nprog);
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH bpf v6 2/4] bpf: Fix UAF due to missing link type check in mprog
2026-07-22 16:25 ` Emil Tsalapatis
@ 2026-07-23 3:14 ` Pu Lehui
2026-07-28 1:56 ` Pu Lehui
0 siblings, 1 reply; 15+ messages in thread
From: Pu Lehui @ 2026-07-23 3:14 UTC (permalink / raw)
To: Emil Tsalapatis, bpf, linux-kernel, Amery Hung, Mykyta Yatsenko
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Pu Lehui
On 2026/7/23 0:25, Emil Tsalapatis wrote:
> On Wed Jul 22, 2026 at 3:23 AM EDT, Pu Lehui wrote:
>> From: Pu Lehui <pulehui@huawei.com>
>>
>> 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 doing
>> 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
>> /* 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. Therefore, we need to validate link->type
>> to reject foreign anchors.
>>
>> Fix this by strictly validating link->type in bpf_mprog_link against the
>> expected link type. mprog APIs is also adjusted to accept and pass down
>> the expected link type. 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@kernel.org>
>> Reviewed-by: Amery Hung <ameryhung@gmail.com>
>> Signed-off-by: Pu Lehui <pulehui@huawei.com>
>
> Unless I'm missing something, at the very least there's no need for the
> extra argument in detach() since we pass UNSPEC to bpf_mprog_link() and
> never use it otherwise.
>
> Even for the attach case, aren't we required to provide a new link
> for attachment, and so already have the ltype available to test
> against?
>
Hi Emil,
The BPF_F_LINK flag only indicates that relative_fd refers to an anchor
link or prog; it is independent of whether the object being attached or
detached is a bare prog or a link. This means that when attaching or
detaching a bare prog relative to an anchor link, we cannot obtain the
expected_link_type because the link parameter passed in is NULL.
Furthermore, as you mentioned, we should support unconditional
detachment. But in the current version, because we strictly check
expected_link_type, the detach operation will fail if the relative link
type does not match.
Therefore, we can simplify the approach to fix this UAF—similar to patch
3—by relying on RCU protection when accessing the prog type of a
relative link. When attaching or detaching a bare prog or a link
relative to an anchor link, we simply verify that
relative_link->prog->type matches the bare prog->type or
link->prog->type. This logic also naturally applies to unconditional
detachment with relative link, such as when the bare prog or link->prog
being detached is NULL.
And the patch will be follow, wdyt?
diff --git a/kernel/bpf/mprog.c b/kernel/bpf/mprog.c
index 1394168062e8..512821c21aa6 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); <-- relative_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;
}
>> ---
>> drivers/net/netkit.c | 13 ++++++-------
>> include/linux/bpf_mprog.h | 6 ++++--
>> kernel/bpf/mprog.c | 23 ++++++++++++++---------
>> kernel/bpf/tcx.c | 13 ++++++-------
>> 4 files changed, 30 insertions(+), 25 deletions(-)
>>
>> diff --git a/drivers/net/netkit.c b/drivers/net/netkit.c
>> index a3931cd82132..99ddf2befb23 100644
>> --- a/drivers/net/netkit.c
>> +++ b/drivers/net/netkit.c
>> @@ -768,7 +768,7 @@ int netkit_prog_attach(const union bpf_attr *attr, struct bpf_prog *prog)
>> }
>> ret = bpf_mprog_attach(entry, &entry_new, prog, NULL, replace_prog,
>> attr->attach_flags, attr->relative_fd,
>> - attr->expected_revision);
>> + attr->expected_revision, BPF_LINK_TYPE_NETKIT);
>> if (!ret) {
>> if (entry != entry_new) {
>> netkit_entry_update(dev, entry_new);
>> @@ -802,7 +802,7 @@ int netkit_prog_detach(const union bpf_attr *attr, struct bpf_prog *prog)
>> goto out;
>> }
>> ret = bpf_mprog_detach(entry, &entry_new, prog, NULL, attr->attach_flags,
>> - attr->relative_fd, attr->expected_revision);
>> + attr->relative_fd, attr->expected_revision, BPF_LINK_TYPE_NETKIT);
>> if (!ret) {
>> if (!bpf_mprog_total(entry_new))
>> entry_new = NULL;
>> @@ -850,7 +850,7 @@ static int netkit_link_prog_attach(struct bpf_link *link, u32 flags,
>> ASSERT_RTNL();
>> entry = netkit_entry_fetch(dev, true);
>> ret = bpf_mprog_attach(entry, &entry_new, link->prog, link, NULL, flags,
>> - id_or_fd, revision);
>> + id_or_fd, revision, BPF_LINK_TYPE_NETKIT);
>> if (!ret) {
>> if (entry != entry_new) {
>> netkit_entry_update(dev, entry_new);
>> @@ -877,7 +877,7 @@ static void netkit_link_release(struct bpf_link *link)
>> ret = -ENOENT;
>> goto out;
>> }
>> - ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0, 0, 0);
>> + ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0, 0, 0, BPF_LINK_TYPE_NETKIT);
>> if (!ret) {
>> if (!bpf_mprog_total(entry_new))
>> entry_new = NULL;
>> @@ -919,9 +919,8 @@ static int netkit_link_update(struct bpf_link *link, struct bpf_prog *nprog,
>> ret = -ENOENT;
>> goto out;
>> }
>> - ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog,
>> - BPF_F_REPLACE | BPF_F_ID,
>> - link->prog->aux->id, 0);
>> + ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog, BPF_F_REPLACE | BPF_F_ID,
>> + link->prog->aux->id, 0, BPF_LINK_TYPE_NETKIT);
>> if (!ret) {
>> WARN_ON_ONCE(entry != entry_new);
>> oprog = xchg(&link->prog, nprog);
>> diff --git a/include/linux/bpf_mprog.h b/include/linux/bpf_mprog.h
>> index 0b9f4caeeb0a..1fbe1a923968 100644
>> --- a/include/linux/bpf_mprog.h
>> +++ b/include/linux/bpf_mprog.h
>> @@ -321,12 +321,14 @@ int bpf_mprog_attach(struct bpf_mprog_entry *entry,
>> struct bpf_mprog_entry **entry_new,
>> struct bpf_prog *prog_new, struct bpf_link *link,
>> struct bpf_prog *prog_old,
>> - u32 flags, u32 id_or_fd, u64 revision);
>> + u32 flags, u32 id_or_fd, u64 revision,
>> + enum bpf_link_type expected_link_type);
>>
>> int bpf_mprog_detach(struct bpf_mprog_entry *entry,
>> struct bpf_mprog_entry **entry_new,
>> struct bpf_prog *prog, struct bpf_link *link,
>> - u32 flags, u32 id_or_fd, u64 revision);
>> + u32 flags, u32 id_or_fd, u64 revision,
>> + enum bpf_link_type expected_link_type);
>>
>> int bpf_mprog_query(const union bpf_attr *attr, union bpf_attr __user *uattr,
>> struct bpf_mprog_entry *entry);
>> diff --git a/kernel/bpf/mprog.c b/kernel/bpf/mprog.c
>> index 1394168062e8..b4a1b35ff569 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)
>> @@ -226,7 +227,8 @@ int bpf_mprog_attach(struct bpf_mprog_entry *entry,
>> struct bpf_mprog_entry **entry_new,
>> struct bpf_prog *prog_new, struct bpf_link *link,
>> struct bpf_prog *prog_old,
>> - u32 flags, u32 id_or_fd, u64 revision)
>> + u32 flags, u32 id_or_fd, u64 revision,
>> + enum bpf_link_type expected_link_type)
>> {
>> struct bpf_tuple rtuple, ntuple = {
>> .prog = prog_new,
>> @@ -243,6 +245,7 @@ int bpf_mprog_attach(struct bpf_mprog_entry *entry,
>> return -EEXIST;
>> ret = bpf_mprog_tuple_relative(&rtuple, id_or_fd,
>> flags & ~BPF_F_REPLACE,
>> + expected_link_type,
>> prog_new->type);
>> if (ret)
>> return ret;
>> @@ -328,7 +331,8 @@ static int bpf_mprog_fetch(struct bpf_mprog_entry *entry,
>> int bpf_mprog_detach(struct bpf_mprog_entry *entry,
>> struct bpf_mprog_entry **entry_new,
>> struct bpf_prog *prog, struct bpf_link *link,
>> - u32 flags, u32 id_or_fd, u64 revision)
>> + u32 flags, u32 id_or_fd, u64 revision,
>> + enum bpf_link_type expected_link_type)
>> {
>> struct bpf_tuple rtuple, dtuple = {
>> .prog = prog,
>> @@ -343,8 +347,9 @@ 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);
>> + expected_link_type,
>> + /* Use UNSPEC as wildcard when prog is NULL */
>> + prog ? prog->type : BPF_PROG_TYPE_UNSPEC);
>> if (ret)
>> return ret;
>> if (dtuple.prog) {
>> diff --git a/kernel/bpf/tcx.c b/kernel/bpf/tcx.c
>> index 02db0113b8e7..f208cef13a98 100644
>> --- a/kernel/bpf/tcx.c
>> +++ b/kernel/bpf/tcx.c
>> @@ -38,7 +38,7 @@ int tcx_prog_attach(const union bpf_attr *attr, struct bpf_prog *prog)
>> }
>> ret = bpf_mprog_attach(entry, &entry_new, prog, NULL, replace_prog,
>> attr->attach_flags, attr->relative_fd,
>> - attr->expected_revision);
>> + attr->expected_revision, BPF_LINK_TYPE_TCX);
>> if (!ret) {
>> if (entry != entry_new) {
>> tcx_entry_update(dev, entry_new, ingress);
>> @@ -76,7 +76,7 @@ int tcx_prog_detach(const union bpf_attr *attr, struct bpf_prog *prog)
>> goto out;
>> }
>> ret = bpf_mprog_detach(entry, &entry_new, prog, NULL, attr->attach_flags,
>> - attr->relative_fd, attr->expected_revision);
>> + attr->relative_fd, attr->expected_revision, BPF_LINK_TYPE_TCX);
>> if (!ret) {
>> if (!tcx_entry_is_active(entry_new))
>> entry_new = NULL;
>> @@ -152,7 +152,7 @@ static int tcx_link_prog_attach(struct bpf_link *link, u32 flags, u32 id_or_fd,
>> if (!entry)
>> return -ENOMEM;
>> ret = bpf_mprog_attach(entry, &entry_new, link->prog, link, NULL, flags,
>> - id_or_fd, revision);
>> + id_or_fd, revision, BPF_LINK_TYPE_TCX);
>> if (!ret) {
>> if (entry != entry_new) {
>> tcx_entry_update(dev, entry_new, ingress);
>> @@ -183,7 +183,7 @@ static void tcx_link_release(struct bpf_link *link)
>> ret = -ENOENT;
>> goto out;
>> }
>> - ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0, 0, 0);
>> + ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0, 0, 0, BPF_LINK_TYPE_TCX);
>> if (!ret) {
>> if (!tcx_entry_is_active(entry_new))
>> entry_new = NULL;
>> @@ -229,9 +229,8 @@ static int tcx_link_update(struct bpf_link *link, struct bpf_prog *nprog,
>> ret = -ENOENT;
>> goto out;
>> }
>> - ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog,
>> - BPF_F_REPLACE | BPF_F_ID,
>> - link->prog->aux->id, 0);
>> + ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog, BPF_F_REPLACE | BPF_F_ID,
>> + link->prog->aux->id, 0, BPF_LINK_TYPE_TCX);
>> if (!ret) {
>> WARN_ON_ONCE(entry != entry_new);
>> oprog = xchg(&link->prog, nprog);
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH bpf v6 1/4] bpf: Fix potential UAF in bpf_netns_link_update_prog
2026-07-22 7:23 ` [PATCH bpf v6 1/4] bpf: Fix potential UAF in bpf_netns_link_update_prog Pu Lehui
@ 2026-07-27 23:01 ` Andrii Nakryiko
2026-07-28 1:44 ` Pu Lehui
0 siblings, 1 reply; 15+ messages in thread
From: Andrii Nakryiko @ 2026-07-27 23:01 UTC (permalink / raw)
To: Pu Lehui
Cc: bpf, linux-kernel, Amery Hung, Emil Tsalapatis, Mykyta Yatsenko,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Pu Lehui
On Wed, Jul 22, 2026 at 12:19 AM Pu Lehui <pulehui@huaweicloud.com> wrote:
>
> From: Pu Lehui <pulehui@huawei.com>
>
> In bpf_netns_link_update_prog, the checks for old_prog and prog type
> are currently performed locklessly before acquiring netns_bpf_mutex.
> This creates a race condition that can lead to a UAF issue.
>
> If two threads concurrently execute BPF_LINK_UPDATE on the same netns
> link, the following execution path can trigger a UAF:
>
> CPU0 CPU1
> bpf_netns_link_update_prog
> if (old_prog && old_prog != link->prog)
> return -EPERM;
> bpf_netns_link_update_prog
> if (old_prog && old_prog != link->prog)
> ...
> old_prog = xchg(&link->prog, new_prog);
> bpf_prog_put(old_prog);
> if (new_prog->type != link->prog->type) <-- trigger UAF
>
> Fix this by moving the old_prog and prog->type checks inside the
> netns_bpf_mutex critical section.
>
> Fixes: 7f045a49fee0 ("bpf: Add link-based BPF program attachment to network namespace")
> Reported-by: Sashiko <sashiko-bot@kernel.org>
> Reviewed-by: Amery Hung <ameryhung@gmail.com>
> Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
> Signed-off-by: Pu Lehui <pulehui@huawei.com>
> ---
> kernel/bpf/net_namespace.c | 14 +++++++++-----
> 1 file changed, 9 insertions(+), 5 deletions(-)
>
the change is good, all other link types do take lock first and then
do all the checking, netns is the only oddball, but see implementation
nit below
> diff --git a/kernel/bpf/net_namespace.c b/kernel/bpf/net_namespace.c
> index 25f30f9edaef..9fc62db1441c 100644
> --- a/kernel/bpf/net_namespace.c
> +++ b/kernel/bpf/net_namespace.c
> @@ -171,13 +171,17 @@ static int bpf_netns_link_update_prog(struct bpf_link *link,
> struct net *net;
> int idx, ret;
>
> - if (old_prog && old_prog != link->prog)
> - return -EPERM;
> - if (new_prog->type != link->prog->type)
> - return -EINVAL;
> -
> mutex_lock(&netns_bpf_mutex);
>
> + if (old_prog && old_prog != link->prog) {
> + ret = -EPERM;
> + goto out_unlock;
> + }
> + if (new_prog->type != link->prog->type) {
> + ret = -EINVAL;
> + goto out_unlock;
can we use guard() and avoid all the goto jumping?
pw-bot: cr
> + }
> +
> net = net_link->net;
> if (!net || !check_net(net)) {
> /* Link auto-detached or netns dying */
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH bpf v6 3/4] bpf: Fix potential UAF when reading bpf link info
2026-07-22 7:23 ` [PATCH bpf v6 3/4] bpf: Fix potential UAF when reading bpf link info Pu Lehui
@ 2026-07-27 23:15 ` Andrii Nakryiko
2026-07-28 1:45 ` Pu Lehui
0 siblings, 1 reply; 15+ messages in thread
From: Andrii Nakryiko @ 2026-07-27 23:15 UTC (permalink / raw)
To: Pu Lehui
Cc: bpf, linux-kernel, Amery Hung, Emil Tsalapatis, Mykyta Yatsenko,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Pu Lehui
On Wed, Jul 22, 2026 at 12:19 AM Pu Lehui <pulehui@huaweicloud.com> wrote:
>
> From: Pu Lehui <pulehui@huawei.com>
>
> In bpf_link_show_fdinfo and bpf_link_get_info_by_fd, link->prog is
> accessed without holding any locks. If the prog is concurrently replaced
> via bpf_link_update, the old prog can be freed, leading to a potential
> UAF issue.
>
> Before dereferencing the prog, both normal RCU and RCU Tasks Trace read
no, either one is enough (and that's why we wait for RCU and RCU Tasks
Trace grace periods, to make sure that both kinds of accesses work).
This paragraph is misleading, just drop it.
> locks would normally be required, as BPF_LINK_TYPE_ITER supports both
> non-sleepable and sleepable progs. However, as commit 57b23c0f612d
> ("bpf: Retire rcu_trace_implies_rcu_gp()") clarifies, an RCU Tasks Trace
> grace period implies an RCU grace period, so holding only
> rcu_read_lock() is already sufficient.
>
> Fixes: 0c991ebc8c69 ("bpf: Implement bpf_prog replacement for an active bpf_cgroup_link")
> Reported-by: Sashiko <sashiko-bot@kernel.org>
> Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
> Reviewed-by: Amery Hung <ameryhung@gmail.com>
> Signed-off-by: Pu Lehui <pulehui@huawei.com>
> ---
> kernel/bpf/syscall.c | 21 +++++++++++++++++----
> 1 file changed, 17 insertions(+), 4 deletions(-)
>
> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> index 6db306d23b47..cad986807d53 100644
> --- a/kernel/bpf/syscall.c
> +++ b/kernel/bpf/syscall.c
> @@ -3471,9 +3471,10 @@ static const char *bpf_link_type_strs[] = {
> static void bpf_link_show_fdinfo(struct seq_file *m, struct file *filp)
> {
> const struct bpf_link *link = filp->private_data;
> - const struct bpf_prog *prog = link->prog;
> + const struct bpf_prog *prog;
> enum bpf_link_type type = link->type;
> char prog_tag[sizeof(prog->tag) * 2 + 1] = { };
> + u32 prog_id;
can be read uninitialized (if link->prog is null), initialize to zero
pw-bot: cr
>
> if (type < ARRAY_SIZE(bpf_link_type_strs) && bpf_link_type_strs[type]) {
> if (link->type == BPF_LINK_TYPE_KPROBE_MULTI)
> @@ -3490,13 +3491,20 @@ static void bpf_link_show_fdinfo(struct seq_file *m, struct file *filp)
> }
> seq_printf(m, "link_id:\t%u\n", link->id);
>
> + rcu_read_lock();
> + prog = READ_ONCE(link->prog);
> if (prog) {
> bin2hex(prog_tag, prog->tag, sizeof(prog->tag));
> + prog_id = prog->aux->id;
> + }
> + rcu_read_unlock();
> +
> + if (prog) {
> seq_printf(m,
> "prog_tag:\t%s\n"
> "prog_id:\t%u\n",
> prog_tag,
> - prog->aux->id);
> + prog_id);
> }
> if (link->ops->show_fdinfo)
> link->ops->show_fdinfo(link, m);
> @@ -5535,6 +5543,7 @@ static int bpf_link_get_info_by_fd(struct file *file,
> {
> struct bpf_link_info __user *uinfo = u64_to_user_ptr(attr->info.info);
> struct bpf_link_info info;
> + const struct bpf_prog *prog;
> u32 info_len = attr->info.info_len;
> int err;
>
> @@ -5549,8 +5558,12 @@ static int bpf_link_get_info_by_fd(struct file *file,
>
> info.type = link->type;
> info.id = link->id;
> - if (link->prog)
> - info.prog_id = link->prog->aux->id;
> +
> + rcu_read_lock();
> + prog = READ_ONCE(link->prog);
> + if (prog)
> + info.prog_id = prog->aux->id;
> + rcu_read_unlock();
>
> if (link->ops->fill_link_info) {
> err = link->ops->fill_link_info(link, &info);
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH bpf v6 0/4] Fixes for bpf link update
2026-07-22 7:23 [PATCH bpf v6 0/4] Fixes for bpf link update Pu Lehui
` (3 preceding siblings ...)
2026-07-22 7:23 ` [PATCH bpf v6 4/4] bpf, cgroup: Fix storage null-ptr-deref after replacing prog Pu Lehui
@ 2026-07-27 23:17 ` Andrii Nakryiko
2026-07-28 1:54 ` Pu Lehui
4 siblings, 1 reply; 15+ messages in thread
From: Andrii Nakryiko @ 2026-07-27 23:17 UTC (permalink / raw)
To: Pu Lehui
Cc: bpf, linux-kernel, Amery Hung, Emil Tsalapatis, Mykyta Yatsenko,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Pu Lehui
On Wed, Jul 22, 2026 at 12:19 AM Pu Lehui <pulehui@huaweicloud.com> wrote:
>
> From: Pu Lehui <pulehui@huawei.com>
>
> This patch series addresses a few concurrency UAF issues within bpf link update.
>
each fix is independent from each other, can you please split patch
set into individual patches, please? some of them are straightforward
and can be applied soon, while others might require a bit more
thorough review and some more iteration, so best to split and apply
individually, IMO
> v6:
> - 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 in bpf_mprog_detach. (Emil)
>
> v5: https://lore.kernel.org/bpf/20260721041048.1394085-1-pulehui@huaweicloud.com
> - Remove rcu_read_lock_trace as rcu_read_unlock is sufficient. (Mykyta)
> - Improve commit msg for patch 2. (Amery)
> - Add Reviewed-by tag by Amery.
>
> v4: https://lore.kernel.org/bpf/20260720134547.1289964-1-pulehui@huaweicloud.com
> - Add new fix for UAF due to missing link type check in mprog. (Sashiko)
> - Add new fix for UAF in bpf_netns_link_update_prog. (Sashiko)
> - Include the storage and flags rollbacks to patch4 for sake of code rigor,
> as it's hard to make update_effective_progs fail in __cgroup_bpf_attach.
>
> v3: https://lore.kernel.org/bpf/20260720033055.1215477-1-pulehui@huaweicloud.com
> - Add new fix for UAF issue when reading bpf link info. (Sashiko)
> - Add new fix for storage not restored when update_effective_progs
> failed. (Sashiko)
>
> v2: https://lore.kernel.org/bpf/20260717073343.958862-1-pulehui@huaweicloud.com
> - Fix invalid access for in-place update when storage changed. (Sashiko)
>
> v1: https://lore.kernel.org/bpf/20260714014659.401063-1-pulehui@huaweicloud.com
>
> Pu Lehui (4):
> bpf: Fix potential UAF in bpf_netns_link_update_prog
> bpf: Fix UAF due to missing link type check in mprog
> bpf: Fix potential UAF when reading bpf link info
> bpf, cgroup: Fix storage null-ptr-deref after replacing prog
>
> drivers/net/netkit.c | 13 ++++++-----
> include/linux/bpf_mprog.h | 6 ++++--
> kernel/bpf/cgroup.c | 44 +++++++++++++++++++++++++++++++++++++-
> kernel/bpf/mprog.c | 23 ++++++++++++--------
> kernel/bpf/net_namespace.c | 14 +++++++-----
> kernel/bpf/syscall.c | 21 ++++++++++++++----
> kernel/bpf/tcx.c | 13 ++++++-----
> 7 files changed, 99 insertions(+), 35 deletions(-)
>
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH bpf v6 1/4] bpf: Fix potential UAF in bpf_netns_link_update_prog
2026-07-27 23:01 ` Andrii Nakryiko
@ 2026-07-28 1:44 ` Pu Lehui
0 siblings, 0 replies; 15+ messages in thread
From: Pu Lehui @ 2026-07-28 1:44 UTC (permalink / raw)
To: Andrii Nakryiko, Pu Lehui
Cc: bpf, linux-kernel, Amery Hung, Emil Tsalapatis, Mykyta Yatsenko,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa
On 2026/7/28 7:01, Andrii Nakryiko wrote:
> On Wed, Jul 22, 2026 at 12:19 AM Pu Lehui <pulehui@huaweicloud.com> wrote:
>>
>> From: Pu Lehui <pulehui@huawei.com>
>>
>> In bpf_netns_link_update_prog, the checks for old_prog and prog type
>> are currently performed locklessly before acquiring netns_bpf_mutex.
>> This creates a race condition that can lead to a UAF issue.
>>
>> If two threads concurrently execute BPF_LINK_UPDATE on the same netns
>> link, the following execution path can trigger a UAF:
>>
>> CPU0 CPU1
>> bpf_netns_link_update_prog
>> if (old_prog && old_prog != link->prog)
>> return -EPERM;
>> bpf_netns_link_update_prog
>> if (old_prog && old_prog != link->prog)
>> ...
>> old_prog = xchg(&link->prog, new_prog);
>> bpf_prog_put(old_prog);
>> if (new_prog->type != link->prog->type) <-- trigger UAF
>>
>> Fix this by moving the old_prog and prog->type checks inside the
>> netns_bpf_mutex critical section.
>>
>> Fixes: 7f045a49fee0 ("bpf: Add link-based BPF program attachment to network namespace")
>> Reported-by: Sashiko <sashiko-bot@kernel.org>
>> Reviewed-by: Amery Hung <ameryhung@gmail.com>
>> Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
>> Signed-off-by: Pu Lehui <pulehui@huawei.com>
>> ---
>> kernel/bpf/net_namespace.c | 14 +++++++++-----
>> 1 file changed, 9 insertions(+), 5 deletions(-)
>>
>
> the change is good, all other link types do take lock first and then
> do all the checking, netns is the only oddball, but see implementation
> nit below
>
>> diff --git a/kernel/bpf/net_namespace.c b/kernel/bpf/net_namespace.c
>> index 25f30f9edaef..9fc62db1441c 100644
>> --- a/kernel/bpf/net_namespace.c
>> +++ b/kernel/bpf/net_namespace.c
>> @@ -171,13 +171,17 @@ static int bpf_netns_link_update_prog(struct bpf_link *link,
>> struct net *net;
>> int idx, ret;
>>
>> - if (old_prog && old_prog != link->prog)
>> - return -EPERM;
>> - if (new_prog->type != link->prog->type)
>> - return -EINVAL;
>> -
>> mutex_lock(&netns_bpf_mutex);
>>
>> + if (old_prog && old_prog != link->prog) {
>> + ret = -EPERM;
>> + goto out_unlock;
>> + }
>> + if (new_prog->type != link->prog->type) {
>> + ret = -EINVAL;
>> + goto out_unlock;
>
> can we use guard() and avoid all the goto jumping?
ok, will update next
>
> pw-bot: cr
>
>
>> + }
>> +
>> net = net_link->net;
>> if (!net || !check_net(net)) {
>> /* Link auto-detached or netns dying */
>> --
>> 2.34.1
>>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH bpf v6 3/4] bpf: Fix potential UAF when reading bpf link info
2026-07-27 23:15 ` Andrii Nakryiko
@ 2026-07-28 1:45 ` Pu Lehui
0 siblings, 0 replies; 15+ messages in thread
From: Pu Lehui @ 2026-07-28 1:45 UTC (permalink / raw)
To: Andrii Nakryiko, Pu Lehui
Cc: bpf, linux-kernel, Amery Hung, Emil Tsalapatis, Mykyta Yatsenko,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa
On 2026/7/28 7:15, Andrii Nakryiko wrote:
> On Wed, Jul 22, 2026 at 12:19 AM Pu Lehui <pulehui@huaweicloud.com> wrote:
>>
>> From: Pu Lehui <pulehui@huawei.com>
>>
>> In bpf_link_show_fdinfo and bpf_link_get_info_by_fd, link->prog is
>> accessed without holding any locks. If the prog is concurrently replaced
>> via bpf_link_update, the old prog can be freed, leading to a potential
>> UAF issue.
>>
>> Before dereferencing the prog, both normal RCU and RCU Tasks Trace read
>
> no, either one is enough (and that's why we wait for RCU and RCU Tasks
> Trace grace periods, to make sure that both kinds of accesses work).
> This paragraph is misleading, just drop it.
ok
>
>> locks would normally be required, as BPF_LINK_TYPE_ITER supports both
>> non-sleepable and sleepable progs. However, as commit 57b23c0f612d
>> ("bpf: Retire rcu_trace_implies_rcu_gp()") clarifies, an RCU Tasks Trace
>> grace period implies an RCU grace period, so holding only
>> rcu_read_lock() is already sufficient.
>>
>> Fixes: 0c991ebc8c69 ("bpf: Implement bpf_prog replacement for an active bpf_cgroup_link")
>> Reported-by: Sashiko <sashiko-bot@kernel.org>
>> Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
>> Reviewed-by: Amery Hung <ameryhung@gmail.com>
>> Signed-off-by: Pu Lehui <pulehui@huawei.com>
>> ---
>> kernel/bpf/syscall.c | 21 +++++++++++++++++----
>> 1 file changed, 17 insertions(+), 4 deletions(-)
>>
>> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
>> index 6db306d23b47..cad986807d53 100644
>> --- a/kernel/bpf/syscall.c
>> +++ b/kernel/bpf/syscall.c
>> @@ -3471,9 +3471,10 @@ static const char *bpf_link_type_strs[] = {
>> static void bpf_link_show_fdinfo(struct seq_file *m, struct file *filp)
>> {
>> const struct bpf_link *link = filp->private_data;
>> - const struct bpf_prog *prog = link->prog;
>> + const struct bpf_prog *prog;
>> enum bpf_link_type type = link->type;
>> char prog_tag[sizeof(prog->tag) * 2 + 1] = { };
>> + u32 prog_id;
>
> can be read uninitialized (if link->prog is null), initialize to zero
will send new
>
> pw-bot: cr
>
>
>>
>> if (type < ARRAY_SIZE(bpf_link_type_strs) && bpf_link_type_strs[type]) {
>> if (link->type == BPF_LINK_TYPE_KPROBE_MULTI)
>> @@ -3490,13 +3491,20 @@ static void bpf_link_show_fdinfo(struct seq_file *m, struct file *filp)
>> }
>> seq_printf(m, "link_id:\t%u\n", link->id);
>>
>> + rcu_read_lock();
>> + prog = READ_ONCE(link->prog);
>> if (prog) {
>> bin2hex(prog_tag, prog->tag, sizeof(prog->tag));
>> + prog_id = prog->aux->id;
>> + }
>> + rcu_read_unlock();
>> +
>> + if (prog) {
>> seq_printf(m,
>> "prog_tag:\t%s\n"
>> "prog_id:\t%u\n",
>> prog_tag,
>> - prog->aux->id);
>> + prog_id);
>> }
>> if (link->ops->show_fdinfo)
>> link->ops->show_fdinfo(link, m);
>> @@ -5535,6 +5543,7 @@ static int bpf_link_get_info_by_fd(struct file *file,
>> {
>> struct bpf_link_info __user *uinfo = u64_to_user_ptr(attr->info.info);
>> struct bpf_link_info info;
>> + const struct bpf_prog *prog;
>> u32 info_len = attr->info.info_len;
>> int err;
>>
>> @@ -5549,8 +5558,12 @@ static int bpf_link_get_info_by_fd(struct file *file,
>>
>> info.type = link->type;
>> info.id = link->id;
>> - if (link->prog)
>> - info.prog_id = link->prog->aux->id;
>> +
>> + rcu_read_lock();
>> + prog = READ_ONCE(link->prog);
>> + if (prog)
>> + info.prog_id = prog->aux->id;
>> + rcu_read_unlock();
>>
>> if (link->ops->fill_link_info) {
>> err = link->ops->fill_link_info(link, &info);
>> --
>> 2.34.1
>>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH bpf v6 0/4] Fixes for bpf link update
2026-07-27 23:17 ` [PATCH bpf v6 0/4] Fixes for bpf link update Andrii Nakryiko
@ 2026-07-28 1:54 ` Pu Lehui
0 siblings, 0 replies; 15+ messages in thread
From: Pu Lehui @ 2026-07-28 1:54 UTC (permalink / raw)
To: Andrii Nakryiko
Cc: bpf, linux-kernel, Amery Hung, Emil Tsalapatis, Mykyta Yatsenko,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Pu Lehui
On 2026/7/28 7:17, Andrii Nakryiko wrote:
> On Wed, Jul 22, 2026 at 12:19 AM Pu Lehui <pulehui@huaweicloud.com> wrote:
>>
>> From: Pu Lehui <pulehui@huawei.com>
>>
>> This patch series addresses a few concurrency UAF issues within bpf link update.
>>
>
> each fix is independent from each other, can you please split patch
> set into individual patches, please? some of them are straightforward
Makes total sense, I'll do this.
> and can be applied soon, while others might require a bit more
> thorough review and some more iteration, so best to split and apply
> individually, IMO
Next time I hit unrelated sashiko issues, I’ll just send them separately
from the start.
>
>> v6:
>> - 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 in bpf_mprog_detach. (Emil)
>>
>> v5: https://lore.kernel.org/bpf/20260721041048.1394085-1-pulehui@huaweicloud.com
>> - Remove rcu_read_lock_trace as rcu_read_unlock is sufficient. (Mykyta)
>> - Improve commit msg for patch 2. (Amery)
>> - Add Reviewed-by tag by Amery.
>>
>> v4: https://lore.kernel.org/bpf/20260720134547.1289964-1-pulehui@huaweicloud.com
>> - Add new fix for UAF due to missing link type check in mprog. (Sashiko)
>> - Add new fix for UAF in bpf_netns_link_update_prog. (Sashiko)
>> - Include the storage and flags rollbacks to patch4 for sake of code rigor,
>> as it's hard to make update_effective_progs fail in __cgroup_bpf_attach.
>>
>> v3: https://lore.kernel.org/bpf/20260720033055.1215477-1-pulehui@huaweicloud.com
>> - Add new fix for UAF issue when reading bpf link info. (Sashiko)
>> - Add new fix for storage not restored when update_effective_progs
>> failed. (Sashiko)
>>
>> v2: https://lore.kernel.org/bpf/20260717073343.958862-1-pulehui@huaweicloud.com
>> - Fix invalid access for in-place update when storage changed. (Sashiko)
>>
>> v1: https://lore.kernel.org/bpf/20260714014659.401063-1-pulehui@huaweicloud.com
>>
>> Pu Lehui (4):
>> bpf: Fix potential UAF in bpf_netns_link_update_prog
>> bpf: Fix UAF due to missing link type check in mprog
>> bpf: Fix potential UAF when reading bpf link info
>> bpf, cgroup: Fix storage null-ptr-deref after replacing prog
>>
>> drivers/net/netkit.c | 13 ++++++-----
>> include/linux/bpf_mprog.h | 6 ++++--
>> kernel/bpf/cgroup.c | 44 +++++++++++++++++++++++++++++++++++++-
>> kernel/bpf/mprog.c | 23 ++++++++++++--------
>> kernel/bpf/net_namespace.c | 14 +++++++-----
>> kernel/bpf/syscall.c | 21 ++++++++++++++----
>> kernel/bpf/tcx.c | 13 ++++++-----
>> 7 files changed, 99 insertions(+), 35 deletions(-)
>>
>> --
>> 2.34.1
>>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH bpf v6 2/4] bpf: Fix UAF due to missing link type check in mprog
2026-07-23 3:14 ` Pu Lehui
@ 2026-07-28 1:56 ` Pu Lehui
2026-07-28 5:50 ` Emil Tsalapatis
0 siblings, 1 reply; 15+ messages in thread
From: Pu Lehui @ 2026-07-28 1:56 UTC (permalink / raw)
To: Emil Tsalapatis, bpf, linux-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Pu Lehui, Amery Hung,
Mykyta Yatsenko
Hi Emil,
Gentle ping~, is that acceptable to you?
On 2026/7/23 11:14, Pu Lehui wrote:
>
> On 2026/7/23 0:25, Emil Tsalapatis wrote:
>> On Wed Jul 22, 2026 at 3:23 AM EDT, Pu Lehui wrote:
>>> From: Pu Lehui <pulehui@huawei.com>
>>>
>>> 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 doing
>>> 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
>>> /* 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. Therefore, we need to validate link->type
>>> to reject foreign anchors.
>>>
>>> Fix this by strictly validating link->type in bpf_mprog_link against the
>>> expected link type. mprog APIs is also adjusted to accept and pass down
>>> the expected link type. 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@kernel.org>
>>> Reviewed-by: Amery Hung <ameryhung@gmail.com>
>>> Signed-off-by: Pu Lehui <pulehui@huawei.com>
>>
>> Unless I'm missing something, at the very least there's no need for the
>> extra argument in detach() since we pass UNSPEC to bpf_mprog_link() and
>> never use it otherwise.
>>
>> Even for the attach case, aren't we required to provide a new link
>> for attachment, and so already have the ltype available to test
>> against?
>>
> Hi Emil,
>
> The BPF_F_LINK flag only indicates that relative_fd refers to an anchor
> link or prog; it is independent of whether the object being attached or
> detached is a bare prog or a link. This means that when attaching or
> detaching a bare prog relative to an anchor link, we cannot obtain the
> expected_link_type because the link parameter passed in is NULL.
>
> Furthermore, as you mentioned, we should support unconditional
> detachment. But in the current version, because we strictly check
> expected_link_type, the detach operation will fail if the relative link
> type does not match.
>
> Therefore, we can simplify the approach to fix this UAF—similar to patch
> 3—by relying on RCU protection when accessing the prog type of a
> relative link. When attaching or detaching a bare prog or a link
> relative to an anchor link, we simply verify that
> relative_link->prog->type matches the bare prog->type or
> link->prog->type. This logic also naturally applies to unconditional
> detachment with relative link, such as when the bare prog or link->prog
> being detached is NULL.
>
> And the patch will be follow, wdyt?
>
> diff --git a/kernel/bpf/mprog.c b/kernel/bpf/mprog.c
> index 1394168062e8..512821c21aa6 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); <-- relative_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;
> }
>
>>> ---
>>> drivers/net/netkit.c | 13 ++++++-------
>>> include/linux/bpf_mprog.h | 6 ++++--
>>> kernel/bpf/mprog.c | 23 ++++++++++++++---------
>>> kernel/bpf/tcx.c | 13 ++++++-------
>>> 4 files changed, 30 insertions(+), 25 deletions(-)
>>>
>>> diff --git a/drivers/net/netkit.c b/drivers/net/netkit.c
>>> index a3931cd82132..99ddf2befb23 100644
>>> --- a/drivers/net/netkit.c
>>> +++ b/drivers/net/netkit.c
>>> @@ -768,7 +768,7 @@ int netkit_prog_attach(const union bpf_attr
>>> *attr, struct bpf_prog *prog)
>>> }
>>> ret = bpf_mprog_attach(entry, &entry_new, prog, NULL,
>>> replace_prog,
>>> attr->attach_flags, attr->relative_fd,
>>> - attr->expected_revision);
>>> + attr->expected_revision, BPF_LINK_TYPE_NETKIT);
>>> if (!ret) {
>>> if (entry != entry_new) {
>>> netkit_entry_update(dev, entry_new);
>>> @@ -802,7 +802,7 @@ int netkit_prog_detach(const union bpf_attr
>>> *attr, struct bpf_prog *prog)
>>> goto out;
>>> }
>>> ret = bpf_mprog_detach(entry, &entry_new, prog, NULL,
>>> attr->attach_flags,
>>> - attr->relative_fd, attr->expected_revision);
>>> + attr->relative_fd, attr->expected_revision,
>>> BPF_LINK_TYPE_NETKIT);
>>> if (!ret) {
>>> if (!bpf_mprog_total(entry_new))
>>> entry_new = NULL;
>>> @@ -850,7 +850,7 @@ static int netkit_link_prog_attach(struct
>>> bpf_link *link, u32 flags,
>>> ASSERT_RTNL();
>>> entry = netkit_entry_fetch(dev, true);
>>> ret = bpf_mprog_attach(entry, &entry_new, link->prog, link,
>>> NULL, flags,
>>> - id_or_fd, revision);
>>> + id_or_fd, revision, BPF_LINK_TYPE_NETKIT);
>>> if (!ret) {
>>> if (entry != entry_new) {
>>> netkit_entry_update(dev, entry_new);
>>> @@ -877,7 +877,7 @@ static void netkit_link_release(struct bpf_link
>>> *link)
>>> ret = -ENOENT;
>>> goto out;
>>> }
>>> - ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0,
>>> 0, 0);
>>> + ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0,
>>> 0, 0, BPF_LINK_TYPE_NETKIT);
>>> if (!ret) {
>>> if (!bpf_mprog_total(entry_new))
>>> entry_new = NULL;
>>> @@ -919,9 +919,8 @@ static int netkit_link_update(struct bpf_link
>>> *link, struct bpf_prog *nprog,
>>> ret = -ENOENT;
>>> goto out;
>>> }
>>> - ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog,
>>> - BPF_F_REPLACE | BPF_F_ID,
>>> - link->prog->aux->id, 0);
>>> + ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog,
>>> BPF_F_REPLACE | BPF_F_ID,
>>> + link->prog->aux->id, 0, BPF_LINK_TYPE_NETKIT);
>>> if (!ret) {
>>> WARN_ON_ONCE(entry != entry_new);
>>> oprog = xchg(&link->prog, nprog);
>>> diff --git a/include/linux/bpf_mprog.h b/include/linux/bpf_mprog.h
>>> index 0b9f4caeeb0a..1fbe1a923968 100644
>>> --- a/include/linux/bpf_mprog.h
>>> +++ b/include/linux/bpf_mprog.h
>>> @@ -321,12 +321,14 @@ int bpf_mprog_attach(struct bpf_mprog_entry
>>> *entry,
>>> struct bpf_mprog_entry **entry_new,
>>> struct bpf_prog *prog_new, struct bpf_link *link,
>>> struct bpf_prog *prog_old,
>>> - u32 flags, u32 id_or_fd, u64 revision);
>>> + u32 flags, u32 id_or_fd, u64 revision,
>>> + enum bpf_link_type expected_link_type);
>>> int bpf_mprog_detach(struct bpf_mprog_entry *entry,
>>> struct bpf_mprog_entry **entry_new,
>>> struct bpf_prog *prog, struct bpf_link *link,
>>> - u32 flags, u32 id_or_fd, u64 revision);
>>> + u32 flags, u32 id_or_fd, u64 revision,
>>> + enum bpf_link_type expected_link_type);
>>> int bpf_mprog_query(const union bpf_attr *attr, union bpf_attr
>>> __user *uattr,
>>> struct bpf_mprog_entry *entry);
>>> diff --git a/kernel/bpf/mprog.c b/kernel/bpf/mprog.c
>>> index 1394168062e8..b4a1b35ff569 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)
>>> @@ -226,7 +227,8 @@ int bpf_mprog_attach(struct bpf_mprog_entry *entry,
>>> struct bpf_mprog_entry **entry_new,
>>> struct bpf_prog *prog_new, struct bpf_link *link,
>>> struct bpf_prog *prog_old,
>>> - u32 flags, u32 id_or_fd, u64 revision)
>>> + u32 flags, u32 id_or_fd, u64 revision,
>>> + enum bpf_link_type expected_link_type)
>>> {
>>> struct bpf_tuple rtuple, ntuple = {
>>> .prog = prog_new,
>>> @@ -243,6 +245,7 @@ int bpf_mprog_attach(struct bpf_mprog_entry *entry,
>>> return -EEXIST;
>>> ret = bpf_mprog_tuple_relative(&rtuple, id_or_fd,
>>> flags & ~BPF_F_REPLACE,
>>> + expected_link_type,
>>> prog_new->type);
>>> if (ret)
>>> return ret;
>>> @@ -328,7 +331,8 @@ static int bpf_mprog_fetch(struct bpf_mprog_entry
>>> *entry,
>>> int bpf_mprog_detach(struct bpf_mprog_entry *entry,
>>> struct bpf_mprog_entry **entry_new,
>>> struct bpf_prog *prog, struct bpf_link *link,
>>> - u32 flags, u32 id_or_fd, u64 revision)
>>> + u32 flags, u32 id_or_fd, u64 revision,
>>> + enum bpf_link_type expected_link_type)
>>> {
>>> struct bpf_tuple rtuple, dtuple = {
>>> .prog = prog,
>>> @@ -343,8 +347,9 @@ 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);
>>> + expected_link_type,
>>> + /* Use UNSPEC as wildcard when prog is NULL */
>>> + prog ? prog->type : BPF_PROG_TYPE_UNSPEC);
>>> if (ret)
>>> return ret;
>>> if (dtuple.prog) {
>>> diff --git a/kernel/bpf/tcx.c b/kernel/bpf/tcx.c
>>> index 02db0113b8e7..f208cef13a98 100644
>>> --- a/kernel/bpf/tcx.c
>>> +++ b/kernel/bpf/tcx.c
>>> @@ -38,7 +38,7 @@ int tcx_prog_attach(const union bpf_attr *attr,
>>> struct bpf_prog *prog)
>>> }
>>> ret = bpf_mprog_attach(entry, &entry_new, prog, NULL,
>>> replace_prog,
>>> attr->attach_flags, attr->relative_fd,
>>> - attr->expected_revision);
>>> + attr->expected_revision, BPF_LINK_TYPE_TCX);
>>> if (!ret) {
>>> if (entry != entry_new) {
>>> tcx_entry_update(dev, entry_new, ingress);
>>> @@ -76,7 +76,7 @@ int tcx_prog_detach(const union bpf_attr *attr,
>>> struct bpf_prog *prog)
>>> goto out;
>>> }
>>> ret = bpf_mprog_detach(entry, &entry_new, prog, NULL,
>>> attr->attach_flags,
>>> - attr->relative_fd, attr->expected_revision);
>>> + attr->relative_fd, attr->expected_revision,
>>> BPF_LINK_TYPE_TCX);
>>> if (!ret) {
>>> if (!tcx_entry_is_active(entry_new))
>>> entry_new = NULL;
>>> @@ -152,7 +152,7 @@ static int tcx_link_prog_attach(struct bpf_link
>>> *link, u32 flags, u32 id_or_fd,
>>> if (!entry)
>>> return -ENOMEM;
>>> ret = bpf_mprog_attach(entry, &entry_new, link->prog, link,
>>> NULL, flags,
>>> - id_or_fd, revision);
>>> + id_or_fd, revision, BPF_LINK_TYPE_TCX);
>>> if (!ret) {
>>> if (entry != entry_new) {
>>> tcx_entry_update(dev, entry_new, ingress);
>>> @@ -183,7 +183,7 @@ static void tcx_link_release(struct bpf_link *link)
>>> ret = -ENOENT;
>>> goto out;
>>> }
>>> - ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0,
>>> 0, 0);
>>> + ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0,
>>> 0, 0, BPF_LINK_TYPE_TCX);
>>> if (!ret) {
>>> if (!tcx_entry_is_active(entry_new))
>>> entry_new = NULL;
>>> @@ -229,9 +229,8 @@ static int tcx_link_update(struct bpf_link *link,
>>> struct bpf_prog *nprog,
>>> ret = -ENOENT;
>>> goto out;
>>> }
>>> - ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog,
>>> - BPF_F_REPLACE | BPF_F_ID,
>>> - link->prog->aux->id, 0);
>>> + ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog,
>>> BPF_F_REPLACE | BPF_F_ID,
>>> + link->prog->aux->id, 0, BPF_LINK_TYPE_TCX);
>>> if (!ret) {
>>> WARN_ON_ONCE(entry != entry_new);
>>> oprog = xchg(&link->prog, nprog);
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH bpf v6 2/4] bpf: Fix UAF due to missing link type check in mprog
2026-07-28 1:56 ` Pu Lehui
@ 2026-07-28 5:50 ` Emil Tsalapatis
0 siblings, 0 replies; 15+ messages in thread
From: Emil Tsalapatis @ 2026-07-28 5:50 UTC (permalink / raw)
To: Pu Lehui, Emil Tsalapatis, bpf, linux-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Pu Lehui, Amery Hung,
Mykyta Yatsenko
On Mon Jul 27, 2026 at 9:56 PM EDT, Pu Lehui wrote:
> Hi Emil,
>
> Gentle ping~, is that acceptable to you?
Hi Pu,
Sorry for the late reply. The updated patch looks way nicer,
imo we should go with it:
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
>
> On 2026/7/23 11:14, Pu Lehui wrote:
>>
>> On 2026/7/23 0:25, Emil Tsalapatis wrote:
>>> On Wed Jul 22, 2026 at 3:23 AM EDT, Pu Lehui wrote:
>>>> From: Pu Lehui <pulehui@huawei.com>
>>>>
>>>> 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 doing
>>>> 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
>>>> /* 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. Therefore, we need to validate link->type
>>>> to reject foreign anchors.
>>>>
>>>> Fix this by strictly validating link->type in bpf_mprog_link against the
>>>> expected link type. mprog APIs is also adjusted to accept and pass down
>>>> the expected link type. 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@kernel.org>
>>>> Reviewed-by: Amery Hung <ameryhung@gmail.com>
>>>> Signed-off-by: Pu Lehui <pulehui@huawei.com>
>>>
>>> Unless I'm missing something, at the very least there's no need for the
>>> extra argument in detach() since we pass UNSPEC to bpf_mprog_link() and
>>> never use it otherwise.
>>>
>>> Even for the attach case, aren't we required to provide a new link
>>> for attachment, and so already have the ltype available to test
>>> against?
>>>
>> Hi Emil,
>>
>> The BPF_F_LINK flag only indicates that relative_fd refers to an anchor
>> link or prog; it is independent of whether the object being attached or
>> detached is a bare prog or a link. This means that when attaching or
>> detaching a bare prog relative to an anchor link, we cannot obtain the
>> expected_link_type because the link parameter passed in is NULL.
>>
>> Furthermore, as you mentioned, we should support unconditional
>> detachment. But in the current version, because we strictly check
>> expected_link_type, the detach operation will fail if the relative link
>> type does not match.
>>
>> Therefore, we can simplify the approach to fix this UAF—similar to patch
>> 3—by relying on RCU protection when accessing the prog type of a
>> relative link. When attaching or detaching a bare prog or a link
>> relative to an anchor link, we simply verify that
>> relative_link->prog->type matches the bare prog->type or
>> link->prog->type. This logic also naturally applies to unconditional
>> detachment with relative link, such as when the bare prog or link->prog
>> being detached is NULL.
>>
>> And the patch will be follow, wdyt?
>>
>> diff --git a/kernel/bpf/mprog.c b/kernel/bpf/mprog.c
>> index 1394168062e8..512821c21aa6 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); <-- relative_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;
>> }
>>
>>>> ---
>>>> drivers/net/netkit.c | 13 ++++++-------
>>>> include/linux/bpf_mprog.h | 6 ++++--
>>>> kernel/bpf/mprog.c | 23 ++++++++++++++---------
>>>> kernel/bpf/tcx.c | 13 ++++++-------
>>>> 4 files changed, 30 insertions(+), 25 deletions(-)
>>>>
>>>> diff --git a/drivers/net/netkit.c b/drivers/net/netkit.c
>>>> index a3931cd82132..99ddf2befb23 100644
>>>> --- a/drivers/net/netkit.c
>>>> +++ b/drivers/net/netkit.c
>>>> @@ -768,7 +768,7 @@ int netkit_prog_attach(const union bpf_attr
>>>> *attr, struct bpf_prog *prog)
>>>> }
>>>> ret = bpf_mprog_attach(entry, &entry_new, prog, NULL,
>>>> replace_prog,
>>>> attr->attach_flags, attr->relative_fd,
>>>> - attr->expected_revision);
>>>> + attr->expected_revision, BPF_LINK_TYPE_NETKIT);
>>>> if (!ret) {
>>>> if (entry != entry_new) {
>>>> netkit_entry_update(dev, entry_new);
>>>> @@ -802,7 +802,7 @@ int netkit_prog_detach(const union bpf_attr
>>>> *attr, struct bpf_prog *prog)
>>>> goto out;
>>>> }
>>>> ret = bpf_mprog_detach(entry, &entry_new, prog, NULL,
>>>> attr->attach_flags,
>>>> - attr->relative_fd, attr->expected_revision);
>>>> + attr->relative_fd, attr->expected_revision,
>>>> BPF_LINK_TYPE_NETKIT);
>>>> if (!ret) {
>>>> if (!bpf_mprog_total(entry_new))
>>>> entry_new = NULL;
>>>> @@ -850,7 +850,7 @@ static int netkit_link_prog_attach(struct
>>>> bpf_link *link, u32 flags,
>>>> ASSERT_RTNL();
>>>> entry = netkit_entry_fetch(dev, true);
>>>> ret = bpf_mprog_attach(entry, &entry_new, link->prog, link,
>>>> NULL, flags,
>>>> - id_or_fd, revision);
>>>> + id_or_fd, revision, BPF_LINK_TYPE_NETKIT);
>>>> if (!ret) {
>>>> if (entry != entry_new) {
>>>> netkit_entry_update(dev, entry_new);
>>>> @@ -877,7 +877,7 @@ static void netkit_link_release(struct bpf_link
>>>> *link)
>>>> ret = -ENOENT;
>>>> goto out;
>>>> }
>>>> - ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0,
>>>> 0, 0);
>>>> + ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0,
>>>> 0, 0, BPF_LINK_TYPE_NETKIT);
>>>> if (!ret) {
>>>> if (!bpf_mprog_total(entry_new))
>>>> entry_new = NULL;
>>>> @@ -919,9 +919,8 @@ static int netkit_link_update(struct bpf_link
>>>> *link, struct bpf_prog *nprog,
>>>> ret = -ENOENT;
>>>> goto out;
>>>> }
>>>> - ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog,
>>>> - BPF_F_REPLACE | BPF_F_ID,
>>>> - link->prog->aux->id, 0);
>>>> + ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog,
>>>> BPF_F_REPLACE | BPF_F_ID,
>>>> + link->prog->aux->id, 0, BPF_LINK_TYPE_NETKIT);
>>>> if (!ret) {
>>>> WARN_ON_ONCE(entry != entry_new);
>>>> oprog = xchg(&link->prog, nprog);
>>>> diff --git a/include/linux/bpf_mprog.h b/include/linux/bpf_mprog.h
>>>> index 0b9f4caeeb0a..1fbe1a923968 100644
>>>> --- a/include/linux/bpf_mprog.h
>>>> +++ b/include/linux/bpf_mprog.h
>>>> @@ -321,12 +321,14 @@ int bpf_mprog_attach(struct bpf_mprog_entry
>>>> *entry,
>>>> struct bpf_mprog_entry **entry_new,
>>>> struct bpf_prog *prog_new, struct bpf_link *link,
>>>> struct bpf_prog *prog_old,
>>>> - u32 flags, u32 id_or_fd, u64 revision);
>>>> + u32 flags, u32 id_or_fd, u64 revision,
>>>> + enum bpf_link_type expected_link_type);
>>>> int bpf_mprog_detach(struct bpf_mprog_entry *entry,
>>>> struct bpf_mprog_entry **entry_new,
>>>> struct bpf_prog *prog, struct bpf_link *link,
>>>> - u32 flags, u32 id_or_fd, u64 revision);
>>>> + u32 flags, u32 id_or_fd, u64 revision,
>>>> + enum bpf_link_type expected_link_type);
>>>> int bpf_mprog_query(const union bpf_attr *attr, union bpf_attr
>>>> __user *uattr,
>>>> struct bpf_mprog_entry *entry);
>>>> diff --git a/kernel/bpf/mprog.c b/kernel/bpf/mprog.c
>>>> index 1394168062e8..b4a1b35ff569 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)
>>>> @@ -226,7 +227,8 @@ int bpf_mprog_attach(struct bpf_mprog_entry *entry,
>>>> struct bpf_mprog_entry **entry_new,
>>>> struct bpf_prog *prog_new, struct bpf_link *link,
>>>> struct bpf_prog *prog_old,
>>>> - u32 flags, u32 id_or_fd, u64 revision)
>>>> + u32 flags, u32 id_or_fd, u64 revision,
>>>> + enum bpf_link_type expected_link_type)
>>>> {
>>>> struct bpf_tuple rtuple, ntuple = {
>>>> .prog = prog_new,
>>>> @@ -243,6 +245,7 @@ int bpf_mprog_attach(struct bpf_mprog_entry *entry,
>>>> return -EEXIST;
>>>> ret = bpf_mprog_tuple_relative(&rtuple, id_or_fd,
>>>> flags & ~BPF_F_REPLACE,
>>>> + expected_link_type,
>>>> prog_new->type);
>>>> if (ret)
>>>> return ret;
>>>> @@ -328,7 +331,8 @@ static int bpf_mprog_fetch(struct bpf_mprog_entry
>>>> *entry,
>>>> int bpf_mprog_detach(struct bpf_mprog_entry *entry,
>>>> struct bpf_mprog_entry **entry_new,
>>>> struct bpf_prog *prog, struct bpf_link *link,
>>>> - u32 flags, u32 id_or_fd, u64 revision)
>>>> + u32 flags, u32 id_or_fd, u64 revision,
>>>> + enum bpf_link_type expected_link_type)
>>>> {
>>>> struct bpf_tuple rtuple, dtuple = {
>>>> .prog = prog,
>>>> @@ -343,8 +347,9 @@ 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);
>>>> + expected_link_type,
>>>> + /* Use UNSPEC as wildcard when prog is NULL */
>>>> + prog ? prog->type : BPF_PROG_TYPE_UNSPEC);
>>>> if (ret)
>>>> return ret;
>>>> if (dtuple.prog) {
>>>> diff --git a/kernel/bpf/tcx.c b/kernel/bpf/tcx.c
>>>> index 02db0113b8e7..f208cef13a98 100644
>>>> --- a/kernel/bpf/tcx.c
>>>> +++ b/kernel/bpf/tcx.c
>>>> @@ -38,7 +38,7 @@ int tcx_prog_attach(const union bpf_attr *attr,
>>>> struct bpf_prog *prog)
>>>> }
>>>> ret = bpf_mprog_attach(entry, &entry_new, prog, NULL,
>>>> replace_prog,
>>>> attr->attach_flags, attr->relative_fd,
>>>> - attr->expected_revision);
>>>> + attr->expected_revision, BPF_LINK_TYPE_TCX);
>>>> if (!ret) {
>>>> if (entry != entry_new) {
>>>> tcx_entry_update(dev, entry_new, ingress);
>>>> @@ -76,7 +76,7 @@ int tcx_prog_detach(const union bpf_attr *attr,
>>>> struct bpf_prog *prog)
>>>> goto out;
>>>> }
>>>> ret = bpf_mprog_detach(entry, &entry_new, prog, NULL,
>>>> attr->attach_flags,
>>>> - attr->relative_fd, attr->expected_revision);
>>>> + attr->relative_fd, attr->expected_revision,
>>>> BPF_LINK_TYPE_TCX);
>>>> if (!ret) {
>>>> if (!tcx_entry_is_active(entry_new))
>>>> entry_new = NULL;
>>>> @@ -152,7 +152,7 @@ static int tcx_link_prog_attach(struct bpf_link
>>>> *link, u32 flags, u32 id_or_fd,
>>>> if (!entry)
>>>> return -ENOMEM;
>>>> ret = bpf_mprog_attach(entry, &entry_new, link->prog, link,
>>>> NULL, flags,
>>>> - id_or_fd, revision);
>>>> + id_or_fd, revision, BPF_LINK_TYPE_TCX);
>>>> if (!ret) {
>>>> if (entry != entry_new) {
>>>> tcx_entry_update(dev, entry_new, ingress);
>>>> @@ -183,7 +183,7 @@ static void tcx_link_release(struct bpf_link *link)
>>>> ret = -ENOENT;
>>>> goto out;
>>>> }
>>>> - ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0,
>>>> 0, 0);
>>>> + ret = bpf_mprog_detach(entry, &entry_new, link->prog, link, 0,
>>>> 0, 0, BPF_LINK_TYPE_TCX);
>>>> if (!ret) {
>>>> if (!tcx_entry_is_active(entry_new))
>>>> entry_new = NULL;
>>>> @@ -229,9 +229,8 @@ static int tcx_link_update(struct bpf_link *link,
>>>> struct bpf_prog *nprog,
>>>> ret = -ENOENT;
>>>> goto out;
>>>> }
>>>> - ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog,
>>>> - BPF_F_REPLACE | BPF_F_ID,
>>>> - link->prog->aux->id, 0);
>>>> + ret = bpf_mprog_attach(entry, &entry_new, nprog, link, oprog,
>>>> BPF_F_REPLACE | BPF_F_ID,
>>>> + link->prog->aux->id, 0, BPF_LINK_TYPE_TCX);
>>>> if (!ret) {
>>>> WARN_ON_ONCE(entry != entry_new);
>>>> oprog = xchg(&link->prog, nprog);
>>
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-07-28 5:50 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-07-22 7:23 [PATCH bpf v6 0/4] Fixes for bpf link update Pu Lehui
2026-07-22 7:23 ` [PATCH bpf v6 1/4] bpf: Fix potential UAF in bpf_netns_link_update_prog Pu Lehui
2026-07-27 23:01 ` Andrii Nakryiko
2026-07-28 1:44 ` Pu Lehui
2026-07-22 7:23 ` [PATCH bpf v6 2/4] bpf: Fix UAF due to missing link type check in mprog Pu Lehui
2026-07-22 16:25 ` Emil Tsalapatis
2026-07-23 3:14 ` Pu Lehui
2026-07-28 1:56 ` Pu Lehui
2026-07-28 5:50 ` Emil Tsalapatis
2026-07-22 7:23 ` [PATCH bpf v6 3/4] bpf: Fix potential UAF when reading bpf link info Pu Lehui
2026-07-27 23:15 ` Andrii Nakryiko
2026-07-28 1:45 ` Pu Lehui
2026-07-22 7:23 ` [PATCH bpf v6 4/4] bpf, cgroup: Fix storage null-ptr-deref after replacing prog Pu Lehui
2026-07-27 23:17 ` [PATCH bpf v6 0/4] Fixes for bpf link update Andrii Nakryiko
2026-07-28 1:54 ` Pu Lehui
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®