Re: [PATCH bpf v6] bpf, cgroup: Fix storage null-ptr-deref after replacing prog

[email protected]
Newsgroups org.kernel.vger.linux-kernel,org.kernel.vger.bpf
Message-ID <5eb10995ebf94dd2c526d65efb472e748e8edf47ab7a7c660b3128011739b151@mail.kernel.org>
> 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.

Is the description "inherits an uninit storage" precise?

Looking at the code, the storage array is not uninitialized in the C
sense. When prog A (which uses no cgroup storage) is attached, both
pl->storage[] and item->cgroup_storage[] are explicitly initialized to
NULL. When compute_effective_progs() copies pl->storage into
item->cgroup_storage via bpf_cgroup_storages_assign(), it copies the
all-NULL array.

So prog B inherits the previous prog's storage array, which is NULL for
the map prog B needs. The distinction also explains the broader case:
if prog A used a different storage map, the inherited pointer would be
non-NULL but belong to the wrong map, leading to a cross-map
out-of-bounds read rather than just a NULL dereference.

Would "the new prog inherits the previous prog's storage array (NULL for
the map the new prog needs)" be more accurate?


> Fix this by rejecting a link update if new_prog's cgroup storage is
> incompatible with link->prog.

Does the commit message capture the full scope of what this fixes?

The message describes only the NULL case from the syzkaller reproducer.
However, cgroup_bpf_storages_compatible() compares map pointers per
stype, so it also rejects the case where prog A uses map M1 and prog B
uses map M2 for the same storage type.

In that case, item->cgroup_storage[stype] would point to M1's live
storage, but prog B's bpf_get_local_storage() would return M1's buffer
while the verifier sized accesses against M2->value_size. Since
bpf_cgroup_storage_calculate_size() sizes the buffer from the owning
map, this would be a cross-map data disclosure or out-of-bounds read,
not just a NULL dereference.

Would it be clearer to mention that the fix also blocks the wrong-map
case to prevent cross-map access?


The commit message opens with "Syzkaller reported" but includes no
Reported-by or Closes tags. Two public syzbot reports match this issue:

  [email protected]
  [email protected]

Both are titled "general protection fault in bpf_get_local_storage" with
KASAN null-ptr-deref at the same location. The reproducers match the
3-step scenario call-for-call.

Should these include the standard syzbot attribution tags so the reports
get auto-closed?

Also, BPF_LINK_UPDATE calls that previously returned 0 now return
-EINVAL. This is an intentional trade-off for a less risky fix, but the
commit message does not mention the uAPI behaviour change. Might that be
worth noting?


> diff --git a/kernel/bpf/cgroup.c b/kernel/bpf/cgroup.c
> index 4355ccb78a9c..5cc9b2d92fd5 100644
> --- a/kernel/bpf/cgroup.c
> +++ b/kernel/bpf/cgroup.c
> @@ -1018,6 +1018,20 @@ static void replace_effective_prog(struct cgroup *cgrp,
>  	}
>  }
>
> +static bool cgroup_bpf_storages_compatible(struct bpf_prog *old_prog,
> +					   struct bpf_prog *new_prog)
> +{
> +	enum bpf_cgroup_storage_type stype;
> +
> +	for_each_cgroup_storage_type(stype) {
> +		if (old_prog->aux->cgroup_storage[stype] !=
> +		    new_prog->aux->cgroup_storage[stype])
> +			return false;
> +	}
> +
> +	return true;
> +}
> +
>  /**
>   * __cgroup_bpf_replace() - Replace link's program and propagate the change
>   *                          to descendants
> @@ -1056,6 +1070,9 @@ static int __cgroup_bpf_replace(struct cgroup *cgrp,
>  	if (!found)
>  		return -ENOENT;
>
> +	if (!cgroup_bpf_storages_compatible(link->link.prog, new_prog))
> +		return -EINVAL;
> +
>  	cgrp->bpf.revisions[atype] += 1;
>  	old_prog = xchg(&link->link.prog, new_prog);
>  	replace_effective_prog(cgrp, atype, pl);


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/31172497940
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.