Re: [PATCH bpf-next v3 09/15] bpf: Add infrastructure to support attaching struct_ops to cgroups

Amery Hung <[email protected]>
Newsgroups org.kernel.vger.bpf,org.kernel.vger.netdev
Message-ID <CAMB2axPVwvHZDkfgCmkY9K0fuzYv9Vq7ZZF565Qtx+FPEnGJeQ@mail.gmail.com>
On Mon, Jul 13, 2026 at 11:21 PM Emil Tsalapatis <[email protected]> wrote:
>
> On Mon Jul 6, 2026 at 1:19 PM EDT, Amery Hung wrote:
> > From: Martin KaFai Lau <[email protected]>
> >
> > This patch adds necessary infrastructure to attach a struct_ops
> > map to a cgroup. The initial need was to support migrating
> > the legacy BPF_PROG_TYPE_SOCK_OPS to a struct_ops.
> > Recently, there are other struct_ops use cases that
> > need to attach struct_ops to a cgroup. For example,
> > the recent BPF OOM and memcg discussion in LSFMMBPF 2026.
> >
> > The motivation is to create a consistent expectation
> > for attaching struct_ops to cgroup instead of each subsystem
> > creating its own infrastructure. This logic includes
> > hierarchy expectation, ordering expectation,
> > attachment API, and rcu gp.
> >
> > There is already an existing implementation for attaching
> > multiple bpf progs to a cgroup. There are also tools
> > built around it for querying. Attaching a struct_ops map
> > (which is a group of bpf programs) could also adhere to
> > a similar API and potentially reuse most of the existing
> > implementation.
> >
> > A couple of ideas have been tried. One of them
> > is to use mprog.c. In terms of the amount of changes,
> > I eventually came to the same conclusion as in
> > commit 120933984460 ("bpf: Implement mprog API on top of existing cgroup progs").
> > I then shifted the focus to reusing the current
> > {update,compute,activate,purge}_effective_progs() which has
> > the main logic that implements the mprog API.
> >
> > Since then, I tried to add a 'struct cgroup *cgroup' member
> > to the existing 'struct bpf_struct_ops_link' and link_create
> > will create a 'struct bpf_struct_ops_link' object to be stored
> > in the pl->link. This turns out to have more changes on
> > both cgroup.c and bpf_struct_ops.c than I like.
> >
> > This patch directly reuses the 'struct bpf_cgroup_link' which
> > cgroup.c already understands. Add 'struct bpf_map *map'
> > to 'struct bpf_cgroup_link'. In the future, as more subsystems
> > are extended by struct_ops, we may consider to make
> > 'struct bpf_map *map' as a primary citizen of a link
> > like 'struct bpf_prog *prog' and directly add
> > 'struct bpf_map *map' to the generic 'struct bpf_link'.
> >
> > The pl->link could be the traditional 'prog' link or the
> > new 'map' link. The places that need to handle them differently
> > have already been refactored into the new prog_list_*() added in
> > the earlier patch. In those new prog_list_*(), this patch will
> > check "pl->link && pl->link->map", learn that it is a 'map' link
> > and handle it correctly.
> >
> > The bpf_prog_array also needs to handle that its item can store
> > the traditional 'prog' or it can store a struct_ops map.
> > The places that need to handle them differently have also
> > been refactored into the new bpf_cgroup_array_*() added
> > in the earlier patch. The two differences are:
> >   - different sentinel (dummy_bpf_prog in prog vs cfi_stub in struct_ops)
> >   - the array for struct_ops may need to go through different
> >     rcu gp.
> > The bpf_cgroup_array_*() functions use the cgroup_bpf_attach_type (ie atype)
> > to distinguish the array is storing prog or storing struct_ops map.
> >
> > This patch also implements a separate struct bpf_link_ops
> > "cgroup_struct_ops_link_ops" to have a separate link_ops implementation
> > that only handles the cgroup's struct_ops link.
> >
> > Questions:
> > - Although this patch did not change it, it is not obvious to me how
> >   the replace_effective_progs() and purge_effective_progs() handle
> >   cases when there are existing BPF_F_PREORDER progs attached
> >   in the hlist.
> >
> > Misc notes:
> > - CGROUP_TCP_SOCK_OPS is added to the 'enum cgroup_bpf_attach_type'.
> >   The actual implementation of the tcp_bpf_ops (a struct_ops)
> >   will be added in the next patch.
> >
> > - free_after_mult_rcu_gp is added to 'struct bpf_struct_ops' such that
> >   the bpf_prog_array can have a mix of sleepable and
> >   non-sleepable prog in a struct_ops. This can tell
> >   how the bpf_prog_array should be freed.
> >
> > - For a struct_ops that supports cgroup attachment, it does not need to
> >   implement its own reg/unreg function. reg/unreg to a cgroup is
> >   done by the common infrastructure added in this patch.
> >
> > - The cgroup's struct_ops link only supports BPF_F_ALLOW_MULTI.
> >   This is enforced internally in cgroup_bpf_struct_ops_attach.
> >   This should be consistent with the current prog's link
> >   behavior in cgroup_bpf_link_attach.
> >
> >   In the future, we may allow each subsystem to choose differently.
> >
> > - A cgroup_atype member is added to 'struct bpf_struct_ops'.
> >   When a subsystem struct_ops needs to support cgroup attachment,
> >   it needs to add a value to 'enum cgroup_bpf_attach_type'
> >   and then assign it to the newly added cgroup_atype member
> >   in the bpf_struct_ops.
> >
> > - During LINK_CREATE in syscall, the patch uses the same
> >   BPF_STRUCT_OPS (in attr->link_create.attach_type).
> >   The bpf_struct_ops_link_create learns the map and
> >   from the map it learns the st_ops. If the st_ops->cgroup_atype
> >   is not 0, it will create a cgroup's link.
> >
> > - When a subsystem registers a struct_ops that supports cgroup
> >   attachment, the struct_ops infrastructure will also ask the
> >   cgroup infrastructure to remember a few things. This is done
> >   by calling cgroup_bpf_struct_ops_register().
> >
> > Signed-off-by: Martin KaFai Lau <[email protected]>
> > Signed-off-by: Amery Hung <[email protected]>
>
> Hi Amery,
>
> > ---
> >  include/linux/bpf-cgroup-defs.h |   1 +
> >  include/linux/bpf-cgroup.h      |  28 +++
> >  include/linux/bpf.h             |  19 +-
> >  include/uapi/linux/bpf.h        |   4 +-
> >  kernel/bpf/bpf_struct_ops.c     |  29 +++
> >  kernel/bpf/btf.c                |  23 +-
> >  kernel/bpf/cgroup.c             | 375 ++++++++++++++++++++++++++++++--
> >  kernel/bpf/syscall.c            |   1 +
> >  tools/include/uapi/linux/bpf.h  |   4 +-
> >  9 files changed, 463 insertions(+), 21 deletions(-)
> >
> > diff --git a/include/linux/bpf-cgroup-defs.h b/include/linux/bpf-cgroup-defs.h
> > index c9e6b26abab6..0147b8bec973 100644
> > --- a/include/linux/bpf-cgroup-defs.h
> > +++ b/include/linux/bpf-cgroup-defs.h
> > @@ -47,6 +47,7 @@ enum cgroup_bpf_attach_type {
> >       CGROUP_INET6_GETSOCKNAME,
> >       CGROUP_UNIX_GETSOCKNAME,
> >       CGROUP_INET_SOCK_RELEASE,
> > +     CGROUP_TCP_SOCK_OPS,
> >       CGROUP_LSM_START,
> >       CGROUP_LSM_END = CGROUP_LSM_START + CGROUP_LSM_NUM - 1,
> >       MAX_CGROUP_BPF_ATTACH_TYPE
> > diff --git a/include/linux/bpf-cgroup.h b/include/linux/bpf-cgroup.h
> > index 4d0cc65976a1..8a75a6cd7309 100644
> > --- a/include/linux/bpf-cgroup.h
> > +++ b/include/linux/bpf-cgroup.h
> > @@ -100,6 +100,8 @@ struct bpf_cgroup_storage {
> >  struct bpf_cgroup_link {
> >       struct bpf_link link;
> >       struct cgroup *cgroup;
> > +     struct bpf_map *map;
> > +     wait_queue_head_t wait_hup;
> >  };
> >
> >  struct bpf_prog_list {
> > @@ -110,6 +112,18 @@ struct bpf_prog_list {
> >       u32 flags;
> >  };
> >
> > +#define bpf_cgroup_struct_ops_foreach(var, item, cgrp, atype)                \
> > +     for (item = rcu_dereference((cgrp)->bpf.effective[atype])->items;\
> > +          ((var) = READ_ONCE(item->kdata));                          \
> > +          item++)
> > +
> > +static inline bool cgroup_bpf_is_struct_ops_atype(enum cgroup_bpf_attach_type atype)
> > +{
> > +     return atype == CGROUP_TCP_SOCK_OPS;
> > +}
> > +void cgroup_bpf_struct_ops_register(int atype, u32 type_id, void *cfi_stubs, bool mult_trace);
> > +int cgroup_bpf_struct_ops_attach(struct bpf_map *map, const union bpf_attr *attr);
> > +
> >  void __init cgroup_bpf_lifetime_notifier_init(void);
> >
> >  int __cgroup_bpf_run_filter_skb(struct sock *sk,
> > @@ -479,6 +493,20 @@ static inline int bpf_percpu_cgroup_storage_update(struct bpf_map *map,
> >       return 0;
> >  }
> >
> > +static inline bool cgroup_bpf_is_struct_ops_atype(int atype)
> > +{
> > +     return false;
> > +}
> > +static inline void cgroup_bpf_struct_ops_register(int atype, u32 type_id, void *cfi_stubs,
> > +                                               bool mult_trace)
> > +{
> > +}
> > +static inline int cgroup_bpf_struct_ops_attach(struct bpf_map *map,
> > +                                            const union bpf_attr *attr)
> > +{
> > +     return -EOPNOTSUPP;
> > +}
> > +
> >  #define cgroup_bpf_enabled(atype) (0)
> >  #define BPF_CGROUP_RUN_SA_PROG_LOCK(sk, uaddr, uaddrlen, atype, t_ctx) ({ 0; })
> >  #define BPF_CGROUP_RUN_SA_PROG(sk, uaddr, uaddrlen, atype) ({ 0; })
> > diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> > index e371a4733135..df95ae690da5 100644
> > --- a/include/linux/bpf.h
> > +++ b/include/linux/bpf.h
> > @@ -2100,11 +2100,18 @@ struct btf_member;
> >   *      unloaded while in use.
> >   * @name: The name of the struct bpf_struct_ops object.
> >   * @func_models: Func models
> > + * @cgroup_atype: A value in enum cgroup_bpf_attach_type for cgroup attachment.
> > + *             0 means the struct_ops type does not support cgroup attachment.
> > + *             If cgroup_atype is non-zero, the @reg and @unreg must be NULL
> > + *             because the attachment/detachment will be handled by the bpf core.
> >   * @free_after_tasks_rcu_gp: Set to true if it needs the bpf core to wait for
> >   *                           a tasks_rcu gp before freeing the struct_ops map
> >   *                           and its progs. It is unnecessary if the @unreg
> >   *                           has waited for the correct rcu gp or the @unreg
> >   *                           has ensured all struct_ops prog has finished running.
> > + * @free_after_mult_rcu_gp: Same as @free_after_tasks_rcu_gp but waiting for
> > + *                          both tasks_trace_rcu and regular rcu grace period.
> > + *                          It is usually needed if the struct_ops has sleepable prog.
> >   */
> >  struct bpf_struct_ops {
> >       const struct bpf_verifier_ops *verifier_ops;
> > @@ -2123,7 +2130,9 @@ struct bpf_struct_ops {
> >       struct module *owner;
> >       const char *name;
> >       struct btf_func_model func_models[BPF_STRUCT_OPS_MAX_NR_MEMBERS];
> > +     int cgroup_atype;
> >       bool free_after_tasks_rcu_gp;
> > +     bool free_after_mult_rcu_gp;
> >  };
> >
> >  /* Every member of a struct_ops type has an instance even a member is not
> > @@ -2258,6 +2267,7 @@ void *bpf_struct_ops_map_cfi_stubs(struct bpf_map *map);
> >  bool bpf_struct_ops_valid_to_reg(struct bpf_map *map);
> >  int bpf_struct_ops_link_update_check(struct bpf_map *new_map, struct bpf_map *old_map,
> >                                    struct bpf_map *expected_old_map);
> > +int bpf_struct_ops_map_cgroup_atype(struct bpf_map *map);
> >
> >  #ifdef CONFIG_NET
> >  /* Define it here to avoid the use of forward declaration */
> > @@ -2330,6 +2340,10 @@ static inline u32 bpf_struct_ops_kdata_map_id(void *kdata)
> >  {
> >       return 0;
> >  }
> > +static inline int bpf_struct_ops_map_cgroup_atype(struct bpf_map *map)
> > +{
> > +     return 0;
> > +}
> >  static inline void *bpf_struct_ops_map_cfi_stubs(struct bpf_map *map)
> >  {
> >       return NULL;
> > @@ -2519,7 +2533,10 @@ u64 bpf_event_output(struct bpf_map *map, u64 flags, void *meta, u64 meta_size,
> >   * since other cpus are walking the array of pointers in parallel.
> >   */
> >  struct bpf_prog_array_item {
> > -     struct bpf_prog *prog;
> > +     union {
> > +             struct bpf_prog *prog;
> > +             void *kdata;
> > +     };
> >       union {
> >               struct bpf_cgroup_storage *cgroup_storage[MAX_BPF_CGROUP_STORAGE_TYPE];
> >               u64 bpf_cookie;
> > diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
> > index c91b5a4bda03..c982801ec7db 100644
> > --- a/include/uapi/linux/bpf.h
> > +++ b/include/uapi/linux/bpf.h
> > @@ -1756,7 +1756,7 @@ union bpf_attr {
> >                       __u32   prog_cnt;
> >                       __u32   count;
> >               };
> > -             __u32           :32;
> > +             __u32           type_id;
> >               /* output: per-program attach_flags.
> >                * not allowed to be set during effective query.
> >                */
> > @@ -6818,6 +6818,8 @@ struct bpf_link_info {
> >               } xdp;
> >               struct {
> >                       __u32 map_id;
> > +                     __u32 :32;
> > +                     __u64 cgroup_id;
> >               } struct_ops;
> >               struct {
> >                       __u32 pf;
> > diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
> > index 3d650a7e9f68..06d72cdca601 100644
> > --- a/kernel/bpf/bpf_struct_ops.c
> > +++ b/kernel/bpf/bpf_struct_ops.c
> > @@ -13,6 +13,7 @@
> >  #include <linux/btf_ids.h>
> >  #include <linux/rcupdate_wait.h>
> >  #include <linux/poll.h>
> > +#include <linux/bpf-cgroup.h>
> >
> >  struct bpf_struct_ops_value {
> >       struct bpf_struct_ops_common_value common;
> > @@ -1076,6 +1077,11 @@ static struct bpf_map *bpf_struct_ops_map_alloc(union bpf_attr *attr)
> >               goto errout;
> >       }
> >
> > +     if (st_ops_desc->st_ops->cgroup_atype && !(attr->map_flags & BPF_F_LINK)) {
> > +             ret = -EOPNOTSUPP;
> > +             goto errout;
> > +     }
> > +
> >       vt = st_ops_desc->value_type;
> >       if (attr->value_size != vt->size) {
> >               ret = -EINVAL;
> > @@ -1116,6 +1122,7 @@ static struct bpf_map *bpf_struct_ops_map_alloc(union bpf_attr *attr)
> >
> >       mutex_init(&st_map->lock);
> >       bpf_map_init_from_attr(map, attr);
> > +     map->free_after_mult_rcu_gp = st_ops_desc->st_ops->free_after_mult_rcu_gp;
> >       map->free_after_rcu_gp = true;
> >
> >       return map;
> > @@ -1244,6 +1251,14 @@ void *bpf_struct_ops_map_kdata(struct bpf_map *map)
> >       return st_map->kvalue.data;
> >  }
> >
> > +int bpf_struct_ops_map_cgroup_atype(struct bpf_map *map)
> > +{
> > +     struct bpf_struct_ops_map *st_map;
> > +
> > +     st_map = container_of(map, struct bpf_struct_ops_map, map);
> > +     return st_map->st_ops_desc->st_ops->cgroup_atype;
> > +}
> > +
> >  void *bpf_struct_ops_map_cfi_stubs(struct bpf_map *map)
> >  {
> >       struct bpf_struct_ops_map *st_map;
> > @@ -1419,6 +1434,7 @@ int bpf_struct_ops_link_create(union bpf_attr *attr)
> >       struct bpf_link_primer link_primer;
> >       struct bpf_struct_ops_map *st_map;
> >       struct bpf_map *map;
> > +     int cgroup_atype;
> >       int err;
> >
> >       map = bpf_map_get(attr->link_create.map_fd);
> > @@ -1432,6 +1448,19 @@ int bpf_struct_ops_link_create(union bpf_attr *attr)
> >               goto err_out;
> >       }
> >
> > +     cgroup_atype = st_map->st_ops_desc->st_ops->cgroup_atype;
> > +     if (cgroup_atype) {
> > +             err = cgroup_bpf_struct_ops_attach(map, attr);
> > +             bpf_map_put(map);
> > +             return err;
> > +     }
> > +
> > +     if (memchr_inv(&attr->link_create.cgroup, 0, sizeof(attr->link_create.cgroup)) ||
> > +         attr->link_create.target_fd) {
> > +             err = -EINVAL;
> > +             goto err_out;
> > +     }
> > +
> >       link = kzalloc_obj(*link, GFP_USER);
> >       if (!link) {
> >               err = -ENOMEM;
> > diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> > index 64572f85edc8..d591f306ace5 100644
> > --- a/kernel/bpf/btf.c
> > +++ b/kernel/bpf/btf.c
> > @@ -20,6 +20,7 @@
> >  #include <linux/btf.h>
> >  #include <linux/btf_ids.h>
> >  #include <linux/bpf.h>
> > +#include <linux/bpf-cgroup.h>
> >  #include <linux/bpf_lsm.h>
> >  #include <linux/skmsg.h>
> >  #include <linux/perf_event.h>
> > @@ -9836,6 +9837,7 @@ btf_add_struct_ops(struct btf *btf, struct bpf_struct_ops *st_ops,
> >                  struct bpf_verifier_log *log)
> >  {
> >       struct btf_struct_ops_tab *tab, *new_tab;
> > +     int cgroup_atype;
> >       int i, err;
> >
> >       tab = btf->struct_ops_tab;
> > @@ -9847,8 +9849,10 @@ btf_add_struct_ops(struct btf *btf, struct bpf_struct_ops *st_ops,
> >               btf->struct_ops_tab = tab;
> >       }
> >
> > +     cgroup_atype = st_ops->cgroup_atype;
> >       for (i = 0; i < tab->cnt; i++)
> > -             if (tab->ops[i].st_ops == st_ops)
> > +             if (tab->ops[i].st_ops == st_ops ||
> > +                 (cgroup_atype && cgroup_atype == tab->ops[i].st_ops->cgroup_atype))
> >                       return -EEXIST;
> >
> >       if (tab->cnt == tab->capacity) {
> > @@ -9868,6 +9872,23 @@ btf_add_struct_ops(struct btf *btf, struct bpf_struct_ops *st_ops,
> >       if (err)
> >               return err;
> >
> > +     if (cgroup_atype) {
> > +             if (!cgroup_bpf_is_struct_ops_atype(cgroup_atype) ||
>
> Can we add a comment here as to why free_after_tasks_rcu_gp is always
> invalid here (which IIUC is because it's
> free_after_mult_rcu_gp)?

This is because the dispatching macro,
bpf_cgroup_struct_ops_foreach(), requires the caller to be in an RCU
read-side critical section. Unlike bpf-tcp-cc, where rcu_read_lock()
is taken inside the trampoline by __bpf_prog_enter() and the trailing
trampoline insns run outside it, here the critical section encloses
the whole trampoline call. Therefore a vanilla rcu gp is enough for
both the kdata and the trampoline image.

>
> > +                 st_ops->reg || st_ops->unreg || st_ops->free_after_tasks_rcu_gp) {
> > +                     bpf_struct_ops_desc_release(&tab->ops[btf->struct_ops_tab->cnt]);
> > +                     return -EINVAL;
> > +             }
> > +
> > +             /* There is no need to unregister from cgroup when the
> > +              * btf_free(). No struct_ops map and its cgroup link
> > +              * can be created once its btf is gone.
> > +              */
>
> Network-style comment, should be regular kernel-style.

Ack.

>
> In terms of the content: What happens if the struct_ops are in a module,
> then we load/unload/reload? IIUC even if the signatures are identical,
> the btf_id could be different so it's not like we can always reuse the
> previous cgroup registration.

The information in struct_ops_{type_id, cfi_stubs, mult_rcu} is never
reused across registration. Every load calls
cgroup_bpf_struct_ops_register() and overwrites all three, so a reload
always installs the new type_id and cfi_stubs.

Note that there was a bug in an earlier version where stale cfi_stubs
could be dereferenced during query. It is fixed by the
!cgroup_bpf_enabled(atype) check in __cgroup_bpf_query(), which
returns before walking the effective array when nothing is attached.

>
> > +             cgroup_bpf_struct_ops_register(cgroup_atype,
> > +                                            tab->ops[btf->struct_ops_tab->cnt].type_id,
> > +                                            st_ops->cfi_stubs,
> > +                                            st_ops->free_after_mult_rcu_gp);
> > +     }
> > +
> >       btf->struct_ops_tab->cnt++;
> >
> >       return 0;
> > diff --git a/kernel/bpf/cgroup.c b/kernel/bpf/cgroup.c
> > index 081d81de1816..745e826c9f61 100644
> > --- a/kernel/bpf/cgroup.c
> > +++ b/kernel/bpf/cgroup.c
> > @@ -24,6 +24,29 @@
> >  DEFINE_STATIC_KEY_ARRAY_FALSE(cgroup_bpf_enabled_key, MAX_CGROUP_BPF_ATTACH_TYPE);
> >  EXPORT_SYMBOL(cgroup_bpf_enabled_key);
> >
> > +static u32 struct_ops_type_id[MAX_CGROUP_BPF_ATTACH_TYPE];
> > +static void *struct_ops_cfi_stubs[MAX_CGROUP_BPF_ATTACH_TYPE];
> > +static bool struct_ops_mult_rcu[MAX_CGROUP_BPF_ATTACH_TYPE];
>
>
> Can we make a struct out of those? Right now we're carrying them loose
> into the module even though every index corresponds to a single
> registered struct_oips.

Sure, will do in v4.

>
> > +
> > +void cgroup_bpf_struct_ops_register(int atype, u32 type_id, void *cfi_stubs, bool mult_rcu)
> > +{
> > +     struct_ops_type_id[atype] = type_id;
> > +     struct_ops_cfi_stubs[atype] = cfi_stubs;
> > +     struct_ops_mult_rcu[atype] = mult_rcu;
> > +}
> > +
> > +static enum cgroup_bpf_attach_type find_atype_by_struct_ops_id(u32 type_id)
> > +{
> > +     enum cgroup_bpf_attach_type atype;
> > +
> > +     for (atype = 0; atype < MAX_CGROUP_BPF_ATTACH_TYPE; atype++) {
> > +             if (cgroup_bpf_is_struct_ops_atype(atype) &&
> > +                 struct_ops_type_id[atype] == type_id)
> > +                     return atype;
> > +     }
> > +     return CGROUP_BPF_ATTACH_TYPE_INVALID;
> > +}
> > +
> >  /*
> >   * cgroup bpf destruction makes heavy use of work items and there can be a lot
> >   * of concurrent destructions.  Use a separate workqueue so that cgroup bpf
> > @@ -306,6 +329,19 @@ static void bpf_cgroup_storages_link(struct bpf_cgroup_storage *storages[],
> >               bpf_cgroup_storage_link(storages[stype], cgrp, attach_type);
> >  }
> >
> > +static void cgroup_struct_ops_link_detach_wake(struct bpf_cgroup_link *link, bool wake_poll)
> > +{
> > +     cgroup_put(link->cgroup);
> > +     link->cgroup = NULL;
> > +
> > +     bpf_map_put(link->map);
> > +     /* READ_ONCE in cgroup_struct_ops_link_poll */
> > +     WRITE_ONCE(link->map, NULL);
> > +
> > +     if (wake_poll)
> > +             wake_up_interruptible_poll(&link->wait_hup, EPOLLHUP);
> > +}
> > +
> >  /* Called when bpf_cgroup_link is auto-detached from dying cgroup.
> >   * It drops cgroup and bpf_prog refcounts, and marks bpf_link as defunct. It
> >   * doesn't free link memory, which will eventually be done by bpf_link's
> > @@ -313,21 +349,37 @@ static void bpf_cgroup_storages_link(struct bpf_cgroup_storage *storages[],
> >   */
> >  static void bpf_cgroup_link_auto_detach(struct bpf_cgroup_link *link)
> >  {
> > -     if (link->link.prog->expected_attach_type == BPF_LSM_CGROUP)
> > -             bpf_trampoline_unlink_cgroup_shim(link->link.prog);
> > -     cgroup_put(link->cgroup);
> > -     link->cgroup = NULL;
> > +     if (link->map) {
> > +             cgroup_struct_ops_link_detach_wake(link, true);
> > +     } else {
> > +             if (link->link.prog->expected_attach_type == BPF_LSM_CGROUP)
> > +                     bpf_trampoline_unlink_cgroup_shim(link->link.prog);
> > +             cgroup_put(link->cgroup);
> > +             link->cgroup = NULL;
> > +     }
>
> Nit: If we write this as:
>         if (link->map) {
>                 cgroup_struct_ops_link_detach_wake(link, true);
>                 return
>         }
>
> it's slightly less churn.

Will do.

>
> > +}
> > +
> > +static void bpf_cgroup_array_free_rcu(struct rcu_head *rcu)
> > +{
> > +     kfree(container_of(rcu, struct bpf_prog_array, rcu));
> >  }
> >
> > -static void bpf_cgroup_array_free(struct bpf_prog_array *array)
> > +static void bpf_cgroup_array_free(struct bpf_prog_array *array,
> > +                               enum cgroup_bpf_attach_type atype)
> >  {
> >       if (!array || array == &bpf_empty_prog_array)
> >               return;
> > -     kfree_rcu(array, rcu);
> > +     if (struct_ops_mult_rcu[atype])
> > +             /* RCU tasks trace grace period implies RCU grace period. */
> > +             call_rcu_tasks_trace(&array->rcu, bpf_cgroup_array_free_rcu);
> > +     else
> > +             kfree_rcu(array, rcu);
> >  }
> >
> >  static void *bpf_cgroup_array_dummy(enum cgroup_bpf_attach_type atype)
> >  {
> > +     if (cgroup_bpf_is_struct_ops_atype(atype))
> > +             return struct_ops_cfi_stubs[atype];
> >       return bpf_prog_dummy();
> >  }
> >
> > @@ -355,7 +407,12 @@ static int bpf_cgroup_array_copy_to_user(struct bpf_prog_array *array,
> >       for (item = array->items; item->prog && i < cnt; item++) {
> >               if (item->prog == bpf_cgroup_array_dummy(atype))
>
> Retroactive nit for bpf_cgroup_array_dummy: The name is pretty confusing, something
> that explain that this is really a tombstone value would be nicer.
> Especially since we're repurposing the cfi_stubs for per-attach type
> tombstones.

The value is not just a tombstone. bpf_cgroup_array_dummy is the same
concept as bpf_prog_dummy. It actually runs instead of skipping. Let's
not change the _dummy name to keep things consistent.

>
> >                       continue;
> > -             id = item->prog->aux->id;
> > +
> > +             if (cgroup_bpf_is_struct_ops_atype(atype))
> > +                     id = bpf_struct_ops_id(item->kdata);
> > +             else
> > +                     id = item->prog->aux->id;
> > +
> >               if (copy_to_user(prog_ids + i, &id, sizeof(id)))
> >                       return -EFAULT;
> >               i++;
> > @@ -417,7 +474,7 @@ static void cgroup_bpf_release(struct work_struct *work)
> >               old_array = rcu_dereference_protected(
> >                               cgrp->bpf.effective[atype],
> >                               lockdep_is_held(&cgroup_mutex));
> > -             bpf_cgroup_array_free(old_array);
> > +             bpf_cgroup_array_free(old_array, atype);
> >       }
> >
> >       list_for_each_entry_safe(storage, stmp, storages, list_cg) {
> > @@ -461,17 +518,26 @@ static struct bpf_prog *prog_list_prog(struct bpf_prog_list *pl)
> >
> >  static void prog_list_init_item(struct bpf_prog_list *pl, struct bpf_prog_array_item *item)
> >  {
> > -     item->prog = prog_list_prog(pl);
> > -     bpf_cgroup_storages_assign(item->cgroup_storage, pl->storage);
> > +     if (pl->link && pl->link->map) {
> > +             item->kdata = bpf_struct_ops_map_kdata(pl->link->map);
> > +     } else {
> > +             item->prog = prog_list_prog(pl);
> > +             bpf_cgroup_storages_assign(item->cgroup_storage, pl->storage);
> > +     }
>
> Same nit as above, an if-return avoid reformatting existing code.

Ack.

>
> >  }
> >
> >  static void prog_list_replace_item(struct bpf_prog_list *pl, struct bpf_prog_array_item *item)
> >  {
> > -     WRITE_ONCE(item->prog, pl->link->link.prog);
> > +     if (pl->link && pl->link->map)
>
> Can we somehow encapsulate the pl->link && pl->link->map to give it a
> more descriptive name? It's mentioned in previous commit descriptions
> what it is for, but as it stands it takes some thought to realize it's
> checking if this is a struct_ops item or a regular prog.

Sure. Will add prog_list_is_struct_ops().

>
> > +             WRITE_ONCE(item->kdata, bpf_struct_ops_map_kdata(pl->link->map));
> > +     else
> > +             WRITE_ONCE(item->prog, pl->link->link.prog);
> >  }
> >
> >  static u32 prog_list_id(struct bpf_prog_list *pl)
> >  {
> > +     if (pl->link && pl->link->map)
>
> Ditto.
>
> > +             return pl->link->map->id;
> >       return prog_list_prog(pl)->aux->id;
> >  }

[...]

> >
> > +static int __cgroup_struct_ops_link_detach(struct bpf_link *link, bool wake_poll)
> > +{
> > +     struct bpf_cgroup_link *cg_link = container_of(link, struct bpf_cgroup_link, link);
> > +     enum cgroup_bpf_attach_type atype;
> > +     struct bpf_prog_list *pl;
> > +     struct bpf_map *map;
> > +     struct cgroup *cgrp;
> > +
> > +     cgroup_lock();
> > +
> > +     cgrp = cg_link->cgroup;
> > +     if (!cgrp) {
> > +             cgroup_unlock();
> > +             return 0;
> > +     }
> > +
> > +     map = cg_link->map;
> > +     atype = bpf_struct_ops_map_cgroup_atype(map);
> > +
> > +     hlist_for_each_entry(pl, &cgrp->bpf.progs[atype], node) {
> > +             if (pl->link == cg_link)
> > +                     break;
> > +     }
>
> What happens if the link isn't found here? Update deals this with
> -ENOENT. Even though it's only possible if there's a bug I think
> we should at least have a warning.

Yes, this is indeed unreachable. Will still add WARN_ON_ONCE(!pl) and
return -ENOENT defensively.

>
> > +
> > +     /* mark deleted so compute_effective_progs() skips it */
> > +     pl->link = NULL;
> > +     if (update_effective_progs(cgrp, atype)) {
> > +             pl->link = cg_link;
> > +             purge_effective_progs(cgrp, pl, atype);
> > +     }
> > +
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.