[PATCH RFC] bpf: Fix null-ptr-deref in bpf_get_local_storage() on link update

0 views
Skip to first unread message

syzbot

unread,
May 13, 2026, 8:05:12 PM (2 days ago) May 13
to syzkaller-upst...@googlegroups.com, syz...@lists.linux.dev
The syzkaller reported a KASAN null-ptr-deref in
bpf_get_local_storage(). This occurs when a BPF program tries to access
its cgroup storage, but the storage pointer in the effective program
array is NULL.

The root cause is a bug in __cgroup_bpf_replace() which handles
BPF_LINK_UPDATE for cgroup links. When replacing a program that does not
use cgroup storage with one that does, __cgroup_bpf_replace() fails to
allocate the new cgroup storage. Furthermore, it uses
replace_effective_prog() which only updates the program pointer in the
effective array and ignores the cgroup_storage array. This leaves the
storage pointers as NULL, leading to a crash when the new program
executes and calls bpf_get_local_storage().

To fix this, rewrite __cgroup_bpf_replace() to properly allocate the new
cgroup storage, update the storage pointers, and rebuild the effective
arrays using update_effective_progs(). This ensures safe, RCU-protected
swapping of both the program and its storage. The
replace_effective_prog() function is fundamentally unsafe when cgroup
storage is involved and is removed entirely.

Additionally, fix a similar rollback bug in __cgroup_bpf_attach(). When
replacing an existing program (BPF_F_REPLACE), if
update_effective_progs() fails, the code restores the old program but
fails to restore the old storage pointers, leaving dangling pointers
after the new storage is freed. Save and restore the old storage
pointers to prevent a potential use-after-free.

Fixes: 0c991ebc8c69d29b7fc44db17075c5aa5253e2ab ("bpf: Implement bpf_prog replacement for an active bpf_cgroup_link")
Assisted-by: Gemini:gemini-3.1-pro-preview Gemini:gemini-3-flash-preview
Reported-by: syzbot+4fe468...@syzkaller.appspotmail.com
Link: https://syzkaller.appspot.com/bug?extid=4fe468a3f7fac86ea2c9
Link: https://syzkaller.appspot.com/ai_job?id=8b1e4143-642b-4b3e-8be9-194c7a66e24a
To: <b...@vger.kernel.org>
To: <marti...@linux.dev>
Cc: <and...@kernel.org>
Cc: <a...@kernel.org>
Cc: <dan...@iogearbox.net>
Cc: <edd...@gmail.com>
Cc: <jo...@kernel.org>
Cc: <linux-...@vger.kernel.org>
Cc: <mem...@gmail.com>
Cc: <so...@kernel.org>
Cc: <yongho...@linux.dev>

---
diff --git a/kernel/bpf/cgroup.c b/kernel/bpf/cgroup.c
index 876f6a81a..625140a6e 100644
--- a/kernel/bpf/cgroup.c
+++ b/kernel/bpf/cgroup.c
@@ -792,6 +792,7 @@ 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 *save_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;
@@ -844,6 +845,7 @@ static int __cgroup_bpf_attach(struct cgroup *cgrp,

if (pl) {
old_prog = pl->prog;
+ bpf_cgroup_storages_assign(save_storage, pl->storage);
} else {
pl = kmalloc_obj(*pl);
if (!pl) {
@@ -894,6 +896,7 @@ static int __cgroup_bpf_attach(struct cgroup *cgrp,
if (old_prog) {
pl->prog = old_prog;
pl->link = NULL;
+ bpf_cgroup_storages_assign(pl->storage, save_storage);
}
bpf_cgroup_storages_free(new_storage);
if (!old_prog) {
@@ -918,51 +921,6 @@ static int cgroup_bpf_attach(struct cgroup *cgrp,
return ret;
}

-/* Swap updated BPF program for given link in effective program arrays across
- * all descendant cgroups. This function is guaranteed to succeed.
- */
-static void replace_effective_prog(struct cgroup *cgrp,
- enum cgroup_bpf_attach_type atype,
- struct bpf_cgroup_link *link)
-{
- struct bpf_prog_array_item *item;
- struct cgroup_subsys_state *css;
- struct bpf_prog_array *progs;
- struct bpf_prog_list *pl;
- struct hlist_head *head;
- struct cgroup *cg;
- int pos;
-
- css_for_each_descendant_pre(css, &cgrp->self) {
- struct cgroup *desc = container_of(css, struct cgroup, self);
-
- if (percpu_ref_is_zero(&desc->bpf.refcnt))
- continue;
-
- /* find position of link in effective progs array */
- for (pos = 0, cg = desc; cg; cg = cgroup_parent(cg)) {
- if (pos && !(cg->bpf.flags[atype] & BPF_F_ALLOW_MULTI))
- continue;
-
- head = &cg->bpf.progs[atype];
- hlist_for_each_entry(pl, head, node) {
- if (!prog_list_prog(pl))
- continue;
- if (pl->link == link)
- goto found;
- pos++;
- }
- }
-found:
- BUG_ON(!cg);
- progs = rcu_dereference_protected(
- desc->bpf.effective[atype],
- lockdep_is_held(&cgroup_mutex));
- item = &progs->items[pos];
- WRITE_ONCE(item->prog, link->link.prog);
- }
-}
-
/**
* __cgroup_bpf_replace() - Replace link's program and propagate the change
* to descendants
@@ -978,10 +936,14 @@ static int __cgroup_bpf_replace(struct cgroup *cgrp,
struct bpf_prog *new_prog)
{
enum cgroup_bpf_attach_type atype;
+ 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 *save_storage[MAX_BPF_CGROUP_STORAGE_TYPE];
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)
@@ -1001,9 +963,27 @@ static int __cgroup_bpf_replace(struct cgroup *cgrp,
if (!found)
return -ENOENT;

+ err = bpf_cgroup_storages_alloc(storage, new_storage,
+ link->link.attach_type, new_prog, cgrp);
+ if (err)
+ return err;
+
+ bpf_cgroup_storages_assign(save_storage, pl->storage);
+ bpf_cgroup_storages_assign(pl->storage, storage);
cgrp->bpf.revisions[atype] += 1;
+
old_prog = xchg(&link->link.prog, new_prog);
- replace_effective_prog(cgrp, atype, link);
+
+ err = update_effective_progs(cgrp, atype);
+ if (err) {
+ xchg(&link->link.prog, old_prog);
+ cgrp->bpf.revisions[atype] -= 1;
+ bpf_cgroup_storages_assign(pl->storage, save_storage);
+ bpf_cgroup_storages_free(new_storage);
+ return err;
+ }
+
+ bpf_cgroup_storages_link(new_storage, cgrp, link->link.attach_type);
bpf_prog_put(old_prog);
return 0;
}


base-commit: 5d6919055dec134de3c40167a490f33c74c12581
--
This is an AI-generated patch subject to moderation.
Reply with '#syz upstream' to send it to the mailing list.
Reply with '#syz reject' to reject it.

See for more information.

Aleksandr Nogikh

unread,
May 15, 2026, 5:41:11 AM (22 hours ago) May 15
to syzbot, syzkaller-upst...@googlegroups.com, syz...@lists.linux.dev
Why is the diff so large? Can it be smaller?

On Thu, May 14, 2026 at 2:05 AM 'syzbot' via
syzkaller-upstream-moderation
> --
> You received this message because you are subscribed to the Google Groups "syzkaller-upstream-moderation" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to syzkaller-upstream-m...@googlegroups.com.
> To view this discussion visit https://groups.google.com/d/msgid/syzkaller-upstream-moderation/6c0af3f2-ec3b-4331-a453-970522943a07%40mail.kernel.org.

Dmitry Vyukov

unread,
1:17 AM (2 hours ago) 1:17 AM
to Aleksandr Nogikh, syzbot, syzkaller-upst...@googlegroups.com, syz...@lists.linux.dev
There was a discussion about this bug, and a patch has already been proposed.
Asked upstream about these patches:
https://lore.kernel.org/all/69546dea.050a02...@google.com/T/#m8d8eb5927df4f06f1048c1afb2607e52224a14e9

On Fri, 15 May 2026 at 11:41, 'Aleksandr Nogikh' via
syzkaller-upstream-moderation
> To view this discussion visit https://groups.google.com/d/msgid/syzkaller-upstream-moderation/CANp29Y6UBYVzgcXCKCXW0qELqfBeb2XDKuK-eJeqUx_YAe9vVQ%40mail.gmail.com.
Reply all
Reply to author
Forward
0 new messages