From: Yazhou Tang Currently, the BPF instruction set allows bpf-to-bpf call (or internal call, pseudo call) to use a 32-bit imm field to represent the relative jump offset. However, when JIT is disabled or falls back to interpreter, the verifier invokes bpf_patch_call_args() to rewrite the call instruction. In this function, the 32-bit imm is downcasted to s16 and stored in the `off` field. void bpf_patch_call_args(struct bpf_insn *insn, u32 stack_depth) { stack_depth = max_t(u32, stack_depth, 1); insn->off = (s16) insn->imm; insn->imm = interpreters_args[(round_up(stack_depth, 32) / 32) - 1] - __bpf_call_base_args; insn->code = BPF_JMP | BPF_CALL_ARGS; } If the original imm exceeds the s16 range (i.e., jump offset > 32KB), this downcast silently truncates the offset, resulting in an incorrect call target. Fix this by explicitly checking the offset boundary in bpf_patch_call_args(). If the offset is out of range, reject the program with -EINVAL and emit a clear verifier log message. Fixes: 1ea47e01ad6e ("bpf: add support for bpf_call to interpreter") Co-developed-by: Tianci Cao Signed-off-by: Tianci Cao Co-developed-by: Shenghao Yuan Signed-off-by: Shenghao Yuan Signed-off-by: Yazhou Tang Reviewed-by: Emil Tsalapatis --- include/linux/bpf.h | 2 +- kernel/bpf/core.c | 7 +++++-- kernel/bpf/verifier.c | 6 +++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index 05b34a6355b0..5daea9f82b80 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -2916,7 +2916,7 @@ int bpf_check_uarg_tail_zero(bpfptr_t uaddr, size_t expected_size, int bpf_check(struct bpf_prog **fp, union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size); #ifndef CONFIG_BPF_JIT_ALWAYS_ON -void bpf_patch_call_args(struct bpf_insn *insn, u32 stack_depth); +int bpf_patch_call_args(struct bpf_insn *insn, u32 stack_depth); #endif struct btf *bpf_get_btf_vmlinux(void); diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index 39ddc7eb49b1..471ce73b760e 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -2396,13 +2396,16 @@ EVAL4(PROG_NAME_LIST, 416, 448, 480, 512) #undef PROG_NAME_LIST #ifdef CONFIG_BPF_SYSCALL -void bpf_patch_call_args(struct bpf_insn *insn, u32 stack_depth) +int bpf_patch_call_args(struct bpf_insn *insn, u32 stack_depth) { - stack_depth = max_t(u32, stack_depth, 1); + if (insn->imm < S16_MIN || insn->imm > S16_MAX) + return -EINVAL; insn->off = (s16) insn->imm; + stack_depth = max_t(u32, stack_depth, 1); insn->imm = interpreters_args[(round_up(stack_depth, 32) / 32) - 1] - __bpf_call_base_args; insn->code = BPF_JMP | BPF_CALL_ARGS; + return 0; } #endif #endif diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index df22bfc572e2..702579f56f7d 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -23112,7 +23112,11 @@ static int fixup_call_args(struct bpf_verifier_env *env) depth = get_callee_stack_depth(env, insn, i); if (depth < 0) return depth; - bpf_patch_call_args(insn, depth); + err = bpf_patch_call_args(insn, depth); + if (err) { + verbose(env, "bpf-to-bpf call offset out of range for interpreter\n"); + return err; + } } err = 0; #endif -- 2.53.0