* [PATCH bpf v4 0/4] Fixes for bpf link update
@ 2026-07-20 13:45 Pu Lehui
2026-07-20 13:45 ` [PATCH bpf v4 1/4] bpf: Fix potential UAF in bpf_netns_link_update_prog Pu Lehui
` (3 more replies)
0 siblings, 4 replies; 13+ messages in thread
From: Pu Lehui @ 2026-07-20 13:45 UTC (permalink / raw)
To: bpf, linux-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Emil Tsalapatis, Pu Lehui,
Pu Lehui
This patch series addresses a few concurrency UAF issues within bpf link update.
v4:
- 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
kernel/bpf/cgroup.c | 44 +++++++++++++++++++++++++++++++++++++-
kernel/bpf/mprog.c | 15 ++++++++-----
kernel/bpf/net_namespace.c | 14 +++++++-----
kernel/bpf/syscall.c | 27 +++++++++++++++++++----
4 files changed, 85 insertions(+), 15 deletions(-)
--
2.34.1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH bpf v4 1/4] bpf: Fix potential UAF in bpf_netns_link_update_prog
2026-07-20 13:45 [PATCH bpf v4 0/4] Fixes for bpf link update Pu Lehui
@ 2026-07-20 13:45 ` Pu Lehui
2026-07-20 13:55 ` sashiko-bot
2026-07-20 17:33 ` Amery Hung
2026-07-20 13:45 ` [PATCH bpf v4 2/4] bpf: Fix UAF due to missing link type check in mprog Pu Lehui
` (2 subsequent siblings)
3 siblings, 2 replies; 13+ messages in thread
From: Pu Lehui @ 2026-07-20 13:45 UTC (permalink / raw)
To: bpf, linux-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Emil Tsalapatis, 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>
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 related [flat|nested] 13+ messages in thread
* [PATCH bpf v4 2/4] bpf: Fix UAF due to missing link type check in mprog
2026-07-20 13:45 [PATCH bpf v4 0/4] Fixes for bpf link update Pu Lehui
2026-07-20 13:45 ` [PATCH bpf v4 1/4] bpf: Fix potential UAF in bpf_netns_link_update_prog Pu Lehui
@ 2026-07-20 13:45 ` Pu Lehui
2026-07-20 18:44 ` Amery Hung
2026-07-20 13:45 ` [PATCH bpf v4 3/4] bpf: Fix potential UAF when reading bpf link info Pu Lehui
2026-07-20 13:45 ` [PATCH bpf v4 4/4] bpf, cgroup: Fix storage null-ptr-deref after replacing prog Pu Lehui
3 siblings, 1 reply; 13+ messages in thread
From: Pu Lehui @ 2026-07-20 13:45 UTC (permalink / raw)
To: bpf, linux-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Emil Tsalapatis, 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 do
BPF_LINK_UPDATE on the abnormal link, it can trigger a UAF issue.
CPU0 CPU1
netkit_link_prog_attach
bpf_mprog_attach
bpf_mprog_tuple_relative
bpf_mprog_link
link = bpf_link_get_from_fd(id_or_fd);
BPF_LINK_UPDATE
...
old_prog = xchg(&link->link.prog, new_prog);
bpf_prog_put(old_prog);
if (type && link->prog->type != type) <-- trigger UAF
Fix this by strictly validate link->type in bpf_mprog_link against the
expected link type. bpf_mprog_tuple_relative is also adjusted to accept
and pass down the expected link type.
Fixes: 053c8e1f235d ("bpf: Add generic attach/detach/query API for multi-progs")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Pu Lehui <pulehui@huawei.com>
---
kernel/bpf/mprog.c | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/kernel/bpf/mprog.c b/kernel/bpf/mprog.c
index 1394168062e8..367f8f10da0a 100644
--- a/kernel/bpf/mprog.c
+++ b/kernel/bpf/mprog.c
@@ -6,7 +6,7 @@
static int bpf_mprog_link(struct bpf_tuple *tuple,
u32 id_or_fd, u32 flags,
- enum bpf_prog_type type)
+ enum bpf_link_type type)
{
struct bpf_link *link = ERR_PTR(-EINVAL);
bool id = flags & BPF_F_ID;
@@ -17,7 +17,7 @@ static int bpf_mprog_link(struct bpf_tuple *tuple,
link = bpf_link_get_from_fd(id_or_fd);
if (IS_ERR(link))
return PTR_ERR(link);
- if (type && link->prog->type != type) {
+ if (type && link->type != type) {
bpf_link_put(link);
return -EINVAL;
}
@@ -52,21 +52,22 @@ static int bpf_mprog_prog(struct bpf_tuple *tuple,
static int bpf_mprog_tuple_relative(struct bpf_tuple *tuple,
u32 id_or_fd, u32 flags,
- enum bpf_prog_type type)
+ enum bpf_link_type ltype,
+ enum bpf_prog_type ptype)
{
bool link = flags & BPF_F_LINK;
bool id = flags & BPF_F_ID;
memset(tuple, 0, sizeof(*tuple));
if (link)
- return bpf_mprog_link(tuple, id_or_fd, flags, type);
+ return bpf_mprog_link(tuple, id_or_fd, flags, ltype);
/* If no relevant flag is set and no id_or_fd was passed, then
* tuple link/prog is just NULLed. This is the case when before/
* after selects first/last position without passing fd.
*/
if (!id && !id_or_fd)
return 0;
- return bpf_mprog_prog(tuple, id_or_fd, flags, type);
+ return bpf_mprog_prog(tuple, id_or_fd, flags, ptype);
}
static void bpf_mprog_tuple_put(struct bpf_tuple *tuple)
@@ -243,6 +244,8 @@ int bpf_mprog_attach(struct bpf_mprog_entry *entry,
return -EEXIST;
ret = bpf_mprog_tuple_relative(&rtuple, id_or_fd,
flags & ~BPF_F_REPLACE,
+ link ? link->type :
+ BPF_LINK_TYPE_UNSPEC,
prog_new->type);
if (ret)
return ret;
@@ -343,6 +346,8 @@ int bpf_mprog_detach(struct bpf_mprog_entry *entry,
if (!bpf_mprog_total(entry))
return -ENOENT;
ret = bpf_mprog_tuple_relative(&rtuple, id_or_fd, flags,
+ link ? link->type :
+ BPF_LINK_TYPE_UNSPEC,
prog ? prog->type :
BPF_PROG_TYPE_UNSPEC);
if (ret)
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH bpf v4 3/4] bpf: Fix potential UAF when reading bpf link info
2026-07-20 13:45 [PATCH bpf v4 0/4] Fixes for bpf link update Pu Lehui
2026-07-20 13:45 ` [PATCH bpf v4 1/4] bpf: Fix potential UAF in bpf_netns_link_update_prog Pu Lehui
2026-07-20 13:45 ` [PATCH bpf v4 2/4] bpf: Fix UAF due to missing link type check in mprog Pu Lehui
@ 2026-07-20 13:45 ` Pu Lehui
2026-07-20 14:16 ` Mykyta Yatsenko
2026-07-20 13:45 ` [PATCH bpf v4 4/4] bpf, cgroup: Fix storage null-ptr-deref after replacing prog Pu Lehui
3 siblings, 1 reply; 13+ messages in thread
From: Pu Lehui @ 2026-07-20 13:45 UTC (permalink / raw)
To: bpf, linux-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Emil Tsalapatis, 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.
Fix this by holding both normal RCU and tasks trace RCU read locks
before dereferencing the prog, as BPF_LINK_TYPE_ITER support both
non-sleepable and sleepable progs.
Fixes: 0c991ebc8c69 ("bpf: Implement bpf_prog replacement for an active bpf_cgroup_link")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Signed-off-by: Pu Lehui <pulehui@huawei.com>
---
kernel/bpf/syscall.c | 27 +++++++++++++++++++++++----
1 file changed, 23 insertions(+), 4 deletions(-)
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 6db306d23b47..2458c68146b9 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,23 @@ static void bpf_link_show_fdinfo(struct seq_file *m, struct file *filp)
}
seq_printf(m, "link_id:\t%u\n", link->id);
+ /* prog can be sleepable */
+ rcu_read_lock_trace();
+ 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();
+ rcu_read_unlock_trace();
+
+ 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 +5546,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 +5561,15 @@ 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;
+
+ /* prog can be sleepable */
+ rcu_read_lock_trace();
+ rcu_read_lock();
+ prog = READ_ONCE(link->prog);
+ if (prog)
+ info.prog_id = prog->aux->id;
+ rcu_read_unlock();
+ rcu_read_unlock_trace();
if (link->ops->fill_link_info) {
err = link->ops->fill_link_info(link, &info);
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH bpf v4 4/4] bpf, cgroup: Fix storage null-ptr-deref after replacing prog
2026-07-20 13:45 [PATCH bpf v4 0/4] Fixes for bpf link update Pu Lehui
` (2 preceding siblings ...)
2026-07-20 13:45 ` [PATCH bpf v4 3/4] bpf: Fix potential UAF when reading bpf link info Pu Lehui
@ 2026-07-20 13:45 ` Pu Lehui
2026-07-20 18:58 ` Amery Hung
3 siblings, 1 reply; 13+ messages in thread
From: Pu Lehui @ 2026-07-20 13:45 UTC (permalink / raw)
To: bpf, linux-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Emil Tsalapatis, 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")
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 related [flat|nested] 13+ messages in thread
* Re: [PATCH bpf v4 1/4] bpf: Fix potential UAF in bpf_netns_link_update_prog
2026-07-20 13:45 ` [PATCH bpf v4 1/4] bpf: Fix potential UAF in bpf_netns_link_update_prog Pu Lehui
@ 2026-07-20 13:55 ` sashiko-bot
2026-07-21 3:41 ` Pu Lehui
2026-07-20 17:33 ` Amery Hung
1 sibling, 1 reply; 13+ messages in thread
From: sashiko-bot @ 2026-07-20 13:55 UTC (permalink / raw)
To: Pu Lehui; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Lockless access to `link->prog` in generic BPF link info retrieval functions and mprog logic can lead to a Use-After-Free when an updateable link replaces its program concurrently.
--
--- Patch [1]: [PATCH bpf v4 1/4] bpf: Fix potential UAF in bpf_netns_link_update_prog ---
commit 1e9631860a41f1ee87e0b9afa5e2e3ca3f35147e
Author: Pu Lehui <pulehui@huawei.com>
bpf: Fix potential UAF in bpf_netns_link_update_prog
This commit fixes a potential use-after-free race condition in
bpf_netns_link_update_prog. By moving the old_prog and prog->type checks
inside the netns_bpf_mutex critical section, it prevents concurrent
BPF_LINK_UPDATE operations from freeing the program while it is being accessed.
> diff --git a/kernel/bpf/net_namespace.c b/kernel/bpf/net_namespace.c
> index 25f30f9edaefd..9fc62db1441c6 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;
> + }
> +
[Severity: High]
This isn't a bug introduced by this patch, but while looking at the lockless
accesses of link->prog being fixed here, I noticed other places that might
have similar race conditions.
For example, in kernel/bpf/syscall.c, bpf_link_show_fdinfo() reads link->prog
locklessly:
kernel/bpf/syscall.c:bpf_link_show_fdinfo() {
...
const struct bpf_prog *prog = link->prog;
...
if (prog) {
bin2hex(prog_tag, prog->tag, sizeof(prog->tag));
...
}
Similarly, bpf_link_get_info_by_fd() in the same file accesses
link->prog->aux->id locklessly, and bpf_mprog_link() in kernel/bpf/mprog.c
checks link->prog->type without locks.
If a user-space thread calls one of these info retrieval functions and is
preempted, could a concurrent BPF_LINK_UPDATE (like tcx_link_update or
the one being fixed in this patch) swap out link->prog via xchg() and free
it?
Since the reading threads do not appear to hold rcu_read_lock() or take a
reference on the program, it seems the RCU grace period could complete,
resulting in a use-after-free when they resume and dereference prog->tag or
prog->aux->id.
Should we add RCU read locks or reference counting around these generic info
retrieval paths to prevent the program from being freed out from under them?
> net = net_link->net;
> if (!net || !check_net(net)) {
> /* Link auto-detached or netns dying */
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260720134547.1289964-1-pulehui@huaweicloud.com?part=1
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf v4 3/4] bpf: Fix potential UAF when reading bpf link info
2026-07-20 13:45 ` [PATCH bpf v4 3/4] bpf: Fix potential UAF when reading bpf link info Pu Lehui
@ 2026-07-20 14:16 ` Mykyta Yatsenko
2026-07-21 3:38 ` Pu Lehui
0 siblings, 1 reply; 13+ messages in thread
From: Mykyta Yatsenko @ 2026-07-20 14:16 UTC (permalink / raw)
To: Pu Lehui, bpf, linux-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Emil Tsalapatis, Pu Lehui
On 7/20/26 2:45 PM, Pu Lehui 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.
>
> Fix this by holding both normal RCU and tasks trace RCU read locks
> before dereferencing the prog, as BPF_LINK_TYPE_ITER support both
> non-sleepable and sleepable progs.
>
> Fixes: 0c991ebc8c69 ("bpf: Implement bpf_prog replacement for an active bpf_cgroup_link")
> Reported-by: Sashiko <sashiko-bot@kernel.org>
> Signed-off-by: Pu Lehui <pulehui@huawei.com>
> ---
> kernel/bpf/syscall.c | 27 +++++++++++++++++++++++----
> 1 file changed, 23 insertions(+), 4 deletions(-)
>
> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> index 6db306d23b47..2458c68146b9 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,23 @@ static void bpf_link_show_fdinfo(struct seq_file *m, struct file *filp)
> }
> seq_printf(m, "link_id:\t%u\n", link->id);
>
> + /* prog can be sleepable */
> + rcu_read_lock_trace();
> + rcu_read_lock();
Only rcu_read_lock() should be enough, see 57b23c0f612d ("bpf: Retire rcu_trace_implies_rcu_gp()")
Did you manage to reproduce the UAF?
> + prog = READ_ONCE(link->prog);
> if (prog) {
> bin2hex(prog_tag, prog->tag, sizeof(prog->tag));
> + prog_id = prog->aux->id;
> + }
> + rcu_read_unlock();
> + rcu_read_unlock_trace();
> +
> + 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 +5546,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 +5561,15 @@ 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;
> +
> + /* prog can be sleepable */
> + rcu_read_lock_trace();
> + rcu_read_lock();
> + prog = READ_ONCE(link->prog);
> + if (prog)
> + info.prog_id = prog->aux->id;
> + rcu_read_unlock();
> + rcu_read_unlock_trace();
>
> if (link->ops->fill_link_info) {
> err = link->ops->fill_link_info(link, &info);
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf v4 1/4] bpf: Fix potential UAF in bpf_netns_link_update_prog
2026-07-20 13:45 ` [PATCH bpf v4 1/4] bpf: Fix potential UAF in bpf_netns_link_update_prog Pu Lehui
2026-07-20 13:55 ` sashiko-bot
@ 2026-07-20 17:33 ` Amery Hung
1 sibling, 0 replies; 13+ messages in thread
From: Amery Hung @ 2026-07-20 17:33 UTC (permalink / raw)
To: Pu Lehui, bpf, linux-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Emil Tsalapatis, Pu Lehui
On 7/20/26 6:45 AM, Pu Lehui 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>
> Signed-off-by: Pu Lehui <pulehui@huawei.com>
Reviewed-by: Amery Hung <ameryhung@gmail.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 */
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf v4 2/4] bpf: Fix UAF due to missing link type check in mprog
2026-07-20 13:45 ` [PATCH bpf v4 2/4] bpf: Fix UAF due to missing link type check in mprog Pu Lehui
@ 2026-07-20 18:44 ` Amery Hung
2026-07-21 3:39 ` Pu Lehui
0 siblings, 1 reply; 13+ messages in thread
From: Amery Hung @ 2026-07-20 18:44 UTC (permalink / raw)
To: Pu Lehui, bpf, linux-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Emil Tsalapatis, Pu Lehui
On 7/20/26 6:45 AM, 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 do
> BPF_LINK_UPDATE on the abnormal link, it can trigger a UAF issue.
>
> CPU0 CPU1
> netkit_link_prog_attach
> bpf_mprog_attach
> bpf_mprog_tuple_relative
> bpf_mprog_link
> link = bpf_link_get_from_fd(id_or_fd);
> BPF_LINK_UPDATE
> ...
> old_prog = xchg(&link->link.prog, new_prog);
> bpf_prog_put(old_prog);
> if (type && link->prog->type != type) <-- trigger UAF
>
> Fix this by strictly validate link->type in bpf_mprog_link against the
> expected link type. bpf_mprog_tuple_relative is also adjusted to accept
> and pass down the expected link type.
>
> Fixes: 053c8e1f235d ("bpf: Add generic attach/detach/query API for multi-progs")
> Reported-by: Sashiko <sashiko-bot@kernel.org>
> Signed-off-by: Pu Lehui <pulehui@huawei.com>
Reviewed-by: Amery Hung <ameryhung@gmail.com>
The message can be improved. 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.
There are also some grammar errors:
If do -> If doing
strictly validate -> strictly validating.
> ---
> kernel/bpf/mprog.c | 15 ++++++++++-----
> 1 file changed, 10 insertions(+), 5 deletions(-)
>
> diff --git a/kernel/bpf/mprog.c b/kernel/bpf/mprog.c
> index 1394168062e8..367f8f10da0a 100644
> --- a/kernel/bpf/mprog.c
> +++ b/kernel/bpf/mprog.c
> @@ -6,7 +6,7 @@
>
> static int bpf_mprog_link(struct bpf_tuple *tuple,
> u32 id_or_fd, u32 flags,
> - enum bpf_prog_type type)
> + enum bpf_link_type type)
> {
> struct bpf_link *link = ERR_PTR(-EINVAL);
> bool id = flags & BPF_F_ID;
> @@ -17,7 +17,7 @@ static int bpf_mprog_link(struct bpf_tuple *tuple,
> link = bpf_link_get_from_fd(id_or_fd);
> if (IS_ERR(link))
> return PTR_ERR(link);
> - if (type && link->prog->type != type) {
> + if (type && link->type != type) {
> bpf_link_put(link);
> return -EINVAL;
> }
> @@ -52,21 +52,22 @@ static int bpf_mprog_prog(struct bpf_tuple *tuple,
>
> static int bpf_mprog_tuple_relative(struct bpf_tuple *tuple,
> u32 id_or_fd, u32 flags,
> - enum bpf_prog_type type)
> + enum bpf_link_type ltype,
> + enum bpf_prog_type ptype)
> {
> bool link = flags & BPF_F_LINK;
> bool id = flags & BPF_F_ID;
>
> memset(tuple, 0, sizeof(*tuple));
> if (link)
> - return bpf_mprog_link(tuple, id_or_fd, flags, type);
> + return bpf_mprog_link(tuple, id_or_fd, flags, ltype);
> /* If no relevant flag is set and no id_or_fd was passed, then
> * tuple link/prog is just NULLed. This is the case when before/
> * after selects first/last position without passing fd.
> */
> if (!id && !id_or_fd)
> return 0;
> - return bpf_mprog_prog(tuple, id_or_fd, flags, type);
> + return bpf_mprog_prog(tuple, id_or_fd, flags, ptype);
> }
>
> static void bpf_mprog_tuple_put(struct bpf_tuple *tuple)
> @@ -243,6 +244,8 @@ int bpf_mprog_attach(struct bpf_mprog_entry *entry,
> return -EEXIST;
> ret = bpf_mprog_tuple_relative(&rtuple, id_or_fd,
> flags & ~BPF_F_REPLACE,
> + link ? link->type :
> + BPF_LINK_TYPE_UNSPEC,
nit: keep it in a line. It is well under 100 cols.
> prog_new->type);
> if (ret)
> return ret;
> @@ -343,6 +346,8 @@ int bpf_mprog_detach(struct bpf_mprog_entry *entry,
> if (!bpf_mprog_total(entry))
> return -ENOENT;
> ret = bpf_mprog_tuple_relative(&rtuple, id_or_fd, flags,
> + link ? link->type :
> + BPF_LINK_TYPE_UNSPEC,
Same here.
> prog ? prog->type :
> BPF_PROG_TYPE_UNSPEC);
> if (ret)
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf v4 4/4] bpf, cgroup: Fix storage null-ptr-deref after replacing prog
2026-07-20 13:45 ` [PATCH bpf v4 4/4] bpf, cgroup: Fix storage null-ptr-deref after replacing prog Pu Lehui
@ 2026-07-20 18:58 ` Amery Hung
0 siblings, 0 replies; 13+ messages in thread
From: Amery Hung @ 2026-07-20 18:58 UTC (permalink / raw)
To: Pu Lehui, bpf, linux-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Emil Tsalapatis, Pu Lehui
On 7/20/26 6:45 AM, Pu Lehui wrote:
> 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")
> Signed-off-by: Pu Lehui <pulehui@huawei.com>
Reviewed-by: Amery Hung <ameryhung@gmail.com>
[...]
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf v4 3/4] bpf: Fix potential UAF when reading bpf link info
2026-07-20 14:16 ` Mykyta Yatsenko
@ 2026-07-21 3:38 ` Pu Lehui
0 siblings, 0 replies; 13+ messages in thread
From: Pu Lehui @ 2026-07-21 3:38 UTC (permalink / raw)
To: Mykyta Yatsenko, bpf, linux-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Emil Tsalapatis, Pu Lehui
On 2026/7/20 22:16, Mykyta Yatsenko wrote:
>
> On 7/20/26 2:45 PM, Pu Lehui 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.
>>
>> Fix this by holding both normal RCU and tasks trace RCU read locks
>> before dereferencing the prog, as BPF_LINK_TYPE_ITER support both
>> non-sleepable and sleepable progs.
>>
>> Fixes: 0c991ebc8c69 ("bpf: Implement bpf_prog replacement for an active bpf_cgroup_link")
>> Reported-by: Sashiko <sashiko-bot@kernel.org>
>> Signed-off-by: Pu Lehui <pulehui@huawei.com>
>> ---
>> kernel/bpf/syscall.c | 27 +++++++++++++++++++++++----
>> 1 file changed, 23 insertions(+), 4 deletions(-)
>>
>> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
>> index 6db306d23b47..2458c68146b9 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,23 @@ static void bpf_link_show_fdinfo(struct seq_file *m, struct file *filp)
>> }
>> seq_printf(m, "link_id:\t%u\n", link->id);
>>
>> + /* prog can be sleepable */
>> + rcu_read_lock_trace();
>> + rcu_read_lock();
>
> Only rcu_read_lock() should be enough, see 57b23c0f612d ("bpf: Retire rcu_trace_implies_rcu_gp()")
> Did you manage to reproduce the UAF?
This is also what I found a bit weird, although nesting rcu and rcu_tt
isn't a problem in itself. Based on commit 57b23c0f612d, RCU Tasks Trace
grace period implies RCU grace period., so rcu_read_lock is already
sufficient. Thank you very much, Mykyta. :)
As for reproducing UAF, it's not so difficult to trigger:
Sleep for a while in bpf_link_show_fdinfo after fetching link->prog to
enlarge the race window and allow the RCU grace period to advance.
```
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -3411,6 +3411,8 @@ static void bpf_link_show_fdinfo(struct seq_file
*m, struct file *filp)
}
seq_printf(m, "link_id:\t%u\n", link->id);
+ msleep(2000);
+
if (prog) {
bin2hex(prog_tag, prog->tag, sizeof(prog->tag));
seq_printf(m,
```
and the following selftests:
prog_tests/link_info_race.c
```
#include <pthread.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include <test_progs.h>
#include "link_info_race.skel.h"
#define MAX_ITERATIONS 1000000
static volatile bool stop_flag;
static void *updater_thread(void *arg)
{
int link_fd = *(int *)arg;
struct link_info_race *skel = NULL;
int prog_fd1, prog_fd2;
int err, i = 0;
skel = link_info_race__open_and_load();
if (!skel) {
fprintf(stderr, "updater: failed to open skeleton\n");
return NULL;
}
prog_fd1 = bpf_program__fd(skel->progs.iter_prog_non_sleepable);
prog_fd2 = bpf_program__fd(skel->progs.iter_prog_sleepable);
while (!stop_flag && i++ < MAX_ITERATIONS) {
int new_fd = (i % 2) ? prog_fd1 : prog_fd2;
err = bpf_link_update(link_fd, new_fd, NULL);
if (err && errno != EINVAL && errno != EBUSY) {
fprintf(stderr, "updater: bpf_link_update failed: %s\n",
strerror(errno));
}
}
link_info_race__destroy(skel);
return NULL;
}
static void *reader_thread(void *arg)
{
int link_fd = *(int *)arg;
char buf[4096];
int fd, i = 0;
struct bpf_link_info info;
__u32 len;
while (!stop_flag && i++ < MAX_ITERATIONS) {
snprintf(buf, sizeof(buf), "/proc/self/fdinfo/%d", link_fd);
fd = open(buf, O_RDONLY);
if (fd >= 0) {
read(fd, buf, sizeof(buf) - 1);
close(fd);
}
}
return NULL;
}
void test_link_info_race(void)
{
struct link_info_race *skel = NULL;
pthread_t tid_updater, tid_reader;
int link_fd, err;
skel = link_info_race__open_and_load();
if (!ASSERT_OK_PTR(skel, "open_and_load"))
return;
link_fd =
bpf_link_create(bpf_program__fd(skel->progs.iter_prog_non_sleepable),
0, BPF_TRACE_ITER, NULL);
if (!ASSERT_GE(link_fd, 0, "bpf_link_create")) {
link_info_race__destroy(skel);
return;
}
stop_flag = false;
err = pthread_create(&tid_updater, NULL, updater_thread, &link_fd);
if (!ASSERT_OK(err, "pthread_create_updater")) {
close(link_fd);
link_info_race__destroy(skel);
return;
}
err = pthread_create(&tid_reader, NULL, reader_thread, &link_fd);
if (!ASSERT_OK(err, "pthread_create_reader")) {
stop_flag = true;
pthread_join(tid_updater, NULL);
close(link_fd);
link_info_race__destroy(skel);
return;
}
sleep(10);
stop_flag = true;
pthread_join(tid_updater, NULL);
pthread_join(tid_reader, NULL);
close(link_fd);
link_info_race__destroy(skel);
}
```
progs/link_info_race.c
```
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
char _license[] SEC("license") = "GPL";
SEC("iter/task")
int iter_prog_non_sleepable(struct bpf_iter__task *ctx)
{
return 0;
}
SEC("iter.s/task")
int iter_prog_sleepable(struct bpf_iter__task *ctx)
{
return 0;
}
```
It will trigger UAF:
```
root@(none):/mnt/bpf# ./test_progs -v -a link_info_race
bpf_testmod.ko is already unloaded.
Loading bpf_testmod.ko...
Successfully loaded bpf_testmod.ko.
test_link_info_race:PASS:open_and_load 0 nsec
test_link_info_race:PASS:bpf_link_create 0 nsec
test_link_info_race:PASS:pthread_create_updater 0 nsec
test_link_info_race:PASS:pthread_create_reader 0 nsec
WATCHDOG: test case link_info_race executes for 10 seconds...
[ 65.037682] Unable to handle kernel access to user memory without
uaccess routines at virtual address 0000000000000020
[ 65.038461] Current test_progs pgtable: 4K pagesize, 57-bit VAs,
pgdp=0x00000001020af000
[ 65.038758] [0000000000000020] pgd=000000004061ec01, p4d=0000000000000000
[ 65.039910] Oops [#1]
[ 65.040065] Modules linked in: bpf_testmod(OE) [last unloaded:
bpf_testmod(OE)]
[ 65.041009] CPU: 2 UID: 0 PID: 111 Comm: test_progs Tainted: G
OE 7.2.0-rc3-g0bcca2a42cc5-dirty #12 PREEMPTLAZY
[ 65.041576] Tainted: [O]=OOT_MODULE, [E]=UNSIGNED_MODULE
[ 65.041788] Hardware name: riscv-virtio,qemu (DT)
[ 65.042087] epc : bpf_link_show_fdinfo+0xe6/0x188
[ 65.043062] ra : bpf_link_show_fdinfo+0xdc/0x188
[ 65.043276] epc : ffffffff801a1cce ra : ffffffff801a1cc4 sp :
ff2000000058bbc0
[ 65.043538] gp : ffffffff81dba048 tp : ff60000080f53500 t0 :
ffffffff8001e2d8
[ 65.043787] t1 : 0000000000006000 t2 : ff60000080f53610 s0 :
ff2000000058bc20
[ 65.044044] s1 : ff6000008184b800 a0 : ff6000008209c4d0 a1 :
ff200000000bd020
[ 65.044288] a2 : ff2000000058bbd0 a3 : 0000000000000030 a4 :
ff2000000058bbe0
[ 65.044542] a5 : 0000000000000000 a6 : ffffffff8105e620 a7 :
000000000000000e
[ 65.044799] s2 : ff6000008209c4d0 s3 : ff200000000bd000 s4 :
ff600000802ebc80
[ 65.045049] s5 : 00007fffbeabc880 s6 : 0000000000000000 s7 :
0000000000080000
[ 65.045305] s8 : 00007fffbf57dce0 s9 : ff2000000058bd00 s10:
00007fffbeabc140
[ 65.045554] s11: 0000000000000001 t3 : ff600001fffc3900 t4 :
ff60000080f53600
[ 65.045828] t5 : 000000000000024f t6 : 000000000000024f ssp :
0000000000000000
[ 65.046078] status: 0000000200000120 badaddr: 0000000000000020 cause:
000000000000000d
[ 65.046482] [<ffffffff801a1cce>] bpf_link_show_fdinfo+0xe6/0x188
[ 65.046855] [<ffffffff803bd01e>] seq_show+0x14e/0x1f0
[ 65.047068] [<ffffffff80354d04>] seq_read_iter+0xc4/0x350
[ 65.047284] [<ffffffff80355074>] seq_read+0xe4/0x118
[ 65.047485] [<ffffffff8031d3ec>] vfs_read+0xb4/0x2e0
[ 65.047681] [<ffffffff8031e312>] ksys_read+0x6a/0xf0
[ 65.047875] [<ffffffff8031e3b8>] __riscv_sys_read+0x20/0x30
[ 65.048075] [<ffffffff80a727fe>] do_trap_ecall_u+0x256/0x458
[ 65.048315] [<ffffffff80a81634>] handle_exception+0x16c/0x178
[ 65.049112] Code: fb04 3097 003a 80e7 bac0 b783 0509 0613 fb04 854a
(5394) 1597
[ 65.049821] ---[ end trace 0000000000000000 ]---
[ 65.050483] Kernel panic - not syncing: Fatal exception
[ 65.050874] SMP: stopping secondary CPUs
[ 65.051646] ---[ end Kernel panic - not syncing: Fatal exception ]---
```
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf v4 2/4] bpf: Fix UAF due to missing link type check in mprog
2026-07-20 18:44 ` Amery Hung
@ 2026-07-21 3:39 ` Pu Lehui
0 siblings, 0 replies; 13+ messages in thread
From: Pu Lehui @ 2026-07-21 3:39 UTC (permalink / raw)
To: Amery Hung, bpf, linux-kernel
Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Yonghong Song, Song Liu, Jiri Olsa, Emil Tsalapatis, Pu Lehui
On 2026/7/21 2:44, Amery Hung wrote:
>
>
> On 7/20/26 6:45 AM, 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 do
>> BPF_LINK_UPDATE on the abnormal link, it can trigger a UAF issue.
>>
>> CPU0 CPU1
>> netkit_link_prog_attach
>> bpf_mprog_attach
>> bpf_mprog_tuple_relative
>> bpf_mprog_link
>> link = bpf_link_get_from_fd(id_or_fd);
>> BPF_LINK_UPDATE
>> ...
>> old_prog =
>> xchg(&link->link.prog, new_prog);
>> bpf_prog_put(old_prog);
>> if (type && link->prog->type != type) <-- trigger UAF
>>
>> Fix this by strictly validate link->type in bpf_mprog_link against the
>> expected link type. bpf_mprog_tuple_relative is also adjusted to accept
>> and pass down the expected link type.
>>
>> Fixes: 053c8e1f235d ("bpf: Add generic attach/detach/query API for
>> multi-progs")
>> Reported-by: Sashiko <sashiko-bot@kernel.org>
>> Signed-off-by: Pu Lehui <pulehui@huawei.com>
>
> Reviewed-by: Amery Hung <ameryhung@gmail.com>
>
> The message can be improved. 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.
>
> There are also some grammar errors:
> If do -> If doing
> strictly validate -> strictly validating.
make sense to me, thanks Amery, will respin soon.
>
>> ---
>> kernel/bpf/mprog.c | 15 ++++++++++-----
>> 1 file changed, 10 insertions(+), 5 deletions(-)
>>
>> diff --git a/kernel/bpf/mprog.c b/kernel/bpf/mprog.c
>> index 1394168062e8..367f8f10da0a 100644
>> --- a/kernel/bpf/mprog.c
>> +++ b/kernel/bpf/mprog.c
>> @@ -6,7 +6,7 @@
>> static int bpf_mprog_link(struct bpf_tuple *tuple,
>> u32 id_or_fd, u32 flags,
>> - enum bpf_prog_type type)
>> + enum bpf_link_type type)
>> {
>> struct bpf_link *link = ERR_PTR(-EINVAL);
>> bool id = flags & BPF_F_ID;
>> @@ -17,7 +17,7 @@ static int bpf_mprog_link(struct bpf_tuple *tuple,
>> link = bpf_link_get_from_fd(id_or_fd);
>> if (IS_ERR(link))
>> return PTR_ERR(link);
>> - if (type && link->prog->type != type) {
>> + if (type && link->type != type) {
>> bpf_link_put(link);
>> return -EINVAL;
>> }
>> @@ -52,21 +52,22 @@ static int bpf_mprog_prog(struct bpf_tuple *tuple,
>> static int bpf_mprog_tuple_relative(struct bpf_tuple *tuple,
>> u32 id_or_fd, u32 flags,
>> - enum bpf_prog_type type)
>> + enum bpf_link_type ltype,
>> + enum bpf_prog_type ptype)
>> {
>> bool link = flags & BPF_F_LINK;
>> bool id = flags & BPF_F_ID;
>> memset(tuple, 0, sizeof(*tuple));
>> if (link)
>> - return bpf_mprog_link(tuple, id_or_fd, flags, type);
>> + return bpf_mprog_link(tuple, id_or_fd, flags, ltype);
>> /* If no relevant flag is set and no id_or_fd was passed, then
>> * tuple link/prog is just NULLed. This is the case when before/
>> * after selects first/last position without passing fd.
>> */
>> if (!id && !id_or_fd)
>> return 0;
>> - return bpf_mprog_prog(tuple, id_or_fd, flags, type);
>> + return bpf_mprog_prog(tuple, id_or_fd, flags, ptype);
>> }
>> static void bpf_mprog_tuple_put(struct bpf_tuple *tuple)
>> @@ -243,6 +244,8 @@ int bpf_mprog_attach(struct bpf_mprog_entry *entry,
>> return -EEXIST;
>> ret = bpf_mprog_tuple_relative(&rtuple, id_or_fd,
>> flags & ~BPF_F_REPLACE,
>> + link ? link->type :
>> + BPF_LINK_TYPE_UNSPEC,
>
> nit: keep it in a line. It is well under 100 cols.
>
>> prog_new->type);
>> if (ret)
>> return ret;
>> @@ -343,6 +346,8 @@ int bpf_mprog_detach(struct bpf_mprog_entry *entry,
>> if (!bpf_mprog_total(entry))
>> return -ENOENT;
>> ret = bpf_mprog_tuple_relative(&rtuple, id_or_fd, flags,
>> + link ? link->type :
>> + BPF_LINK_TYPE_UNSPEC,
>
> Same here.
>
>> prog ? prog->type :
>> BPF_PROG_TYPE_UNSPEC);
>> if (ret)
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf v4 1/4] bpf: Fix potential UAF in bpf_netns_link_update_prog
2026-07-20 13:55 ` sashiko-bot
@ 2026-07-21 3:41 ` Pu Lehui
0 siblings, 0 replies; 13+ messages in thread
From: Pu Lehui @ 2026-07-21 3:41 UTC (permalink / raw)
To: sashiko-reviews; +Cc: bpf
On 2026/7/20 21:55, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
>
> Pre-existing issues:
> - [High] Lockless access to `link->prog` in generic BPF link info retrieval functions and mprog logic can lead to a Use-After-Free when an updateable link replaces its program concurrently.
> --
>
> --- Patch [1]: [PATCH bpf v4 1/4] bpf: Fix potential UAF in bpf_netns_link_update_prog ---
> commit 1e9631860a41f1ee87e0b9afa5e2e3ca3f35147e
> Author: Pu Lehui <pulehui@huawei.com>
>
> bpf: Fix potential UAF in bpf_netns_link_update_prog
>
> This commit fixes a potential use-after-free race condition in
> bpf_netns_link_update_prog. By moving the old_prog and prog->type checks
> inside the netns_bpf_mutex critical section, it prevents concurrent
> BPF_LINK_UPDATE operations from freeing the program while it is being accessed.
>
>> diff --git a/kernel/bpf/net_namespace.c b/kernel/bpf/net_namespace.c
>> index 25f30f9edaefd..9fc62db1441c6 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;
>> + }
>> +
>
> [Severity: High]
> This isn't a bug introduced by this patch, but while looking at the lockless
> accesses of link->prog being fixed here, I noticed other places that might
> have similar race conditions.
>
> For example, in kernel/bpf/syscall.c, bpf_link_show_fdinfo() reads link->prog
> locklessly:
>
> kernel/bpf/syscall.c:bpf_link_show_fdinfo() {
> ...
> const struct bpf_prog *prog = link->prog;
> ...
> if (prog) {
> bin2hex(prog_tag, prog->tag, sizeof(prog->tag));
> ...
> }
>
> Similarly, bpf_link_get_info_by_fd() in the same file accesses
> link->prog->aux->id locklessly, and bpf_mprog_link() in kernel/bpf/mprog.c
> checks link->prog->type without locks.
patch 3 have been covered bpf_link_get_info_by_fd and
bpf_link_show_fdinfo. patch 2 have been covered bpf_mprog_link
>
> If a user-space thread calls one of these info retrieval functions and is
> preempted, could a concurrent BPF_LINK_UPDATE (like tcx_link_update or
> the one being fixed in this patch) swap out link->prog via xchg() and free
> it?
>
> Since the reading threads do not appear to hold rcu_read_lock() or take a
> reference on the program, it seems the RCU grace period could complete,
> resulting in a use-after-free when they resume and dereference prog->tag or
> prog->aux->id.
>
> Should we add RCU read locks or reference counting around these generic info
> retrieval paths to prevent the program from being freed out from under them?
>
>> net = net_link->net;
>> if (!net || !check_net(net)) {
>> /* Link auto-detached or netns dying */
>
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-07-21 3:41 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-20 13:45 [PATCH bpf v4 0/4] Fixes for bpf link update Pu Lehui
2026-07-20 13:45 ` [PATCH bpf v4 1/4] bpf: Fix potential UAF in bpf_netns_link_update_prog Pu Lehui
2026-07-20 13:55 ` sashiko-bot
2026-07-21 3:41 ` Pu Lehui
2026-07-20 17:33 ` Amery Hung
2026-07-20 13:45 ` [PATCH bpf v4 2/4] bpf: Fix UAF due to missing link type check in mprog Pu Lehui
2026-07-20 18:44 ` Amery Hung
2026-07-21 3:39 ` Pu Lehui
2026-07-20 13:45 ` [PATCH bpf v4 3/4] bpf: Fix potential UAF when reading bpf link info Pu Lehui
2026-07-20 14:16 ` Mykyta Yatsenko
2026-07-21 3:38 ` Pu Lehui
2026-07-20 13:45 ` [PATCH bpf v4 4/4] bpf, cgroup: Fix storage null-ptr-deref after replacing prog Pu Lehui
2026-07-20 18:58 ` Amery Hung
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.