[PATCH] bpf, x86: Sign-extend narrow signed kfunc returns

From: Jérémy Jean

Date: Sun Aug 23 2026 - 16:40:14 EST


bpf_res_spin_lock() returns a 32-bit int. On failure, the verifier
models R0 as a signed 64-bit value in [-MAX_ERRNO, -1].

On x86-64, returning -EDEADLK writes 0xffffffdd to EAX and clears the
upper half of RAX. Since the JIT leaves the native return value as-is,
BPF sees 0x00000000ffffffdd instead of the sign-extended
0xffffffffffffffdd. A 64-bit signed comparison therefore treats the
value as positive, while the verifier treats it as -35.

As a result, a signed comparison against zero can take one path during
verification and another at run time. With rqspinlock aliases, this can
lead to unmatched bpf_res_spin_unlock() calls, corrupting the per-CPU
rqspinlock state and unbalancing the preemption count.

Use the kfunc's BTF model to sign-extend signed 8-, 16-, and 32-bit
returns into R0 after the native call.

Fixes: 0de2046137f9 ("bpf: Implement verifier support for rqspinlock")
Assisted-by: Codex:gpt-5
Signed-off-by: Jérémy Jean <Jeremy.Jean@xxxxxxxxxxxxxxxxx>
---
arch/x86/net/bpf_jit_comp.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)

diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 1a9fb530adc3..713ac70613ff 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -1726,6 +1726,23 @@ static int emit_kfunc_arena_args(struct bpf_prog *bpf_prog,
return prog - start;
}

+static int emit_kfunc_return(const struct bpf_prog *bpf_prog,
+ const struct bpf_insn *insn, u8 **pprog)
+{
+ const struct btf_func_model *fm;
+
+ fm = bpf_jit_find_kfunc_model(bpf_prog, insn);
+ if (!fm)
+ return -EINVAL;
+ if (!(fm->ret_flags & BTF_FMODEL_SIGNED_ARG) || fm->ret_size == 8)
+ return 0;
+ if (fm->ret_size != 1 && fm->ret_size != 2 && fm->ret_size != 4)
+ return -EINVAL;
+
+ emit_movsx_reg(pprog, fm->ret_size * 8, true, BPF_REG_0, BPF_REG_0);
+ return 0;
+}
+
static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *addrs, u8 *image,
u8 *rw_image, int oldproglen, struct jit_context *ctx, bool jmp_padding)
{
@@ -2664,6 +2681,11 @@ st: insn_off = insn->off;
ip += x86_call_depth_emit_accounting(&prog, func, ip);
if (emit_call(&prog, func, ip))
return -EINVAL;
+ if (src_reg == BPF_PSEUDO_KFUNC_CALL) {
+ err = emit_kfunc_return(bpf_prog, insn, &prog);
+ if (err)
+ return err;
+ }
if (priv_frame_ptr)
pop_r9(&prog);
break;
--
2.47.3