* [PATCH 6.16.y 0/6] cBPF JIT spray hardening
@ 2026-07-14 18:58 ` Pawan Gupta
0 siblings, 0 replies; 17+ messages in thread
From: Pawan Gupta @ 2026-07-14 18:58 UTC (permalink / raw)
To: stable, Greg Kroah-Hartman, Sasha Levin
Cc: bpf, linux-arm-kernel, loongarch, linuxppc-dev, linux-riscv, x86,
Alexei Starovoitov, Daniel Borkmann, Dave Hansen
Hi,
These backports harden BPF JIT against spectre-v2 class of attacks. Without
a predictor flush, execution of new BPF program may use stale prediction
left behind by the freed one.
To avoid this, issue an IBPB flush on all CPUs on JIT program allocation.
The flush is conditional to spectre-v2 mitigation applied.
Patch 1-2: Adds the predictor flush hook and enables it on x86 via IBPB.
bpf: Support for hardening against JIT spraying
x86/bugs: Enable IBPB flush on BPF JIT allocation
Patch 3-6: Narrow the flush to only unprivileged JIT allocations
to avoid redundant flushes. Also adds pack-selection changes
that minimizes flushes.
bpf: Restrict JIT predictor flush to cBPF
bpf: Skip redundant IBPB in pack allocator
bpf: Prefer packs that won't trigger an IBPB flush on allocation
bpf: Prefer dirty packs for eBPF allocations
This one is mostly similar to 6.18:
https://lore.kernel.org/all/20260713-cbpf-jit-spray-hardening-6-18-y-v1-0-755f60c55705@linux.intel.com/
---
Pawan Gupta (6):
bpf: Support for hardening against JIT spraying
x86/bugs: Enable IBPB flush on BPF JIT allocation
bpf: Restrict JIT predictor flush to cBPF
bpf: Skip redundant IBPB in pack allocator
bpf: Prefer packs that won't trigger an IBPB flush on allocation
bpf: Prefer dirty packs for eBPF allocations
arch/arm64/net/bpf_jit_comp.c | 4 +--
arch/powerpc/net/bpf_jit_comp.c | 4 +--
arch/riscv/net/bpf_jit_comp64.c | 2 +-
arch/riscv/net/bpf_jit_core.c | 3 +-
arch/x86/include/asm/nospec-branch.h | 4 +++
arch/x86/kernel/cpu/bugs.c | 50 +++++++++++++++++++++++---
arch/x86/net/bpf_jit_comp.c | 5 +--
include/linux/filter.h | 15 ++++++--
kernel/bpf/core.c | 68 ++++++++++++++++++++++++++++++++----
kernel/bpf/dispatcher.c | 2 +-
10 files changed, 135 insertions(+), 22 deletions(-)
---
base-commit: d997d33eb340e2add100eac1222e107cc1396e76
change-id: 20260714-cbpf-jit-spray-hardening-6-16-y-5c50449f820c
Best regards,
--
Pawan
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 6.16.y 1/6] bpf: Support for hardening against JIT spraying
2026-07-14 18:58 ` Pawan Gupta
@ 2026-07-14 18:58 ` Pawan Gupta
-1 siblings, 0 replies; 17+ messages in thread
From: Pawan Gupta @ 2026-07-14 18:58 UTC (permalink / raw)
To: stable, Greg Kroah-Hartman, Sasha Levin
Cc: bpf, linux-arm-kernel, loongarch, linuxppc-dev, linux-riscv, x86,
Alexei Starovoitov, Daniel Borkmann
commit 96cce16e26dd02a8678f1e87f88a4b5cdb63b995 upstream.
The BPF JIT allocator packs many small programs into larger executable
allocations and reuses space within those allocations as programs are
loaded and freed. When fresh code is written into space that a previous
program occupied, an indirect jump into the new program can reuse a branch
prediction left behind by the old one.
Flush the indirect branch predictors before reusing JIT memory so that
indirect jumps into a newly written program don't reuse predictions from an
old program that occupied the same space.
Introduce bpf_arch_pred_flush_enabled static key and bpf_arch_pred_flush
static call for flushing the branch predictors on JIT memory reuse.
Architectures that need a flush, can update it to a predictor flush
function. By default, its a NOP and does not emit any CALL.
Allocations larger than a pack are not covered by this flush. That is safe
because cBPF programs (the unprivileged attack surface) are bounded well
below a pack size. Issue a warning if this assumption is ever violated
while the flush is active.
Signed-off-by: Pawan Gupta <pawan.kumar.gupta@linux.intel.com>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
include/linux/filter.h | 10 ++++++++++
kernel/bpf/core.c | 19 +++++++++++++++++++
2 files changed, 29 insertions(+)
diff --git a/include/linux/filter.h b/include/linux/filter.h
index f5cf4d35d83e..65e2e804a5cd 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -22,6 +22,7 @@
#include <linux/vmalloc.h>
#include <linux/sockptr.h>
#include <crypto/sha1.h>
+#include <linux/static_call.h>
#include <linux/u64_stats_sync.h>
#include <net/sch_generic.h>
@@ -1237,6 +1238,15 @@ extern long bpf_jit_limit_max;
typedef void (*bpf_jit_fill_hole_t)(void *area, unsigned int size);
+/*
+ * Flush the indirect branch predictors before reusing JIT memory, so that
+ * indirect jumps into a newly written program don't reuse predictions left
+ * behind by an old program that occupied the same space.
+ */
+void bpf_arch_pred_flush(void);
+DECLARE_STATIC_CALL(bpf_arch_pred_flush, bpf_arch_pred_flush);
+DECLARE_STATIC_KEY_FALSE(bpf_pred_flush_enabled);
+
void bpf_jit_fill_hole_with_zero(void *area, unsigned int size);
struct bpf_binary_header *
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 17e5cf18da1e..48208c3f5814 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/static_call.h>
#include <asm/barrier.h>
#include <linux/unaligned.h>
@@ -892,6 +893,15 @@ void bpf_jit_fill_hole_with_zero(void *area, unsigned int size)
memset(area, 0, size);
}
+DEFINE_STATIC_CALL_NULL(bpf_arch_pred_flush, bpf_arch_pred_flush);
+
+/*
+ * Enabled once bpf_arch_pred_flush points at a real flush routine. Lets the
+ * pack allocator test "is a predictor flush wired up at all" with a cheap
+ * static branch instead of repeatedly querying the static call target.
+ */
+DEFINE_STATIC_KEY_FALSE(bpf_pred_flush_enabled);
+
#define BPF_PROG_SIZE_TO_NBITS(size) (round_up(size, BPF_PROG_CHUNK_SIZE) / BPF_PROG_CHUNK_SIZE)
static DEFINE_MUTEX(pack_mutex);
@@ -951,6 +961,14 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns)
mutex_lock(&pack_mutex);
if (size > BPF_PROG_PACK_SIZE) {
+ /*
+ * Allocations larger than a pack get their own pages, and
+ * predictors are not flushed for such allocation. This is only
+ * safe because cBPF programs (the unprivileged attack surface)
+ * are bounded well below a pack size.
+ */
+ if (static_branch_unlikely(&bpf_pred_flush_enabled))
+ pr_warn_once("BPF: Predictors not flushed for allocations greater than BPF_PROG_PACK_SIZE\n");
size = round_up(size, PAGE_SIZE);
ptr = bpf_jit_alloc_exec(size);
if (ptr) {
@@ -981,6 +999,7 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns)
pos = 0;
found_free_area:
+ static_call_cond(bpf_arch_pred_flush)();
bitmap_set(pack->bitmap, pos, nbits);
ptr = (void *)(pack->ptr) + (pos << BPF_PROG_CHUNK_SHIFT);
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 6.16.y 1/6] bpf: Support for hardening against JIT spraying
@ 2026-07-14 18:58 ` Pawan Gupta
0 siblings, 0 replies; 17+ messages in thread
From: Pawan Gupta @ 2026-07-14 18:58 UTC (permalink / raw)
To: stable, Greg Kroah-Hartman, Sasha Levin
Cc: bpf, linux-arm-kernel, loongarch, linuxppc-dev, linux-riscv, x86,
Alexei Starovoitov, Daniel Borkmann
commit 96cce16e26dd02a8678f1e87f88a4b5cdb63b995 upstream.
The BPF JIT allocator packs many small programs into larger executable
allocations and reuses space within those allocations as programs are
loaded and freed. When fresh code is written into space that a previous
program occupied, an indirect jump into the new program can reuse a branch
prediction left behind by the old one.
Flush the indirect branch predictors before reusing JIT memory so that
indirect jumps into a newly written program don't reuse predictions from an
old program that occupied the same space.
Introduce bpf_arch_pred_flush_enabled static key and bpf_arch_pred_flush
static call for flushing the branch predictors on JIT memory reuse.
Architectures that need a flush, can update it to a predictor flush
function. By default, its a NOP and does not emit any CALL.
Allocations larger than a pack are not covered by this flush. That is safe
because cBPF programs (the unprivileged attack surface) are bounded well
below a pack size. Issue a warning if this assumption is ever violated
while the flush is active.
Signed-off-by: Pawan Gupta <pawan.kumar.gupta@linux.intel.com>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
include/linux/filter.h | 10 ++++++++++
kernel/bpf/core.c | 19 +++++++++++++++++++
2 files changed, 29 insertions(+)
diff --git a/include/linux/filter.h b/include/linux/filter.h
index f5cf4d35d83e..65e2e804a5cd 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -22,6 +22,7 @@
#include <linux/vmalloc.h>
#include <linux/sockptr.h>
#include <crypto/sha1.h>
+#include <linux/static_call.h>
#include <linux/u64_stats_sync.h>
#include <net/sch_generic.h>
@@ -1237,6 +1238,15 @@ extern long bpf_jit_limit_max;
typedef void (*bpf_jit_fill_hole_t)(void *area, unsigned int size);
+/*
+ * Flush the indirect branch predictors before reusing JIT memory, so that
+ * indirect jumps into a newly written program don't reuse predictions left
+ * behind by an old program that occupied the same space.
+ */
+void bpf_arch_pred_flush(void);
+DECLARE_STATIC_CALL(bpf_arch_pred_flush, bpf_arch_pred_flush);
+DECLARE_STATIC_KEY_FALSE(bpf_pred_flush_enabled);
+
void bpf_jit_fill_hole_with_zero(void *area, unsigned int size);
struct bpf_binary_header *
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 17e5cf18da1e..48208c3f5814 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/static_call.h>
#include <asm/barrier.h>
#include <linux/unaligned.h>
@@ -892,6 +893,15 @@ void bpf_jit_fill_hole_with_zero(void *area, unsigned int size)
memset(area, 0, size);
}
+DEFINE_STATIC_CALL_NULL(bpf_arch_pred_flush, bpf_arch_pred_flush);
+
+/*
+ * Enabled once bpf_arch_pred_flush points at a real flush routine. Lets the
+ * pack allocator test "is a predictor flush wired up at all" with a cheap
+ * static branch instead of repeatedly querying the static call target.
+ */
+DEFINE_STATIC_KEY_FALSE(bpf_pred_flush_enabled);
+
#define BPF_PROG_SIZE_TO_NBITS(size) (round_up(size, BPF_PROG_CHUNK_SIZE) / BPF_PROG_CHUNK_SIZE)
static DEFINE_MUTEX(pack_mutex);
@@ -951,6 +961,14 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns)
mutex_lock(&pack_mutex);
if (size > BPF_PROG_PACK_SIZE) {
+ /*
+ * Allocations larger than a pack get their own pages, and
+ * predictors are not flushed for such allocation. This is only
+ * safe because cBPF programs (the unprivileged attack surface)
+ * are bounded well below a pack size.
+ */
+ if (static_branch_unlikely(&bpf_pred_flush_enabled))
+ pr_warn_once("BPF: Predictors not flushed for allocations greater than BPF_PROG_PACK_SIZE\n");
size = round_up(size, PAGE_SIZE);
ptr = bpf_jit_alloc_exec(size);
if (ptr) {
@@ -981,6 +999,7 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns)
pos = 0;
found_free_area:
+ static_call_cond(bpf_arch_pred_flush)();
bitmap_set(pack->bitmap, pos, nbits);
ptr = (void *)(pack->ptr) + (pos << BPF_PROG_CHUNK_SHIFT);
--
2.43.0
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 6.16.y 1/6] bpf: Support for hardening against JIT spraying
2026-07-14 18:58 ` Pawan Gupta
(?)
@ 2026-07-14 19:17 ` sashiko-bot
-1 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-07-14 19:17 UTC (permalink / raw)
To: Pawan Gupta; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The branch predictor flush is bypassed for BPF allocations larger than BPF_PROG_PACK_SIZE, leaving privileged BPF programs vulnerable to Branch Target Injection (BTI) / Spectre v2 attacks. The author's rationale for skipping the flush assumes attackers cannot allocate large programs. However, an attacker can train the BTB using small programs and free them; a victim's subsequent large allocation can reuse the same virtual memory and will skip the flush, inheriting the poisoned BTB entries. Additionally, on configurations where PMD_SIZE is undefined, BPF_PROG_PACK_SIZE defaults to PAGE_SIZE (typically 4KB), which unprivileged cBPF programs (up to 32KB) can exceed, directly bypassing the flush.
--
--- Patch [1]: [PATCH 6.16.y 1/6] bpf: Support for hardening against JIT spraying ---
commit 86ef6d6286895019e1dbe8a1db211825e4c28b04
Author: Pawan Gupta <pawan.kumar.gupta@linux.intel.com>
bpf: Support for hardening against JIT spraying
This commit introduces a branch predictor flush before reusing BPF JIT memory.
It aims to prevent indirect jumps in a newly written program from reusing
predictions left behind by an old program that occupied the same space.
> diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
> index 17e5cf18da1ef..48208c3f58143 100644
> --- a/kernel/bpf/core.c
> +++ b/kernel/bpf/core.c
[ ... ]
> @@ -951,6 +961,14 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns)
>
> mutex_lock(&pack_mutex);
> if (size > BPF_PROG_PACK_SIZE) {
> + /*
> + * Allocations larger than a pack get their own pages, and
> + * predictors are not flushed for such allocation. This is only
> + * safe because cBPF programs (the unprivileged attack surface)
> + * are bounded well below a pack size.
> + */
> + if (static_branch_unlikely(&bpf_pred_flush_enabled))
> + pr_warn_once("BPF: Predictors not flushed for allocations greater than BPF_PROG_PACK_SIZE\n");
> size = round_up(size, PAGE_SIZE);
> ptr = bpf_jit_alloc_exec(size);
> if (ptr) {
[Severity: High]
Does skipping the flush for large allocations leave them vulnerable to BTB
poisoning if the virtual memory is reused?
If an attacker trains the BTB using small programs and frees them, and then a
victim loads a large program that reuses the same virtual memory, wouldn't the
large allocation skip the flush and inherit the poisoned BTB entries?
Additionally, on configurations where PMD_SIZE is undefined,
BPF_PROG_PACK_SIZE defaults to PAGE_SIZE (typically 4KB). Could unprivileged
cBPF programs (which can be up to 32KB) exceed this and bypass the flush
directly?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260714-cbpf-jit-spray-hardening-6-16-y-v1-0-2fc3e16263ac@linux.intel.com?part=1
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 6.16.y 2/6] x86/bugs: Enable IBPB flush on BPF JIT allocation
2026-07-14 18:58 ` Pawan Gupta
@ 2026-07-14 18:58 ` Pawan Gupta
-1 siblings, 0 replies; 17+ messages in thread
From: Pawan Gupta @ 2026-07-14 18:58 UTC (permalink / raw)
To: stable, Greg Kroah-Hartman, Sasha Levin
Cc: bpf, linux-arm-kernel, loongarch, linuxppc-dev, linux-riscv, x86,
Alexei Starovoitov, Daniel Borkmann, Dave Hansen
commit a3af84b0fa00ead01fcd0e28b5d773ff25990a0d upstream.
Enable hardening against JIT spraying when Spectre-v2 mitigations are in
use. Specifically, issue an IBPB flush on BPF JIT memory reuse. Skip
enabling the IBPB flush if the BPF dispatcher is already using a retpoline
sequence.
This hardening applies only when BPF-JIT is in use. Guard the enabling
under CONFIG_BPF_JIT so that bugs.c still builds with CONFIG_BPF_JIT=n.
Signed-off-by: Pawan Gupta <pawan.kumar.gupta@linux.intel.com>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
Acked-by: Dave Hansen <dave.hansen@linux.intel.com>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
arch/x86/include/asm/nospec-branch.h | 4 +++
arch/x86/kernel/cpu/bugs.c | 50 ++++++++++++++++++++++++++++++++----
2 files changed, 49 insertions(+), 5 deletions(-)
diff --git a/arch/x86/include/asm/nospec-branch.h b/arch/x86/include/asm/nospec-branch.h
index e29f82466f43..0c95f6ac5c99 100644
--- a/arch/x86/include/asm/nospec-branch.h
+++ b/arch/x86/include/asm/nospec-branch.h
@@ -386,6 +386,10 @@ extern void srso_alias_return_thunk(void);
extern void entry_untrain_ret(void);
extern void write_ibpb(void);
+#ifdef CONFIG_BPF_JIT
+extern void bpf_arch_ibpb(void);
+#endif
+
#ifdef CONFIG_X86_64
extern void clear_bhb_loop(void);
#endif
diff --git a/arch/x86/kernel/cpu/bugs.c b/arch/x86/kernel/cpu/bugs.c
index 65e253ef5218..512d58d6040c 100644
--- a/arch/x86/kernel/cpu/bugs.c
+++ b/arch/x86/kernel/cpu/bugs.c
@@ -16,6 +16,7 @@
#include <linux/sched/smt.h>
#include <linux/pgtable.h>
#include <linux/bpf.h>
+#include <linux/filter.h>
#include <asm/spec-ctrl.h>
#include <asm/cmdline.h>
@@ -1625,8 +1626,21 @@ static inline const char *spectre_v2_module_string(void)
{
return spectre_v2_bad_module ? " - vulnerable module loaded" : "";
}
+
+/*
+ * The "retpoline sequence" is the "call;mov;ret" sequence that
+ * replaces normal indirect branch instructions. Differentiate
+ * *the* retpoline sequence from the LFENCE-prefixed indirect
+ * branches that simply use the retpoline infrastructure.
+ */
+static inline bool retpoline_seq_enabled(void)
+{
+ return boot_cpu_has(X86_FEATURE_RETPOLINE) && !boot_cpu_has(X86_FEATURE_RETPOLINE_LFENCE);
+}
+
#else
static inline const char *spectre_v2_module_string(void) { return ""; }
+static inline bool retpoline_seq_enabled(void) { return false; }
#endif
#define SPECTRE_V2_LFENCE_MSG "WARNING: LFENCE mitigation is not recommended for this CPU, data leaks possible!\n"
@@ -2138,8 +2152,7 @@ static void __init bhi_apply_mitigation(void)
return;
/* Retpoline mitigates against BHI unless the CPU has RRSBA behavior */
- if (boot_cpu_has(X86_FEATURE_RETPOLINE) &&
- !boot_cpu_has(X86_FEATURE_RETPOLINE_LFENCE)) {
+ if (retpoline_seq_enabled()) {
spec_ctrl_disable_kernel_rrsba();
if (rrsba_disabled)
return;
@@ -2234,6 +2247,27 @@ static void __init spectre_v2_update_mitigation(void)
pr_info("%s\n", spectre_v2_strings[spectre_v2_enabled]);
}
+#ifdef CONFIG_BPF_JIT
+static void __bpf_arch_ibpb(void *unused)
+{
+ write_ibpb();
+}
+
+void bpf_arch_ibpb(void)
+{
+ on_each_cpu(__bpf_arch_ibpb, NULL, 1);
+}
+
+static bool __init cpu_wants_ibpb_bpf(void)
+{
+ /* A genuine retpoline already neutralizes ring0 indirect predictions */
+ if (retpoline_seq_enabled())
+ return false;
+
+ return boot_cpu_has(X86_FEATURE_IBPB);
+}
+#endif
+
static void __init spectre_v2_apply_mitigation(void)
{
if (spectre_v2_enabled == SPECTRE_V2_EIBRS && unprivileged_ebpf_enabled())
@@ -2310,6 +2344,14 @@ static void __init spectre_v2_apply_mitigation(void)
setup_force_cpu_cap(X86_FEATURE_USE_IBRS_FW);
pr_info("Enabling Restricted Speculation for firmware calls\n");
}
+
+#ifdef CONFIG_BPF_JIT
+ if (cpu_wants_ibpb_bpf()) {
+ static_call_update(bpf_arch_pred_flush, bpf_arch_ibpb);
+ static_branch_enable(&bpf_pred_flush_enabled);
+ pr_info("Enabling IBPB for BPF\n");
+ }
+#endif
}
static void update_stibp_msr(void * __unused)
@@ -3421,9 +3463,7 @@ static const char *spectre_bhi_state(void)
return "; BHI: BHI_DIS_S";
else if (boot_cpu_has(X86_FEATURE_CLEAR_BHB_LOOP))
return "; BHI: SW loop, KVM: SW loop";
- else if (boot_cpu_has(X86_FEATURE_RETPOLINE) &&
- !boot_cpu_has(X86_FEATURE_RETPOLINE_LFENCE) &&
- rrsba_disabled)
+ else if (retpoline_seq_enabled() && rrsba_disabled)
return "; BHI: Retpoline";
else if (boot_cpu_has(X86_FEATURE_CLEAR_BHB_VMEXIT))
return "; BHI: Vulnerable, KVM: SW loop";
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 6.16.y 2/6] x86/bugs: Enable IBPB flush on BPF JIT allocation
@ 2026-07-14 18:58 ` Pawan Gupta
0 siblings, 0 replies; 17+ messages in thread
From: Pawan Gupta @ 2026-07-14 18:58 UTC (permalink / raw)
To: stable, Greg Kroah-Hartman, Sasha Levin
Cc: bpf, linux-arm-kernel, loongarch, linuxppc-dev, linux-riscv, x86,
Alexei Starovoitov, Daniel Borkmann, Dave Hansen
commit a3af84b0fa00ead01fcd0e28b5d773ff25990a0d upstream.
Enable hardening against JIT spraying when Spectre-v2 mitigations are in
use. Specifically, issue an IBPB flush on BPF JIT memory reuse. Skip
enabling the IBPB flush if the BPF dispatcher is already using a retpoline
sequence.
This hardening applies only when BPF-JIT is in use. Guard the enabling
under CONFIG_BPF_JIT so that bugs.c still builds with CONFIG_BPF_JIT=n.
Signed-off-by: Pawan Gupta <pawan.kumar.gupta@linux.intel.com>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
Acked-by: Dave Hansen <dave.hansen@linux.intel.com>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
arch/x86/include/asm/nospec-branch.h | 4 +++
arch/x86/kernel/cpu/bugs.c | 50 ++++++++++++++++++++++++++++++++----
2 files changed, 49 insertions(+), 5 deletions(-)
diff --git a/arch/x86/include/asm/nospec-branch.h b/arch/x86/include/asm/nospec-branch.h
index e29f82466f43..0c95f6ac5c99 100644
--- a/arch/x86/include/asm/nospec-branch.h
+++ b/arch/x86/include/asm/nospec-branch.h
@@ -386,6 +386,10 @@ extern void srso_alias_return_thunk(void);
extern void entry_untrain_ret(void);
extern void write_ibpb(void);
+#ifdef CONFIG_BPF_JIT
+extern void bpf_arch_ibpb(void);
+#endif
+
#ifdef CONFIG_X86_64
extern void clear_bhb_loop(void);
#endif
diff --git a/arch/x86/kernel/cpu/bugs.c b/arch/x86/kernel/cpu/bugs.c
index 65e253ef5218..512d58d6040c 100644
--- a/arch/x86/kernel/cpu/bugs.c
+++ b/arch/x86/kernel/cpu/bugs.c
@@ -16,6 +16,7 @@
#include <linux/sched/smt.h>
#include <linux/pgtable.h>
#include <linux/bpf.h>
+#include <linux/filter.h>
#include <asm/spec-ctrl.h>
#include <asm/cmdline.h>
@@ -1625,8 +1626,21 @@ static inline const char *spectre_v2_module_string(void)
{
return spectre_v2_bad_module ? " - vulnerable module loaded" : "";
}
+
+/*
+ * The "retpoline sequence" is the "call;mov;ret" sequence that
+ * replaces normal indirect branch instructions. Differentiate
+ * *the* retpoline sequence from the LFENCE-prefixed indirect
+ * branches that simply use the retpoline infrastructure.
+ */
+static inline bool retpoline_seq_enabled(void)
+{
+ return boot_cpu_has(X86_FEATURE_RETPOLINE) && !boot_cpu_has(X86_FEATURE_RETPOLINE_LFENCE);
+}
+
#else
static inline const char *spectre_v2_module_string(void) { return ""; }
+static inline bool retpoline_seq_enabled(void) { return false; }
#endif
#define SPECTRE_V2_LFENCE_MSG "WARNING: LFENCE mitigation is not recommended for this CPU, data leaks possible!\n"
@@ -2138,8 +2152,7 @@ static void __init bhi_apply_mitigation(void)
return;
/* Retpoline mitigates against BHI unless the CPU has RRSBA behavior */
- if (boot_cpu_has(X86_FEATURE_RETPOLINE) &&
- !boot_cpu_has(X86_FEATURE_RETPOLINE_LFENCE)) {
+ if (retpoline_seq_enabled()) {
spec_ctrl_disable_kernel_rrsba();
if (rrsba_disabled)
return;
@@ -2234,6 +2247,27 @@ static void __init spectre_v2_update_mitigation(void)
pr_info("%s\n", spectre_v2_strings[spectre_v2_enabled]);
}
+#ifdef CONFIG_BPF_JIT
+static void __bpf_arch_ibpb(void *unused)
+{
+ write_ibpb();
+}
+
+void bpf_arch_ibpb(void)
+{
+ on_each_cpu(__bpf_arch_ibpb, NULL, 1);
+}
+
+static bool __init cpu_wants_ibpb_bpf(void)
+{
+ /* A genuine retpoline already neutralizes ring0 indirect predictions */
+ if (retpoline_seq_enabled())
+ return false;
+
+ return boot_cpu_has(X86_FEATURE_IBPB);
+}
+#endif
+
static void __init spectre_v2_apply_mitigation(void)
{
if (spectre_v2_enabled == SPECTRE_V2_EIBRS && unprivileged_ebpf_enabled())
@@ -2310,6 +2344,14 @@ static void __init spectre_v2_apply_mitigation(void)
setup_force_cpu_cap(X86_FEATURE_USE_IBRS_FW);
pr_info("Enabling Restricted Speculation for firmware calls\n");
}
+
+#ifdef CONFIG_BPF_JIT
+ if (cpu_wants_ibpb_bpf()) {
+ static_call_update(bpf_arch_pred_flush, bpf_arch_ibpb);
+ static_branch_enable(&bpf_pred_flush_enabled);
+ pr_info("Enabling IBPB for BPF\n");
+ }
+#endif
}
static void update_stibp_msr(void * __unused)
@@ -3421,9 +3463,7 @@ static const char *spectre_bhi_state(void)
return "; BHI: BHI_DIS_S";
else if (boot_cpu_has(X86_FEATURE_CLEAR_BHB_LOOP))
return "; BHI: SW loop, KVM: SW loop";
- else if (boot_cpu_has(X86_FEATURE_RETPOLINE) &&
- !boot_cpu_has(X86_FEATURE_RETPOLINE_LFENCE) &&
- rrsba_disabled)
+ else if (retpoline_seq_enabled() && rrsba_disabled)
return "; BHI: Retpoline";
else if (boot_cpu_has(X86_FEATURE_CLEAR_BHB_VMEXIT))
return "; BHI: Vulnerable, KVM: SW loop";
--
2.43.0
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 6.16.y 2/6] x86/bugs: Enable IBPB flush on BPF JIT allocation
2026-07-14 18:58 ` Pawan Gupta
(?)
@ 2026-07-14 19:36 ` sashiko-bot
-1 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-07-14 19:36 UTC (permalink / raw)
To: Pawan Gupta; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The IBPB flush for BPF JIT memory reuse is bypassed for allocations larger than `BPF_PROG_PACK_SIZE`, allowing unprivileged-to-privileged BTB poisoning (JIT spraying) when memory is reused.
- [High] System-wide IPI storm allowing Denial of Service via frequent BPF program loads.
--
--- Patch [2]: [PATCH 6.16.y 2/6] x86/bugs: Enable IBPB flush on BPF JIT allocation ---
commit 952665895a964d3f1620c223a7c5e6f20ea4be8a
Author: Pawan Gupta <pawan.kumar.gupta@linux.intel.com>
x86/bugs: Enable IBPB flush on BPF JIT allocation
Enable hardening against JIT spraying when Spectre-v2 mitigations are in
use. Specifically, issue an IBPB flush on BPF JIT memory reuse. Skip
enabling the IBPB flush if the BPF dispatcher is already using a retpoline
sequence.
> diff --git a/arch/x86/kernel/cpu/bugs.c b/arch/x86/kernel/cpu/bugs.c
> index 65e253ef52184..512d58d6040c7 100644
> --- a/arch/x86/kernel/cpu/bugs.c
> +++ b/arch/x86/kernel/cpu/bugs.c
> @@ -2234,6 +2247,27 @@ static void __init spectre_v2_update_mitigation(void)
> pr_info("%s\n", spectre_v2_strings[spectre_v2_enabled]);
> }
>
> +#ifdef CONFIG_BPF_JIT
> +static void __bpf_arch_ibpb(void *unused)
> +{
> + write_ibpb();
> +}
> +
> +void bpf_arch_ibpb(void)
> +{
> + on_each_cpu(__bpf_arch_ibpb, NULL, 1);
> +}
[Severity: High]
Could this implementation cause a system-wide IPI storm allowing a denial of
service via frequent BPF program loads?
When bpf_prog_pack_alloc() in kernel/bpf/core.c allocates a chunk, it
synchronously calls bpf_arch_pred_flush() which maps here. This issues
on_each_cpu(), sending inter-processor interrupts to all online CPU cores and
blocking until they complete write_ibpb().
Because an unprivileged user can loop creating cBPF programs via
setsockopt(..., SO_ATTACH_FILTER, ...) and this allocation has no rate
limiting, won't this trivially cause an IPI storm?
> +
> +static bool __init cpu_wants_ibpb_bpf(void)
> +{
> + /* A genuine retpoline already neutralizes ring0 indirect predictions */
> + if (retpoline_seq_enabled())
> + return false;
> +
> + return boot_cpu_has(X86_FEATURE_IBPB);
> +}
> +#endif
[ ... ]
> @@ -2310,6 +2344,14 @@ static void __init spectre_v2_apply_mitigation(void)
> setup_force_cpu_cap(X86_FEATURE_USE_IBRS_FW);
> pr_info("Enabling Restricted Speculation for firmware calls\n");
> }
> +
> +#ifdef CONFIG_BPF_JIT
> + if (cpu_wants_ibpb_bpf()) {
> + static_call_update(bpf_arch_pred_flush, bpf_arch_ibpb);
> + static_branch_enable(&bpf_pred_flush_enabled);
[Severity: High]
Does this allow a bypass of the JIT spraying mitigation for allocations
larger than BPF_PROG_PACK_SIZE?
In kernel/bpf/core.c, bpf_prog_pack_alloc() bypasses the predictor flush
for large allocations:
if (size > BPF_PROG_PACK_SIZE) {
/* ... */
if (static_branch_unlikely(&bpf_pred_flush_enabled))
pr_warn_once("...");
size = round_up(size, PAGE_SIZE);
ptr = bpf_jit_alloc_exec(size);
goto out;
}
Could an unprivileged attacker load many small cBPF programs to fill a
BPF_PROG_PACK_SIZE block, poison the branch predictor, and then unload them
to return the block to the execmem allocator?
If a privileged user then loads a large BPF program, couldn't the victim's
program be loaded into this exact poisoned address space without a flush,
allowing the attacker to hijack control flow?
> + pr_info("Enabling IBPB for BPF\n");
> + }
> +#endif
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260714-cbpf-jit-spray-hardening-6-16-y-v1-0-2fc3e16263ac@linux.intel.com?part=2
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 6.16.y 3/6] bpf: Restrict JIT predictor flush to cBPF
2026-07-14 18:58 ` Pawan Gupta
@ 2026-07-14 18:59 ` Pawan Gupta
-1 siblings, 0 replies; 17+ messages in thread
From: Pawan Gupta @ 2026-07-14 18:59 UTC (permalink / raw)
To: stable, Greg Kroah-Hartman, Sasha Levin
Cc: bpf, linux-arm-kernel, loongarch, linuxppc-dev, linux-riscv, x86,
Alexei Starovoitov, Daniel Borkmann
commit 0bb99f2cfaae6822d734d69722de30af823efdf3 upstream.
Currently predictor flush on memory reuse is done for all BPF JIT
allocations, but only cBPF programs can be loaded by an unprivileged user.
eBPF is privileged by default, and flushing predictors for all CPUs on
every eBPF reuse penalizes the common case for no security benefit.
eBPF allocations can be frequent on busy systems, only flush predictors
for cBPF programs. Trampoline and dispatcher allocations also skip the
flush as they are eBPF-only.
[pawan: backport had various conflicts in arches bpf_int_jit_compile().
loongarch bpf_int_jit_compile() doesn't use pack allocator,
dropped "was_classic" hunk. Also for arch_alloc_bpf_trampoline()]
Signed-off-by: Pawan Gupta <pawan.kumar.gupta@linux.intel.com>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
arch/arm64/net/bpf_jit_comp.c | 4 ++--
arch/powerpc/net/bpf_jit_comp.c | 4 ++--
arch/riscv/net/bpf_jit_comp64.c | 2 +-
arch/riscv/net/bpf_jit_core.c | 3 ++-
arch/x86/net/bpf_jit_comp.c | 5 +++--
include/linux/filter.h | 5 +++--
kernel/bpf/core.c | 13 ++++++++-----
kernel/bpf/dispatcher.c | 2 +-
8 files changed, 22 insertions(+), 16 deletions(-)
diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c
index 58f838b310bc..fec93c352f35 100644
--- a/arch/arm64/net/bpf_jit_comp.c
+++ b/arch/arm64/net/bpf_jit_comp.c
@@ -1962,7 +1962,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
image_size = extable_offset + extable_size;
ro_header = bpf_jit_binary_pack_alloc(image_size, &ro_image_ptr,
sizeof(u32), &header, &image_ptr,
- jit_fill_hole);
+ jit_fill_hole, was_classic);
if (!ro_header) {
prog = orig_prog;
goto out_off;
@@ -2594,7 +2594,7 @@ int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags,
void *arch_alloc_bpf_trampoline(unsigned int size)
{
- return bpf_prog_pack_alloc(size, jit_fill_hole);
+ return bpf_prog_pack_alloc(size, jit_fill_hole, false);
}
void arch_free_bpf_trampoline(void *image, unsigned int size)
diff --git a/arch/powerpc/net/bpf_jit_comp.c b/arch/powerpc/net/bpf_jit_comp.c
index c0684733e9d6..ea64203f768f 100644
--- a/arch/powerpc/net/bpf_jit_comp.c
+++ b/arch/powerpc/net/bpf_jit_comp.c
@@ -244,7 +244,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *fp)
alloclen = proglen + FUNCTION_DESCR_SIZE + fixup_len + extable_len;
fhdr = bpf_jit_binary_pack_alloc(alloclen, &fimage, 4, &hdr, &image,
- bpf_jit_fill_ill_insns);
+ bpf_jit_fill_ill_insns, bpf_prog_was_classic(fp));
if (!fhdr) {
fp = org_fp;
goto out_addrs;
@@ -442,7 +442,7 @@ bool bpf_jit_supports_far_kfunc_call(void)
void *arch_alloc_bpf_trampoline(unsigned int size)
{
- return bpf_prog_pack_alloc(size, bpf_jit_fill_ill_insns);
+ return bpf_prog_pack_alloc(size, bpf_jit_fill_ill_insns, false);
}
void arch_free_bpf_trampoline(void *image, unsigned int size)
diff --git a/arch/riscv/net/bpf_jit_comp64.c b/arch/riscv/net/bpf_jit_comp64.c
index 9883a55d61b5..f67533a7ef3d 100644
--- a/arch/riscv/net/bpf_jit_comp64.c
+++ b/arch/riscv/net/bpf_jit_comp64.c
@@ -1280,7 +1280,7 @@ int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags,
void *arch_alloc_bpf_trampoline(unsigned int size)
{
- return bpf_prog_pack_alloc(size, bpf_fill_ill_insns);
+ return bpf_prog_pack_alloc(size, bpf_fill_ill_insns, false);
}
void arch_free_bpf_trampoline(void *image, unsigned int size)
diff --git a/arch/riscv/net/bpf_jit_core.c b/arch/riscv/net/bpf_jit_core.c
index f6ca5cfa6b2f..a2eed555ed96 100644
--- a/arch/riscv/net/bpf_jit_core.c
+++ b/arch/riscv/net/bpf_jit_core.c
@@ -125,7 +125,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
bpf_jit_binary_pack_alloc(prog_size + extable_size,
&jit_data->ro_image, sizeof(u32),
&jit_data->header, &jit_data->image,
- bpf_fill_ill_insns);
+ bpf_fill_ill_insns,
+ bpf_prog_was_classic(prog));
if (!jit_data->ro_header) {
prog = orig_prog;
goto out_offset;
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 15672cb926fc..e8034c3d9599 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -3347,7 +3347,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
void *arch_alloc_bpf_trampoline(unsigned int size)
{
- return bpf_prog_pack_alloc(size, jit_fill_hole);
+ return bpf_prog_pack_alloc(size, jit_fill_hole, false);
}
void arch_free_bpf_trampoline(void *image, unsigned int size)
@@ -3687,7 +3687,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
/* allocate module memory for x86 insns and extable */
header = bpf_jit_binary_pack_alloc(roundup(proglen, align) + extable_size,
&image, align, &rw_header, &rw_image,
- jit_fill_hole);
+ jit_fill_hole,
+ bpf_prog_was_classic(prog));
if (!header) {
prog = orig_prog;
goto out_addrs;
diff --git a/include/linux/filter.h b/include/linux/filter.h
index 65e2e804a5cd..8a2285db0866 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1261,7 +1261,7 @@ void bpf_jit_free(struct bpf_prog *fp);
struct bpf_binary_header *
bpf_jit_binary_pack_hdr(const struct bpf_prog *fp);
-void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns);
+void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns, bool was_classic);
void bpf_prog_pack_free(void *ptr, u32 size);
static inline bool bpf_prog_kallsyms_verify_off(const struct bpf_prog *fp)
@@ -1275,7 +1275,8 @@ bpf_jit_binary_pack_alloc(unsigned int proglen, u8 **ro_image,
unsigned int alignment,
struct bpf_binary_header **rw_hdr,
u8 **rw_image,
- bpf_jit_fill_hole_t bpf_fill_ill_insns);
+ bpf_jit_fill_hole_t bpf_fill_ill_insns,
+ bool was_classic);
int bpf_jit_binary_pack_finalize(struct bpf_binary_header *ro_header,
struct bpf_binary_header *rw_header);
void bpf_jit_binary_pack_free(struct bpf_binary_header *ro_header,
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 48208c3f5814..63569d23cfa0 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -952,7 +952,7 @@ static struct bpf_prog_pack *alloc_new_pack(bpf_jit_fill_hole_t bpf_fill_ill_ins
return NULL;
}
-void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns)
+void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns, bool was_classic)
{
unsigned int nbits = BPF_PROG_SIZE_TO_NBITS(size);
struct bpf_prog_pack *pack;
@@ -967,7 +967,7 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns)
* safe because cBPF programs (the unprivileged attack surface)
* are bounded well below a pack size.
*/
- if (static_branch_unlikely(&bpf_pred_flush_enabled))
+ if (was_classic && static_branch_unlikely(&bpf_pred_flush_enabled))
pr_warn_once("BPF: Predictors not flushed for allocations greater than BPF_PROG_PACK_SIZE\n");
size = round_up(size, PAGE_SIZE);
ptr = bpf_jit_alloc_exec(size);
@@ -999,7 +999,9 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns)
pos = 0;
found_free_area:
- static_call_cond(bpf_arch_pred_flush)();
+ /* Flush only for cBPF as it may contain a crafted gadget */
+ if (static_branch_unlikely(&bpf_pred_flush_enabled) && was_classic)
+ static_call_cond(bpf_arch_pred_flush)();
bitmap_set(pack->bitmap, pos, nbits);
ptr = (void *)(pack->ptr) + (pos << BPF_PROG_CHUNK_SHIFT);
@@ -1159,7 +1161,8 @@ bpf_jit_binary_pack_alloc(unsigned int proglen, u8 **image_ptr,
unsigned int alignment,
struct bpf_binary_header **rw_header,
u8 **rw_image,
- bpf_jit_fill_hole_t bpf_fill_ill_insns)
+ bpf_jit_fill_hole_t bpf_fill_ill_insns,
+ bool was_classic)
{
struct bpf_binary_header *ro_header;
u32 size, hole, start;
@@ -1172,7 +1175,7 @@ bpf_jit_binary_pack_alloc(unsigned int proglen, u8 **image_ptr,
if (bpf_jit_charge_modmem(size))
return NULL;
- ro_header = bpf_prog_pack_alloc(size, bpf_fill_ill_insns);
+ ro_header = bpf_prog_pack_alloc(size, bpf_fill_ill_insns, was_classic);
if (!ro_header) {
bpf_jit_uncharge_modmem(size);
return NULL;
diff --git a/kernel/bpf/dispatcher.c b/kernel/bpf/dispatcher.c
index b77db7413f8c..ea2d60dc1fee 100644
--- a/kernel/bpf/dispatcher.c
+++ b/kernel/bpf/dispatcher.c
@@ -145,7 +145,7 @@ void bpf_dispatcher_change_prog(struct bpf_dispatcher *d, struct bpf_prog *from,
mutex_lock(&d->mutex);
if (!d->image) {
- d->image = bpf_prog_pack_alloc(PAGE_SIZE, bpf_jit_fill_hole_with_zero);
+ d->image = bpf_prog_pack_alloc(PAGE_SIZE, bpf_jit_fill_hole_with_zero, false);
if (!d->image)
goto out;
d->rw_image = bpf_jit_alloc_exec(PAGE_SIZE);
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 6.16.y 3/6] bpf: Restrict JIT predictor flush to cBPF
@ 2026-07-14 18:59 ` Pawan Gupta
0 siblings, 0 replies; 17+ messages in thread
From: Pawan Gupta @ 2026-07-14 18:59 UTC (permalink / raw)
To: stable, Greg Kroah-Hartman, Sasha Levin
Cc: bpf, linux-arm-kernel, loongarch, linuxppc-dev, linux-riscv, x86,
Alexei Starovoitov, Daniel Borkmann
commit 0bb99f2cfaae6822d734d69722de30af823efdf3 upstream.
Currently predictor flush on memory reuse is done for all BPF JIT
allocations, but only cBPF programs can be loaded by an unprivileged user.
eBPF is privileged by default, and flushing predictors for all CPUs on
every eBPF reuse penalizes the common case for no security benefit.
eBPF allocations can be frequent on busy systems, only flush predictors
for cBPF programs. Trampoline and dispatcher allocations also skip the
flush as they are eBPF-only.
[pawan: backport had various conflicts in arches bpf_int_jit_compile().
loongarch bpf_int_jit_compile() doesn't use pack allocator,
dropped "was_classic" hunk. Also for arch_alloc_bpf_trampoline()]
Signed-off-by: Pawan Gupta <pawan.kumar.gupta@linux.intel.com>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
arch/arm64/net/bpf_jit_comp.c | 4 ++--
arch/powerpc/net/bpf_jit_comp.c | 4 ++--
arch/riscv/net/bpf_jit_comp64.c | 2 +-
arch/riscv/net/bpf_jit_core.c | 3 ++-
arch/x86/net/bpf_jit_comp.c | 5 +++--
include/linux/filter.h | 5 +++--
kernel/bpf/core.c | 13 ++++++++-----
kernel/bpf/dispatcher.c | 2 +-
8 files changed, 22 insertions(+), 16 deletions(-)
diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c
index 58f838b310bc..fec93c352f35 100644
--- a/arch/arm64/net/bpf_jit_comp.c
+++ b/arch/arm64/net/bpf_jit_comp.c
@@ -1962,7 +1962,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
image_size = extable_offset + extable_size;
ro_header = bpf_jit_binary_pack_alloc(image_size, &ro_image_ptr,
sizeof(u32), &header, &image_ptr,
- jit_fill_hole);
+ jit_fill_hole, was_classic);
if (!ro_header) {
prog = orig_prog;
goto out_off;
@@ -2594,7 +2594,7 @@ int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags,
void *arch_alloc_bpf_trampoline(unsigned int size)
{
- return bpf_prog_pack_alloc(size, jit_fill_hole);
+ return bpf_prog_pack_alloc(size, jit_fill_hole, false);
}
void arch_free_bpf_trampoline(void *image, unsigned int size)
diff --git a/arch/powerpc/net/bpf_jit_comp.c b/arch/powerpc/net/bpf_jit_comp.c
index c0684733e9d6..ea64203f768f 100644
--- a/arch/powerpc/net/bpf_jit_comp.c
+++ b/arch/powerpc/net/bpf_jit_comp.c
@@ -244,7 +244,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *fp)
alloclen = proglen + FUNCTION_DESCR_SIZE + fixup_len + extable_len;
fhdr = bpf_jit_binary_pack_alloc(alloclen, &fimage, 4, &hdr, &image,
- bpf_jit_fill_ill_insns);
+ bpf_jit_fill_ill_insns, bpf_prog_was_classic(fp));
if (!fhdr) {
fp = org_fp;
goto out_addrs;
@@ -442,7 +442,7 @@ bool bpf_jit_supports_far_kfunc_call(void)
void *arch_alloc_bpf_trampoline(unsigned int size)
{
- return bpf_prog_pack_alloc(size, bpf_jit_fill_ill_insns);
+ return bpf_prog_pack_alloc(size, bpf_jit_fill_ill_insns, false);
}
void arch_free_bpf_trampoline(void *image, unsigned int size)
diff --git a/arch/riscv/net/bpf_jit_comp64.c b/arch/riscv/net/bpf_jit_comp64.c
index 9883a55d61b5..f67533a7ef3d 100644
--- a/arch/riscv/net/bpf_jit_comp64.c
+++ b/arch/riscv/net/bpf_jit_comp64.c
@@ -1280,7 +1280,7 @@ int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags,
void *arch_alloc_bpf_trampoline(unsigned int size)
{
- return bpf_prog_pack_alloc(size, bpf_fill_ill_insns);
+ return bpf_prog_pack_alloc(size, bpf_fill_ill_insns, false);
}
void arch_free_bpf_trampoline(void *image, unsigned int size)
diff --git a/arch/riscv/net/bpf_jit_core.c b/arch/riscv/net/bpf_jit_core.c
index f6ca5cfa6b2f..a2eed555ed96 100644
--- a/arch/riscv/net/bpf_jit_core.c
+++ b/arch/riscv/net/bpf_jit_core.c
@@ -125,7 +125,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
bpf_jit_binary_pack_alloc(prog_size + extable_size,
&jit_data->ro_image, sizeof(u32),
&jit_data->header, &jit_data->image,
- bpf_fill_ill_insns);
+ bpf_fill_ill_insns,
+ bpf_prog_was_classic(prog));
if (!jit_data->ro_header) {
prog = orig_prog;
goto out_offset;
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 15672cb926fc..e8034c3d9599 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -3347,7 +3347,7 @@ static int __arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *rw_im
void *arch_alloc_bpf_trampoline(unsigned int size)
{
- return bpf_prog_pack_alloc(size, jit_fill_hole);
+ return bpf_prog_pack_alloc(size, jit_fill_hole, false);
}
void arch_free_bpf_trampoline(void *image, unsigned int size)
@@ -3687,7 +3687,8 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_prog *prog)
/* allocate module memory for x86 insns and extable */
header = bpf_jit_binary_pack_alloc(roundup(proglen, align) + extable_size,
&image, align, &rw_header, &rw_image,
- jit_fill_hole);
+ jit_fill_hole,
+ bpf_prog_was_classic(prog));
if (!header) {
prog = orig_prog;
goto out_addrs;
diff --git a/include/linux/filter.h b/include/linux/filter.h
index 65e2e804a5cd..8a2285db0866 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1261,7 +1261,7 @@ void bpf_jit_free(struct bpf_prog *fp);
struct bpf_binary_header *
bpf_jit_binary_pack_hdr(const struct bpf_prog *fp);
-void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns);
+void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns, bool was_classic);
void bpf_prog_pack_free(void *ptr, u32 size);
static inline bool bpf_prog_kallsyms_verify_off(const struct bpf_prog *fp)
@@ -1275,7 +1275,8 @@ bpf_jit_binary_pack_alloc(unsigned int proglen, u8 **ro_image,
unsigned int alignment,
struct bpf_binary_header **rw_hdr,
u8 **rw_image,
- bpf_jit_fill_hole_t bpf_fill_ill_insns);
+ bpf_jit_fill_hole_t bpf_fill_ill_insns,
+ bool was_classic);
int bpf_jit_binary_pack_finalize(struct bpf_binary_header *ro_header,
struct bpf_binary_header *rw_header);
void bpf_jit_binary_pack_free(struct bpf_binary_header *ro_header,
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 48208c3f5814..63569d23cfa0 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -952,7 +952,7 @@ static struct bpf_prog_pack *alloc_new_pack(bpf_jit_fill_hole_t bpf_fill_ill_ins
return NULL;
}
-void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns)
+void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns, bool was_classic)
{
unsigned int nbits = BPF_PROG_SIZE_TO_NBITS(size);
struct bpf_prog_pack *pack;
@@ -967,7 +967,7 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns)
* safe because cBPF programs (the unprivileged attack surface)
* are bounded well below a pack size.
*/
- if (static_branch_unlikely(&bpf_pred_flush_enabled))
+ if (was_classic && static_branch_unlikely(&bpf_pred_flush_enabled))
pr_warn_once("BPF: Predictors not flushed for allocations greater than BPF_PROG_PACK_SIZE\n");
size = round_up(size, PAGE_SIZE);
ptr = bpf_jit_alloc_exec(size);
@@ -999,7 +999,9 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns)
pos = 0;
found_free_area:
- static_call_cond(bpf_arch_pred_flush)();
+ /* Flush only for cBPF as it may contain a crafted gadget */
+ if (static_branch_unlikely(&bpf_pred_flush_enabled) && was_classic)
+ static_call_cond(bpf_arch_pred_flush)();
bitmap_set(pack->bitmap, pos, nbits);
ptr = (void *)(pack->ptr) + (pos << BPF_PROG_CHUNK_SHIFT);
@@ -1159,7 +1161,8 @@ bpf_jit_binary_pack_alloc(unsigned int proglen, u8 **image_ptr,
unsigned int alignment,
struct bpf_binary_header **rw_header,
u8 **rw_image,
- bpf_jit_fill_hole_t bpf_fill_ill_insns)
+ bpf_jit_fill_hole_t bpf_fill_ill_insns,
+ bool was_classic)
{
struct bpf_binary_header *ro_header;
u32 size, hole, start;
@@ -1172,7 +1175,7 @@ bpf_jit_binary_pack_alloc(unsigned int proglen, u8 **image_ptr,
if (bpf_jit_charge_modmem(size))
return NULL;
- ro_header = bpf_prog_pack_alloc(size, bpf_fill_ill_insns);
+ ro_header = bpf_prog_pack_alloc(size, bpf_fill_ill_insns, was_classic);
if (!ro_header) {
bpf_jit_uncharge_modmem(size);
return NULL;
diff --git a/kernel/bpf/dispatcher.c b/kernel/bpf/dispatcher.c
index b77db7413f8c..ea2d60dc1fee 100644
--- a/kernel/bpf/dispatcher.c
+++ b/kernel/bpf/dispatcher.c
@@ -145,7 +145,7 @@ void bpf_dispatcher_change_prog(struct bpf_dispatcher *d, struct bpf_prog *from,
mutex_lock(&d->mutex);
if (!d->image) {
- d->image = bpf_prog_pack_alloc(PAGE_SIZE, bpf_jit_fill_hole_with_zero);
+ d->image = bpf_prog_pack_alloc(PAGE_SIZE, bpf_jit_fill_hole_with_zero, false);
if (!d->image)
goto out;
d->rw_image = bpf_jit_alloc_exec(PAGE_SIZE);
--
2.43.0
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 6.16.y 3/6] bpf: Restrict JIT predictor flush to cBPF
2026-07-14 18:59 ` Pawan Gupta
(?)
@ 2026-07-14 19:28 ` sashiko-bot
-1 siblings, 0 replies; 17+ messages in thread
From: sashiko-bot @ 2026-07-14 19:28 UTC (permalink / raw)
To: Pawan Gupta; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Restricting the JIT predictor flush exclusively to cBPF programs bypasses the Spectre v2/BHB mitigation for eBPF programs, exposing privileged eBPF code, trampolines, and dispatchers to Branch Target Injection.
--
--- Patch [3]: [PATCH 6.16.y 3/6] bpf: Restrict JIT predictor flush to cBPF ---
commit 8b8cdfd6b31b7472477f24189e670075eceab23b
Author: Pawan Gupta <pawan.kumar.gupta@linux.intel.com>
bpf: Restrict JIT predictor flush to cBPF
This commit restricts the JIT predictor flush to only cBPF programs when
reusing memory in the BPF pack allocator. It skips the flush for eBPF
programs, trampolines, and dispatchers to improve performance.
> diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
> index 48208c3f58143..63569d23cfa09 100644
> --- a/kernel/bpf/core.c
> +++ b/kernel/bpf/core.c
[ ... ]
> @@ -999,7 +999,9 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns)
> pos = 0;
>
> found_free_area:
> - static_call_cond(bpf_arch_pred_flush)();
> + /* Flush only for cBPF as it may contain a crafted gadget */
> + if (static_branch_unlikely(&bpf_pred_flush_enabled) && was_classic)
> + static_call_cond(bpf_arch_pred_flush)();
[Severity: High]
Does restricting the predictor flush to only cBPF allocations bypass the
Spectre v2/BHB mitigation when memory is reused?
The JIT predictor flush clears branch history left by the previous occupant of
the memory. By gating the flush on was_classic in bpf_prog_pack_alloc(), the
mitigation is skipped if the new allocation is an eBPF program, dispatcher, or
trampoline.
If a local unprivileged user loads a cBPF program, trains the branch predictor
to jump to a specific offset, and frees the program, the kernel might reuse
this memory chunk for a privileged eBPF program. Because the new allocation is
eBPF, was_classic is false and the flush is skipped, potentially allowing the
attacker to trigger a misprediction and speculatively execute accidental
gadgets in the new privileged code.
Additionally, if unprivileged eBPF is enabled, an attacker can load an
unprivileged eBPF program, train the predictor, free it, and load another
unprivileged eBPF program with crafted gadgets. Since bpf_prog_was_classic()
returns false for native eBPF programs, the flush is also skipped for the new
allocation, leaving the crafted gadgets vulnerable to speculative execution.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260714-cbpf-jit-spray-hardening-6-16-y-v1-0-2fc3e16263ac@linux.intel.com?part=3
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 6.16.y 4/6] bpf: Skip redundant IBPB in pack allocator
2026-07-14 18:58 ` Pawan Gupta
@ 2026-07-14 18:59 ` Pawan Gupta
-1 siblings, 0 replies; 17+ messages in thread
From: Pawan Gupta @ 2026-07-14 18:59 UTC (permalink / raw)
To: stable, Greg Kroah-Hartman, Sasha Levin
Cc: bpf, linux-arm-kernel, loongarch, linuxppc-dev, linux-riscv, x86,
Alexei Starovoitov, Daniel Borkmann
commit a23c1c5396a91680703360d1ee28a44657c503c4 upstream.
bpf_prog_pack_alloc() issues IBPB on all CPUs on every cBPF allocation,
even when reusing chunks from an existing pack where no new memory was
touched since the last IBPB.
Since IBPB on all CPUs is heavy, Dave Hansen suggested to track allocation
since last IBPB, and only issue IBPB at reuse for the chunks that have not
seen an IBPB since they were last freed.
Track per-pack whether an IBPB is needed via arch_flush_needed. Set it when
allocating a chunk, reset on IBPB flush. On reuse, conditionally issue the
flush. Since IBPB invalidates all BTB entries, clear the flag on all packs
after flushing.
Signed-off-by: Pawan Gupta <pawan.kumar.gupta@linux.intel.com>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
kernel/bpf/core.c | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 63569d23cfa0..9fc2decac07d 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -885,6 +885,7 @@ int bpf_jit_add_poke_descriptor(struct bpf_prog *prog,
struct bpf_prog_pack {
struct list_head list;
void *ptr;
+ bool arch_flush_needed;
unsigned long bitmap[];
};
@@ -938,6 +939,8 @@ static struct bpf_prog_pack *alloc_new_pack(bpf_jit_fill_hole_t bpf_fill_ill_ins
bpf_fill_ill_insns(pack->ptr, BPF_PROG_PACK_SIZE);
bitmap_zero(pack->bitmap, BPF_PROG_PACK_SIZE / BPF_PROG_CHUNK_SIZE);
+ if (static_branch_unlikely(&bpf_pred_flush_enabled))
+ pack->arch_flush_needed = true;
set_vm_flush_reset_perms(pack->ptr);
err = set_memory_rox((unsigned long)pack->ptr,
BPF_PROG_PACK_SIZE / PAGE_SIZE);
@@ -1000,8 +1003,15 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns, bool
found_free_area:
/* Flush only for cBPF as it may contain a crafted gadget */
- if (static_branch_unlikely(&bpf_pred_flush_enabled) && was_classic)
+ if (static_branch_unlikely(&bpf_pred_flush_enabled) &&
+ pack->arch_flush_needed &&
+ was_classic) {
+ struct bpf_prog_pack *p;
+
static_call_cond(bpf_arch_pred_flush)();
+ list_for_each_entry(p, &pack_list, list)
+ p->arch_flush_needed = false;
+ }
bitmap_set(pack->bitmap, pos, nbits);
ptr = (void *)(pack->ptr) + (pos << BPF_PROG_CHUNK_SHIFT);
@@ -1039,6 +1049,9 @@ void bpf_prog_pack_free(void *ptr, u32 size)
"bpf_prog_pack bug: missing bpf_arch_text_invalidate?\n");
bitmap_clear(pack->bitmap, pos, nbits);
+
+ if (static_branch_unlikely(&bpf_pred_flush_enabled))
+ pack->arch_flush_needed = true;
if (bitmap_find_next_zero_area(pack->bitmap, BPF_PROG_CHUNK_COUNT, 0,
BPF_PROG_CHUNK_COUNT, 0) == 0) {
list_del(&pack->list);
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 6.16.y 4/6] bpf: Skip redundant IBPB in pack allocator
@ 2026-07-14 18:59 ` Pawan Gupta
0 siblings, 0 replies; 17+ messages in thread
From: Pawan Gupta @ 2026-07-14 18:59 UTC (permalink / raw)
To: stable, Greg Kroah-Hartman, Sasha Levin
Cc: bpf, linux-arm-kernel, loongarch, linuxppc-dev, linux-riscv, x86,
Alexei Starovoitov, Daniel Borkmann
commit a23c1c5396a91680703360d1ee28a44657c503c4 upstream.
bpf_prog_pack_alloc() issues IBPB on all CPUs on every cBPF allocation,
even when reusing chunks from an existing pack where no new memory was
touched since the last IBPB.
Since IBPB on all CPUs is heavy, Dave Hansen suggested to track allocation
since last IBPB, and only issue IBPB at reuse for the chunks that have not
seen an IBPB since they were last freed.
Track per-pack whether an IBPB is needed via arch_flush_needed. Set it when
allocating a chunk, reset on IBPB flush. On reuse, conditionally issue the
flush. Since IBPB invalidates all BTB entries, clear the flag on all packs
after flushing.
Signed-off-by: Pawan Gupta <pawan.kumar.gupta@linux.intel.com>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
kernel/bpf/core.c | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 63569d23cfa0..9fc2decac07d 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -885,6 +885,7 @@ int bpf_jit_add_poke_descriptor(struct bpf_prog *prog,
struct bpf_prog_pack {
struct list_head list;
void *ptr;
+ bool arch_flush_needed;
unsigned long bitmap[];
};
@@ -938,6 +939,8 @@ static struct bpf_prog_pack *alloc_new_pack(bpf_jit_fill_hole_t bpf_fill_ill_ins
bpf_fill_ill_insns(pack->ptr, BPF_PROG_PACK_SIZE);
bitmap_zero(pack->bitmap, BPF_PROG_PACK_SIZE / BPF_PROG_CHUNK_SIZE);
+ if (static_branch_unlikely(&bpf_pred_flush_enabled))
+ pack->arch_flush_needed = true;
set_vm_flush_reset_perms(pack->ptr);
err = set_memory_rox((unsigned long)pack->ptr,
BPF_PROG_PACK_SIZE / PAGE_SIZE);
@@ -1000,8 +1003,15 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns, bool
found_free_area:
/* Flush only for cBPF as it may contain a crafted gadget */
- if (static_branch_unlikely(&bpf_pred_flush_enabled) && was_classic)
+ if (static_branch_unlikely(&bpf_pred_flush_enabled) &&
+ pack->arch_flush_needed &&
+ was_classic) {
+ struct bpf_prog_pack *p;
+
static_call_cond(bpf_arch_pred_flush)();
+ list_for_each_entry(p, &pack_list, list)
+ p->arch_flush_needed = false;
+ }
bitmap_set(pack->bitmap, pos, nbits);
ptr = (void *)(pack->ptr) + (pos << BPF_PROG_CHUNK_SHIFT);
@@ -1039,6 +1049,9 @@ void bpf_prog_pack_free(void *ptr, u32 size)
"bpf_prog_pack bug: missing bpf_arch_text_invalidate?\n");
bitmap_clear(pack->bitmap, pos, nbits);
+
+ if (static_branch_unlikely(&bpf_pred_flush_enabled))
+ pack->arch_flush_needed = true;
if (bitmap_find_next_zero_area(pack->bitmap, BPF_PROG_CHUNK_COUNT, 0,
BPF_PROG_CHUNK_COUNT, 0) == 0) {
list_del(&pack->list);
--
2.43.0
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 6.16.y 5/6] bpf: Prefer packs that won't trigger an IBPB flush on allocation
2026-07-14 18:58 ` Pawan Gupta
@ 2026-07-14 18:59 ` Pawan Gupta
-1 siblings, 0 replies; 17+ messages in thread
From: Pawan Gupta @ 2026-07-14 18:59 UTC (permalink / raw)
To: stable, Greg Kroah-Hartman, Sasha Levin
Cc: bpf, linux-arm-kernel, loongarch, linuxppc-dev, linux-riscv, x86,
Alexei Starovoitov, Daniel Borkmann
commit a9b1f19a6a673ba06820898d0f1ad02883ea1639 upstream.
Currently BPF pack allocator picks the chunks from the first available
pack. While this is okay, it naturally leads to more frequent flushes
when there are multiple packs in the system that weren't used since the
last flush.
As an optimization prefer allocating the new programs from packs that
are unused since last flush. When all packs are dirty, allocation forces
a flush and marks all packs clean.
Below are some future optimizations ideas:
1. Currently, the "dirty" tracking is only done at the pack-level.
Flush frequency can further be reduced with chunk-level tracking.
This requires a new bitmap per-pack to track the dirty state.
2. IBPB flush is done on all CPUs, even if only a single CPU ran the
BPF program. On a system with hundreds of CPUs this could be a
major bottleneck forcing hundreds of IPIs to deliver the flush.
The solution is to track the CPUs where a BPF program ran, and
issue IBPB only on those CPUs.
3. Avoid IBPB when flush is already done at other sources (e.g.
context switch).
Signed-off-by: Pawan Gupta <pawan.kumar.gupta@linux.intel.com>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
kernel/bpf/core.c | 27 ++++++++++++++++++++++++---
1 file changed, 24 insertions(+), 3 deletions(-)
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 9fc2decac07d..81ba423d0f8e 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -958,8 +958,8 @@ static struct bpf_prog_pack *alloc_new_pack(bpf_jit_fill_hole_t bpf_fill_ill_ins
void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns, bool was_classic)
{
unsigned int nbits = BPF_PROG_SIZE_TO_NBITS(size);
- struct bpf_prog_pack *pack;
- unsigned long pos;
+ struct bpf_prog_pack *pack, *fallback_pack = NULL;
+ unsigned long pos, fallback_pos = 0;
void *ptr = NULL;
mutex_lock(&pack_mutex);
@@ -991,8 +991,29 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns, bool
list_for_each_entry(pack, &pack_list, list) {
pos = bitmap_find_next_zero_area(pack->bitmap, BPF_PROG_CHUNK_COUNT, 0,
nbits, 0);
- if (pos < BPF_PROG_CHUNK_COUNT)
+ if (pos >= BPF_PROG_CHUNK_COUNT)
+ continue;
+ /* Flush not enabled, use any pack */
+ if (!static_branch_unlikely(&bpf_pred_flush_enabled))
goto found_free_area;
+ /*
+ * cBPF reuse of a dirty pack triggers a flush, so prefer a
+ * clean pack for cBPF. eBPF never flushes, so pick the first
+ * free pack, dirty or clean.
+ */
+ if (!was_classic || !pack->arch_flush_needed)
+ goto found_free_area;
+ if (!fallback_pack) {
+ fallback_pack = pack;
+ fallback_pos = pos;
+ }
+ }
+
+ /* No preferred pack found */
+ if (fallback_pack) {
+ pack = fallback_pack;
+ pos = fallback_pos;
+ goto found_free_area;
}
pack = alloc_new_pack(bpf_fill_ill_insns);
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 6.16.y 5/6] bpf: Prefer packs that won't trigger an IBPB flush on allocation
@ 2026-07-14 18:59 ` Pawan Gupta
0 siblings, 0 replies; 17+ messages in thread
From: Pawan Gupta @ 2026-07-14 18:59 UTC (permalink / raw)
To: stable, Greg Kroah-Hartman, Sasha Levin
Cc: bpf, linux-arm-kernel, loongarch, linuxppc-dev, linux-riscv, x86,
Alexei Starovoitov, Daniel Borkmann
commit a9b1f19a6a673ba06820898d0f1ad02883ea1639 upstream.
Currently BPF pack allocator picks the chunks from the first available
pack. While this is okay, it naturally leads to more frequent flushes
when there are multiple packs in the system that weren't used since the
last flush.
As an optimization prefer allocating the new programs from packs that
are unused since last flush. When all packs are dirty, allocation forces
a flush and marks all packs clean.
Below are some future optimizations ideas:
1. Currently, the "dirty" tracking is only done at the pack-level.
Flush frequency can further be reduced with chunk-level tracking.
This requires a new bitmap per-pack to track the dirty state.
2. IBPB flush is done on all CPUs, even if only a single CPU ran the
BPF program. On a system with hundreds of CPUs this could be a
major bottleneck forcing hundreds of IPIs to deliver the flush.
The solution is to track the CPUs where a BPF program ran, and
issue IBPB only on those CPUs.
3. Avoid IBPB when flush is already done at other sources (e.g.
context switch).
Signed-off-by: Pawan Gupta <pawan.kumar.gupta@linux.intel.com>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
kernel/bpf/core.c | 27 ++++++++++++++++++++++++---
1 file changed, 24 insertions(+), 3 deletions(-)
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 9fc2decac07d..81ba423d0f8e 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -958,8 +958,8 @@ static struct bpf_prog_pack *alloc_new_pack(bpf_jit_fill_hole_t bpf_fill_ill_ins
void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns, bool was_classic)
{
unsigned int nbits = BPF_PROG_SIZE_TO_NBITS(size);
- struct bpf_prog_pack *pack;
- unsigned long pos;
+ struct bpf_prog_pack *pack, *fallback_pack = NULL;
+ unsigned long pos, fallback_pos = 0;
void *ptr = NULL;
mutex_lock(&pack_mutex);
@@ -991,8 +991,29 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns, bool
list_for_each_entry(pack, &pack_list, list) {
pos = bitmap_find_next_zero_area(pack->bitmap, BPF_PROG_CHUNK_COUNT, 0,
nbits, 0);
- if (pos < BPF_PROG_CHUNK_COUNT)
+ if (pos >= BPF_PROG_CHUNK_COUNT)
+ continue;
+ /* Flush not enabled, use any pack */
+ if (!static_branch_unlikely(&bpf_pred_flush_enabled))
goto found_free_area;
+ /*
+ * cBPF reuse of a dirty pack triggers a flush, so prefer a
+ * clean pack for cBPF. eBPF never flushes, so pick the first
+ * free pack, dirty or clean.
+ */
+ if (!was_classic || !pack->arch_flush_needed)
+ goto found_free_area;
+ if (!fallback_pack) {
+ fallback_pack = pack;
+ fallback_pos = pos;
+ }
+ }
+
+ /* No preferred pack found */
+ if (fallback_pack) {
+ pack = fallback_pack;
+ pos = fallback_pos;
+ goto found_free_area;
}
pack = alloc_new_pack(bpf_fill_ill_insns);
--
2.43.0
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 6.16.y 6/6] bpf: Prefer dirty packs for eBPF allocations
2026-07-14 18:58 ` Pawan Gupta
@ 2026-07-14 18:59 ` Pawan Gupta
-1 siblings, 0 replies; 17+ messages in thread
From: Pawan Gupta @ 2026-07-14 18:59 UTC (permalink / raw)
To: stable, Greg Kroah-Hartman, Sasha Levin
Cc: bpf, linux-arm-kernel, loongarch, linuxppc-dev, linux-riscv, x86,
Alexei Starovoitov, Daniel Borkmann
commit b72e29e0f7ee329d89f86db8700c8ea99b4a370a upstream.
The pack allocator only flushes predictors when reusing a dirty pack for
cBPF, eBPF allocations never trigger a flush. Currently, eBPF picks the
first free pack, which could be a clean pack. As an optimization, leaving
a clean pack for cBPF can avoid flushes.
Prefer dirty packs for eBPF and keep clean packs free for cBPF. This
mirrors the existing cBPF preference for clean packs: each program kind
prefers the pack that avoids an extra flush, and falls back to the other
kind only when no preferred pack has room. eBPF reuse of a dirty pack is
harmless since eBPF being privileged does not flush.
Signed-off-by: Pawan Gupta <pawan.kumar.gupta@linux.intel.com>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
kernel/bpf/core.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 81ba423d0f8e..0bed7fe9821e 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -998,10 +998,10 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns, bool
goto found_free_area;
/*
* cBPF reuse of a dirty pack triggers a flush, so prefer a
- * clean pack for cBPF. eBPF never flushes, so pick the first
- * free pack, dirty or clean.
+ * clean pack for cBPF. eBPF never flushes, so steer it to a
+ * dirty pack and keep clean packs free for cBPF.
*/
- if (!was_classic || !pack->arch_flush_needed)
+ if (was_classic ^ pack->arch_flush_needed)
goto found_free_area;
if (!fallback_pack) {
fallback_pack = pack;
--
2.43.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 6.16.y 6/6] bpf: Prefer dirty packs for eBPF allocations
@ 2026-07-14 18:59 ` Pawan Gupta
0 siblings, 0 replies; 17+ messages in thread
From: Pawan Gupta @ 2026-07-14 18:59 UTC (permalink / raw)
To: stable, Greg Kroah-Hartman, Sasha Levin
Cc: bpf, linux-arm-kernel, loongarch, linuxppc-dev, linux-riscv, x86,
Alexei Starovoitov, Daniel Borkmann
commit b72e29e0f7ee329d89f86db8700c8ea99b4a370a upstream.
The pack allocator only flushes predictors when reusing a dirty pack for
cBPF, eBPF allocations never trigger a flush. Currently, eBPF picks the
first free pack, which could be a clean pack. As an optimization, leaving
a clean pack for cBPF can avoid flushes.
Prefer dirty packs for eBPF and keep clean packs free for cBPF. This
mirrors the existing cBPF preference for clean packs: each program kind
prefers the pack that avoids an extra flush, and falls back to the other
kind only when no preferred pack has room. eBPF reuse of a dirty pack is
harmless since eBPF being privileged does not flush.
Signed-off-by: Pawan Gupta <pawan.kumar.gupta@linux.intel.com>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
kernel/bpf/core.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 81ba423d0f8e..0bed7fe9821e 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -998,10 +998,10 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns, bool
goto found_free_area;
/*
* cBPF reuse of a dirty pack triggers a flush, so prefer a
- * clean pack for cBPF. eBPF never flushes, so pick the first
- * free pack, dirty or clean.
+ * clean pack for cBPF. eBPF never flushes, so steer it to a
+ * dirty pack and keep clean packs free for cBPF.
*/
- if (!was_classic || !pack->arch_flush_needed)
+ if (was_classic ^ pack->arch_flush_needed)
goto found_free_area;
if (!fallback_pack) {
fallback_pack = pack;
--
2.43.0
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply related [flat|nested] 17+ messages in thread