* [PATCH 6.6.y 0/6] cBPF JIT spray hardening
@ 2026-07-17 17:05 ` Pawan Gupta
0 siblings, 0 replies; 16+ messages in thread
From: Pawan Gupta @ 2026-07-17 17:05 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
---
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/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 | 3 +-
include/linux/filter.h | 15 ++++++--
kernel/bpf/core.c | 68 ++++++++++++++++++++++++++++++++----
kernel/bpf/dispatcher.c | 2 +-
7 files changed, 129 insertions(+), 16 deletions(-)
---
base-commit: da47cbc254661aa66d61ef061485a7080305c4be
change-id: 20260716-cbpf-jit-spray-hardening-6-6-y-ac66e7f4237d
Best regards,
--
Pawan
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 6.6.y 0/6] cBPF JIT spray hardening
@ 2026-07-17 17:05 ` Pawan Gupta
0 siblings, 0 replies; 16+ messages in thread
From: Pawan Gupta @ 2026-07-17 17:05 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
---
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/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 | 3 +-
include/linux/filter.h | 15 ++++++--
kernel/bpf/core.c | 68 ++++++++++++++++++++++++++++++++----
kernel/bpf/dispatcher.c | 2 +-
7 files changed, 129 insertions(+), 16 deletions(-)
---
base-commit: da47cbc254661aa66d61ef061485a7080305c4be
change-id: 20260716-cbpf-jit-spray-hardening-6-6-y-ac66e7f4237d
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] 16+ messages in thread
* [PATCH 6.6.y 1/6] bpf: Support for hardening against JIT spraying
2026-07-17 17:05 ` Pawan Gupta
@ 2026-07-17 17:05 ` Pawan Gupta
-1 siblings, 0 replies; 16+ messages in thread
From: Pawan Gupta @ 2026-07-17 17:05 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 30fe140d4888..72926bc394ad 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>
@@ -1041,6 +1042,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 d708cf3e6207..0d8008711629 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -37,6 +37,7 @@
#include <linux/nospec.h>
#include <linux/bpf_mem_alloc.h>
#include <linux/memcontrol.h>
+#include <linux/static_call.h>
#include <asm/barrier.h>
#include <asm/unaligned.h>
@@ -858,6 +859,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);
@@ -910,6 +920,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) {
@@ -933,6 +951,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] 16+ messages in thread
* [PATCH 6.6.y 1/6] bpf: Support for hardening against JIT spraying
@ 2026-07-17 17:05 ` Pawan Gupta
0 siblings, 0 replies; 16+ messages in thread
From: Pawan Gupta @ 2026-07-17 17:05 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 30fe140d4888..72926bc394ad 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>
@@ -1041,6 +1042,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 d708cf3e6207..0d8008711629 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -37,6 +37,7 @@
#include <linux/nospec.h>
#include <linux/bpf_mem_alloc.h>
#include <linux/memcontrol.h>
+#include <linux/static_call.h>
#include <asm/barrier.h>
#include <asm/unaligned.h>
@@ -858,6 +859,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);
@@ -910,6 +920,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) {
@@ -933,6 +951,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] 16+ messages in thread
* [PATCH 6.6.y 2/6] x86/bugs: Enable IBPB flush on BPF JIT allocation
2026-07-17 17:05 ` Pawan Gupta
@ 2026-07-17 17:06 ` Pawan Gupta
-1 siblings, 0 replies; 16+ messages in thread
From: Pawan Gupta @ 2026-07-17 17:06 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.
[ pawan: Use entry_ibpb() instead of write_ibpb(). JIT hardening enable
moved to spectre_v2_select_mitigation() because there is no
spectre_v2_apply_mitigation()]
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 fb469ace3839..c7d019b0ef4a 100644
--- a/arch/x86/include/asm/nospec-branch.h
+++ b/arch/x86/include/asm/nospec-branch.h
@@ -418,6 +418,10 @@ extern void srso_alias_untrain_ret(void);
extern void entry_untrain_ret(void);
extern void entry_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 ef1d3a5024ed..32a27c19acde 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>
@@ -1360,8 +1361,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"
@@ -1835,8 +1849,7 @@ static void __init bhi_select_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;
@@ -1858,6 +1871,27 @@ static void __init bhi_select_mitigation(void)
pr_info("Spectre BHI mitigation: SW BHB clearing on syscall\n");
}
+#ifdef CONFIG_BPF_JIT
+static void __bpf_arch_ibpb(void *unused)
+{
+ entry_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_select_mitigation(void)
{
enum spectre_v2_mitigation_cmd cmd = spectre_v2_parse_cmdline();
@@ -2041,6 +2075,14 @@ static void __init spectre_v2_select_mitigation(void)
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
+
/* Set up IBPB and STIBP depending on the general spectre V2 command */
spectre_v2_cmd = cmd;
}
@@ -3210,9 +3252,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_LOOP_ON_VMEXIT))
return "; BHI: Vulnerable, KVM: SW loop";
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH 6.6.y 2/6] x86/bugs: Enable IBPB flush on BPF JIT allocation
@ 2026-07-17 17:06 ` Pawan Gupta
0 siblings, 0 replies; 16+ messages in thread
From: Pawan Gupta @ 2026-07-17 17:06 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.
[ pawan: Use entry_ibpb() instead of write_ibpb(). JIT hardening enable
moved to spectre_v2_select_mitigation() because there is no
spectre_v2_apply_mitigation()]
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 fb469ace3839..c7d019b0ef4a 100644
--- a/arch/x86/include/asm/nospec-branch.h
+++ b/arch/x86/include/asm/nospec-branch.h
@@ -418,6 +418,10 @@ extern void srso_alias_untrain_ret(void);
extern void entry_untrain_ret(void);
extern void entry_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 ef1d3a5024ed..32a27c19acde 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>
@@ -1360,8 +1361,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"
@@ -1835,8 +1849,7 @@ static void __init bhi_select_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;
@@ -1858,6 +1871,27 @@ static void __init bhi_select_mitigation(void)
pr_info("Spectre BHI mitigation: SW BHB clearing on syscall\n");
}
+#ifdef CONFIG_BPF_JIT
+static void __bpf_arch_ibpb(void *unused)
+{
+ entry_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_select_mitigation(void)
{
enum spectre_v2_mitigation_cmd cmd = spectre_v2_parse_cmdline();
@@ -2041,6 +2075,14 @@ static void __init spectre_v2_select_mitigation(void)
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
+
/* Set up IBPB and STIBP depending on the general spectre V2 command */
spectre_v2_cmd = cmd;
}
@@ -3210,9 +3252,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_LOOP_ON_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] 16+ messages in thread
* [PATCH 6.6.y 3/6] bpf: Restrict JIT predictor flush to cBPF
2026-07-17 17:05 ` Pawan Gupta
@ 2026-07-17 17:06 ` Pawan Gupta
-1 siblings, 0 replies; 16+ messages in thread
From: Pawan Gupta @ 2026-07-17 17:06 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 dropped "was_classic" hunk for arches that do not
support pack allocator]
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/riscv/net/bpf_jit_core.c | 3 ++-
arch/x86/net/bpf_jit_comp.c | 3 ++-
include/linux/filter.h | 5 +++--
kernel/bpf/core.c | 13 ++++++++-----
kernel/bpf/dispatcher.c | 2 +-
5 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/arch/riscv/net/bpf_jit_core.c b/arch/riscv/net/bpf_jit_core.c
index 7b70ccb7fec3..8d8c5eb2e8da 100644
--- a/arch/riscv/net/bpf_jit_core.c
+++ b/arch/riscv/net/bpf_jit_core.c
@@ -123,7 +123,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 0be138fbd0a0..8028a5a4ab64 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -2927,7 +2927,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 72926bc394ad..f2e351cc543d 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1065,7 +1065,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(struct bpf_binary_header *hdr);
static inline bool bpf_prog_kallsyms_verify_off(const struct bpf_prog *fp)
@@ -1079,7 +1079,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_prog *prog,
struct bpf_binary_header *ro_header,
struct bpf_binary_header *rw_header);
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 0d8008711629..509f1b9c2b2e 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -911,7 +911,7 @@ static struct bpf_prog_pack *alloc_new_pack(bpf_jit_fill_hole_t bpf_fill_ill_ins
return pack;
}
-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;
@@ -926,7 +926,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);
@@ -951,7 +951,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);
@@ -1111,7 +1113,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;
@@ -1124,7 +1127,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 fa3e9225aedc..b3f164e31c6b 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] 16+ messages in thread
* [PATCH 6.6.y 3/6] bpf: Restrict JIT predictor flush to cBPF
@ 2026-07-17 17:06 ` Pawan Gupta
0 siblings, 0 replies; 16+ messages in thread
From: Pawan Gupta @ 2026-07-17 17:06 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 dropped "was_classic" hunk for arches that do not
support pack allocator]
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/riscv/net/bpf_jit_core.c | 3 ++-
arch/x86/net/bpf_jit_comp.c | 3 ++-
include/linux/filter.h | 5 +++--
kernel/bpf/core.c | 13 ++++++++-----
kernel/bpf/dispatcher.c | 2 +-
5 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/arch/riscv/net/bpf_jit_core.c b/arch/riscv/net/bpf_jit_core.c
index 7b70ccb7fec3..8d8c5eb2e8da 100644
--- a/arch/riscv/net/bpf_jit_core.c
+++ b/arch/riscv/net/bpf_jit_core.c
@@ -123,7 +123,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 0be138fbd0a0..8028a5a4ab64 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -2927,7 +2927,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 72926bc394ad..f2e351cc543d 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1065,7 +1065,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(struct bpf_binary_header *hdr);
static inline bool bpf_prog_kallsyms_verify_off(const struct bpf_prog *fp)
@@ -1079,7 +1079,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_prog *prog,
struct bpf_binary_header *ro_header,
struct bpf_binary_header *rw_header);
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 0d8008711629..509f1b9c2b2e 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -911,7 +911,7 @@ static struct bpf_prog_pack *alloc_new_pack(bpf_jit_fill_hole_t bpf_fill_ill_ins
return pack;
}
-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;
@@ -926,7 +926,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);
@@ -951,7 +951,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);
@@ -1111,7 +1113,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;
@@ -1124,7 +1127,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 fa3e9225aedc..b3f164e31c6b 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] 16+ messages in thread
* [PATCH 6.6.y 4/6] bpf: Skip redundant IBPB in pack allocator
2026-07-17 17:05 ` Pawan Gupta
@ 2026-07-17 17:06 ` Pawan Gupta
-1 siblings, 0 replies; 16+ messages in thread
From: Pawan Gupta @ 2026-07-17 17:06 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 509f1b9c2b2e..a2e5684d6239 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -851,6 +851,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[];
};
@@ -906,6 +907,8 @@ static struct bpf_prog_pack *alloc_new_pack(bpf_jit_fill_hole_t bpf_fill_ill_ins
bitmap_zero(pack->bitmap, BPF_PROG_PACK_SIZE / BPF_PROG_CHUNK_SIZE);
list_add_tail(&pack->list, &pack_list);
+ if (static_branch_unlikely(&bpf_pred_flush_enabled))
+ pack->arch_flush_needed = true;
set_vm_flush_reset_perms(pack->ptr);
set_memory_rox((unsigned long)pack->ptr, BPF_PROG_PACK_SIZE / PAGE_SIZE);
return pack;
@@ -952,8 +955,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);
@@ -991,6 +1001,9 @@ void bpf_prog_pack_free(struct bpf_binary_header *hdr)
"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] 16+ messages in thread
* [PATCH 6.6.y 4/6] bpf: Skip redundant IBPB in pack allocator
@ 2026-07-17 17:06 ` Pawan Gupta
0 siblings, 0 replies; 16+ messages in thread
From: Pawan Gupta @ 2026-07-17 17:06 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 509f1b9c2b2e..a2e5684d6239 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -851,6 +851,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[];
};
@@ -906,6 +907,8 @@ static struct bpf_prog_pack *alloc_new_pack(bpf_jit_fill_hole_t bpf_fill_ill_ins
bitmap_zero(pack->bitmap, BPF_PROG_PACK_SIZE / BPF_PROG_CHUNK_SIZE);
list_add_tail(&pack->list, &pack_list);
+ if (static_branch_unlikely(&bpf_pred_flush_enabled))
+ pack->arch_flush_needed = true;
set_vm_flush_reset_perms(pack->ptr);
set_memory_rox((unsigned long)pack->ptr, BPF_PROG_PACK_SIZE / PAGE_SIZE);
return pack;
@@ -952,8 +955,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);
@@ -991,6 +1001,9 @@ void bpf_prog_pack_free(struct bpf_binary_header *hdr)
"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] 16+ messages in thread
* [PATCH 6.6.y 5/6] bpf: Prefer packs that won't trigger an IBPB flush on allocation
2026-07-17 17:05 ` Pawan Gupta
@ 2026-07-17 17:06 ` Pawan Gupta
-1 siblings, 0 replies; 16+ messages in thread
From: Pawan Gupta @ 2026-07-17 17:06 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 a2e5684d6239..7dc59570cd25 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -917,8 +917,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);
@@ -943,8 +943,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] 16+ messages in thread
* [PATCH 6.6.y 5/6] bpf: Prefer packs that won't trigger an IBPB flush on allocation
@ 2026-07-17 17:06 ` Pawan Gupta
0 siblings, 0 replies; 16+ messages in thread
From: Pawan Gupta @ 2026-07-17 17:06 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 a2e5684d6239..7dc59570cd25 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -917,8 +917,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);
@@ -943,8 +943,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] 16+ messages in thread
* [PATCH 6.6.y 6/6] bpf: Prefer dirty packs for eBPF allocations
2026-07-17 17:05 ` Pawan Gupta
@ 2026-07-17 17:07 ` Pawan Gupta
-1 siblings, 0 replies; 16+ messages in thread
From: Pawan Gupta @ 2026-07-17 17:07 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 7dc59570cd25..4669a57265ad 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -950,10 +950,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] 16+ messages in thread
* [PATCH 6.6.y 6/6] bpf: Prefer dirty packs for eBPF allocations
@ 2026-07-17 17:07 ` Pawan Gupta
0 siblings, 0 replies; 16+ messages in thread
From: Pawan Gupta @ 2026-07-17 17:07 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 7dc59570cd25..4669a57265ad 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -950,10 +950,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] 16+ messages in thread
* Re: [PATCH 6.6.y 3/6] bpf: Restrict JIT predictor flush to cBPF
2026-07-17 17:06 ` Pawan Gupta
(?)
@ 2026-07-17 17:20 ` sashiko-bot
-1 siblings, 0 replies; 16+ messages in thread
From: sashiko-bot @ 2026-07-17 17:20 UTC (permalink / raw)
To: Pawan Gupta; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Spectre v2 mitigation bypass in BPF JIT allocator due to restricted predictor flush.
--
--- Patch [3]: [PATCH 6.6.y 3/6] bpf: Restrict JIT predictor flush to cBPF ---
commit 8a65f5d94c81da9af1257fa2e8b48ff8c73c9963
Author: Pawan Gupta <pawan.kumar.gupta@linux.intel.com>
bpf: Restrict JIT predictor flush to cBPF
This commit restricts the predictor flush on memory reuse for BPF JIT
allocations to only cBPF programs. It adds a was_classic parameter to
bpf_prog_pack_alloc to determine if the flush is necessary, assuming eBPF
allocations do not require the overhead of flushing predictors on reuse.
> diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
> index 0d8008711629c..509f1b9c2b2e2 100644
> --- a/kernel/bpf/core.c
> +++ b/kernel/bpf/core.c
[ ... ]
> @@ -951,7 +951,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 skipping the flush for eBPF allocations leave the branch predictor
vulnerable if the memory was previously used by a cBPF program?
If an attacker loads a cBPF program, trains the CPU branch predictor to
target a specific offset, and then frees it, could a subsequent eBPF program
allocated in the same memory chunk skip the flush because was_classic is
false? This might allow an attacker to trigger a kernel indirect branch that
mispredicts into the newly loaded eBPF program at the trained offset.
Also, if kernel.unprivileged_bpf_disabled=0, couldn't an attacker directly
use unprivileged eBPF programs to bypass the flush completely, since their
type is not BPF_PROG_TYPE_UNSPEC?
> bitmap_set(pack->bitmap, pos, nbits);
> ptr = (void *)(pack->ptr) + (pos << BPF_PROG_CHUNK_SHIFT);
[ ... ]
> diff --git a/kernel/bpf/dispatcher.c b/kernel/bpf/dispatcher.c
> index fa3e9225aedc0..b3f164e31c6bb 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);
[Severity: High]
Similarly, does explicitly passing false here for the dispatcher skip the
flush, leaving it vulnerable to unintentional gadget exploitation via
leftover branch predictions?
> if (!d->image)
> goto out;
> d->rw_image = bpf_jit_alloc_exec(PAGE_SIZE);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260717-cbpf-jit-spray-hardening-6-6-y-v1-0-e04f1b2893de@linux.intel.com?part=3
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH 6.6.y 6/6] bpf: Prefer dirty packs for eBPF allocations
2026-07-17 17:07 ` Pawan Gupta
(?)
@ 2026-07-17 17:31 ` sashiko-bot
-1 siblings, 0 replies; 16+ messages in thread
From: sashiko-bot @ 2026-07-17 17:31 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 patch actively steers eBPF programs into dirty packs without flushing the branch predictor, leaving privileged eBPF vulnerable to Cross-Domain BTB Poisoning and allowing unprivileged eBPF to preserve BTB pre-training.
--
--- Patch [6]: [PATCH 6.6.y 6/6] bpf: Prefer dirty packs for eBPF allocations ---
commit b2416181326f994ad8a8ff6f867915a656a73187
Author: Pawan Gupta <pawan.kumar.gupta@linux.intel.com>
bpf: Prefer dirty packs for eBPF allocations
This commit modifies bpf_prog_pack_alloc() to prefer dirty packs for eBPF
allocations. It steers eBPF programs into dirty packs and keeps clean packs
free for cBPF to reduce branch predictor flushes.
> diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
> index 7dc59570cd25a..4669a57265ad7 100644
> --- a/kernel/bpf/core.c
> +++ b/kernel/bpf/core.c
> @@ -950,10 +950,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;
[Severity: High]
Does steering eBPF programs into dirty packs without flushing the branch
predictor expose them to Cross-Domain BTB Poisoning?
If an attacker loads a cBPF program to train the branch predictor and then
frees it, the pack becomes dirty. When an eBPF program is subsequently loaded,
this logic steers it directly into the dirty pack.
Because the flush condition later in bpf_prog_pack_alloc() only triggers when
was_classic is true:
/* Flush only for cBPF as it may contain a crafted gadget */
if (static_branch_unlikely(&bpf_pred_flush_enabled) &&
pack->arch_flush_needed &&
was_classic) {
...
}
Could this bypass the IBPB flush for eBPF allocations, leaving pre-trained
malicious BTB predictions intact for an attacker to exploit?
> if (!fallback_pack) {
> fallback_pack = pack;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260717-cbpf-jit-spray-hardening-6-6-y-v1-0-e04f1b2893de@linux.intel.com?part=6
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-07-17 17:31 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-17 17:05 [PATCH 6.6.y 0/6] cBPF JIT spray hardening Pawan Gupta
2026-07-17 17:05 ` Pawan Gupta
2026-07-17 17:05 ` [PATCH 6.6.y 1/6] bpf: Support for hardening against JIT spraying Pawan Gupta
2026-07-17 17:05 ` Pawan Gupta
2026-07-17 17:06 ` [PATCH 6.6.y 2/6] x86/bugs: Enable IBPB flush on BPF JIT allocation Pawan Gupta
2026-07-17 17:06 ` Pawan Gupta
2026-07-17 17:06 ` [PATCH 6.6.y 3/6] bpf: Restrict JIT predictor flush to cBPF Pawan Gupta
2026-07-17 17:06 ` Pawan Gupta
2026-07-17 17:20 ` sashiko-bot
2026-07-17 17:06 ` [PATCH 6.6.y 4/6] bpf: Skip redundant IBPB in pack allocator Pawan Gupta
2026-07-17 17:06 ` Pawan Gupta
2026-07-17 17:06 ` [PATCH 6.6.y 5/6] bpf: Prefer packs that won't trigger an IBPB flush on allocation Pawan Gupta
2026-07-17 17:06 ` Pawan Gupta
2026-07-17 17:07 ` [PATCH 6.6.y 6/6] bpf: Prefer dirty packs for eBPF allocations Pawan Gupta
2026-07-17 17:07 ` Pawan Gupta
2026-07-17 17:31 ` sashiko-bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.