* [PATCH bpf-next v2 0/2] bpf: Fix x86 JIT stopping before jumps converge
@ 2026-10-22 12:04 Mykyta Yatsenko
2026-10-22 12:04 ` [PATCH bpf-next v2 1/2] bpf: Size backward jumps by the insns they jump over Mykyta Yatsenko
` (2 more replies)
0 siblings, 3 replies; 9+ messages in thread
From: Mykyta Yatsenko @ 2026-10-22 12:04 UTC (permalink / raw)
To: bpf, ast, andrii, daniel, kernel-team, eddyz87, memxor; +Cc: Mykyta Yatsenko
The x86 JIT stops its sizing passes once two passes give the same image
length. A backward jump can grow while other jumps shrink, so the length
may repeat with jump sizes still changing. The final pass then emits code
of a different size than was sized, hits "bpf_jit: fatal error" and the
program fails to load with -ENOTSUPP.
Patch 1 sizes backward jumps by the insns they jump over, so no insn
grows between passes and a repeated length means nothing moved.
Patch 2 adds a selftest which fails to load without the fix.
This was hit in CI by cgroup_tcp_skb with the unpublished branch
inversion series, whose layout change happened to trigger it.
Signed-off-by: Mykyta Yatsenko <yatsenko@meta.com>
---
Changes in v2:
- Size backward jumps by the insns they jump over instead of checking
for moved insns, drop the ja -1 special case and the imm8 limit of 123
(Alexei)
- Start every insn at BPF_MAX_INSN_SIZE, so none grows in the first pass
- Check that the selftest program is JITed
- Link to v1: https://patch.msgid.link/20261021-jit_size_fix-v1-0-b89fd32cafd6@meta.com
---
Mykyta Yatsenko (2):
bpf: Size backward jumps by the insns they jump over
selftests/bpf: Test JIT jumps which change at a repeated image length
arch/x86/net/bpf_jit_comp.c | 109 ++++++---------------
tools/testing/selftests/bpf/prog_tests/jit_insns.c | 59 +++++++++++
2 files changed, 90 insertions(+), 78 deletions(-)
---
base-commit: 65953903cd7bce4c4392ad6ab629cba78f24644e
change-id: 20261021-jit_size_fix-da316b8d492c
Best regards,
--
Mykyta Yatsenko <yatsenko@meta.com>
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH bpf-next v2 1/2] bpf: Size backward jumps by the insns they jump over
2026-10-22 12:04 [PATCH bpf-next v2 0/2] bpf: Fix x86 JIT stopping before jumps converge Mykyta Yatsenko
@ 2026-10-22 12:04 ` Mykyta Yatsenko
2026-10-22 13:18 ` Daniel Borkmann
2026-10-22 12:04 ` [PATCH bpf-next v2 2/2] selftests/bpf: Test JIT jumps which change at a repeated image length Mykyta Yatsenko
2026-10-24 4:00 ` [PATCH bpf-next v2 0/2] bpf: Fix x86 JIT stopping before jumps converge patchwork-bot+netdevbpf
2 siblings, 1 reply; 9+ messages in thread
From: Mykyta Yatsenko @ 2026-10-22 12:04 UTC (permalink / raw)
To: bpf, ast, andrii, daniel, kernel-team, eddyz87, memxor; +Cc: Mykyta Yatsenko
From: Mykyta Yatsenko <yatsenko@meta.com>
A backward jump is sized from its target's current-pass address and its
own previous-pass address, so it can grow while other jumps shrink. Two
passes may then give the same image length with jumps still changing,
which ends in "bpf_jit: fatal error", and a jump growing in a padding
pass ends in "unexpected jmp_cond padding".
Shift the jump's previous-pass address by how far the current pass has
moved it, so a backward jump is sized by the insns it jumps over, like
a forward one. Also start every insn at BPF_MAX_INSN_SIZE instead of 64,
so none can grow in the first pass. Then no insn grows from one pass to
the next, and a repeated image length means nothing moved. The 123
limit on imm8 offsets worked around that growth and is dropped. The
ja -1 special case is subsumed, such a jump is now imm8 from the first
pass.
Suggested-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: Mykyta Yatsenko <yatsenko@meta.com>
---
arch/x86/net/bpf_jit_comp.c | 109 +++++++++++++-------------------------------
1 file changed, 31 insertions(+), 78 deletions(-)
diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 6a2fe3d0039d..96336e51c1c5 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -97,56 +97,6 @@ static bool is_imm8(int value)
return value <= 127 && value >= -128;
}
-/*
- * Let us limit the positive offset to be <= 123.
- * This is to ensure eventual jit convergence For the following patterns:
- * ...
- * pass4, final_proglen=4391:
- * ...
- * 20e: 48 85 ff test rdi,rdi
- * 211: 74 7d je 0x290
- * 213: 48 8b 77 00 mov rsi,QWORD PTR [rdi+0x0]
- * ...
- * 289: 48 85 ff test rdi,rdi
- * 28c: 74 17 je 0x2a5
- * 28e: e9 7f ff ff ff jmp 0x212
- * 293: bf 03 00 00 00 mov edi,0x3
- * Note that insn at 0x211 is 2-byte cond jump insn for offset 0x7d (-125)
- * and insn at 0x28e is 5-byte jmp insn with offset -129.
- *
- * pass5, final_proglen=4392:
- * ...
- * 20e: 48 85 ff test rdi,rdi
- * 211: 0f 84 80 00 00 00 je 0x297
- * 217: 48 8b 77 00 mov rsi,QWORD PTR [rdi+0x0]
- * ...
- * 28d: 48 85 ff test rdi,rdi
- * 290: 74 1a je 0x2ac
- * 292: eb 84 jmp 0x218
- * 294: bf 03 00 00 00 mov edi,0x3
- * Note that insn at 0x211 is 6-byte cond jump insn now since its offset
- * becomes 0x80 based on previous round (0x293 - 0x213 = 0x80).
- * At the same time, insn at 0x292 is a 2-byte insn since its offset is
- * -124.
- *
- * pass6 will repeat the same code as in pass4 and this will prevent
- * eventual convergence.
- *
- * To fix this issue, we need to break je (2->6 bytes) <-> jmp (5->2 bytes)
- * cycle in the above. In the above example je offset <= 0x7c should work.
- *
- * For other cases, je <-> je needs offset <= 0x7b to avoid no convergence
- * issue. For jmp <-> je and jmp <-> jmp cases, jmp offset <= 0x7c should
- * avoid no convergence issue.
- *
- * Overall, let us limit the positive offset for 8bit cond/uncond jmp insn
- * to maximum 123 (0x7b). This way, the jit pass can eventually converge.
- */
-static bool is_imm8_jmp_offset(int value)
-{
- return value <= 123 && value >= -128;
-}
-
static bool is_simm32(s64 value)
{
return value == (s64)(s32)value;
@@ -2298,6 +2248,17 @@ static void emit_priv_frame_ptr(u8 **pprog, void __percpu *priv_frame_ptr)
#define INSN_SZ_DIFF (((addrs[i] - addrs[i - 1]) - (prog - temp)))
+/*
+ * addrs[] below i are from this pass, addrs[i] and above are from the previous
+ * one. 'moved' is how far back this pass moved the start of the current insn.
+ * With it a jump in either direction is sized by the insns it jumps over, plus
+ * its own previous size for a backward jump, none of which grow.
+ */
+static int jmp_distance(const int *addrs, int i, int off, int moved)
+{
+ return addrs[i + off] - addrs[i] + (off < 0 ? moved : 0);
+}
+
#define __LOAD_TCC_PTR(off) \
emit_ldx(&prog, BPF_DW, BPF_REG_0, BPF_REG_FP, off)
/* mov rax, qword ptr [rbp - rounded_stack_depth - 16] */
@@ -2588,7 +2549,7 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
u64 arena_vm_start = 0, user_vm_start = 0;
void __percpu *priv_stack_ptr;
int i, excnt = 0;
- int ilen, proglen = 0;
+ int ilen, moved, proglen = 0;
u8 *ip, *prog = temp;
u32 stack_depth;
int callee_saved_size;
@@ -2702,6 +2663,7 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
if (rw_image)
memcpy(rw_image + proglen, temp, ilen);
proglen += ilen;
+ moved = addrs[0] - proglen;
addrs[0] = proglen;
prog = temp;
@@ -3747,8 +3709,8 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
default: /* to silence GCC warning */
return -EFAULT;
}
- jmp_offset = addrs[i + insn->off] - addrs[i];
- if (is_imm8_jmp_offset(jmp_offset)) {
+ jmp_offset = jmp_distance(addrs, i, insn->off, moved);
+ if (is_imm8(jmp_offset)) {
if (jmp_padding) {
/* To keep the jmp_offset valid, the extra bytes are
* padded before the jump insn, so we subtract the
@@ -3788,23 +3750,10 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
break;
case BPF_JMP | BPF_JA:
case BPF_JMP32 | BPF_JA:
- if (BPF_CLASS(insn->code) == BPF_JMP) {
- if (insn->off == -1)
- /* -1 jmp instructions will always jump
- * backwards two bytes. Explicitly handling
- * this case avoids wasting too many passes
- * when there are long sequences of replaced
- * dead code.
- */
- jmp_offset = -2;
- else
- jmp_offset = addrs[i + insn->off] - addrs[i];
- } else {
- if (insn->imm == -1)
- jmp_offset = -2;
- else
- jmp_offset = addrs[i + insn->imm] - addrs[i];
- }
+ if (BPF_CLASS(insn->code) == BPF_JMP)
+ jmp_offset = jmp_distance(addrs, i, insn->off, moved);
+ else
+ jmp_offset = jmp_distance(addrs, i, insn->imm, moved);
if (!jmp_offset) {
/*
@@ -3833,7 +3782,7 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
break;
}
emit_jmp:
- if (is_imm8_jmp_offset(jmp_offset)) {
+ if (is_imm8(jmp_offset)) {
if (jmp_padding) {
/* To avoid breaking jmp_offset, the extra bytes
* are padded before the actual jmp insn, so
@@ -3869,7 +3818,7 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
emit_unwind_ret_addr(&prog, ip))
return -EINVAL;
if (seen_exit) {
- jmp_offset = ctx->cleanup_addr - addrs[i];
+ jmp_offset = ctx->cleanup_addr - addrs[i] + moved;
goto emit_jmp;
}
seen_exit = true;
@@ -3930,6 +3879,7 @@ static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *
memcpy(rw_image + proglen, temp, ilen);
}
proglen += ilen;
+ moved = addrs[i] - proglen;
addrs[i] = proglen;
prog = temp;
}
@@ -4967,20 +4917,23 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr
/*
* Before first pass, make a rough estimation of addrs[]
- * each BPF instruction is translated to less than 64 bytes
+ * each BPF instruction is translated to at most BPF_MAX_INSN_SIZE
+ * bytes, so no insn grows in the first pass
*/
for (proglen = 0, i = 0; i <= prog->len; i++) {
- proglen += 64;
+ proglen += BPF_MAX_INSN_SIZE;
addrs[i] = proglen;
}
ctx.cleanup_addr = proglen;
skip_init_addrs:
/*
- * JITed image shrinks with every pass and the loop iterates
- * until the image stops shrinking. Very large BPF programs
- * may converge on the last pass. In such case do one more
- * pass to emit the final image.
+ * Jumps are sized by the insns they jump over, which start at
+ * their maximum size, so no insn grows from one pass to the
+ * next. The loop iterates until the image stops shrinking, at
+ * which point no insn has moved. Very large BPF programs may
+ * converge on the last pass. In such case do one more pass to
+ * emit the final image.
*/
for (pass = 0; pass < MAX_PASSES || image; pass++) {
if (!padding && pass >= PADDING_PASSES)
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH bpf-next v2 2/2] selftests/bpf: Test JIT jumps which change at a repeated image length
2026-10-22 12:04 [PATCH bpf-next v2 0/2] bpf: Fix x86 JIT stopping before jumps converge Mykyta Yatsenko
2026-10-22 12:04 ` [PATCH bpf-next v2 1/2] bpf: Size backward jumps by the insns they jump over Mykyta Yatsenko
@ 2026-10-22 12:04 ` Mykyta Yatsenko
2026-10-22 12:44 ` bot+bpf-ci
2026-10-22 13:21 ` Daniel Borkmann
2026-10-24 4:00 ` [PATCH bpf-next v2 0/2] bpf: Fix x86 JIT stopping before jumps converge patchwork-bot+netdevbpf
2 siblings, 2 replies; 9+ messages in thread
From: Mykyta Yatsenko @ 2026-10-22 12:04 UTC (permalink / raw)
To: bpf, ast, andrii, daniel, kernel-team, eddyz87, memxor; +Cc: Mykyta Yatsenko
From: Mykyta Yatsenko <yatsenko@meta.com>
Cover the case where two JIT passes give the same image length while
jumps still change size, which used to fail with -ENOTSUPP on x86-64.
Signed-off-by: Mykyta Yatsenko <yatsenko@meta.com>
---
tools/testing/selftests/bpf/prog_tests/jit_insns.c | 59 ++++++++++++++++++++++
1 file changed, 59 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/jit_insns.c b/tools/testing/selftests/bpf/prog_tests/jit_insns.c
index dabefdb8a353..52bffe27199b 100644
--- a/tools/testing/selftests/bpf/prog_tests/jit_insns.c
+++ b/tools/testing/selftests/bpf/prog_tests/jit_insns.c
@@ -2881,6 +2881,63 @@ static void test_convergence(void)
}
}
+/*
+ * On x86-64 a backward jump sized from its own previous-pass address grew
+ * here while others shrank, so two passes gave the same image length with
+ * jumps still changing. The fillers are 7 and 2 bytes of native code, the
+ * conditional jumps are never taken.
+ */
+static void test_same_len_not_converged(void)
+{
+ struct bpf_insn insns[] = {
+ BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, IN0),
+ BPF_LDX_MEM(BPF_DW, BPF_REG_4, BPF_REG_1, IN1),
+ BPF_JMP_IMM(BPF_JA, 0, 0, 1),
+ BPF_JMP_IMM(BPF_JA, 0, 0, 29),
+ BPF_JMP32_REG(BPF_JEQ, BPF_REG_0, BPF_REG_4, 20),
+ BPF_JMP32_REG(BPF_JEQ, BPF_REG_0, BPF_REG_4, -3),
+ BPF_JMP32_REG(BPF_JEQ, BPF_REG_0, BPF_REG_4, 22),
+ BPF_JMP32_REG(BPF_JEQ, BPF_REG_0, BPF_REG_4, 2),
+ BPF_MOV32_IMM(BPF_REG_2, 0),
+ BPF_JMP32_REG(BPF_JEQ, BPF_REG_0, BPF_REG_4, 23),
+ BPF_MOV64_IMM(BPF_REG_2, -1),
+ BPF_MOV64_IMM(BPF_REG_2, -1),
+ BPF_MOV64_IMM(BPF_REG_2, -1),
+ BPF_MOV64_IMM(BPF_REG_2, -1),
+ BPF_MOV64_IMM(BPF_REG_2, -1),
+ BPF_MOV64_IMM(BPF_REG_2, -1),
+ BPF_MOV64_IMM(BPF_REG_2, -1),
+ BPF_MOV64_IMM(BPF_REG_2, -1),
+ BPF_MOV64_IMM(BPF_REG_2, -1),
+ BPF_MOV64_IMM(BPF_REG_2, -1),
+ BPF_MOV64_IMM(BPF_REG_2, -1),
+ BPF_MOV64_IMM(BPF_REG_2, -1),
+ BPF_MOV64_IMM(BPF_REG_2, -1),
+ BPF_MOV32_IMM(BPF_REG_2, 0),
+ BPF_JMP_IMM(BPF_JA, 0, 0, -22),
+ BPF_MOV64_IMM(BPF_REG_2, -1),
+ BPF_MOV32_IMM(BPF_REG_2, 0),
+ BPF_MOV32_IMM(BPF_REG_2, 0),
+ BPF_JMP32_REG(BPF_JEQ, BPF_REG_0, BPF_REG_4, 3),
+ BPF_MOV32_IMM(BPF_REG_2, 0),
+ BPF_JMP32_REG(BPF_JEQ, BPF_REG_0, BPF_REG_4, 2),
+ BPF_JMP_IMM(BPF_JA, 0, 0, -22),
+ BPF_MOV32_IMM(BPF_REG_2, 0),
+ BPF_ALU64_IMM(BPF_ADD, BPF_REG_0, 10),
+ BPF_STX_MEM(BPF_DW, BPF_REG_1, BPF_REG_0, RES),
+ BPF_MOV64_IMM(BPF_REG_0, 0),
+ BPF_EXIT_INSN(),
+ };
+ int fd;
+
+ fd = load_prog(insns, ARRAY_SIZE(insns), "same len not converged");
+ if (fd < 0)
+ return;
+ ASSERT_GT(jited_len(fd), 0, "same len not converged jited");
+ prog_run(fd, 1, 2, 11, "same len not converged");
+ close(fd);
+}
+
/*
* Calls into a subprogram with all five arguments, which clobbers a subset
* of r6-r9 and uses the deepest stack slot the budget leaves, directly or
@@ -3087,6 +3144,8 @@ void test_jit_insns(void)
test_max_jmp();
if (test__start_subtest("convergence"))
test_convergence();
+ if (test__start_subtest("same_len_not_converged"))
+ test_same_len_not_converged();
if (test__start_subtest("calls"))
test_calls();
if (test__start_subtest("helper_calls"))
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH bpf-next v2 2/2] selftests/bpf: Test JIT jumps which change at a repeated image length
2026-10-22 12:04 ` [PATCH bpf-next v2 2/2] selftests/bpf: Test JIT jumps which change at a repeated image length Mykyta Yatsenko
@ 2026-10-22 12:44 ` bot+bpf-ci
2026-10-22 13:05 ` Mykyta Yatsenko
2026-10-22 13:21 ` Daniel Borkmann
1 sibling, 1 reply; 9+ messages in thread
From: bot+bpf-ci @ 2026-10-22 12:44 UTC (permalink / raw)
To: mykyta.yatsenko5, bpf, ast, andrii, daniel, kernel-team, eddyz87,
memxor
Cc: yatsenko, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song,
mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2549 bytes --]
> selftests/bpf: Test JIT jumps which change at a repeated image length
TL;DR: Test issue: the new jit_insns subtest asserts the program was JITed, so
it may fail instead of skipping when no JIT is available (JIT disabled or
unsupported architecture); suggests gating on is_jit_enabled().
> diff --git a/tools/testing/selftests/bpf/prog_tests/jit_insns.c b/tools/testing/selftests/bpf/prog_tests/jit_insns.c
> --- a/tools/testing/selftests/bpf/prog_tests/jit_insns.c
> +++ b/tools/testing/selftests/bpf/prog_tests/jit_insns.c
[ ... ]
> @@ -2881,6 +2881,63 @@ static void test_convergence(void)
> }
> }
>
> +/*
> + * On x86-64 a backward jump sized from its own previous-pass address grew
> + * here while others shrank, so two passes gave the same image length with
> + * jumps still changing. The fillers are 7 and 2 bytes of native code, the
> + * conditional jumps are never taken.
> + */
> +static void test_same_len_not_converged(void)
> +{
[ ... ]
> + int fd;
> +
> + fd = load_prog(insns, ARRAY_SIZE(insns), "same len not converged");
> + if (fd < 0)
> + return;
> + ASSERT_GT(jited_len(fd), 0, "same len not converged jited");
This isn't a bug, but should this assertion be skipped when no JIT is
available?
jited_len() returns 0 when the program was not JITed, and test_jit_insns()
itself is not gated on is_jit_enabled(); only serial_test_jit_insns_blinded()
is, and it calls test__skip() when there is no JIT.
On a kernel without CONFIG_BPF_JIT_ALWAYS_ON and with
net.core.bpf_jit_enable=0, or on an architecture without an eBPF JIT,
__bpf_prog_select_runtime() falls back to the interpreter. The load
succeeds, jited_prog_len is 0, the program still runs correctly, and this
subtest fails on ASSERT_GT(jited_len(fd), 0, ...).
Other selftests treat a missing JIT as a condition to skip or ignore, for
example bpf_obj_id.c:
env.jit_enabled && !prog_infos[i].jited_prog_len
and btf_rust.c, which calls test__skip() when !info.jited_prog_len.
Would something like this keep the check for JIT-enabled setups, where it
catches a silent fallback to the interpreter, without failing when no JIT is
configured?
if (is_jit_enabled())
ASSERT_GT(jited_len(fd), 0, "same len not converged jited");
> + prog_run(fd, 1, 2, 11, "same len not converged");
> + close(fd);
> +}
> +
[ ... ]
---
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/39777661721
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH bpf-next v2 2/2] selftests/bpf: Test JIT jumps which change at a repeated image length
2026-10-22 12:44 ` bot+bpf-ci
@ 2026-10-22 13:05 ` Mykyta Yatsenko
2026-10-22 13:24 ` Daniel Borkmann
0 siblings, 1 reply; 9+ messages in thread
From: Mykyta Yatsenko @ 2026-10-22 13:05 UTC (permalink / raw)
To: bot+bpf-ci, bpf, ast, andrii, daniel, kernel-team, eddyz87,
memxor
Cc: yatsenko, martin.lau, yonghong.song, mason, ihor.solodrai
On 10/22/26 1:44 PM, bot+bpf-ci@kernel.org wrote:
>> selftests/bpf: Test JIT jumps which change at a repeated image length
>
> TL;DR: Test issue: the new jit_insns subtest asserts the program was JITed, so
> it may fail instead of skipping when no JIT is available (JIT disabled or
> unsupported architecture); suggests gating on is_jit_enabled().
>
>> diff --git a/tools/testing/selftests/bpf/prog_tests/jit_insns.c b/tools/testing/selftests/bpf/prog_tests/jit_insns.c
>> --- a/tools/testing/selftests/bpf/prog_tests/jit_insns.c
>> +++ b/tools/testing/selftests/bpf/prog_tests/jit_insns.c
>
> [ ... ]
>
>> @@ -2881,6 +2881,63 @@ static void test_convergence(void)
>> }
>> }
>>
>> +/*
>> + * On x86-64 a backward jump sized from its own previous-pass address grew
>> + * here while others shrank, so two passes gave the same image length with
>> + * jumps still changing. The fillers are 7 and 2 bytes of native code, the
>> + * conditional jumps are never taken.
>> + */
>> +static void test_same_len_not_converged(void)
>> +{
>
> [ ... ]
>
>> + int fd;
>> +
>> + fd = load_prog(insns, ARRAY_SIZE(insns), "same len not converged");
>> + if (fd < 0)
>> + return;
>> + ASSERT_GT(jited_len(fd), 0, "same len not converged jited");
>
> This isn't a bug, but should this assertion be skipped when no JIT is
> available?
>
We should probably skip this entire test file if JIT is off. Out of scope
of this change though.
> jited_len() returns 0 when the program was not JITed, and test_jit_insns()
> itself is not gated on is_jit_enabled(); only serial_test_jit_insns_blinded()
> is, and it calls test__skip() when there is no JIT.
>
> On a kernel without CONFIG_BPF_JIT_ALWAYS_ON and with
> net.core.bpf_jit_enable=0, or on an architecture without an eBPF JIT,
> __bpf_prog_select_runtime() falls back to the interpreter. The load
> succeeds, jited_prog_len is 0, the program still runs correctly, and this
> subtest fails on ASSERT_GT(jited_len(fd), 0, ...).
>
> Other selftests treat a missing JIT as a condition to skip or ignore, for
> example bpf_obj_id.c:
>
> env.jit_enabled && !prog_infos[i].jited_prog_len
>
> and btf_rust.c, which calls test__skip() when !info.jited_prog_len.
>
> Would something like this keep the check for JIT-enabled setups, where it
> catches a silent fallback to the interpreter, without failing when no JIT is
> configured?
>
> if (is_jit_enabled())
> ASSERT_GT(jited_len(fd), 0, "same len not converged jited");
>
>> + prog_run(fd, 1, 2, 11, "same len not converged");
>> + close(fd);
>> +}
>> +
>
> [ ... ]
>
>
> ---
> 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/39777661721
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH bpf-next v2 1/2] bpf: Size backward jumps by the insns they jump over
2026-10-22 12:04 ` [PATCH bpf-next v2 1/2] bpf: Size backward jumps by the insns they jump over Mykyta Yatsenko
@ 2026-10-22 13:18 ` Daniel Borkmann
0 siblings, 0 replies; 9+ messages in thread
From: Daniel Borkmann @ 2026-10-22 13:18 UTC (permalink / raw)
To: Mykyta Yatsenko, bpf, ast, andrii, kernel-team, eddyz87, memxor
Cc: Mykyta Yatsenko
On 10/22/26 2:04 PM, Mykyta Yatsenko wrote:
> From: Mykyta Yatsenko <yatsenko@meta.com>
>
> A backward jump is sized from its target's current-pass address and its
> own previous-pass address, so it can grow while other jumps shrink. Two
> passes may then give the same image length with jumps still changing,
> which ends in "bpf_jit: fatal error", and a jump growing in a padding
> pass ends in "unexpected jmp_cond padding".
>
> Shift the jump's previous-pass address by how far the current pass has
> moved it, so a backward jump is sized by the insns it jumps over, like
> a forward one. Also start every insn at BPF_MAX_INSN_SIZE instead of 64,
> so none can grow in the first pass. Then no insn grows from one pass to
> the next, and a repeated image length means nothing moved. The 123
> limit on imm8 offsets worked around that growth and is dropped. The
> ja -1 special case is subsumed, such a jump is now imm8 from the first
> pass.
>
> Suggested-by: Alexei Starovoitov <ast@kernel.org>
> Signed-off-by: Mykyta Yatsenko <yatsenko@meta.com>
small nit $subj should be "bpf, x86: [...]"
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH bpf-next v2 2/2] selftests/bpf: Test JIT jumps which change at a repeated image length
2026-10-22 12:04 ` [PATCH bpf-next v2 2/2] selftests/bpf: Test JIT jumps which change at a repeated image length Mykyta Yatsenko
2026-10-22 12:44 ` bot+bpf-ci
@ 2026-10-22 13:21 ` Daniel Borkmann
1 sibling, 0 replies; 9+ messages in thread
From: Daniel Borkmann @ 2026-10-22 13:21 UTC (permalink / raw)
To: Mykyta Yatsenko, bpf, ast, andrii, kernel-team, eddyz87, memxor
Cc: Mykyta Yatsenko
On 10/22/26 2:04 PM, Mykyta Yatsenko wrote:
> From: Mykyta Yatsenko <yatsenko@meta.com>
>
> Cover the case where two JIT passes give the same image length while
> jumps still change size, which used to fail with -ENOTSUPP on x86-64.
>
> Signed-off-by: Mykyta Yatsenko <yatsenko@meta.com>
Acked-by: Daniel Borkmann <daniel@iogearbox.net>
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH bpf-next v2 2/2] selftests/bpf: Test JIT jumps which change at a repeated image length
2026-10-22 13:05 ` Mykyta Yatsenko
@ 2026-10-22 13:24 ` Daniel Borkmann
0 siblings, 0 replies; 9+ messages in thread
From: Daniel Borkmann @ 2026-10-22 13:24 UTC (permalink / raw)
To: Mykyta Yatsenko, bot+bpf-ci, bpf, ast, andrii, kernel-team,
eddyz87, memxor
Cc: yatsenko, martin.lau, yonghong.song, mason, ihor.solodrai
On 10/22/26 3:05 PM, Mykyta Yatsenko wrote:
> On 10/22/26 1:44 PM, bot+bpf-ci@kernel.org wrote:
>>> selftests/bpf: Test JIT jumps which change at a repeated image length
>>
>> TL;DR: Test issue: the new jit_insns subtest asserts the program was JITed, so
>> it may fail instead of skipping when no JIT is available (JIT disabled or
>> unsupported architecture); suggests gating on is_jit_enabled().
>>
>>> diff --git a/tools/testing/selftests/bpf/prog_tests/jit_insns.c b/tools/testing/selftests/bpf/prog_tests/jit_insns.c
>>> --- a/tools/testing/selftests/bpf/prog_tests/jit_insns.c
>>> +++ b/tools/testing/selftests/bpf/prog_tests/jit_insns.c
>>
>> [ ... ]
>>
>>> @@ -2881,6 +2881,63 @@ static void test_convergence(void)
>>> }
>>> }
>>>
>>> +/*
>>> + * On x86-64 a backward jump sized from its own previous-pass address grew
>>> + * here while others shrank, so two passes gave the same image length with
>>> + * jumps still changing. The fillers are 7 and 2 bytes of native code, the
>>> + * conditional jumps are never taken.
>>> + */
>>> +static void test_same_len_not_converged(void)
>>> +{
>>
>> [ ... ]
>>
>>> + int fd;
>>> +
>>> + fd = load_prog(insns, ARRAY_SIZE(insns), "same len not converged");
>>> + if (fd < 0)
>>> + return;
>>> + ASSERT_GT(jited_len(fd), 0, "same len not converged jited");
>>
>> This isn't a bug, but should this assertion be skipped when no JIT is
>> available?
>
> We should probably skip this entire test file if JIT is off. Out of scope
> of this change though.
Agree, out of scope in here.
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH bpf-next v2 0/2] bpf: Fix x86 JIT stopping before jumps converge
2026-10-22 12:04 [PATCH bpf-next v2 0/2] bpf: Fix x86 JIT stopping before jumps converge Mykyta Yatsenko
2026-10-22 12:04 ` [PATCH bpf-next v2 1/2] bpf: Size backward jumps by the insns they jump over Mykyta Yatsenko
2026-10-22 12:04 ` [PATCH bpf-next v2 2/2] selftests/bpf: Test JIT jumps which change at a repeated image length Mykyta Yatsenko
@ 2026-10-24 4:00 ` patchwork-bot+netdevbpf
2 siblings, 0 replies; 9+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-10-24 4:00 UTC (permalink / raw)
To: Mykyta Yatsenko
Cc: bpf, ast, andrii, daniel, kernel-team, eddyz87, memxor, yatsenko
Hello:
This series was applied to bpf/bpf.git (master)
by Alexei Starovoitov <ast@kernel.org>:
On Thu, 22 Oct 2026 05:04:54 -0700 you wrote:
> The x86 JIT stops its sizing passes once two passes give the same image
> length. A backward jump can grow while other jumps shrink, so the length
> may repeat with jump sizes still changing. The final pass then emits code
> of a different size than was sized, hits "bpf_jit: fatal error" and the
> program fails to load with -ENOTSUPP.
>
> Patch 1 sizes backward jumps by the insns they jump over, so no insn
> grows between passes and a repeated length means nothing moved.
> Patch 2 adds a selftest which fails to load without the fix.
>
> [...]
Here is the summary with links:
- [bpf-next,v2,1/2] bpf: Size backward jumps by the insns they jump over
https://git.kernel.org/bpf/bpf/c/ebf0ab370bcd
- [bpf-next,v2,2/2] selftests/bpf: Test JIT jumps which change at a repeated image length
https://git.kernel.org/bpf/bpf/c/a938f51d3522
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-10-24 4:00 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-22 12:04 [PATCH bpf-next v2 0/2] bpf: Fix x86 JIT stopping before jumps converge Mykyta Yatsenko
2026-10-22 12:04 ` [PATCH bpf-next v2 1/2] bpf: Size backward jumps by the insns they jump over Mykyta Yatsenko
2026-10-22 13:18 ` Daniel Borkmann
2026-10-22 12:04 ` [PATCH bpf-next v2 2/2] selftests/bpf: Test JIT jumps which change at a repeated image length Mykyta Yatsenko
2026-10-22 12:44 ` bot+bpf-ci
2026-10-22 13:05 ` Mykyta Yatsenko
2026-10-22 13:24 ` Daniel Borkmann
2026-10-22 13:21 ` Daniel Borkmann
2026-10-24 4:00 ` [PATCH bpf-next v2 0/2] bpf: Fix x86 JIT stopping before jumps converge patchwork-bot+netdevbpf
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox