Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

riscv, bpf: Emit fixed-length instructions for BPF_PSEUDO_FUNC

For BPF_PSEUDO_FUNC instruction, verifier will refill imm with
correct addresses of bpf_calls and then run last pass of JIT.
Since the emit_imm of RV64 is variable-length, which will emit
appropriate length instructions accorroding to the imm, it may
broke ctx->offset, and lead to unpredictable problem, such as
inaccurate jump. So let's fix it with fixed-length instructions.

Fixes: 69c087ba6225 ("bpf: Add bpf_for_each_map_elem() helper")
Suggested-by: Björn Töpel <bjorn@rivosinc.com>
Signed-off-by: Pu Lehui <pulehui@huawei.com>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Reviewed-by: Björn Töpel <bjorn@kernel.org>
Acked-by: Björn Töpel <bjorn@kernel.org>
Link: https://lore.kernel.org/bpf/20221206091410.1584784-1-pulehui@huaweicloud.com

authored by

Pu Lehui and committed by
Daniel Borkmann
b54b6003 08388efe

+28 -1
+28 -1
arch/riscv/net/bpf_jit_comp64.c
··· 136 136 val < ((1L << 31) - (1L << 11)); 137 137 } 138 138 139 + /* Emit fixed-length instructions for address */ 140 + static int emit_addr(u8 rd, u64 addr, bool extra_pass, struct rv_jit_context *ctx) 141 + { 142 + u64 ip = (u64)(ctx->insns + ctx->ninsns); 143 + s64 off = addr - ip; 144 + s64 upper = (off + (1 << 11)) >> 12; 145 + s64 lower = off & 0xfff; 146 + 147 + if (extra_pass && !in_auipc_jalr_range(off)) { 148 + pr_err("bpf-jit: target offset 0x%llx is out of range\n", off); 149 + return -ERANGE; 150 + } 151 + 152 + emit(rv_auipc(rd, upper), ctx); 153 + emit(rv_addi(rd, rd, lower), ctx); 154 + return 0; 155 + } 156 + 157 + /* Emit variable-length instructions for 32-bit and 64-bit imm */ 139 158 static void emit_imm(u8 rd, s64 val, struct rv_jit_context *ctx) 140 159 { 141 160 /* Note that the immediate from the add is sign-extended, ··· 1069 1050 u64 imm64; 1070 1051 1071 1052 imm64 = (u64)insn1.imm << 32 | (u32)imm; 1072 - emit_imm(rd, imm64, ctx); 1053 + if (bpf_pseudo_func(insn)) { 1054 + /* fixed-length insns for extra jit pass */ 1055 + ret = emit_addr(rd, imm64, extra_pass, ctx); 1056 + if (ret) 1057 + return ret; 1058 + } else { 1059 + emit_imm(rd, imm64, ctx); 1060 + } 1061 + 1073 1062 return 1; 1074 1063 } 1075 1064