bpf, mips: Add support for BPF_MOVSX in the JITs

Both MIPS JITs ignore insn->off when lowering register MOV
instructions, so BPF_MOVSX is emitted as an ordinary move. Since
build_insn() accepts it, affected programs are silently miscompiled
instead of falling back to the interpreter.

Decode the MOVSX width from insn->off in the register-move helpers.
On MIPS32, propagate the sign into the high word for ALU64 while
preserving the existing ALU32 zero-extension handling.

Use seb/seh on MIPS32 R2-or-newer CPUs and shift pairs otherwise.
On MIPS64, seb/seh require a sign-extended 32-bit source. Normalizing
an arbitrary BPF register first would cost another instruction, so
retain the doubleword shift pairs.

Signed-off-by: Nicholas Dudar <main.kalliope@gmail.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://patch.msgid.link/20260912041220.444715-3-main.kalliope@gmail.com
This commit is contained in:
Nicholas Dudar 2026-09-12 00:12:20 -04:00 committed by Alexei Starovoitov
parent 86187bd3cd
commit 07ebb8c386
No known key found for this signature in database
2 changed files with 104 additions and 18 deletions

View File

@ -190,20 +190,67 @@ static void emit_zext_ver(struct jit_context *ctx, const u8 dst[])
}
}
/* Register move operation (32-bit) */
static void emit_mov_r32(struct jit_context *ctx, const u8 dst[],
const u8 src[])
/* Sign-extend an 8- or 16-bit field into a native register. */
static void emit_movsx_r(struct jit_context *ctx, u8 dst, u8 src, s16 off)
{
emit_mov_r(ctx, lo(dst), lo(src));
int shift;
if (cpu_has_mips32r2 || cpu_has_mips32r6) {
if (off == 8)
emit(ctx, seb, dst, src);
else
emit(ctx, seh, dst, src);
} else {
shift = 32 - off;
emit(ctx, sll, dst, src, shift);
emit(ctx, sra, dst, dst, shift);
}
}
/* Register move operation (32-bit), optionally with sign extension */
static void emit_mov_r32(struct jit_context *ctx, const u8 dst[],
const u8 src[], s16 off)
{
switch (off) {
case 8:
case 16:
emit_movsx_r(ctx, lo(dst), lo(src), off);
break;
default:
/* off == 0 is MOV; the verifier rejects other offsets. */
emit_mov_r(ctx, lo(dst), lo(src));
break;
}
clobber_reg(ctx, lo(dst));
emit_zext_ver(ctx, dst);
}
/* Register move operation (64-bit) */
/* Register move operation (64-bit), optionally with sign extension */
static void emit_mov_r64(struct jit_context *ctx, const u8 dst[],
const u8 src[])
const u8 src[], s16 off)
{
emit_mov_r(ctx, lo(dst), lo(src));
emit_mov_r(ctx, hi(dst), hi(src));
switch (off) {
case 8:
case 16:
emit_movsx_r(ctx, lo(dst), lo(src), off);
emit(ctx, sra, hi(dst), lo(dst), 31);
break;
case 32:
emit(ctx, move, lo(dst), lo(src));
emit(ctx, sra, hi(dst), lo(dst), 31);
break;
default:
/*
* off == 0 is ordinary MOV. The verifier rejects other
* offsets; defined exceptions require
* bpf_jit_supports_percpu_insn() or bpf_jit_supports_arena(),
* neither implemented by MIPS.
*/
emit_mov_r(ctx, lo(dst), lo(src));
emit_mov_r(ctx, hi(dst), hi(src));
break;
}
clobber_reg64(ctx, dst);
}
/* Load delay slot, if ISA mandates it */
@ -1510,7 +1557,7 @@ int build_insn(const struct bpf_insn *insn, struct jit_context *ctx)
/* Special mov32 for zext */
emit_mov_i(ctx, hi(dst), 0);
} else {
emit_mov_r32(ctx, dst, src);
emit_mov_r32(ctx, dst, src, off);
}
break;
/* dst = -dst */
@ -1579,7 +1626,7 @@ int build_insn(const struct bpf_insn *insn, struct jit_context *ctx)
break;
/* dst = src (64-bit) */
case BPF_ALU64 | BPF_MOV | BPF_X:
emit_mov_r64(ctx, dst, src);
emit_mov_r64(ctx, dst, src, off);
break;
/* dst = -dst (64-bit) */
case BPF_ALU64 | BPF_NEG:

View File

@ -120,17 +120,56 @@ static void emit_zext_ver(struct jit_context *ctx, u8 dst)
emit_zext(ctx, dst);
}
/* Register move operation (32-bit) */
static void emit_mov_r32(struct jit_context *ctx, u8 dst, u8 src)
/* Sign-extend an 8- or 16-bit field into a native register. */
static void emit_movsx_r(struct jit_context *ctx, u8 dst, u8 src, s16 off)
{
emit_mov_r(ctx, dst, src);
/* seb/seh would need a sign-extended 32-bit source first. */
int shift = 32 - off;
/* dsll32 and dsra32 add 32 to the shift argument. */
emit(ctx, dsll32, dst, src, shift);
emit(ctx, dsra32, dst, dst, shift);
}
/* Register move operation (32-bit), optionally with sign extension */
static void emit_mov_r32(struct jit_context *ctx, u8 dst, u8 src, s16 off)
{
switch (off) {
case 8:
case 16:
emit_movsx_r(ctx, dst, src, off);
break;
default:
/* off == 0 is MOV; the verifier rejects other offsets. */
emit_mov_r(ctx, dst, src);
break;
}
clobber_reg(ctx, dst);
emit_zext_ver(ctx, dst);
}
/* Register move operation (64-bit) */
static void emit_mov_r64(struct jit_context *ctx, u8 dst, u8 src)
/* Register move operation (64-bit), optionally with sign extension */
static void emit_mov_r64(struct jit_context *ctx, u8 dst, u8 src, s16 off)
{
emit_mov_r(ctx, dst, src);
switch (off) {
case 8:
case 16:
emit_movsx_r(ctx, dst, src, off);
break;
case 32:
emit_sext(ctx, dst, src);
break;
default:
/*
* off == 0 is ordinary MOV. The verifier rejects other
* offsets; defined exceptions require
* bpf_jit_supports_percpu_insn() or bpf_jit_supports_arena(),
* neither implemented by MIPS.
*/
emit_mov_r(ctx, dst, src);
break;
}
clobber_reg(ctx, dst);
}
/* dst = imm (64-bit) */
@ -694,7 +733,7 @@ int build_insn(const struct bpf_insn *insn, struct jit_context *ctx)
/* Special mov32 for zext */
emit_zext(ctx, dst);
} else {
emit_mov_r32(ctx, dst, src);
emit_mov_r32(ctx, dst, src, off);
}
break;
/* dst = -dst */
@ -779,7 +818,7 @@ int build_insn(const struct bpf_insn *insn, struct jit_context *ctx)
break;
/* dst = src (64-bit) */
case BPF_ALU64 | BPF_MOV | BPF_X:
emit_mov_r64(ctx, dst, src);
emit_mov_r64(ctx, dst, src, off);
break;
/* dst = -dst (64-bit) */
case BPF_ALU64 | BPF_NEG: