History

bpf: handle ldimm64 properly in check_cfg()

ldimm64 instructions are 16-byte long, and so have to be handled appropriately in check_cfg(), just like the rest of BPF verifier does. This has implications in three places: - when determining next instruction for non-jump instructions; - when determining next instruction for callback address ldimm64 instructions (in visit_func_call_insn()); - when checking for unreachable instructions, where second half of ldimm64 is expected to be unreachable; We take this also as an opportunity to report jump into the middle of ldimm64. And adjust few test_verifier tests accordingly.

Andrii Nakryiko committed 3 years ago

Commit 3feb2631 parent fe69a1b3 files · +30 −13Browse files

Unified · Split

3 files
  1. include/linux/bpf.h +6 −2
  2. kernel/bpf/verifier.c +20 −7
  3. tools/testing/selftests/bpf/verifier/ld_imm64.c +4 −4
include/linux/bpf.h+6−2
@@ -909,10 +909,14 @@ bpf_ctx_record_field_size(struct bpf_insn_access_aux *aux, u32 size) · 908 unmodified lines
909 aux->ctx_field_size = size;909 aux->ctx_field_size = size;
910}910}
911 911
912static bool bpf_is_ldimm64(const struct bpf_insn *insn)
913{
914 return insn->code == (BPF_LD | BPF_IMM | BPF_DW);
915}
916
912static inline bool bpf_pseudo_func(const struct bpf_insn *insn)917static inline bool bpf_pseudo_func(const struct bpf_insn *insn)
913{918{
914 return insn->code == (BPF_LD | BPF_IMM | BPF_DW) &&919 return bpf_is_ldimm64(insn) && insn->src_reg == BPF_PSEUDO_FUNC;
915 insn->src_reg == BPF_PSEUDO_FUNC;
916}920}
917 921
918struct bpf_prog_ops {922struct bpf_prog_ops {
kernel/bpf/verifier.c+20−7
@@ -15439,15 +15439,16 @@ static int visit_func_call_insn(int t, struct bpf_insn *insns, · 15438 unmodified lines
15439 struct bpf_verifier_env *env,15439 struct bpf_verifier_env *env,
15440 bool visit_callee)15440 bool visit_callee)
15441{15441{
15442 int ret;15442 int ret, insn_sz;
15443 15443
15444 ret = push_insn(t, t + 1, FALLTHROUGH, env, false);15444 insn_sz = bpf_is_ldimm64(&insns[t]) ? 2 : 1;
15445 ret = push_insn(t, t + insn_sz, FALLTHROUGH, env, false);
15445 if (ret)15446 if (ret)
15446 return ret;15447 return ret;
15447 15448
15448 mark_prune_point(env, t + 1);15449 mark_prune_point(env, t + insn_sz);
15449 /* when we exit from subprog, we need to record non-linear history */15450 /* when we exit from subprog, we need to record non-linear history */
15450 mark_jmp_point(env, t + 1);15451 mark_jmp_point(env, t + insn_sz);
15451 15452
15452 if (visit_callee) {15453 if (visit_callee) {
15453 mark_prune_point(env, t);15454 mark_prune_point(env, t);
@@ -15469,15 +15470,17 @@ static int visit_func_call_insn(int t, struct bpf_insn *insns, · 15 unmodified lines
15469static int visit_insn(int t, struct bpf_verifier_env *env)15470static int visit_insn(int t, struct bpf_verifier_env *env)
15470{15471{
15471 struct bpf_insn *insns = env->prog->insnsi, *insn = &insns[t];15472 struct bpf_insn *insns = env->prog->insnsi, *insn = &insns[t];
15472 int ret, off;15473 int ret, off, insn_sz;
15473 15474
15474 if (bpf_pseudo_func(insn))15475 if (bpf_pseudo_func(insn))
15475 return visit_func_call_insn(t, insns, env, true);15476 return visit_func_call_insn(t, insns, env, true);
15476 15477
15477 /* All non-branch instructions have a single fall-through edge. */15478 /* All non-branch instructions have a single fall-through edge. */
15478 if (BPF_CLASS(insn->code) != BPF_JMP &&15479 if (BPF_CLASS(insn->code) != BPF_JMP &&
15479 BPF_CLASS(insn->code) != BPF_JMP32)15480 BPF_CLASS(insn->code) != BPF_JMP32) {
15480 return push_insn(t, t + 1, FALLTHROUGH, env, false);15481 insn_sz = bpf_is_ldimm64(insn) ? 2 : 1;
15482 return push_insn(t, t + insn_sz, FALLTHROUGH, env, false);
15483 }
15481 15484
15482 switch (BPF_OP(insn->code)) {15485 switch (BPF_OP(insn->code)) {
15483 case BPF_EXIT:15486 case BPF_EXIT:
@@ -15607,11 +15610,21 @@ walk_cfg: · 123 unmodified lines
15607 }15610 }
15608 15611
15609 for (i = 0; i < insn_cnt; i++) {15612 for (i = 0; i < insn_cnt; i++) {
15613 struct bpf_insn *insn = &env->prog->insnsi[i];
15614
15610 if (insn_state[i] != EXPLORED) {15615 if (insn_state[i] != EXPLORED) {
15611 verbose(env, "unreachable insn %d\n", i);15616 verbose(env, "unreachable insn %d\n", i);
15612 ret = -EINVAL;15617 ret = -EINVAL;
15613 goto err_free;15618 goto err_free;
15614 }15619 }
15620 if (bpf_is_ldimm64(insn)) {
15621 if (insn_state[i + 1] != 0) {
15622 verbose(env, "jump into the middle of ldimm64 insn %d\n", i);
15623 ret = -EINVAL;
15624 goto err_free;
15625 }
15626 i++; /* skip second half of ldimm64 */
15627 }
15615 }15628 }
15616 ret = 0; /* cfg looks good */15629 ret = 0; /* cfg looks good */
15617 15630
tools/testing/selftests/bpf/verifier/ld_imm64.c+4−4
@@ -9,8 +9,8 @@ · 8 unmodified lines
9 BPF_MOV64_IMM(BPF_REG_0, 2),9 BPF_MOV64_IMM(BPF_REG_0, 2),
10 BPF_EXIT_INSN(),10 BPF_EXIT_INSN(),
11 },11 },
12 .errstr = "invalid BPF_LD_IMM insn",12 .errstr = "jump into the middle of ldimm64 insn 1",
13 .errstr_unpriv = "R1 pointer comparison",13 .errstr_unpriv = "jump into the middle of ldimm64 insn 1",
14 .result = REJECT,14 .result = REJECT,
15},15},
16{16{
@@ -23,8 +23,8 @@ · 6 unmodified lines
23 BPF_LD_IMM64(BPF_REG_0, 1),23 BPF_LD_IMM64(BPF_REG_0, 1),
24 BPF_EXIT_INSN(),24 BPF_EXIT_INSN(),
25 },25 },
26 .errstr = "invalid BPF_LD_IMM insn",26 .errstr = "jump into the middle of ldimm64 insn 1",
27 .errstr_unpriv = "R1 pointer comparison",27 .errstr_unpriv = "jump into the middle of ldimm64 insn 1",
28 .result = REJECT,28 .result = REJECT,
29},29},
30{30{