public inbox for linux-arm-kernel@lists.infradead.org
 help / color / mirror / Atom feed
* [PATCH bpf-next] bpf, arm32: Reject BPF_PSEUDO_CALL in the JIT
@ 2026-04-17 10:30 Puranjay Mohan
  2026-04-17 11:17 ` bot+bpf-ci
  2026-04-17 11:21 ` Daniel Borkmann
  0 siblings, 2 replies; 4+ messages in thread
From: Puranjay Mohan @ 2026-04-17 10:30 UTC (permalink / raw)
  To: bpf, linux-arm-kernel
  Cc: Puranjay Mohan, Jonas Rebmann, Alexei Starovoitov,
	Daniel Borkmann, Andrii Nakryiko, Martin KaFai Lau,
	Eduard Zingerman, Kumar Kartikeya Dwivedi, Song Liu, Russell King,
	kernel

The ARM32 BPF JIT does not support BPF-to-BPF function calls
(subprogram calls). When insn->src_reg == BPF_PSEUDO_CALL, the
imm field contains a pc-relative offset to another BPF function,
not a helper function index.

When a program containing BPF-to-BPF calls is loaded, the verifier
invokes bpf_jit_subprogs() which calls bpf_int_jit_compile() for each
subprogram. Since ARM32 does not reject BPF_PSEUDO_CALL, the JIT
silently emits code for the call using the wrong address computation:

    func = __bpf_call_base + imm

where imm is actually a pc-relative subprogram offset, producing
a bogus function pointer. Because build_body() reports success,
bpf_jit_binary_alloc() is reached and a JIT image is allocated.

ARM32 also lacks the jit_data/extra_pass mechanism needed for
the second JIT pass in bpf_jit_subprogs(). On the second pass,
bpf_int_jit_compile() performs a full fresh compilation,
allocating a new JIT binary and overwriting prog->bpf_func. The
first allocation is never freed. bpf_jit_subprogs() then detects
the function pointer changed and aborts with -ENOTSUPP, but the
original JIT binary has already been leaked. Each program
load/unload cycle leaks one JIT binary allocation, as reported
by kmemleak:

    unreferenced object 0xbf0a1000 (size 4096):
      backtrace:
        bpf_jit_binary_alloc+0x64/0xfc
        bpf_int_jit_compile+0x14c/0x348
        bpf_jit_subprogs+0x4fc/0xa60

Fix this by rejecting BPF_PSEUDO_CALL early in build_insn(),
falling through to the existing 'notyet' path. This causes
build_body() to fail before any JIT binary is allocated, so
bpf_int_jit_compile() returns the original program unjitted.
bpf_jit_subprogs() then sees !prog->jited and cleanly falls
back to the interpreter.

Fixes: 1c2a088a6626 ("bpf: x64: add JIT support for multi-function programs")
Reported-by: Jonas Rebmann <jre@pengutronix.de>
Closes: https://lore.kernel.org/bpf/b63e9174-7a3d-4e22-8294-16df07a4af89@pengutronix.de
Tested-by: Jonas Rebmann <jre@pengutronix.de>
Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
---
 arch/arm/net/bpf_jit_32.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/arch/arm/net/bpf_jit_32.c b/arch/arm/net/bpf_jit_32.c
index deeb8f292454..91fef10e88bc 100644
--- a/arch/arm/net/bpf_jit_32.c
+++ b/arch/arm/net/bpf_jit_32.c
@@ -2047,6 +2047,8 @@ static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx)
 	/* function call */
 	case BPF_JMP | BPF_CALL:
 	{
+		if (insn->src_reg == BPF_PSEUDO_CALL)
+			goto notyet;
 		const s8 *r0 = bpf2a32[BPF_REG_0];
 		const s8 *r1 = bpf2a32[BPF_REG_1];
 		const s8 *r2 = bpf2a32[BPF_REG_2];

base-commit: 1f5ffc672165ff851063a5fd044b727ab2517ae3
-- 
2.52.0



^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-04-17 14:16 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-17 10:30 [PATCH bpf-next] bpf, arm32: Reject BPF_PSEUDO_CALL in the JIT Puranjay Mohan
2026-04-17 11:17 ` bot+bpf-ci
2026-04-17 14:16   ` Puranjay Mohan
2026-04-17 11:21 ` Daniel Borkmann

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox