i will apply the patch, but i would appreciate in case you add some WARN_ONCE in case some CT hits the limit of available bpf programs.
This will ease issue investigations + at some point might hints us that we need to increase the default, for example. -- Best regards, Konstantin Khorenko, Virtuozzo Linux Kernel Team On 5/29/26 16:42, Pavel Tikhomirov wrote: > Without a per-VE cap a single container could exhaust the system-wide > bpf JIT memory budget by loading excessive numbers of CGROUP_DEVICE > programs via the VE_FEATURE_BPF path. > > Add bpf_prog_avail_nr / bpf_prog_max_nr counters to ve_struct and > enforce them in bpf_prog_load() for non-bpf-capable callers loading > CGROUP_DEVICE programs. > > Lifetime note: A BPF program loadded in VE takes a reference to ve, when > container is stopped, all open fds to the BPF programm will be closed > and when container manager removes container cgroups the BPF program > will be released and thus releasing the reference to VE. > > Default max number note: It is somehow similar to ve.netif_max_nr, there > each docker container creates two veths, and I also observe that docker > container loads two bpf programs (one by dockerd, one by systemd). So > let's use the same number. > > https://virtuozzo.atlassian.net/browse/VSTOR-131947 > Signed-off-by: Pavel Tikhomirov <[email protected]> > Feature: ve: allow BPF in Containers > > -- > v2: Also put load_ve definition under CONFIG_VE. > --- > include/linux/bpf.h | 8 ++++++++ > include/linux/ve.h | 4 ++++ > kernel/bpf/core.c | 8 ++++++++ > kernel/bpf/syscall.c | 37 +++++++++++++++++++++++++++++++++++++ > kernel/ve/ve.c | 5 +++++ > 5 files changed, 62 insertions(+) > > diff --git a/include/linux/bpf.h b/include/linux/bpf.h > index 80175c7a21c27..0212806d5efc2 100644 > --- a/include/linux/bpf.h > +++ b/include/linux/bpf.h > @@ -56,6 +56,7 @@ struct cgroup; > struct bpf_token; > struct user_namespace; > struct super_block; > +struct ve_struct; > struct inode; > > extern struct idr btf_idr; > @@ -1522,6 +1523,13 @@ struct bpf_prog_aux { > void *security; > #endif > struct bpf_token *token; > +#ifdef CONFIG_VE > + /* VE that loaded the program via VE_FEATURE_BPF path and against whose > + * bpf_prog_avail_nr counter the program is accounted. NULL for programs > + * loaded through the regular (non VE-restricted) path. > + */ > + struct ve_struct *owner_ve; > +#endif > struct bpf_prog_offload *offload; > struct btf *btf; > struct bpf_func_info *func_info; > diff --git a/include/linux/ve.h b/include/linux/ve.h > index 224acf012821f..88b4d531c466e 100644 > --- a/include/linux/ve.h > +++ b/include/linux/ve.h > @@ -76,6 +76,9 @@ struct ve_struct { > atomic_t netif_avail_nr; > int netif_max_nr; > > + atomic_t bpf_prog_avail_nr; > + int bpf_prog_max_nr; > + > atomic64_t _uevent_seqnum; > > int _randomize_va_space; > @@ -149,6 +152,7 @@ extern int nr_ve; > > #define NETNS_MAX_NR_DEFAULT 256 /* number of net-namespaces per-VE */ > #define NETIF_MAX_NR_DEFAULT 256 /* number of net-interfaces per-VE */ > +#define BPF_PROG_MAX_NR_DEFAULT 256 /* number of loaded BPF progs > per-VE */ > > extern unsigned int sysctl_ve_mount_nr; > > diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c > index 4de8774458aca..7aaf73180fcdc 100644 > --- a/kernel/bpf/core.c > +++ b/kernel/bpf/core.c > @@ -38,6 +38,7 @@ > #include <linux/bpf_mem_alloc.h> > #include <linux/memcontrol.h> > #include <linux/execmem.h> > +#include <linux/ve.h> > > #include <asm/barrier.h> > #include <linux/unaligned.h> > @@ -2828,6 +2829,13 @@ void bpf_prog_free(struct bpf_prog *fp) > if (aux->dst_prog) > bpf_prog_put(aux->dst_prog); > bpf_token_put(aux->token); > +#ifdef CONFIG_VE > + if (aux->owner_ve) { > + atomic_inc(&aux->owner_ve->bpf_prog_avail_nr); > + put_ve(aux->owner_ve); > + aux->owner_ve = NULL; > + } > +#endif > INIT_WORK(&aux->work, bpf_prog_free_deferred); > schedule_work(&aux->work); > } > diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c > index 0475a72c93c06..481ae62429097 100644 > --- a/kernel/bpf/syscall.c > +++ b/kernel/bpf/syscall.c > @@ -2663,6 +2663,9 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t > uattr, u32 uattr_size) > struct bpf_prog *prog, *dst_prog = NULL; > struct btf *attach_btf = NULL; > struct bpf_token *token = NULL; > +#ifdef CONFIG_VE > + struct ve_struct *load_ve = NULL; > +#endif > bool bpf_cap; > int err; > char license[128]; > @@ -2744,6 +2747,22 @@ static int bpf_prog_load(union bpf_attr *attr, > bpfptr_t uattr, u32 uattr_size) > if (is_perfmon_prog_type(type) && !bpf_token_capable(token, > CAP_PERFMON)) > goto put_token; > > +#ifdef CONFIG_VE > + /* Restrict the number of BPF programs that can be loaded via the > + * VE-allowed path. Without this, a single container could exhaust > + * the system-wide bpf JIT memory budget by loading excessive > + * numbers of CGROUP_DEVICE programs. > + */ > + if (!bpf_cap && type == BPF_PROG_TYPE_CGROUP_DEVICE) { > + load_ve = get_exec_env(); > + if (atomic_dec_if_positive(&load_ve->bpf_prog_avail_nr) < 0) { > + load_ve = NULL; > + err = -ENOSPC; > + goto put_token; > + } > + } > +#endif > + > /* attach_prog_fd/attach_btf_obj_fd can specify fd of either bpf_prog > * or btf, we need to check which one it is > */ > @@ -2809,6 +2828,16 @@ static int bpf_prog_load(union bpf_attr *attr, > bpfptr_t uattr, u32 uattr_size) > prog->aux->dev_bound = !!attr->prog_ifindex; > prog->aux->xdp_has_frags = attr->prog_flags & BPF_F_XDP_HAS_FRAGS; > > +#ifdef CONFIG_VE > + /* Hand the avail_nr slot reservation over to the prog. bpf_prog_free() > + * will release it via put_ve + counter increment. > + */ > + if (load_ve) { > + prog->aux->owner_ve = get_ve(load_ve); > + load_ve = NULL; > + } > +#endif > + > /* move token into prog->aux, reuse taken refcnt */ > prog->aux->token = token; > token = NULL; > @@ -2932,6 +2961,14 @@ static int bpf_prog_load(union bpf_attr *attr, > bpfptr_t uattr, u32 uattr_size) > btf_put(prog->aux->attach_btf); > bpf_prog_free(prog); > put_token: > +#ifdef CONFIG_VE > + /* The load_ve is non-NULL only if we decremented bpf_prog_avail_nr > + * but did not hand the reservation off to the prog yet (i.e. failure > + * happened before bpf_prog_alloc()). Roll back the counter. > + */ > + if (load_ve) > + atomic_inc(&load_ve->bpf_prog_avail_nr); > +#endif > bpf_token_put(token); > return err; > } > diff --git a/kernel/ve/ve.c b/kernel/ve/ve.c > index 198c82f010cc1..48da546117bb7 100644 > --- a/kernel/ve/ve.c > +++ b/kernel/ve/ve.c > @@ -76,6 +76,8 @@ struct ve_struct ve0 = { > .netns_max_nr = INT_MAX, > .netif_avail_nr = ATOMIC_INIT(INT_MAX), > .netif_max_nr = INT_MAX, > + .bpf_prog_avail_nr = ATOMIC_INIT(INT_MAX), > + .bpf_prog_max_nr = INT_MAX, > .fsync_enable = FSYNC_FILTERED, > ._randomize_va_space = > #ifdef CONFIG_COMPAT_BRK > @@ -983,6 +985,9 @@ static struct cgroup_subsys_state *ve_create(struct > cgroup_subsys_state *parent_ > atomic_set(&ve->netif_avail_nr, NETIF_MAX_NR_DEFAULT); > ve->netif_max_nr = NETIF_MAX_NR_DEFAULT; > > + atomic_set(&ve->bpf_prog_avail_nr, BPF_PROG_MAX_NR_DEFAULT); > + ve->bpf_prog_max_nr = BPF_PROG_MAX_NR_DEFAULT; > + > err = ve_log_init(ve); > if (err) > goto err_log; _______________________________________________ Devel mailing list [email protected] https://lists.openvz.org/mailman/listinfo/devel
