* Re: [Devel] [PATCH v2 VZ10 1/2] ve/bpf: Limit number of BPF programs loadable per-VE [not found] ` <3ca2d5c2-3d7e-4694-ab65-d8b3fa8b6844@virtuozzo.com> @ 2026-08-26 16:48 ` Konstantin Khorenko 2026-08-28 15:47 ` Pavel Tikhomirov 0 siblings, 1 reply; 2+ messages in thread From: Konstantin Khorenko @ 2026-08-26 16:48 UTC (permalink / raw) To: Pavel Tikhomirov, devel a kind ping -- Best regards, Konstantin Khorenko, Virtuozzo Linux Kernel Team On 6/18/26 18:41, Konstantin Khorenko wrote: > 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 <ptikhomirov@virtuozzo.com> >> 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 Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel ^ permalink raw reply [flat|nested] 2+ messages in thread
* Re: [Devel] [PATCH v2 VZ10 1/2] ve/bpf: Limit number of BPF programs loadable per-VE 2026-08-26 16:48 ` [Devel] [PATCH v2 VZ10 1/2] ve/bpf: Limit number of BPF programs loadable per-VE Konstantin Khorenko @ 2026-08-28 15:47 ` Pavel Tikhomirov 0 siblings, 0 replies; 2+ messages in thread From: Pavel Tikhomirov @ 2026-08-28 15:47 UTC (permalink / raw) To: Konstantin Khorenko, devel I thought we've decided in slack that having failcounts is enough, so after "[PATCH VZ10 v7 7/9] ve: Introduce per-VE failcount" ads bpf_prog_failcount, warning is not really needed to be aware about lack of bpf programs. On 8/26/26 18:48, Konstantin Khorenko wrote: > a kind ping > > -- > Best regards, > > Konstantin Khorenko, > Virtuozzo Linux Kernel Team > > On 6/18/26 18:41, Konstantin Khorenko wrote: >> 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 <ptikhomirov@virtuozzo.com> >>> 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; >> > -- Best regards, Pavel Tikhomirov Senior Software Developer, Virtuozzo. _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel ^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-08-28 15:48 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
[not found] <20260529144215.939517-1-ptikhomirov@virtuozzo.com>
[not found] ` <20260529144215.939517-2-ptikhomirov@virtuozzo.com>
[not found] ` <3ca2d5c2-3d7e-4694-ab65-d8b3fa8b6844@virtuozzo.com>
2026-08-26 16:48 ` [Devel] [PATCH v2 VZ10 1/2] ve/bpf: Limit number of BPF programs loadable per-VE Konstantin Khorenko
2026-08-28 15:47 ` Pavel Tikhomirov
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox