* [PATCH bpf-next v2 0/5] bpf, x86: enable EXECMEM_ROX_CACHE for BPF allocations
@ 2026-07-11 9:43 Mike Rapoport (Microsoft)
2026-07-11 9:43 ` [PATCH bpf-next v2 1/5] bpf: dispatcher: allocate bpf_dispatcher->rw_image with vmalloc() Mike Rapoport (Microsoft)
` (4 more replies)
0 siblings, 5 replies; 13+ messages in thread
From: Mike Rapoport (Microsoft) @ 2026-07-11 9:43 UTC (permalink / raw)
To: Alexei Starovoitov, Andrii Nakryiko, Andy Lutomirski,
Borislav Petkov, Daniel Borkmann, Dave Hansen, Eduard Zingerman,
Ingo Molnar, Kumar Kartikeya Dwivedi, Peter Zijlstra, Song Liu,
Thomas Gleixner
Cc: Emil Tsalapatis, Jiri Olsa, John Fastabend, Martin KaFai Lau,
Mike Rapoport, H. Peter Anvin, Yonghong Song, bpf, linux-kernel,
x86
Hi,
BPF allocations of executable memory on x86 are essentially read-only. Most
paths that call bpf_jit_alloc_exec() immediately make it ROX with
set_memory_rox().
The code generation, at least on x86, uses separately allocated writable
buffers and then updates the actual text memory with text_poke().
These patches do several small adjustments to how BPF allocates executable
memory and enable EXECMEM_ROX_CACHE for BPF allocations on x86.
---
v2 changes:
* rebased on the current bpf-next
v1: https://patch.msgid.link/20260626-execmem-x86-rox-bpf-v0-v1-0-45a0b0ed4fe9@kernel.org
---
Mike Rapoport (Microsoft) (5):
bpf: dispatcher: allocate bpf_dispatcher->rw_image with vmalloc()
bpf: drop __weak from bpf_jit_alloc_exec() and bpf_jit_free_exec()
bpf: alloc_prog_pack(): skip ROX management for already ROX memory
bpf, x86: make sure allocation in arch_bpf_trampoline_size() is writable
x86/bpf: enable EXECMEM_ROX_CACHE for BPF allocations
arch/x86/mm/init.c | 4 ++--
arch/x86/net/bpf_jit_comp.c | 5 ++---
include/linux/filter.h | 1 +
kernel/bpf/core.c | 30 +++++++++++++++++++++---------
kernel/bpf/dispatcher.c | 5 ++++-
5 files changed, 30 insertions(+), 15 deletions(-)
---
base-commit: 520d7d7942025a58a0c79a61b905a556cf3cb524
change-id: 20260626-execmem-x86-rox-bpf-v0-b4241ade80df
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH bpf-next v2 1/5] bpf: dispatcher: allocate bpf_dispatcher->rw_image with vmalloc()
2026-07-11 9:43 [PATCH bpf-next v2 0/5] bpf, x86: enable EXECMEM_ROX_CACHE for BPF allocations Mike Rapoport (Microsoft)
@ 2026-07-11 9:43 ` Mike Rapoport (Microsoft)
2026-07-11 9:55 ` sashiko-bot
2026-07-11 9:43 ` [PATCH bpf-next v2 2/5] bpf: drop __weak from bpf_jit_alloc_exec() and bpf_jit_free_exec() Mike Rapoport (Microsoft)
` (3 subsequent siblings)
4 siblings, 1 reply; 13+ messages in thread
From: Mike Rapoport (Microsoft) @ 2026-07-11 9:43 UTC (permalink / raw)
To: Alexei Starovoitov, Andrii Nakryiko, Andy Lutomirski,
Borislav Petkov, Daniel Borkmann, Dave Hansen, Eduard Zingerman,
Ingo Molnar, Kumar Kartikeya Dwivedi, Peter Zijlstra, Song Liu,
Thomas Gleixner
Cc: Emil Tsalapatis, Jiri Olsa, John Fastabend, Martin KaFai Lau,
Mike Rapoport, H. Peter Anvin, Yonghong Song, bpf, linux-kernel,
x86
bpf_dispatcher->rw_image is a temporary writable buffer that
arch_prepare_bpf_dispatcher() fills and then copies into
bpf_dispatcher->image using bpf_arch_text_copy().
The rel32 offsets emitted by emit_bpf_dispatcher() are calculated against
->image, so ->rw_image does not need to live in the module address range.
Allocate ->rw_image with vmalloc() to avoid permissions dance when
EXECMEM_BPF will be backed by ROX caches.
Also saves a bit of space in the more scarce module address space.
Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
kernel/bpf/dispatcher.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/kernel/bpf/dispatcher.c b/kernel/bpf/dispatcher.c
index ea2d60dc1fee..d85b3e8ad2e4 100644
--- a/kernel/bpf/dispatcher.c
+++ b/kernel/bpf/dispatcher.c
@@ -148,7 +148,10 @@ void bpf_dispatcher_change_prog(struct bpf_dispatcher *d, struct bpf_prog *from,
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);
+ /* d->rw_image doesn't need to be in module memory range, so we
+ * can use vmalloc.
+ */
+ d->rw_image = vmalloc(PAGE_SIZE);
if (!d->rw_image) {
bpf_prog_pack_free(d->image, PAGE_SIZE);
d->image = NULL;
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH bpf-next v2 2/5] bpf: drop __weak from bpf_jit_alloc_exec() and bpf_jit_free_exec()
2026-07-11 9:43 [PATCH bpf-next v2 0/5] bpf, x86: enable EXECMEM_ROX_CACHE for BPF allocations Mike Rapoport (Microsoft)
2026-07-11 9:43 ` [PATCH bpf-next v2 1/5] bpf: dispatcher: allocate bpf_dispatcher->rw_image with vmalloc() Mike Rapoport (Microsoft)
@ 2026-07-11 9:43 ` Mike Rapoport (Microsoft)
2026-07-11 9:43 ` [PATCH bpf-next v2 3/5] bpf: alloc_prog_pack(): skip ROX management for already ROX memory Mike Rapoport (Microsoft)
` (2 subsequent siblings)
4 siblings, 0 replies; 13+ messages in thread
From: Mike Rapoport (Microsoft) @ 2026-07-11 9:43 UTC (permalink / raw)
To: Alexei Starovoitov, Andrii Nakryiko, Andy Lutomirski,
Borislav Petkov, Daniel Borkmann, Dave Hansen, Eduard Zingerman,
Ingo Molnar, Kumar Kartikeya Dwivedi, Peter Zijlstra, Song Liu,
Thomas Gleixner
Cc: Emil Tsalapatis, Jiri Olsa, John Fastabend, Martin KaFai Lau,
Mike Rapoport, H. Peter Anvin, Yonghong Song, bpf, linux-kernel,
x86
bpf_jit_alloc_exec() and bpf_jit_free_exec() are wrappers for the
corresponding execmem APIs.
Architectures define the properties of the memory range needed by BPF in
their initialization of execmem and don't need to override neither of
them.
Drop the __weak qualifier from bpf_jit_alloc_exec() and
bpf_jit_free_exec().
Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
kernel/bpf/core.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 47fe047ad30b..fc75625dc951 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -1116,12 +1116,12 @@ void bpf_jit_uncharge_modmem(u32 size)
atomic_long_sub(size, &bpf_jit_current);
}
-void *__weak bpf_jit_alloc_exec(unsigned long size)
+void *bpf_jit_alloc_exec(unsigned long size)
{
return execmem_alloc(EXECMEM_BPF, size);
}
-void __weak bpf_jit_free_exec(void *addr)
+void bpf_jit_free_exec(void *addr)
{
execmem_free(addr);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH bpf-next v2 3/5] bpf: alloc_prog_pack(): skip ROX management for already ROX memory
2026-07-11 9:43 [PATCH bpf-next v2 0/5] bpf, x86: enable EXECMEM_ROX_CACHE for BPF allocations Mike Rapoport (Microsoft)
2026-07-11 9:43 ` [PATCH bpf-next v2 1/5] bpf: dispatcher: allocate bpf_dispatcher->rw_image with vmalloc() Mike Rapoport (Microsoft)
2026-07-11 9:43 ` [PATCH bpf-next v2 2/5] bpf: drop __weak from bpf_jit_alloc_exec() and bpf_jit_free_exec() Mike Rapoport (Microsoft)
@ 2026-07-11 9:43 ` Mike Rapoport (Microsoft)
2026-07-11 10:00 ` sashiko-bot
2026-07-11 9:43 ` [PATCH bpf-next v2 4/5] bpf, x86: make sure allocation in arch_bpf_trampoline_size() is writable Mike Rapoport (Microsoft)
2026-07-11 9:43 ` [PATCH bpf-next v2 5/5] x86/bpf: enable EXECMEM_ROX_CACHE for BPF allocations Mike Rapoport (Microsoft)
4 siblings, 1 reply; 13+ messages in thread
From: Mike Rapoport (Microsoft) @ 2026-07-11 9:43 UTC (permalink / raw)
To: Alexei Starovoitov, Andrii Nakryiko, Andy Lutomirski,
Borislav Petkov, Daniel Borkmann, Dave Hansen, Eduard Zingerman,
Ingo Molnar, Kumar Kartikeya Dwivedi, Peter Zijlstra, Song Liu,
Thomas Gleixner
Cc: Emil Tsalapatis, Jiri Olsa, John Fastabend, Martin KaFai Lau,
Mike Rapoport, H. Peter Anvin, Yonghong Song, bpf, linux-kernel,
x86
execmem_alloc() can return ROX memory that is already filled with
architecture defined trapping instructions.
In preparation for enabling this mode for BPF on x86, make sure that there
is no redundant management of the ROX memory.
There is no need to fill allocated memory with trapping instructions, to
request permissions reset on free and to set ROX permissions as this all
is handled by execmem_alloc().
Add bpf_jit_mem_is_rox() wrapper for execmem_is_rox(), use it to check if
execmem_alloc() returns ROX memory and skip the redundant steps in that
case.
Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
kernel/bpf/core.c | 21 ++++++++++++++-------
1 file changed, 14 insertions(+), 7 deletions(-)
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index fc75625dc951..1b89c18cf246 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -916,6 +916,11 @@ static LIST_HEAD(pack_list);
#define BPF_PROG_CHUNK_COUNT (BPF_PROG_PACK_SIZE / BPF_PROG_CHUNK_SIZE)
+static bool bpf_jit_mem_is_rox(void)
+{
+ return execmem_is_rox(EXECMEM_BPF);
+}
+
static struct bpf_prog_pack *alloc_new_pack(bpf_jit_fill_hole_t bpf_fill_ill_insns)
{
struct bpf_prog_pack *pack;
@@ -927,16 +932,18 @@ static struct bpf_prog_pack *alloc_new_pack(bpf_jit_fill_hole_t bpf_fill_ill_ins
pack->ptr = bpf_jit_alloc_exec(BPF_PROG_PACK_SIZE);
if (!pack->ptr)
goto out;
- 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);
- if (err)
- goto out;
+ if (!bpf_jit_mem_is_rox()) {
+ bpf_fill_ill_insns(pack->ptr, BPF_PROG_PACK_SIZE);
+ set_vm_flush_reset_perms(pack->ptr);
+ err = set_memory_rox((unsigned long)pack->ptr,
+ BPF_PROG_PACK_SIZE / PAGE_SIZE);
+ if (err)
+ goto out;
+ }
list_add_tail(&pack->list, &pack_list);
return pack;
@@ -965,7 +972,7 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns, bool
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) {
+ if (ptr && !bpf_jit_mem_is_rox()) {
int err;
bpf_fill_ill_insns(ptr, size);
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH bpf-next v2 4/5] bpf, x86: make sure allocation in arch_bpf_trampoline_size() is writable
2026-07-11 9:43 [PATCH bpf-next v2 0/5] bpf, x86: enable EXECMEM_ROX_CACHE for BPF allocations Mike Rapoport (Microsoft)
` (2 preceding siblings ...)
2026-07-11 9:43 ` [PATCH bpf-next v2 3/5] bpf: alloc_prog_pack(): skip ROX management for already ROX memory Mike Rapoport (Microsoft)
@ 2026-07-11 9:43 ` Mike Rapoport (Microsoft)
2026-07-11 10:02 ` sashiko-bot
2026-07-11 9:43 ` [PATCH bpf-next v2 5/5] x86/bpf: enable EXECMEM_ROX_CACHE for BPF allocations Mike Rapoport (Microsoft)
4 siblings, 1 reply; 13+ messages in thread
From: Mike Rapoport (Microsoft) @ 2026-07-11 9:43 UTC (permalink / raw)
To: Alexei Starovoitov, Andrii Nakryiko, Andy Lutomirski,
Borislav Petkov, Daniel Borkmann, Dave Hansen, Eduard Zingerman,
Ingo Molnar, Kumar Kartikeya Dwivedi, Peter Zijlstra, Song Liu,
Thomas Gleixner
Cc: Emil Tsalapatis, Jiri Olsa, John Fastabend, Martin KaFai Lau,
Mike Rapoport, H. Peter Anvin, Yonghong Song, bpf, linux-kernel,
x86
arch_bpf_trampoline_size() allocates a buffer to get actual size required
for a trampoline.
This buffer must be in the module address space because
__arch_prepare_bpf_trampoline() calculates rel32 offsets relatively to
that buffer.
In preparation for enabling ROX mode for EXECMEM_BPF make sure that the
allocated memory is writable.
Add bpf_jit_alloc_exec_rw() wrapper for execmem_alloc_rw() and use it for
buffer allocation in arch_bpf_trampoline_size().
Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
arch/x86/net/bpf_jit_comp.c | 5 ++---
include/linux/filter.h | 1 +
kernel/bpf/core.c | 5 +++++
3 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index de7515ea1bea..b2feec81e231 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -3703,13 +3703,12 @@ int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags,
int ret;
/* Allocate a temporary buffer for __arch_prepare_bpf_trampoline().
- * This will NOT cause fragmentation in direct map, as we do not
- * call set_memory_*() on this buffer.
*
* We cannot use kvmalloc here, because we need image to be in
* module memory range.
+ * Since it must be writable use bpf_jit_alloc_exec_rw().
*/
- image = bpf_jit_alloc_exec(PAGE_SIZE);
+ image = bpf_jit_alloc_exec_rw(PAGE_SIZE);
if (!image)
return -ENOMEM;
diff --git a/include/linux/filter.h b/include/linux/filter.h
index 14acb2455746..32d5297c557e 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1333,6 +1333,7 @@ bpf_jit_binary_alloc(unsigned int proglen, u8 **image_ptr,
void bpf_jit_binary_free(struct bpf_binary_header *hdr);
u64 bpf_jit_alloc_exec_limit(void);
void *bpf_jit_alloc_exec(unsigned long size);
+void *bpf_jit_alloc_exec_rw(unsigned long size);
void bpf_jit_free_exec(void *addr);
void bpf_jit_free(struct bpf_prog *fp);
struct bpf_binary_header *
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 1b89c18cf246..e2076667b245 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -1128,6 +1128,11 @@ void *bpf_jit_alloc_exec(unsigned long size)
return execmem_alloc(EXECMEM_BPF, size);
}
+void *bpf_jit_alloc_exec_rw(unsigned long size)
+{
+ return execmem_alloc_rw(EXECMEM_BPF, size);
+}
+
void bpf_jit_free_exec(void *addr)
{
execmem_free(addr);
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH bpf-next v2 5/5] x86/bpf: enable EXECMEM_ROX_CACHE for BPF allocations
2026-07-11 9:43 [PATCH bpf-next v2 0/5] bpf, x86: enable EXECMEM_ROX_CACHE for BPF allocations Mike Rapoport (Microsoft)
` (3 preceding siblings ...)
2026-07-11 9:43 ` [PATCH bpf-next v2 4/5] bpf, x86: make sure allocation in arch_bpf_trampoline_size() is writable Mike Rapoport (Microsoft)
@ 2026-07-11 9:43 ` Mike Rapoport (Microsoft)
2026-07-11 9:59 ` sashiko-bot
4 siblings, 1 reply; 13+ messages in thread
From: Mike Rapoport (Microsoft) @ 2026-07-11 9:43 UTC (permalink / raw)
To: Alexei Starovoitov, Andrii Nakryiko, Andy Lutomirski,
Borislav Petkov, Daniel Borkmann, Dave Hansen, Eduard Zingerman,
Ingo Molnar, Kumar Kartikeya Dwivedi, Peter Zijlstra, Song Liu,
Thomas Gleixner
Cc: Emil Tsalapatis, Jiri Olsa, John Fastabend, Martin KaFai Lau,
Mike Rapoport, H. Peter Anvin, Yonghong Song, bpf, linux-kernel,
x86
BPF core and x86 JIT use text poking and temporary writable buffers and
thus can handle ROX memory.
Enable ROX cache for EXECMEM_BPF when configuration and CPU features allow
that.
Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
arch/x86/mm/init.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/arch/x86/mm/init.c b/arch/x86/mm/init.c
index fb67217fddcd..079f8c7e9e3c 100644
--- a/arch/x86/mm/init.c
+++ b/arch/x86/mm/init.c
@@ -1107,10 +1107,10 @@ struct execmem_info __init *execmem_arch_setup(void)
.alignment = MODULE_ALIGN,
},
[EXECMEM_BPF] = {
- .flags = EXECMEM_KASAN_SHADOW,
+ .flags = flags,
.start = start,
.end = MODULES_END,
- .pgprot = PAGE_KERNEL,
+ .pgprot = pgprot,
.alignment = MODULE_ALIGN,
},
[EXECMEM_MODULE_DATA] = {
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next v2 1/5] bpf: dispatcher: allocate bpf_dispatcher->rw_image with vmalloc()
2026-07-11 9:43 ` [PATCH bpf-next v2 1/5] bpf: dispatcher: allocate bpf_dispatcher->rw_image with vmalloc() Mike Rapoport (Microsoft)
@ 2026-07-11 9:55 ` sashiko-bot
2026-07-11 16:28 ` Mike Rapoport
0 siblings, 1 reply; 13+ messages in thread
From: sashiko-bot @ 2026-07-11 9:55 UTC (permalink / raw)
To: Mike Rapoport (Microsoft); +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Uninitialized kernel memory is copied into an executable BPF dispatcher region, leaking data and creating unintended ROP/JOP gadgets.
--
--- Patch [1]: [PATCH bpf-next v2 1/5] bpf: dispatcher: allocate bpf_dispatcher->rw_image with vmalloc() ---
commit 12eba9f58f961b936cd08cfa17b30375eb214d2a
Author: Mike Rapoport (Microsoft) <rppt@kernel.org>
bpf: dispatcher: allocate bpf_dispatcher->rw_image with vmalloc()
This commit changes bpf_dispatcher->rw_image allocation to use vmalloc()
instead of bpf_jit_alloc_exec() since the rel32 offsets are calculated
against the final image, removing the need for the temporary buffer to live
in the module address range.
> diff --git a/kernel/bpf/dispatcher.c b/kernel/bpf/dispatcher.c
> index ea2d60dc1feeb..d85b3e8ad2e4e 100644
> --- a/kernel/bpf/dispatcher.c
> +++ b/kernel/bpf/dispatcher.c
> @@ -148,7 +148,10 @@ void bpf_dispatcher_change_prog(struct bpf_dispatcher *d, struct bpf_prog *from,
> 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);
> + /* d->rw_image doesn't need to be in module memory range, so we
> + * can use vmalloc.
> + */
> + d->rw_image = vmalloc(PAGE_SIZE);
[Severity: High]
This isn't a bug introduced by this patch, but since we are changing the
allocation here, does this code leak uninitialized kernel memory into the
executable dispatcher image?
vmalloc() does not zero-initialize the memory, and neither did the previous
bpf_jit_alloc_exec() call. When the architecture-specific code generates the
dispatcher instructions into this buffer, it only writes a few hundred bytes.
Later, bpf_dispatcher_update() copies exactly 2048 bytes from this partially
uninitialized temporary buffer into the executable RO+X dispatcher image:
bpf_dispatcher_update()
...
bpf_arch_text_copy(new, tmp, PAGE_SIZE / 2);
Could this overwrite the safe int3 trap instructions placed by
bpf_prog_pack_alloc() with random bytes from the uninitialized kernel memory,
potentially creating unintended ROP/JOP gadgets? Would it be safer to use
vzalloc() here instead?
> if (!d->rw_image) {
> bpf_prog_pack_free(d->image, PAGE_SIZE);
> d->image = NULL;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260711-execmem-x86-rox-bpf-v0-v2-0-bfd956d35119@kernel.org?part=1
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next v2 5/5] x86/bpf: enable EXECMEM_ROX_CACHE for BPF allocations
2026-07-11 9:43 ` [PATCH bpf-next v2 5/5] x86/bpf: enable EXECMEM_ROX_CACHE for BPF allocations Mike Rapoport (Microsoft)
@ 2026-07-11 9:59 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-07-11 9:59 UTC (permalink / raw)
To: Mike Rapoport (Microsoft); +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Uninitialized kernel memory from vmalloc/kvmalloc is copied into executable ROX BPF images (dispatcher and trampoline), causing an information leak and a wild execution risk.
--
--- Patch [5]: [PATCH bpf-next v2 5/5] x86/bpf: enable EXECMEM_ROX_CACHE for BPF allocations ---
commit 12eba9f58f961b936cd08cfa17b30375eb214d2a
Author: Mike Rapoport (Microsoft) <rppt@kernel.org>
bpf: dispatcher: allocate bpf_dispatcher->rw_image with vmalloc()
This commit changes the allocation of bpf_dispatcher->rw_image to use
vmalloc() instead of bpf_jit_alloc_exec(), avoiding a permissions dance
when EXECMEM_BPF is backed by ROX caches. It relies on the fact that rel32
offsets are calculated against the actual ROX image, so the temporary
buffer does not need to reside in the module address range.
> diff --git a/kernel/bpf/dispatcher.c b/kernel/bpf/dispatcher.c
> index ea2d60dc1feeb..d85b3e8ad2e4e 100644
> --- a/kernel/bpf/dispatcher.c
> +++ b/kernel/bpf/dispatcher.c
> @@ -148,7 +148,10 @@ void bpf_dispatcher_change_prog(struct bpf_dispatcher *d, struct bpf_prog *from,
> 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);
> + /* d->rw_image doesn't need to be in module memory range, so we
> + * can use vmalloc.
> + */
> + d->rw_image = vmalloc(PAGE_SIZE);
[Severity: High]
Does this code leak uninitialized kernel memory into the executable ROX BPF
image?
By changing the allocation to vmalloc() without zeroing, any trailing bytes
beyond the emitted instructions are left uninitialized.
When this buffer is later used in bpf_dispatcher_update(), a full 2048 bytes
are copied:
kernel/bpf/dispatcher.c:bpf_dispatcher_update() {
if (bpf_dispatcher_prepare(d, new, tmp))
return;
if (IS_ERR(bpf_arch_text_copy(new, tmp, PAGE_SIZE / 2)))
return;
}
Because the uninitialized bytes (which can decode into random executable
gadgets) replace the intended trap or padding instructions (e.g., INT3 or
zeroes), does this also create a wild execution risk if execution falls
through past the generated code?
This is a pre-existing issue, but the same pattern appears in
arch_prepare_bpf_trampoline():
arch/x86/net/bpf_jit_comp.c:arch_prepare_bpf_trampoline() {
/* rw_image doesn't need to be in module memory range, so we can
* use kvmalloc.
*/
rw_image = kvmalloc(size, GFP_KERNEL);
if (!rw_image)
return -ENOMEM;
ret = __arch_prepare_bpf_trampoline(im, rw_image, rw_image + size, image, m,
flags, tnodes, func_addr);
if (ret < 0)
goto out;
tmp = bpf_arch_text_copy(image, rw_image, size);
}
Because kvmalloc() does not zero-initialize the memory, is BPF_INSN_SAFETY
padding left uninitialized before being unconditionally copied to the ROX
image?
> if (!d->rw_image) {
> bpf_prog_pack_free(d->image, PAGE_SIZE);
> d->image = NULL;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260711-execmem-x86-rox-bpf-v0-v2-0-bfd956d35119@kernel.org?part=5
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next v2 3/5] bpf: alloc_prog_pack(): skip ROX management for already ROX memory
2026-07-11 9:43 ` [PATCH bpf-next v2 3/5] bpf: alloc_prog_pack(): skip ROX management for already ROX memory Mike Rapoport (Microsoft)
@ 2026-07-11 10:00 ` sashiko-bot
2026-07-11 15:27 ` Mike Rapoport
0 siblings, 1 reply; 13+ messages in thread
From: sashiko-bot @ 2026-07-11 10:00 UTC (permalink / raw)
To: Mike Rapoport (Microsoft); +Cc: bpf
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] BPF trampolines and non-pack JIT binary allocators will crash due to writing directly to ROX memory when EXECMEM_ROX_CACHE is enabled.
--
--- Patch [3]: [PATCH bpf-next v2 3/5] bpf: alloc_prog_pack(): skip ROX management for already ROX memory ---
commit 81282c502cc862cd297ba34160f028b0565526c2
Author: Mike Rapoport (Microsoft) <rppt@kernel.org>
bpf: alloc_prog_pack(): skip ROX management for already ROX memory
execmem_alloc() can return ROX memory that is already filled with
architecture defined trapping instructions.
In preparation for enabling this mode for BPF on x86, make sure that there
is no redundant management of the ROX memory.
There is no need to fill allocated memory with trapping instructions, to
request permissions reset on free and to set ROX permissions as this all
is handled by execmem_alloc().
Add bpf_jit_mem_is_rox() wrapper for execmem_is_rox(), use it to check if
execmem_alloc() returns ROX memory and skip the redundant steps in that
case.
> diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
> index fc75625dc9513..1b89c18cf2461 100644
> --- a/kernel/bpf/core.c
> +++ b/kernel/bpf/core.c
> @@ -916,6 +916,11 @@ static LIST_HEAD(pack_list);
>
> #define BPF_PROG_CHUNK_COUNT (BPF_PROG_PACK_SIZE / BPF_PROG_CHUNK_SIZE)
>
> +static bool bpf_jit_mem_is_rox(void)
> +{
> + return execmem_is_rox(EXECMEM_BPF);
> +}
> +
[ ... ]
> @@ -965,7 +972,7 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns, bool
> 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) {
> + if (ptr && !bpf_jit_mem_is_rox()) {
> int err;
>
> bpf_fill_ill_insns(ptr, size);
[Severity: Medium]
Since this patch protects bpf_prog_pack_alloc() and alloc_new_pack() from
writing to memory that might already be ROX, does it also need to protect
other callers of bpf_jit_alloc_exec() that write directly to the allocated
memory?
For example, bpf_jit_binary_alloc() allocates memory and directly writes
illegal instructions:
kernel/bpf/core.c:bpf_jit_binary_alloc() {
...
hdr = bpf_jit_alloc_exec(size);
if (!hdr) {
bpf_jit_uncharge_modmem(size);
return NULL;
}
/* Fill space with illegal/arch-dep instructions. */
bpf_fill_ill_insns(hdr, size);
...
}
Similarly, on x86, arch_bpf_trampoline_size() allocates memory and passes
it as a read-write buffer:
arch/x86/net/bpf_jit_comp.c:arch_bpf_trampoline_size() {
...
image = bpf_jit_alloc_exec(PAGE_SIZE);
if (!image)
return -ENOMEM;
ret = __arch_prepare_bpf_trampoline(&im, image, image + PAGE_SIZE, image,
m, flags, tnodes, func_addr);
...
}
Could these paths cause a page fault when EXECMEM_ROX_CACHE is enabled,
since they attempt to write directly to the read-only memory?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260711-execmem-x86-rox-bpf-v0-v2-0-bfd956d35119@kernel.org?part=3
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next v2 4/5] bpf, x86: make sure allocation in arch_bpf_trampoline_size() is writable
2026-07-11 9:43 ` [PATCH bpf-next v2 4/5] bpf, x86: make sure allocation in arch_bpf_trampoline_size() is writable Mike Rapoport (Microsoft)
@ 2026-07-11 10:02 ` sashiko-bot
2026-07-11 15:36 ` Mike Rapoport
0 siblings, 1 reply; 13+ messages in thread
From: sashiko-bot @ 2026-07-11 10:02 UTC (permalink / raw)
To: Mike Rapoport (Microsoft); +Cc: bpf
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Medium] Unnecessary page table modifications and hugepage shattering during trampoline size calculation.
Pre-existing issues:
- [High] Ignoring `execmem_restore_rox` failure leads to NX memory being added to the ROX cache.
--
--- Patch [4]: [PATCH bpf-next v2 4/5] bpf, x86: make sure allocation in arch_bpf_trampoline_size() is writable ---
commit 837979675b78fb56749ec25f57d0084b1bf805ee
Author: Mike Rapoport (Microsoft) <rppt@kernel.org>
bpf, x86: make sure allocation in arch_bpf_trampoline_size() is writable
arch_bpf_trampoline_size() allocates a buffer to get actual size required
for a trampoline.
This buffer must be in the module address space because
__arch_prepare_bpf_trampoline() calculates rel32 offsets relatively to
that buffer.
> diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> index de7515ea1beae..b2feec81e2312 100644
> --- a/arch/x86/net/bpf_jit_comp.c
> +++ b/arch/x86/net/bpf_jit_comp.c
> @@ -3703,13 +3703,12 @@ int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags,
> int ret;
>
> /* Allocate a temporary buffer for __arch_prepare_bpf_trampoline().
> - * This will NOT cause fragmentation in direct map, as we do not
> - * call set_memory_*() on this buffer.
> *
> * We cannot use kvmalloc here, because we need image to be in
> * module memory range.
> + * Since it must be writable use bpf_jit_alloc_exec_rw().
> */
> - image = bpf_jit_alloc_exec(PAGE_SIZE);
> + image = bpf_jit_alloc_exec_rw(PAGE_SIZE);
[Severity: Medium]
Does this allocation cause unnecessary page table modifications and hugepage
shattering?
Allocating via bpf_jit_alloc_exec_rw() forces page table permission changes
(set_memory_nx and set_memory_rw), which triggers cross-CPU TLB flushes
and permanently splits 2MB hugepages in the ROX cache's direct map. When
the buffer is immediately freed, these operations are repeated.
Since arch_bpf_trampoline_size() only needs a dummy pointer in the module
memory range to satisfy relative jump offset checks during instruction
emission, could we pass func_addr as the dummy image pointer and use
kvmalloc() for rw_image instead?
> if (!image)
> return -ENOMEM;
>
[ ... ]
> diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
> index 1b89c18cf2461..e2076667b2451 100644
> --- a/kernel/bpf/core.c
> +++ b/kernel/bpf/core.c
> @@ -1128,6 +1128,11 @@ void *bpf_jit_alloc_exec(unsigned long size)
> return execmem_alloc(EXECMEM_BPF, size);
> }
>
> +void *bpf_jit_alloc_exec_rw(unsigned long size)
> +{
> + return execmem_alloc_rw(EXECMEM_BPF, size);
> +}
> +
> void bpf_jit_free_exec(void *addr)
> {
> execmem_free(addr);
[Severity: High]
This is a pre-existing issue, but there appears to be a flaw when freeing
memory to the ROX cache in __execmem_cache_free():
mm/execmem.c:__execmem_cache_free() {
...
execmem_fill_trapping_insns(ptr, size);
execmem_restore_rox(ptr, size);
err = execmem_cache_add_locked(ptr, size, gfp_mask);
...
}
Does this ignore the return value of execmem_restore_rox()?
If execmem_restore_rox() fails (e.g., due to OOM when allocating page
tables), the memory remains Non-Executable (NX). Because the return value
is ignored, the NX memory is unconditionally added back to the ROX cache.
When subsequent allocations return this NX memory and the BPF JIT attempts
to execute code from it, will it trigger a fatal page fault?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260711-execmem-x86-rox-bpf-v0-v2-0-bfd956d35119@kernel.org?part=4
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next v2 3/5] bpf: alloc_prog_pack(): skip ROX management for already ROX memory
2026-07-11 10:00 ` sashiko-bot
@ 2026-07-11 15:27 ` Mike Rapoport
0 siblings, 0 replies; 13+ messages in thread
From: Mike Rapoport @ 2026-07-11 15:27 UTC (permalink / raw)
To: sashiko-reviews; +Cc: bpf
On Sat, Jul 11, 2026 at 10:00:31AM +0000, sashiko-bot@kernel.org wrote:
>
> bpf: alloc_prog_pack(): skip ROX management for already ROX memory
>
> > @@ -965,7 +972,7 @@ void *bpf_prog_pack_alloc(u32 size, bpf_jit_fill_hole_t bpf_fill_ill_insns, bool
> > 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) {
> > + if (ptr && !bpf_jit_mem_is_rox()) {
> > int err;
> >
> > bpf_fill_ill_insns(ptr, size);
>
> [Severity: Medium]
> Since this patch protects bpf_prog_pack_alloc() and alloc_new_pack() from
> writing to memory that might already be ROX, does it also need to protect
> other callers of bpf_jit_alloc_exec() that write directly to the allocated
> memory?
>
> For example, bpf_jit_binary_alloc() allocates memory and directly writes
> illegal instructions:
>
> kernel/bpf/core.c:bpf_jit_binary_alloc() {
> ...
> hdr = bpf_jit_alloc_exec(size);
> if (!hdr) {
> bpf_jit_uncharge_modmem(size);
> return NULL;
> }
>
> /* Fill space with illegal/arch-dep instructions. */
> bpf_fill_ill_insns(hdr, size);
> ...
> }
ROX caches are only supported on x86-64, and this is never called there.
> Similarly, on x86, arch_bpf_trampoline_size() allocates memory and passes
> it as a read-write buffer:
>
> arch/x86/net/bpf_jit_comp.c:arch_bpf_trampoline_size() {
> ...
> image = bpf_jit_alloc_exec(PAGE_SIZE);
> if (!image)
> return -ENOMEM;
>
> ret = __arch_prepare_bpf_trampoline(&im, image, image + PAGE_SIZE, image,
> m, flags, tnodes, func_addr);
> ...
> }
>
> Could these paths cause a page fault when EXECMEM_ROX_CACHE is enabled,
> since they attempt to write directly to the read-only memory?
This is handled by the next patch.
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next v2 4/5] bpf, x86: make sure allocation in arch_bpf_trampoline_size() is writable
2026-07-11 10:02 ` sashiko-bot
@ 2026-07-11 15:36 ` Mike Rapoport
0 siblings, 0 replies; 13+ messages in thread
From: Mike Rapoport @ 2026-07-11 15:36 UTC (permalink / raw)
To: sashiko-reviews; +Cc: bpf
On Sat, Jul 11, 2026 at 10:02:45AM +0000, sashiko-bot@kernel.org wrote:
>
> bpf, x86: make sure allocation in arch_bpf_trampoline_size() is writable
>
> arch_bpf_trampoline_size() allocates a buffer to get actual size required
> for a trampoline.
>
> This buffer must be in the module address space because
> __arch_prepare_bpf_trampoline() calculates rel32 offsets relatively to
> that buffer.
>
> > diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> > index de7515ea1beae..b2feec81e2312 100644
> > --- a/arch/x86/net/bpf_jit_comp.c
> > +++ b/arch/x86/net/bpf_jit_comp.c
> > @@ -3703,13 +3703,12 @@ int arch_bpf_trampoline_size(const struct btf_func_model *m, u32 flags,
> > int ret;
> >
> > /* Allocate a temporary buffer for __arch_prepare_bpf_trampoline().
> > - * This will NOT cause fragmentation in direct map, as we do not
> > - * call set_memory_*() on this buffer.
> > *
> > * We cannot use kvmalloc here, because we need image to be in
> > * module memory range.
> > + * Since it must be writable use bpf_jit_alloc_exec_rw().
> > */
> > - image = bpf_jit_alloc_exec(PAGE_SIZE);
> > + image = bpf_jit_alloc_exec_rw(PAGE_SIZE);
>
> [Severity: Medium]
> Does this allocation cause unnecessary page table modifications and hugepage
> shattering?
No.
> Allocating via bpf_jit_alloc_exec_rw() forces page table permission changes
> (set_memory_nx and set_memory_rw), which triggers cross-CPU TLB flushes
> and permanently splits 2MB hugepages in the ROX cache's direct map. When
> the buffer is immediately freed, these operations are repeated.
2M page is split temporarily. When the buffer is freed and ROX is restored,
2M page is collapsed.
> [Severity: High]
> This is a pre-existing issue, but there appears to be a flaw when freeing
> memory to the ROX cache in __execmem_cache_free():
>
> mm/execmem.c:__execmem_cache_free() {
> ...
> execmem_fill_trapping_insns(ptr, size);
> execmem_restore_rox(ptr, size);
>
> err = execmem_cache_add_locked(ptr, size, gfp_mask);
> ...
> }
>
> Does this ignore the return value of execmem_restore_rox()?
Answered this one already:
https://lore.kernel.org/bpf/ak-ZubgH13_MrXy4@kernel.org/
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next v2 1/5] bpf: dispatcher: allocate bpf_dispatcher->rw_image with vmalloc()
2026-07-11 9:55 ` sashiko-bot
@ 2026-07-11 16:28 ` Mike Rapoport
0 siblings, 0 replies; 13+ messages in thread
From: Mike Rapoport @ 2026-07-11 16:28 UTC (permalink / raw)
To: sashiko-reviews; +Cc: bpf
On Sat, Jul 11, 2026 at 09:55:04AM +0000, sashiko-bot@kernel.org wrote:
> bpf: dispatcher: allocate bpf_dispatcher->rw_image with vmalloc()
>
> This commit changes bpf_dispatcher->rw_image allocation to use vmalloc()
> instead of bpf_jit_alloc_exec() since the rel32 offsets are calculated
> against the final image, removing the need for the temporary buffer to live
> in the module address range.
>
> > diff --git a/kernel/bpf/dispatcher.c b/kernel/bpf/dispatcher.c
> > index ea2d60dc1feeb..d85b3e8ad2e4e 100644
> > --- a/kernel/bpf/dispatcher.c
> > +++ b/kernel/bpf/dispatcher.c
> > @@ -148,7 +148,10 @@ void bpf_dispatcher_change_prog(struct bpf_dispatcher *d, struct bpf_prog *from,
> > 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);
> > + /* d->rw_image doesn't need to be in module memory range, so we
> > + * can use vmalloc.
> > + */
> > + d->rw_image = vmalloc(PAGE_SIZE);
>
> [Severity: High]
> This isn't a bug introduced by this patch, but since we are changing the
> allocation here, does this code leak uninitialized kernel memory into the
> executable dispatcher image?
>
> vmalloc() does not zero-initialize the memory, and neither did the previous
> bpf_jit_alloc_exec() call. When the architecture-specific code generates the
> dispatcher instructions into this buffer, it only writes a few hundred bytes.
>
> Later, bpf_dispatcher_update() copies exactly 2048 bytes from this partially
> uninitialized temporary buffer into the executable RO+X dispatcher image:
>
> bpf_dispatcher_update()
> ...
> bpf_arch_text_copy(new, tmp, PAGE_SIZE / 2);
>
> Could this overwrite the safe int3 trap instructions placed by
> bpf_prog_pack_alloc() with random bytes from the uninitialized kernel memory,
> potentially creating unintended ROP/JOP gadgets? Would it be safer to use
> vzalloc() here instead?
This one seems real unless I'm missing something in how that memory is
initialized.
I'll change vmalloc() to vzalloc() to make this better, but the proper fix
to either initialize ->rw_image to invalid instructions or copy only the
actually generated part is out of scope for this set.
> > if (!d->rw_image) {
> > bpf_prog_pack_free(d->image, PAGE_SIZE);
> > d->image = NULL;
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-07-11 16:28 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-11 9:43 [PATCH bpf-next v2 0/5] bpf, x86: enable EXECMEM_ROX_CACHE for BPF allocations Mike Rapoport (Microsoft)
2026-07-11 9:43 ` [PATCH bpf-next v2 1/5] bpf: dispatcher: allocate bpf_dispatcher->rw_image with vmalloc() Mike Rapoport (Microsoft)
2026-07-11 9:55 ` sashiko-bot
2026-07-11 16:28 ` Mike Rapoport
2026-07-11 9:43 ` [PATCH bpf-next v2 2/5] bpf: drop __weak from bpf_jit_alloc_exec() and bpf_jit_free_exec() Mike Rapoport (Microsoft)
2026-07-11 9:43 ` [PATCH bpf-next v2 3/5] bpf: alloc_prog_pack(): skip ROX management for already ROX memory Mike Rapoport (Microsoft)
2026-07-11 10:00 ` sashiko-bot
2026-07-11 15:27 ` Mike Rapoport
2026-07-11 9:43 ` [PATCH bpf-next v2 4/5] bpf, x86: make sure allocation in arch_bpf_trampoline_size() is writable Mike Rapoport (Microsoft)
2026-07-11 10:02 ` sashiko-bot
2026-07-11 15:36 ` Mike Rapoport
2026-07-11 9:43 ` [PATCH bpf-next v2 5/5] x86/bpf: enable EXECMEM_ROX_CACHE for BPF allocations Mike Rapoport (Microsoft)
2026-07-11 9:59 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox