mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: bot+bpf-ci@kernel.org
To: visitorckw@gmail.com,corbet@lwn.net,skhan@linuxfoundation.org,geert@linux-m68k.org,ast@kernel.org,daniel@iogearbox.net,eddyz87@gmail.com,memxor@gmail.com,andrii@kernel.org,gerg@linux-m68k.org
Cc: rdunlap@infradead.org,martin.lau@linux.dev,song@kernel.org,yonghong.song@linux.dev,jolsa@kernel.org,emil@etsalapatis.com,ihor.solodrai@linux.dev,linux-doc@vger.kernel.org,linux-kernel@vger.kernel.org,linux-m68k@lists.linux-m68k.org,bpf@vger.kernel.org,jserv@ccns.ncku.edu.tw,marscheng@google.com,eleanor15x@gmail.com,visitorckw@gmail.com,daniel@thingy.jp,ast@kernel.org,andrii@kernel.org,daniel@iogearbox.net,martin.lau@kernel.org,eddyz87@gmail.com,yonghong.song@linux.dev,mason@kernel.org,ihor.solodrai@linux.dev
Subject: Re: [PATCH v6] m68k, bpf: Add initial BPF JIT compiler support
Date: Sun, 27 Sep 2026 21:43:04 +0000 (UTC)	[thread overview]
Message-ID: <3e07af39920f472e97ee3cf73eddc49a72a11dffb2e11e9b7c5b7cc1d80affef@mail.kernel.org> (raw)
In-Reply-To: <20260927205501.672807-1-visitorckw@gmail.com>

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

> diff --git a/arch/m68k/net/bpf_jit_comp.c b/arch/m68k/net/bpf_jit_comp.c
> new file mode 100644
> index 000000000000..a04ce91eb8e8
> --- /dev/null
> +++ b/arch/m68k/net/bpf_jit_comp.c

[ ... ]

> +static int jit_atomic32(u32 *ptr, u32 val, int op, u32 cmp)
> +{
> +	unsigned long flags;
> +	u32 old;
> +
> +	local_irq_save(flags);
> +	old = *ptr;
> +	switch (op) {
> +	case BPF_ADD:
> +		*ptr = old + val;
> +		break;
> +	case BPF_AND:
> +		*ptr = old & val;
> +		break;
> +	case BPF_OR:
> +		*ptr = old | val;
> +		break;
> +	case BPF_XOR:
> +		*ptr = old ^ val;
> +		break;
> +	case BPF_XCHG:
> +		*ptr = val;
> +		break;
> +	case BPF_CMPXCHG:
> +		if (old == cmp)
> +			*ptr = val;
> +		break;
> +	}
> +	local_irq_restore(flags);
> +
> +	return old;
> +}

[ ... ]

> +static void emit_atomic(const struct bpf_insn *insn, struct jit_ctx *ctx)
> +{
> +	const s8 *src = bpf_get_reg64_ref(insn->src_reg, tmp2, ctx);
> +	const s8 *dst = bpf_get_reg64_ref(insn->dst_reg, tmp1, ctx);
> +
> +	if (BPF_SIZE(insn->code) == BPF_W) {
> +		s8 s_lo = bpf_get_reg32(src[1], tmp2[1], ctx);
> +
> +		if (insn->imm != BPF_CMPXCHG) {
> +			if (is_stacked(dst[1])) {
> +				emit_16(ctx, 0x206e);		/* movea.l d16(%fp), %a0 */
> +				emit_16(ctx, (u16)STACK_OFFSET(dst[1]));
> +			} else {
> +				emit_16(ctx, 0x2040 | dst[1]);	/* movea.l dst, %a0 */
> +			}
> +
> +			emit_16(ctx, 0xd1fc);			/* adda.l #imm, %a0 */
> +			emit_16(ctx, (u16)((s16)insn->off >> 16));
> +			emit_16(ctx, (u16)insn->off);
> +
> +			if (BPF_OP(insn->code) == BPF_XCHG) {
> +				emit_16(ctx, 0x4eb9);
> +				emit_32(ctx, (u32)jit_atomic32);
> +			} else {
> +				emit_16(ctx, 0x2f00 | s_lo);
> +				emit_16(ctx, 0x2f3c);
> +				emit_32(ctx, insn->imm);
> +				emit_16(ctx, 0x2f08);
> +				emit_16(ctx, 0x4eb9);
> +				emit_32(ctx, (u32)jit_atomic32);
> +				emit_16(ctx, 0x4fef);
> +				emit_16(ctx, 12);
> +			}

[ ... ]

> +	case BPF_STX | BPF_ATOMIC | BPF_W:
> +	case BPF_STX | BPF_ATOMIC | BPF_DW:
> +		emit_atomic(insn, ctx);
> +		break;

Can this code handle 32-bit BPF_LOAD_ACQ and BPF_STORE_REL atomics?

Looking at build_insn(), every BPF_STX | BPF_ATOMIC | BPF_W instruction is
sent to emit_atomic() without checking insn->imm.

On 32-bit arches, the verifier accepts BPF_W-sized BPF_LOAD_ACQ (0x100)
and BPF_STORE_REL (0x110). In check_atomic() from kernel/bpf/verifier.c,
these operations are only rejected when BPF_SIZE is BPF_DW and
BITS_PER_LONG != 64:

    case BPF_LOAD_ACQ:
        if (BPF_SIZE(insn->code) == BPF_DW && BITS_PER_LONG != 64) {
            ...
            return -EOPNOTSUPP;
        }
        return check_atomic_load(env, insn);

For BPF_LOAD_ACQ, emit_atomic() loads %a0 from dst_reg + off and calls
jit_atomic32(ptr = dst_reg + off, val = src, op = 0x100, cmp). The switch
in jit_atomic32() has no case for 0x100, so it only runs `old = *ptr;` on
dst_reg + off.

But dst_reg is the destination register for a load. check_load_mem() in
the verifier only checks dst_reg with DST_OP_NO_MARK, so it can hold any
scalar or be uninitialized. The resulting dereference of dst_reg + off
(instead of src_reg + off) can read from an arbitrary address with no
exception-table entry.

Also, is_fetch is false (0x100 & BPF_FETCH == 0), so dst_reg is never
written back. The verifier tracks dst_reg as holding the loaded value, but
at runtime it keeps its old value.

For BPF_STORE_REL, jit_atomic32() is called with op = 0x110. No case
matches, so the store is silently dropped.

Other 32-bit JITs (mips32, x86_32, riscv32, parisc32) reject unsupported
atomic imm values. Could emit_atomic() or build_insn() accept only the
imm values that jit_atomic32/jit_atomic64 implement and return -EOPNOTSUPP
for the rest?

[ ... ]

> +static void build_prologue(struct jit_ctx *ctx)
> +{
> +	int bpf_stack = MAX_BPF_STACK;
> +	int total_stack = SCRATCH_SIZE + bpf_stack;

Can this stack reservation overflow the kernel stack?

Every function reserves SCRATCH_SIZE + MAX_BPF_STACK = 96 + 512 = 608
bytes, plus 16 bytes for d2-d5, 8 bytes for the return address and saved
%fp, and emit_call() pushes 40 bytes of arguments for each bpf-to-bpf
call.

That's 632 bytes for the main frame and 672 bytes for each nested subprog
frame, independent of prog->aux->stack_depth.

The verifier allows MAX_CALL_FRAMES (16) nested frames. For CAP_BPF
programs, it rounds each frame's stack_depth up to 16, so subprogs with no
stack use cost nothing against the 512-byte combined limit.

A program with a chain of 16 small static subprogs is therefore accepted
and uses 632 + 15 * 672 = 10712 bytes of kernel stack on top of the
caller's stack.

On m68k, THREAD_SIZE is 8KB for every configuration where this JIT is
enabled, thread_info sits at the base of the stack, and there is no guard
page. Even 12 levels (632 + 11 * 672 = 8024 bytes) overflow by themselves.

Shallower nesting can overflow when a program runs in softirq on top of an
already deep stack. Callbacks invoked from helpers such as bpf_loop add a
C frame plus another full JIT frame per level.

The main entry needs MAX_BPF_STACK because a tail-call target reuses its
frame. Subprogs can never be tail-call targets, and they cannot issue tail
calls here because bpf_jit_supports_subprog_tailcalls() is the weak false
default.

Could subprog frames be sized from the actual stack_depth instead of always
reserving MAX_BPF_STACK?

[ ... ]

> +	case BPF_JMP | BPF_CALL:
> +		return emit_call(insn, ctx);

Does this code copy back the callee's R2 for aggregate returns?

For a subprog whose BTF return type is 9-16 bytes,
bpf_compute_subprog_ret_regs() in kernel/bpf/verifier.c sets
subprog_info->ret_reg_pair and prog->jit_required.

On exit, prepare_func_exit() copies the callee's R2 state into the caller,
including pointer types for static subprogs.

In this JIT, BPF R2 is not a machine register. It is a slot at
STACK_OFFSET() from %fp, so each frame has its own copy.

The callee writes R2 into its own frame, BPF_EXIT runs build_epilogue(),
and unlk discards that frame. emit_call() only fixes up d0/d1 for R0 and
never copies R2 back.

The caller's R2 slot keeps its old value (for example the argument it
passed in).

This creates a verifier/runtime mismatch. For example, after
'r2 = 0xdeadbeef; call sub' where sub returns {r0, r2 = map_value_ptr},
the verifier thinks r2 is a PTR_TO_MAP_VALUE in '*(u64 *)(r2 + 0) = x',
but at run time the store goes to 0xdeadbeef.

Because jit_required is set, there is no interpreter fallback.

Every other JIT that supports BPF_PSEUDO_CALL keeps R2 in a hardware
register, and arm32/x86_32 reject subprog calls.

Could the callee's R2 be copied into the caller's R2 slot after a
BPF_PSEUDO_CALL, for example by returning it in d2/d3 or through a scratch
location?

[ ... ]

> +	default:
> +		return -EOPNOTSUPP;
> +	}

Does this code handle the BPF_ST | BPF_NOSPEC instruction?

build_insn() has no 'case BPF_ST | BPF_NOSPEC:', so the speculation
barrier the verifier inserts falls to the default and returns -EOPNOTSUPP.

m68k does not override bpf_jit_bypass_spec_v1() or v4(), which default to
false.

For any program loaded without CAP_PERFMON while mitigations are on
(unprivileged, CAP_BPF-only, or through a BPF token), the verifier emits
BPF_ST_NOSPEC() for speculative-path sanitation and after stack spills, in
kernel/bpf/fixups.c:958-962 and 1035-1042.

build_body() then fails and the whole program is not JITed. With
CONFIG_BPF_JIT_ALWAYS_ON, which this patch makes selectable by adding
HAVE_EBPF_JIT, and whenever prog->jit_required is set, the load fails with
-ENOTSUPP instead of falling back to the interpreter.

test_bpf.ko runs as root and so never hits this.

Every other eBPF JIT, including arm32, mips32 and x86_32, handles
BPF_ST | BPF_NOSPEC as a no-op or a barrier.

Could this case be added, or should bpf_jit_bypass_spec_v1/v4() be
overridden if m68k does not speculate?


---
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/36350420232

  reply	other threads:[~2026-09-27 21:43 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-27 20:55 Kuan-Wei Chiu
2026-09-27 21:43 ` bot+bpf-ci [this message]
2026-09-28 12:15 ` Geert Uytterhoeven

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=3e07af39920f472e97ee3cf73eddc49a72a11dffb2e11e9b7c5b7cc1d80affef@mail.kernel.org \
    --to=bot+bpf-ci@kernel.org \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=corbet@lwn.net \
    --cc=daniel@iogearbox.net \
    --cc=daniel@thingy.jp \
    --cc=eddyz87@gmail.com \
    --cc=eleanor15x@gmail.com \
    --cc=emil@etsalapatis.com \
    --cc=geert@linux-m68k.org \
    --cc=gerg@linux-m68k.org \
    --cc=ihor.solodrai@linux.dev \
    --cc=jolsa@kernel.org \
    --cc=jserv@ccns.ncku.edu.tw \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-m68k@lists.linux-m68k.org \
    --cc=marscheng@google.com \
    --cc=martin.lau@kernel.org \
    --cc=martin.lau@linux.dev \
    --cc=mason@kernel.org \
    --cc=memxor@gmail.com \
    --cc=rdunlap@infradead.org \
    --cc=skhan@linuxfoundation.org \
    --cc=song@kernel.org \
    --cc=visitorckw@gmail.com \
    --cc=yonghong.song@linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®