* [PATCH bpf v2 0/2] bpf, x86: Fix i386 JIT signed ALU32 DIV/MOD and MOVSX
@ 2026-11-13 17:23 Nicholas Dudar
2026-11-13 17:23 ` [PATCH bpf v2 1/2] bpf, x86: Fix signed division and modulo in the i386 JIT Nicholas Dudar
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Nicholas Dudar @ 2026-11-13 17:23 UTC (permalink / raw)
To: Wang YanQing, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
Yonghong Song
Cc: Martin KaFai Lau, Song Liu, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, H. Peter Anvin, bpf, linux-kernel, visitorckw,
Nicholas Dudar
The i386 JIT accepts signed ALU32 DIV/MOD and register MOVSX, but
ignores the offset that selects them and emits an unsigned div and a
plain move. The results differ from RFC9669 and the interpreter, and a
verifier-accepted program can use values outside the range proved by
the verifier. In i386 VM tests, three verifier-accepted programs
supplied helper lengths beyond the verifier's bounds before these
fixes, and the helper wrote that many bytes. With the fixes the lengths
stayed within the bounds. The test programs are not included here and
are available to maintainers on request.
The series is meant as a minimal fix that closes this gap between the
verifier and the JIT. It makes the two instructions the JIT already
accepts produce the values the verifier assumes and adds no new
instruction support.
Tested on i386 under KVM. vmtest.sh has no i386 target and test_progs
does not build in full as a 32-bit binary, so it was built with
jit_insns, verifier_sdiv and verifier_movsx only, with a strtol() to
strtoll() change in test_loader.c. The commands and output are in the
patches. The six jit_insns subtests that still fail (arena_zero_off,
cast_to_user, staggered_jumps, acq_rel, probe_mem, shift_counts) fail
without the series as well.
On the unpatched kernel the unprivileged subtest "MOV32SX, S8,
range_check" hangs in the kernel: the wrong value takes a branch the
verifier proved dead, which for unprivileged programs is a jump to
itself. The unprivileged verifier_movsx subtests were therefore run
with both patches only. There it passes, and two others differ from the
expected xlated text by an additional zero extension on i386.
Claude Mythos 5.2 assisted with this patch series.
v1 -> v2:
- Dropped MEMSX, 32-bit-offset unconditional jumps, unconditional
byte swaps and the load-emitter refactor
- Added the signed DIV/MOD change, which was posted on its own [1]
and is superseded by this series; the code is unchanged
- Sent as fixes for bpf with Fixes tags instead of new support for
bpf-next, since the JIT already accepts both instructions and
miscompiles them
- Tested with jit_insns and the verifier tests of test_progs
[1] https://lore.kernel.org/bpf/20260731002950.179573-1-main.kalliope@gmail.com/
v1: https://lore.kernel.org/bpf/20260915021338.1520964-1-main.kalliope@gmail.com/
---
Nicholas Dudar (2):
bpf, x86: Fix signed division and modulo in the i386 JIT
bpf, x86: Fix sign-extension moves in the i386 JIT
arch/x86/net/bpf_jit_comp32.c | 76 ++++++++++++++++++++++++++++++++++++++-----
1 file changed, 68 insertions(+), 8 deletions(-)
---
base-commit: b7cc45994c3f9c5f24397fcc0251f6c23bff9bd8
change-id: 20261112-i386_bpf_jit_wrong_code_20261028-7246b0231fce
Best regards,
--
Nicholas Dudar <main.kalliope@gmail.com>
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH bpf v2 1/2] bpf, x86: Fix signed division and modulo in the i386 JIT
2026-11-13 17:23 [PATCH bpf v2 0/2] bpf, x86: Fix i386 JIT signed ALU32 DIV/MOD and MOVSX Nicholas Dudar
@ 2026-11-13 17:23 ` Nicholas Dudar
2026-11-13 17:23 ` [PATCH bpf v2 2/2] bpf, x86: Fix sign-extension moves " Nicholas Dudar
2026-11-13 17:35 ` [PATCH bpf v2 0/2] bpf, x86: Fix i386 JIT signed ALU32 DIV/MOD and MOVSX Kuan-Wei Chiu
2 siblings, 0 replies; 5+ messages in thread
From: Nicholas Dudar @ 2026-11-13 17:23 UTC (permalink / raw)
To: Wang YanQing, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
Yonghong Song
Cc: Martin KaFai Lau, Song Liu, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, H. Peter Anvin, bpf, linux-kernel, visitorckw,
Nicholas Dudar
RFC9669 defines SDIV and SMOD, encoded as DIV and MOD with an offset of
1, as signed division and modulo, which the interpreter implements. The
i386 JIT ignores the offset and emits an unsigned div for both, so for
ALU32 -1 s/ 2 returns 0x7fffffff instead of 0. A verifier-accepted
program can therefore use values outside the range proved by the
verifier.
Emit cdq and idiv when the offset is 1. The verifier has rejected or
patched out a zero divisor and INT_MIN s/ -1 before the JIT runs, so
idiv cannot trap. ALU64 DIV and MOD are still left to the interpreter.
On an unpatched i386 kernel, an SDIV and an SMOD test program each
passed a helper a length beyond the verifier's bound. Both stayed
within it after this change.
Found by manually comparing the signed DIV/MOD handling of the BPF
JITs.
On i386 under qemu, with test_progs built as a 32-bit binary from the
same tree:
# ./test_progs -t verifier_movsx,verifier_sdiv,jit_insns \
-d jit_insns_blinded,'verifier_movsx/*@unpriv' -w 600
[...]
#191/3 jit_insns/alu_imm:OK
#191/4 jit_insns/alu_reg:OK
#191/5 jit_insns/alu_imm_regs:OK
#191/6 jit_insns/div_const:OK
#191/7 jit_insns/alu_reg_pairs:OK
#191/8 jit_insns/alu_edges:OK
[...]
#650 verifier_sdiv:OK (SKIP: 16/144)
Summary: 1/167 PASSED, 16 SKIPPED, 2/11 FAILED
# ./test_progs -t jit_insns_blinded -w 600
[...]
#192 jit_insns_blinded:OK
Summary: 1/14 PASSED, 0 SKIPPED, 0/0 FAILED
Without this change, the six jit_insns subtests above, four of
jit_insns_blinded and 50 of verifier_sdiv fail. The 11 failures left
are the five MOVSX ones fixed by the next patch and six jit_insns
subtests that fail without the series as well.
Fixes: ec0e2da95f72 ("bpf: Support new signed div/mod instructions.")
Signed-off-by: Nicholas Dudar <main.kalliope@gmail.com>
---
arch/x86/net/bpf_jit_comp32.c | 25 +++++++++++++++++--------
1 file changed, 17 insertions(+), 8 deletions(-)
diff --git a/arch/x86/net/bpf_jit_comp32.c b/arch/x86/net/bpf_jit_comp32.c
index 852baf2e4db4..012161da3e82 100644
--- a/arch/x86/net/bpf_jit_comp32.c
+++ b/arch/x86/net/bpf_jit_comp32.c
@@ -432,11 +432,13 @@ static inline void emit_ia32_to_be_r64(const u8 dst[], s32 val,
* ALU operation (32 bit)
* dst = dst (div|mod) src
*/
-static inline void emit_ia32_div_mod_r(const u8 op, const u8 dst, const u8 src,
- bool dstk, bool sstk, u8 **pprog)
+static inline void emit_ia32_div_mod_r(const struct bpf_insn *insn, const u8 dst,
+ const u8 src, bool dstk, bool sstk, u8 **pprog)
{
u8 *prog = *pprog;
int cnt = 0;
+ const u8 op = BPF_OP(insn->code);
+ const bool is_signed = (insn->off == 1);
if (sstk)
/* mov ecx,dword ptr [ebp+off] */
@@ -454,10 +456,17 @@ static inline void emit_ia32_div_mod_r(const u8 op, const u8 dst, const u8 src,
/* mov eax,dst */
EMIT2(0x8B, add_2reg(0xC0, dst, IA32_EAX));
- /* xor edx,edx */
- EMIT2(0x31, add_2reg(0xC0, IA32_EDX, IA32_EDX));
- /* div ecx */
- EMIT2(0xF7, add_1reg(0xF0, IA32_ECX));
+ if (is_signed) {
+ /* cdq */
+ EMIT1(0x99);
+ /* idiv ecx */
+ EMIT2(0xF7, add_1reg(0xF8, IA32_ECX));
+ } else {
+ /* xor edx,edx */
+ EMIT2(0x31, add_2reg(0xC0, IA32_EDX, IA32_EDX));
+ /* div ecx */
+ EMIT2(0xF7, add_1reg(0xF0, IA32_ECX));
+ }
if (op == BPF_MOD) {
if (dstk)
@@ -1795,14 +1804,14 @@ static int do_jit(struct bpf_prog *bpf_prog, int *addrs, u8 *image,
case BPF_ALU | BPF_MOD | BPF_X:
switch (BPF_SRC(code)) {
case BPF_X:
- emit_ia32_div_mod_r(BPF_OP(code), dst_lo,
+ emit_ia32_div_mod_r(insn, dst_lo,
src_lo, dstk, sstk, &prog);
break;
case BPF_K:
/* mov ecx,imm32*/
EMIT2_off32(0xC7, add_1reg(0xC0, IA32_ECX),
imm32);
- emit_ia32_div_mod_r(BPF_OP(code), dst_lo,
+ emit_ia32_div_mod_r(insn, dst_lo,
IA32_ECX, dstk, false,
&prog);
break;
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH bpf v2 2/2] bpf, x86: Fix sign-extension moves in the i386 JIT
2026-11-13 17:23 [PATCH bpf v2 0/2] bpf, x86: Fix i386 JIT signed ALU32 DIV/MOD and MOVSX Nicholas Dudar
2026-11-13 17:23 ` [PATCH bpf v2 1/2] bpf, x86: Fix signed division and modulo in the i386 JIT Nicholas Dudar
@ 2026-11-13 17:23 ` Nicholas Dudar
2026-11-13 17:35 ` [PATCH bpf v2 0/2] bpf, x86: Fix i386 JIT signed ALU32 DIV/MOD and MOVSX Kuan-Wei Chiu
2 siblings, 0 replies; 5+ messages in thread
From: Nicholas Dudar @ 2026-11-13 17:23 UTC (permalink / raw)
To: Wang YanQing, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
Yonghong Song
Cc: Martin KaFai Lau, Song Liu, Jiri Olsa, Emil Tsalapatis,
Ihor Solodrai, H. Peter Anvin, bpf, linux-kernel, visitorckw,
Nicholas Dudar
RFC9669 defines MOVSX, encoded as a register MOV with an offset of 8,
16 or 32, as dst = (s8,s16,s32)src, which the interpreter implements.
The i386 JIT ignores the offset and emits a plain move, so r0 = (s8)r0
with 0x80 returns 128 instead of -128. A verifier-accepted program can
therefore use values outside the range proved by the verifier.
Sign-extend the low 8, 16 or 32 bits of the source. For ALU64, fill the
high word with the sign; for ALU32, zero it unless the verifier inserts
the zero extension.
On an unpatched i386 kernel, a MOVSX test program passed a helper a
length beyond the verifier's bound. It stayed within the bound after
this change.
Found in a follow-up sweep of the JIT sources for instructions that are
selected by the offset field, and by reading the i386 MOV handler.
On i386 under qemu, with test_progs built as a 32-bit binary from the
same tree:
# ./test_progs -t verifier_movsx,verifier_sdiv,jit_insns \
-d jit_insns_blinded,'verifier_movsx/*@unpriv' -w 600
[...]
#191/28 jit_insns/movsx:OK
[...]
#649/1 verifier_movsx/MOV32SX, S8:OK
#649/3 verifier_movsx/MOV32SX, S16:OK
#649/5 verifier_movsx/MOV64SX, S8:OK
#649/7 verifier_movsx/MOV64SX, S16:OK
[...]
#649 verifier_movsx:OK
Summary: 2/172 PASSED, 16 SKIPPED, 1/6 FAILED
Without this change, the five subtests above fail. The six failures
left are jit_insns subtests that fail without the series as well.
Fixes: 8100928c8814 ("bpf: Support new sign-extension mov insns")
Signed-off-by: Nicholas Dudar <main.kalliope@gmail.com>
---
arch/x86/net/bpf_jit_comp32.c | 51 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 51 insertions(+)
diff --git a/arch/x86/net/bpf_jit_comp32.c b/arch/x86/net/bpf_jit_comp32.c
index 012161da3e82..2ed3f8aad8d5 100644
--- a/arch/x86/net/bpf_jit_comp32.c
+++ b/arch/x86/net/bpf_jit_comp32.c
@@ -266,6 +266,51 @@ static inline void emit_ia32_mov_r64(const bool is64, const u8 dst[],
emit_ia32_mov_i(dst_hi, 0, dstk, pprog);
}
+/* dst = sign_extend(src, insn->off) */
+static inline void emit_ia32_movsx_r64(const struct bpf_insn *insn, u8 **pprog,
+ const struct bpf_prog_aux *aux)
+{
+ const u8 *dst = bpf2ia32[insn->dst_reg];
+ const u8 *src = bpf2ia32[insn->src_reg];
+ bool dstk = insn->dst_reg != BPF_REG_AX;
+ bool sstk = insn->src_reg != BPF_REG_AX;
+ bool is64 = BPF_CLASS(insn->code) == BPF_ALU64;
+ u8 *prog = *pprog;
+ int cnt = 0;
+
+ if (sstk)
+ /* mov eax,dword ptr [ebp+off] */
+ EMIT3(0x8B, add_2reg(0x40, IA32_EBP, IA32_EAX),
+ STACK_VAR(src_lo));
+ else
+ /* mov eax,src_lo */
+ EMIT2(0x89, add_2reg(0xC0, IA32_EAX, src_lo));
+
+ switch (insn->off) {
+ case 8:
+ /* movsx eax,al */
+ EMIT3(0x0F, 0xBE, 0xC0);
+ break;
+ case 16:
+ /* movsx eax,ax */
+ EMIT3(0x0F, 0xBF, 0xC0);
+ break;
+ case 32:
+ /* EAX already holds the low word; CDQ supplies the high word. */
+ break;
+ }
+
+ emit_ia32_mov_r(dst_lo, IA32_EAX, dstk, false, &prog);
+ if (is64) {
+ EMIT1(0x99); /* cdq */
+ emit_ia32_mov_r(dst_hi, IA32_EDX, dstk, false, &prog);
+ } else if (!aux->verifier_zext) {
+ emit_ia32_mov_i(dst_hi, 0, dstk, &prog);
+ }
+
+ *pprog = prog;
+}
+
/* Sign extended move */
static inline void emit_ia32_mov_i64(const bool is64, const u8 dst[],
const u32 val, bool dstk, u8 **pprog)
@@ -1706,6 +1751,12 @@ static int do_jit(struct bpf_prog *bpf_prog, int *addrs, u8 *image,
emit_ia32_mov_i(dst_hi, 0, dstk, &prog);
break;
}
+ if (insn->off == 8 || insn->off == 16 ||
+ (is64 && insn->off == 32)) {
+ emit_ia32_movsx_r64(insn, &prog,
+ bpf_prog->aux);
+ break;
+ }
emit_ia32_mov_r64(is64, dst, src, dstk, sstk,
&prog, bpf_prog->aux);
break;
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH bpf v2 0/2] bpf, x86: Fix i386 JIT signed ALU32 DIV/MOD and MOVSX
2026-11-13 17:23 [PATCH bpf v2 0/2] bpf, x86: Fix i386 JIT signed ALU32 DIV/MOD and MOVSX Nicholas Dudar
2026-11-13 17:23 ` [PATCH bpf v2 1/2] bpf, x86: Fix signed division and modulo in the i386 JIT Nicholas Dudar
2026-11-13 17:23 ` [PATCH bpf v2 2/2] bpf, x86: Fix sign-extension moves " Nicholas Dudar
@ 2026-11-13 17:35 ` Kuan-Wei Chiu
2026-11-13 17:40 ` Nicholas Dudar
2 siblings, 1 reply; 5+ messages in thread
From: Kuan-Wei Chiu @ 2026-11-13 17:35 UTC (permalink / raw)
To: Nicholas Dudar
Cc: Wang YanQing, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
Yonghong Song, Martin KaFai Lau, Song Liu, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, H. Peter Anvin, bpf,
linux-kernel
On Fri, Nov 13, 2026 at 12:23:41PM -0500, Nicholas Dudar wrote:
> The i386 JIT accepts signed ALU32 DIV/MOD and register MOVSX, but
> ignores the offset that selects them and emits an unsigned div and a
> plain move. The results differ from RFC9669 and the interpreter, and a
> verifier-accepted program can use values outside the range proved by
> the verifier. In i386 VM tests, three verifier-accepted programs
> supplied helper lengths beyond the verifier's bounds before these
> fixes, and the helper wrote that many bytes. With the fixes the lengths
> stayed within the bounds. The test programs are not included here and
> are available to maintainers on request.
>
> The series is meant as a minimal fix that closes this gap between the
> verifier and the JIT. It makes the two instructions the JIT already
> accepts produce the values the verifier assumes and adds no new
> instruction support.
>
> Tested on i386 under KVM. vmtest.sh has no i386 target and test_progs
> does not build in full as a 32-bit binary, so it was built with
> jit_insns, verifier_sdiv and verifier_movsx only, with a strtol() to
> strtoll() change in test_loader.c. The commands and output are in the
> patches. The six jit_insns subtests that still fail (arena_zero_off,
> cast_to_user, staggered_jumps, acq_rel, probe_mem, shift_counts) fail
> without the series as well.
>
> On the unpatched kernel the unprivileged subtest "MOV32SX, S8,
> range_check" hangs in the kernel: the wrong value takes a branch the
> verifier proved dead, which for unprivileged programs is a jump to
> itself. The unprivileged verifier_movsx subtests were therefore run
> with both patches only. There it passes, and two others differ from the
> expected xlated text by an additional zero extension on i386.
>
> Claude Mythos 5.2 assisted with this patch series.
>
> v1 -> v2:
> - Dropped MEMSX, 32-bit-offset unconditional jumps, unconditional
> byte swaps and the load-emitter refactor
> - Added the signed DIV/MOD change, which was posted on its own [1]
> and is superseded by this series; the code is unchanged
> - Sent as fixes for bpf with Fixes tags instead of new support for
> bpf-next, since the JIT already accepts both instructions and
> miscompiles them
> - Tested with jit_insns and the verifier tests of test_progs
>
> [1] https://lore.kernel.org/bpf/20260731002950.179573-1-main.kalliope@gmail.com/
>
> v1: https://lore.kernel.org/bpf/20260915021338.1520964-1-main.kalliope@gmail.com/
> ---
> Nicholas Dudar (2):
> bpf, x86: Fix signed division and modulo in the i386 JIT
> bpf, x86: Fix sign-extension moves in the i386 JIT
>
> arch/x86/net/bpf_jit_comp32.c | 76 ++++++++++++++++++++++++++++++++++++++-----
> 1 file changed, 68 insertions(+), 8 deletions(-)
IIRC Alexei mentioned previously that he didn't think anyone still
cared about i386 JIT, so the i386 JIT patches were not merged?
Now we are seeing more i386 JIT patches. If we really think no one
cares about this platform anymore, maybe we should remove it to avoid
receiving more patches that we don't plan to merge?
Regards,
Kuan-Wei
> ---
> base-commit: b7cc45994c3f9c5f24397fcc0251f6c23bff9bd8
> change-id: 20261112-i386_bpf_jit_wrong_code_20261028-7246b0231fce
>
> Best regards,
> --
> Nicholas Dudar <main.kalliope@gmail.com>
>
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH bpf v2 0/2] bpf, x86: Fix i386 JIT signed ALU32 DIV/MOD and MOVSX
2026-11-13 17:35 ` [PATCH bpf v2 0/2] bpf, x86: Fix i386 JIT signed ALU32 DIV/MOD and MOVSX Kuan-Wei Chiu
@ 2026-11-13 17:40 ` Nicholas Dudar
0 siblings, 0 replies; 5+ messages in thread
From: Nicholas Dudar @ 2026-11-13 17:40 UTC (permalink / raw)
To: Kuan-Wei Chiu
Cc: Wang YanQing, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
Yonghong Song, Martin KaFai Lau, Song Liu, Jiri Olsa,
Emil Tsalapatis, Ihor Solodrai, H. Peter Anvin, bpf,
linux-kernel
On Sat, Nov 14, 2026 at 01:35:20AM +0800, Kuan-Wei Chiu wrote:
> IIRC Alexei mentioned previously that he didn't think anyone still
> cared about i386 JIT, so the i386 JIT patches were not merged?
> Now we are seeing more i386 JIT patches. If we really think no one
> cares about this platform anymore, maybe we should remove it to avoid
> receiving more patches that we don't plan to merge?
Agreed that the i386 JIT should not get more work than it needs, and
that is what v2 is cut down to. v1 added support for new instructions,
which Alexei turned down, and all of that is dropped. What is left are
two instructions the JIT already accepts and compiles wrong, so a
verifier-accepted program can run with values outside the range the
verifier proved.
Whether to keep the JIT at all is the maintainers' call. As long as it
is in the tree, these two patches are the minimum to close that gap,
and I don't plan to send more for it.
Thanks,
Nicholas Dudar
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-11-13 17:40 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-13 17:23 [PATCH bpf v2 0/2] bpf, x86: Fix i386 JIT signed ALU32 DIV/MOD and MOVSX Nicholas Dudar
2026-11-13 17:23 ` [PATCH bpf v2 1/2] bpf, x86: Fix signed division and modulo in the i386 JIT Nicholas Dudar
2026-11-13 17:23 ` [PATCH bpf v2 2/2] bpf, x86: Fix sign-extension moves " Nicholas Dudar
2026-11-13 17:35 ` [PATCH bpf v2 0/2] bpf, x86: Fix i386 JIT signed ALU32 DIV/MOD and MOVSX Kuan-Wei Chiu
2026-11-13 17:40 ` Nicholas Dudar
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®