BPF List
 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
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ 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] 6+ messages in thread

* Re: [PATCH bpf-next] bpf, arm32: Reject BPF_PSEUDO_CALL in the JIT
  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
  2026-04-17 11:25 ` sashiko-bot
  2 siblings, 1 reply; 6+ messages in thread
From: bot+bpf-ci @ 2026-04-17 11:17 UTC (permalink / raw)
  To: puranjay, bpf, linux-arm-kernel
  Cc: puranjay, jre, ast, daniel, andrii, martin.lau, eddyz87, memxor,
	song, linux, kernel, ast, andrii, daniel, martin.lau, eddyz87,
	yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 1409 bytes --]

> Fixes: 1c2a088a6626 ("bpf: x64: add JIT support for multi-function programs")

Is this the correct Fixes tag? That commit only modified x64 JIT code
and BPF core infrastructure for BPF-to-BPF calls - it never touched
arch/arm/net/bpf_jit_32.c.

The actual bug was introduced by commit 39c13c204bb1 ("arm: eBPF JIT
compiler") from August 2017, which added the ARM32 eBPF JIT with
BPF_CALL handling but failed to reject BPF_PSEUDO_CALL. The code has
been incorrectly computing function addresses as 'func = __bpf_call_base
+ imm' for BPF_PSEUDO_CALL since the ARM32 JIT was first introduced.

Suggested: Fixes: 39c13c204bb1 ("arm: eBPF JIT compiler")

> 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];


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/24561211195

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

* Re: [PATCH bpf-next] bpf, arm32: Reject BPF_PSEUDO_CALL in the JIT
  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 11:21 ` Daniel Borkmann
  2026-04-17 11:25 ` sashiko-bot
  2 siblings, 0 replies; 6+ messages in thread
From: Daniel Borkmann @ 2026-04-17 11:21 UTC (permalink / raw)
  To: Puranjay Mohan, bpf, linux-arm-kernel
  Cc: Jonas Rebmann, Alexei Starovoitov, Andrii Nakryiko,
	Martin KaFai Lau, Eduard Zingerman, Kumar Kartikeya Dwivedi,
	Song Liu, Russell King, kernel

On 4/17/26 12:30 PM, Puranjay Mohan wrote:
> 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>

Acked-by: Daniel Borkmann <daniel@iogearbox.net>

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

* Re: [PATCH bpf-next] bpf, arm32: Reject BPF_PSEUDO_CALL in the JIT
  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 11:21 ` Daniel Borkmann
@ 2026-04-17 11:25 ` sashiko-bot
  2026-04-17 14:23   ` Puranjay Mohan
  2 siblings, 1 reply; 6+ messages in thread
From: sashiko-bot @ 2026-04-17 11:25 UTC (permalink / raw)
  To: Puranjay Mohan; +Cc: bpf

Sashiko AI review found 2 potential issue(s):
- [High] The patch leaves the memory leak unpatched for BPF programs utilizing callbacks (BPF_PSEUDO_FUNC).
- [Low] The patch introduces a C90 'declaration after statement' violation that will cause build failures on stable backports.
--

commit 5b49d0ae3429d9aefefa73b4cf12e6c41845458c
Author: Puranjay Mohan <puranjay@kernel.org>

bpf, arm32: Reject BPF_PSEUDO_CALL in the JIT

The ARM32 BPF JIT does not support subprograms. This patch rejects
BPF_PSEUDO_CALL early in the JIT to prevent leaking a JIT binary
allocation during the second pass in bpf_jit_subprogs().

Link: https://lore.kernel.org/bpf/b63e9174-7a3d-4e22-8294-16df07a4af89@pengutronix.de

> diff --git a/arch/arm/net/bpf_jit_32.c b/arch/arm/net/bpf_jit_32.c
> index deeb8f292454b..91fef10e88bc4 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];

Will this cause a build failure on older stable branches?

This patch is a bugfix tagged for stable backports, but placing code before
declarations violates the -Wdeclaration-after-statement rule enforced in
kernels before 6.5. Should the check be moved below the local variable
declarations?

Also, does this leave the memory leak unpatched for programs utilizing
callbacks like BPF_PSEUDO_FUNC?

When a program uses a callback, the verifier creates a subprogram. During the
first JIT pass, the ARM32 JIT ignores insn->src_reg and compiles the
instruction successfully as a 64-bit immediate load.

Because the first pass succeeds, bpf_jit_subprogs() executes its second pass,
allocates a new binary, overwrites prog->bpf_func, and aborts with -ENOTSUPP,
leaking the first allocation exactly as it did before this patch.

Is an equivalent rejection needed for BPF_PSEUDO_FUNC in the
BPF_LD | BPF_IMM | BPF_DW handler?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260417103004.3552500-1-puranjay@kernel.org?part=1

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

* Re: [PATCH bpf-next] bpf, arm32: Reject BPF_PSEUDO_CALL in the JIT
  2026-04-17 11:17 ` bot+bpf-ci
@ 2026-04-17 14:16   ` Puranjay Mohan
  0 siblings, 0 replies; 6+ messages in thread
From: Puranjay Mohan @ 2026-04-17 14:16 UTC (permalink / raw)
  To: bot+bpf-ci
  Cc: bpf, linux-arm-kernel, jre, ast, daniel, andrii, martin.lau,
	eddyz87, memxor, song, linux, kernel, martin.lau, yonghong.song,
	clm, ihor.solodrai

On Fri, Apr 17, 2026 at 12:17 PM <bot+bpf-ci@kernel.org> wrote:
>
> > Fixes: 1c2a088a6626 ("bpf: x64: add JIT support for multi-function programs")
>
> Is this the correct Fixes tag? That commit only modified x64 JIT code
> and BPF core infrastructure for BPF-to-BPF calls - it never touched
> arch/arm/net/bpf_jit_32.c.
>
> The actual bug was introduced by commit 39c13c204bb1 ("arm: eBPF JIT
> compiler") from August 2017, which added the ARM32 eBPF JIT with
> BPF_CALL handling but failed to reject BPF_PSEUDO_CALL. The code has
> been incorrectly computing function addresses as 'func = __bpf_call_base
> + imm' for BPF_PSEUDO_CALL since the ARM32 JIT was first introduced.
>
> Suggested: Fixes: 39c13c204bb1 ("arm: eBPF JIT compiler")

I knew Claude would say this!

39c13c204bb1 ("arm: eBPF JIT compiler")  came before BPF_PSEUDO_CALL
was introduced, so the bug was introduced when jit_subprogs() was
added.
The fixes tag in the patch is correct!

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

* Re: [PATCH bpf-next] bpf, arm32: Reject BPF_PSEUDO_CALL in the JIT
  2026-04-17 11:25 ` sashiko-bot
@ 2026-04-17 14:23   ` Puranjay Mohan
  0 siblings, 0 replies; 6+ messages in thread
From: Puranjay Mohan @ 2026-04-17 14:23 UTC (permalink / raw)
  To: sashiko; +Cc: bpf

On Fri, Apr 17, 2026 at 12:25 PM <sashiko-bot@kernel.org> wrote:
>
> Sashiko AI review found 2 potential issue(s):
> - [High] The patch leaves the memory leak unpatched for BPF programs utilizing callbacks (BPF_PSEUDO_FUNC).
> - [Low] The patch introduces a C90 'declaration after statement' violation that will cause build failures on stable backports.
> --
>
> commit 5b49d0ae3429d9aefefa73b4cf12e6c41845458c
> Author: Puranjay Mohan <puranjay@kernel.org>
>
> bpf, arm32: Reject BPF_PSEUDO_CALL in the JIT
>
> The ARM32 BPF JIT does not support subprograms. This patch rejects
> BPF_PSEUDO_CALL early in the JIT to prevent leaking a JIT binary
> allocation during the second pass in bpf_jit_subprogs().
>
> Link: https://lore.kernel.org/bpf/b63e9174-7a3d-4e22-8294-16df07a4af89@pengutronix.de
>
> > diff --git a/arch/arm/net/bpf_jit_32.c b/arch/arm/net/bpf_jit_32.c
> > index deeb8f292454b..91fef10e88bc4 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];
>
> Will this cause a build failure on older stable branches?
>
> This patch is a bugfix tagged for stable backports, but placing code before
> declarations violates the -Wdeclaration-after-statement rule enforced in
> kernels before 6.5. Should the check be moved below the local variable
> declarations?
>
> Also, does this leave the memory leak unpatched for programs utilizing
> callbacks like BPF_PSEUDO_FUNC?
>
> When a program uses a callback, the verifier creates a subprogram. During the
> first JIT pass, the ARM32 JIT ignores insn->src_reg and compiles the
> instruction successfully as a 64-bit immediate load.
>
> Because the first pass succeeds, bpf_jit_subprogs() executes its second pass,
> allocates a new binary, overwrites prog->bpf_func, and aborts with -ENOTSUPP,
> leaking the first allocation exactly as it did before this patch.
>
> Is an equivalent rejection needed for BPF_PSEUDO_FUNC in the
> BPF_LD | BPF_IMM | BPF_DW handler?

Yes, both reviews are correct, I will fix them in the next version.

Thanks

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

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

Thread overview: 6+ 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
2026-04-17 11:25 ` sashiko-bot
2026-04-17 14:23   ` Puranjay Mohan

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