From: sashiko-bot@kernel.org
To: "Alexei Starovoitov" <alexei.starovoitov@gmail.com>, bpf@vger.kernel.org
Subject: Re: [PATCH bpf-next 2/5] bpf, x86: Replace 'if r3 != 0 goto pad' after a call with an extable entry
Date: Sun, 18 Oct 2026 01:28:34 +0000 [thread overview]
Message-ID: <sashiko-outbox-175871@kernel.org> (raw)
In-Reply-To: <20261018010246.1643877-3-alexei.starovoitov@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] bpf: stale jump_target in bpf_jit_unwind_site() and bpf_jit_unwind_exit()
--
commit 5cf9f95ceda5d2950b3e2ae8af36899c1bc4137e
Author: Alexei Starovoitov <ast@kernel.org>
bpf, x86: Replace 'if r3 != 0 goto pad' after a call with an extable entry
This commit optimizes unwinding subprogram calls on x86-64 by replacing the
post-call 'if r3 != 0 goto pad' branch with an exception table entry and
redirecting the caller's return address at subprogram exit when unwinding.
> diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
> index b16d74d2681a9..14696555e8a51 100644
> --- a/kernel/bpf/core.c
> +++ b/kernel/bpf/core.c
> @@ -3331,6 +3331,50 @@ bool __weak bpf_jit_supports_unwind(void)
> return false;
> }
>
> +/*
> + * A subprog that can unwind says so in r3 and its caller does
> + * 'if r3 != 0 goto pad' after the call. A JIT can keep that off the normal path.
> + * It doesn't emit the branch and adds an extable entry instead:
> + * insn = return address of the call - 1
> + * fixup = pad - return address of the call
> + * - 1, because what follows the call can have an entry of its own.
> + * An exit with r3 != 0 looks its own return address up and returns to the pad.
> + * When there is no entry it returns as usual and the branch is there.
> + *
> + * Is insn i of the prog such a call?
> + */
> +bool bpf_jit_unwind_site(const struct bpf_verifier_env *env, const struct bpf_prog *prog, int i)
> +{
> + const struct bpf_insn *insn = &prog->insnsi[i];
> +
> + if (!env || i + 1 >= prog->len)
> + return false;
> + if (!bpf_pseudo_call(insn) && !bpf_is_callx(insn))
> + return false;
> + /*
> + * No need to know the callee: the verifier lets r3 be read after
> + * a call only when every callee seen there says something in it.
> + */
> + return insn[1].code == (BPF_JMP | BPF_JNE | BPF_K) && !insn[1].imm &&
> + insn[1].dst_reg == BPF_REG_UNWIND &&
> + !bpf_is_jump_target(env, prog->aux->subprog_start + i + 1);
> +}
> +
> +/* What does 'exit' at insn i of the prog do? */
> +enum bpf_unwind_exit bpf_jit_unwind_exit(const struct bpf_verifier_env *env,
> + const struct bpf_prog *prog, int i)
> +{
> + const struct bpf_insn *insn = &prog->insnsi[i];
> +
> + if (!env || !prog->aux->ret_unwind)
> + return BPF_EXIT_RETURNS;
> + if (!i || bpf_is_jump_target(env, prog->aux->subprog_start + i) ||
> + insn[-1].code != (BPF_ALU64 | BPF_MOV | BPF_K) ||
> + insn[-1].dst_reg != BPF_REG_UNWIND)
> + return BPF_EXIT_LOOK_AT_R3;
> + return insn[-1].imm ? BPF_EXIT_UNWINDS : BPF_EXIT_RETURNS;
> +}
[Severity: Critical]
Is env->insn_aux_data[].jump_target still accurate when bpf_jit_unwind_site()
and bpf_jit_unwind_exit() query bpf_is_jump_target() during JIT compilation?
env->insn_aux_data[].jump_target is populated once during bpf_check_cfg()
before main verification, and several post-verification passes in
kernel/bpf/fixups.c and kernel/bpf/const_fold.c modify instructions without
preserving jump_target on the resulting jump targets:
1. NOP removal in bpf_opt_remove_nops() -> verifier_remove_insns():
Dead conditional branches folded to 'ja +0' (by bpf_prune_dead_branches()
or bpf_opt_hard_wire_dead_code_branches()) and removed fastcall
spills/fills (by bpf_remove_fastcall_spills_fills()) are deleted unless
aux[i].indirect_target is set:
kernel/bpf/fixups.c:bpf_opt_remove_nops() {
...
if (aux[i].indirect_target)
continue;
err = verifier_remove_insns(env, i, 1);
}
verifier_remove_insns() redirects incoming jumps from off to the next
instruction, but overwrites aux_data[off] with aux_data[off + cnt]:
kernel/bpf/fixups.c:verifier_remove_insns() {
...
memmove(aux_data + off, aux_data + off + cnt,
sizeof(*aux_data) * (orig_prog_len - off - cnt));
}
If a jump targeted the removed NOP at k (aux_data[k].jump_target == true)
while k + 1 was only reached by fallthrough (aux_data[k + 1].jump_target ==
false), the instruction shifting into k inherits the incoming jump with
jump_target == false.
In an unwinding subprogram such as:
r3 = 1
if r1 == 0 goto L
r3 = 0
L: if r2 == 0 goto dead /* folded to ja +0 and removed */
exit
removing the NOP at L leaves exit at L with jump_target == false and
insn[-1] == 'r3 = 0'. bpf_jit_unwind_exit() then returns BPF_EXIT_RETURNS
instead of BPF_EXIT_LOOK_AT_R3, so do_jit() omits emit_unwind_ret_addr().
When the 'r1 == 0' branch is taken with r3 = 1, the subprogram returns to
the caller's normal post-call instruction (where do_jit() set
skip_branch = true and stripped 'if r3 != 0 goto pad') instead of the
landing pad, bypassing cleanup on the unwind path. Conversely, if
insn[-1] is 'r3 = 1', bpf_jit_unwind_exit() returns BPF_EXIT_UNWINDS and
unwinds unconditionally even when a jumping path had r3 = 0.
Similarly, if a jump-targeted NOP between a call and 'if r3 != 0 goto pad'
is removed, 'if r3 != 0 goto pad' shifts into insn[1] after the call with
jump_target == false, causing bpf_jit_unwind_site() to return true and
do_jit() to elide the branch via skip_branch = true even though another
jump targets it.
2. PROBE_MEM fixup in bpf_do_misc_fixups():
When bpf_arch_uaddress_limit() is non-zero (such as on x86-64), a
BPF_LDX | BPF_PROBE_MEM (or BPF_PROBE_MEMSX) load into r3 immediately
before exit is expanded via bpf_patch_insn_data():
kernel/bpf/fixups.c:bpf_do_misc_fixups() {
...
*patch++ = BPF_JMP_IMM(BPF_JLE, BPF_REG_AX, uaddress_limit >> 32, 2);
*patch++ = *insn;
*patch++ = BPF_JMP_IMM(BPF_JA, 0, 0, 1);
*patch++ = BPF_MOV64_IMM(insn->dst_reg, 0);
cnt = patch - insn_buf;
new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
}
The synthesized 'goto +1' jumps over 'r3 = 0' to exit without marking
exit as a jump target, and places 'r3 = 0' at insn[-1].
bpf_jit_unwind_exit() then sees jump_target == false and insn[-1] ==
'r3 = 0', returning BPF_EXIT_RETURNS instead of BPF_EXIT_LOOK_AT_R3.
3. Instruction patching in adjust_insn_aux_data():
When expanding an instruction in BPF_PATCH_KEEP_TARGET mode,
indirect_target is moved from data[off + cnt - 1] back to data[off],
whereas jump_target is left on data[off + cnt - 1] and cleared on
data[off]:
kernel/bpf/fixups.c:adjust_insn_aux_data() {
...
if (mode == BPF_PATCH_KEEP_TARGET && data[off + cnt - 1].indirect_target) {
data[off].indirect_target = 1;
data[off + cnt - 1].indirect_target = 0;
}
}
Should jump_target be maintained across verifier_remove_insns(),
adjust_insn_aux_data(), and fixup patches (or recomputed after all instruction
rewrites before JIT compilation)?
> /*
> * How this arch places a by-value kfunc argument, or NULL for one that has
> * not opted in and so only takes an argument of a single eightbyte, which
[ ... ]
>
endasi
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261018010246.1643877-1-alexei.starovoitov@gmail.com?part=2
next prev parent reply other threads:[~2026-10-18 1:28 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-18 1:02 [PATCH bpf-next 0/5] bpf: Unwinding via r3 Alexei Starovoitov
2026-10-18 1:02 ` [PATCH bpf-next 1/5] bpf: Support unwinding " Alexei Starovoitov
2026-10-18 1:32 ` sashiko-bot
2026-10-18 1:48 ` bot+bpf-ci
2026-10-18 1:02 ` [PATCH bpf-next 2/5] bpf, x86: Replace 'if r3 != 0 goto pad' after a call with an extable entry Alexei Starovoitov
2026-10-18 1:28 ` sashiko-bot [this message]
2026-10-18 1:02 ` [PATCH bpf-next 3/5] bpf, arm64: Support unwinding via r3 Alexei Starovoitov
2026-10-18 1:34 ` sashiko-bot
2026-10-18 1:02 ` [PATCH bpf-next 4/5] selftests/bpf: Add tests for " Alexei Starovoitov
2026-10-18 1:38 ` bot+bpf-ci
2026-10-18 1:02 ` [PATCH bpf-next 5/5] selftests/bpf: Add a C++ prog with exceptions Alexei Starovoitov
2026-10-18 1:38 ` bot+bpf-ci
2026-10-18 1:31 ` [PATCH bpf-next 0/5] bpf: Unwinding via r3 Kumar Kartikeya Dwivedi
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=sashiko-outbox-175871@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=alexei.starovoitov@gmail.com \
--cc=bpf@vger.kernel.org \
--cc=sashiko-reviews@lists.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