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

bpf: Stop caching subprog index in the bpf_pseudo_func insn

This patch is to fix an out-of-bound access issue when jit-ing the
bpf_pseudo_func insn (i.e. ld_imm64 with src_reg == BPF_PSEUDO_FUNC)

In jit_subprog(), it currently reuses the subprog index cached in
insn[1].imm. This subprog index is an index into a few array related
to subprogs. For example, in jit_subprog(), it is an index to the newly
allocated 'struct bpf_prog **func' array.

The subprog index was cached in insn[1].imm after add_subprog(). However,
this could become outdated (and too big in this case) if some subprogs
are completely removed during dead code elimination (in
adjust_subprog_starts_after_remove). The cached index in insn[1].imm
is not updated accordingly and causing out-of-bound issue in the later
jit_subprog().

Unlike bpf_pseudo_'func' insn, the current bpf_pseudo_'call' insn
is handling the DCE properly by calling find_subprog(insn->imm) to
figure out the index instead of caching the subprog index.
The existing bpf_adj_branches() will adjust the insn->imm
whenever insn is added or removed.

Instead of having two ways handling subprog index,
this patch is to make bpf_pseudo_func works more like
bpf_pseudo_call.

First change is to stop caching the subprog index result
in insn[1].imm after add_subprog(). The verification
process will use find_subprog(insn->imm) to figure
out the subprog index.

Second change is in bpf_adj_branches() and have it to
adjust the insn->imm for the bpf_pseudo_func insn also
whenever insn is added or removed.

Third change is in jit_subprog(). Like the bpf_pseudo_call handling,
bpf_pseudo_func temporarily stores the find_subprog() result
in insn->off. It is fine because the prog's insn has been finalized
at this point. insn->off will be reset back to 0 later to avoid
confusing the userspace prog dump tool.

Fixes: 69c087ba6225 ("bpf: Add bpf_for_each_map_elem() helper")
Signed-off-by: Martin KaFai Lau <kafai@fb.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://lore.kernel.org/bpf/20211106014014.651018-1-kafai@fb.com

authored by

Martin KaFai Lau and committed by
Alexei Starovoitov
3990ed4c 70bf363d

+27 -23
+6
include/linux/bpf.h
··· 484 484 aux->ctx_field_size = size; 485 485 } 486 486 487 + static inline bool bpf_pseudo_func(const struct bpf_insn *insn) 488 + { 489 + return insn->code == (BPF_LD | BPF_IMM | BPF_DW) && 490 + insn->src_reg == BPF_PSEUDO_FUNC; 491 + } 492 + 487 493 struct bpf_prog_ops { 488 494 int (*test_run)(struct bpf_prog *prog, const union bpf_attr *kattr, 489 495 union bpf_attr __user *uattr);
+7
kernel/bpf/core.c
··· 390 390 i = end_new; 391 391 insn = prog->insnsi + end_old; 392 392 } 393 + if (bpf_pseudo_func(insn)) { 394 + ret = bpf_adj_delta_to_imm(insn, pos, end_old, 395 + end_new, i, probe_pass); 396 + if (ret) 397 + return ret; 398 + continue; 399 + } 393 400 code = insn->code; 394 401 if ((BPF_CLASS(code) != BPF_JMP && 395 402 BPF_CLASS(code) != BPF_JMP32) ||
+14 -23
kernel/bpf/verifier.c
··· 240 240 insn->src_reg == BPF_PSEUDO_KFUNC_CALL; 241 241 } 242 242 243 - static bool bpf_pseudo_func(const struct bpf_insn *insn) 244 - { 245 - return insn->code == (BPF_LD | BPF_IMM | BPF_DW) && 246 - insn->src_reg == BPF_PSEUDO_FUNC; 247 - } 248 - 249 243 struct bpf_call_arg_meta { 250 244 struct bpf_map *map_ptr; 251 245 bool raw_mode; ··· 1954 1960 return -EPERM; 1955 1961 } 1956 1962 1957 - if (bpf_pseudo_func(insn)) { 1963 + if (bpf_pseudo_func(insn) || bpf_pseudo_call(insn)) 1958 1964 ret = add_subprog(env, i + insn->imm + 1); 1959 - if (ret >= 0) 1960 - /* remember subprog */ 1961 - insn[1].imm = ret; 1962 - } else if (bpf_pseudo_call(insn)) { 1963 - ret = add_subprog(env, i + insn->imm + 1); 1964 - } else { 1965 + else 1965 1966 ret = add_kfunc_call(env, insn->imm, insn->off); 1966 - } 1967 1967 1968 1968 if (ret < 0) 1969 1969 return ret; ··· 9375 9387 9376 9388 if (insn->src_reg == BPF_PSEUDO_FUNC) { 9377 9389 struct bpf_prog_aux *aux = env->prog->aux; 9378 - u32 subprogno = insn[1].imm; 9390 + u32 subprogno = find_subprog(env, 9391 + env->insn_idx + insn->imm + 1); 9379 9392 9380 9393 if (!aux->func_info) { 9381 9394 verbose(env, "missing btf func_info\n"); ··· 12546 12557 return 0; 12547 12558 12548 12559 for (i = 0, insn = prog->insnsi; i < prog->len; i++, insn++) { 12549 - if (bpf_pseudo_func(insn)) { 12550 - env->insn_aux_data[i].call_imm = insn->imm; 12551 - /* subprog is encoded in insn[1].imm */ 12560 + if (!bpf_pseudo_func(insn) && !bpf_pseudo_call(insn)) 12552 12561 continue; 12553 - } 12554 12562 12555 - if (!bpf_pseudo_call(insn)) 12556 - continue; 12557 12563 /* Upon error here we cannot fall back to interpreter but 12558 12564 * need a hard reject of the program. Thus -EFAULT is 12559 12565 * propagated in any case. ··· 12569 12585 env->insn_aux_data[i].call_imm = insn->imm; 12570 12586 /* point imm to __bpf_call_base+1 from JITs point of view */ 12571 12587 insn->imm = 1; 12588 + if (bpf_pseudo_func(insn)) 12589 + /* jit (e.g. x86_64) may emit fewer instructions 12590 + * if it learns a u32 imm is the same as a u64 imm. 12591 + * Force a non zero here. 12592 + */ 12593 + insn[1].imm = 1; 12572 12594 } 12573 12595 12574 12596 err = bpf_prog_alloc_jited_linfo(prog); ··· 12659 12669 insn = func[i]->insnsi; 12660 12670 for (j = 0; j < func[i]->len; j++, insn++) { 12661 12671 if (bpf_pseudo_func(insn)) { 12662 - subprog = insn[1].imm; 12672 + subprog = insn->off; 12663 12673 insn[0].imm = (u32)(long)func[subprog]->bpf_func; 12664 12674 insn[1].imm = ((u64)(long)func[subprog]->bpf_func) >> 32; 12665 12675 continue; ··· 12710 12720 for (i = 0, insn = prog->insnsi; i < prog->len; i++, insn++) { 12711 12721 if (bpf_pseudo_func(insn)) { 12712 12722 insn[0].imm = env->insn_aux_data[i].call_imm; 12713 - insn[1].imm = find_subprog(env, i + insn[0].imm + 1); 12723 + insn[1].imm = insn->off; 12724 + insn->off = 0; 12714 12725 continue; 12715 12726 } 12716 12727 if (!bpf_pseudo_call(insn))