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
909909 aux->ctx_field_size = size;
910910 }
911911
912+static bool bpf_is_ldimm64(const struct bpf_insn *insn)
913+{
914+ return insn->code == (BPF_LD | BPF_IMM | BPF_DW);
915+}
916+
912917 static inline bool bpf_pseudo_func(const struct bpf_insn *insn)
913918 {
914− return insn->code == (BPF_LD | BPF_IMM | BPF_DW) &&
915− insn->src_reg == BPF_PSEUDO_FUNC;
919+ return bpf_is_ldimm64(insn) && insn->src_reg == BPF_PSEUDO_FUNC;
916920 }
917921
918922 struct 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
1543915439 struct bpf_verifier_env *env,
1544015440 bool visit_callee)
1544115441 {
15442− int ret;
15442+ int ret, insn_sz;
1544315443
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);
1544515446 if (ret)
1544615447 return ret;
1544715448
15448− mark_prune_point(env, t + 1);
15449+ mark_prune_point(env, t + insn_sz);
1544915450 /* 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);
1545115452
1545215453 if (visit_callee) {
1545315454 mark_prune_point(env, t);
@@ -15469,15 +15470,17 @@ static int visit_func_call_insn(int t, struct bpf_insn *insns, · 15 unmodified lines
1546915470 static int visit_insn(int t, struct bpf_verifier_env *env)
1547015471 {
1547115472 struct bpf_insn *insns = env->prog->insnsi, *insn = &insns[t];
15472− int ret, off;
15473+ int ret, off, insn_sz;
1547315474
1547415475 if (bpf_pseudo_func(insn))
1547515476 return visit_func_call_insn(t, insns, env, true);
1547615477
1547715478 /* All non-branch instructions have a single fall-through edge. */
1547815479 if (BPF_CLASS(insn->code) != BPF_JMP &&
15479− BPF_CLASS(insn->code) != BPF_JMP32)
15480− return push_insn(t, t + 1, FALLTHROUGH, env, false);
15480+ BPF_CLASS(insn->code) != BPF_JMP32) {
15481+ insn_sz = bpf_is_ldimm64(insn) ? 2 : 1;
15482+ return push_insn(t, t + insn_sz, FALLTHROUGH, env, false);
15483+ }
1548115484
1548215485 switch (BPF_OP(insn->code)) {
1548315486 case BPF_EXIT:
@@ -15607,11 +15610,21 @@ walk_cfg: · 123 unmodified lines
1560715610 }
1560815611
1560915612 for (i = 0; i < insn_cnt; i++) {
15613+ struct bpf_insn *insn = &env->prog->insnsi[i];
15614+
1561015615 if (insn_state[i] != EXPLORED) {
1561115616 verbose(env, "unreachable insn %d\n", i);
1561215617 ret = -EINVAL;
1561315618 goto err_free;
1561415619 }
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+ }
1561515628 }
1561615629 ret = 0; /* cfg looks good */
1561715630
tools/testing/selftests/bpf/verifier/ld_imm64.c+4−4
@@ -9,8 +9,8 @@ · 8 unmodified lines
99 BPF_MOV64_IMM(BPF_REG_0, 2),
1010 BPF_EXIT_INSN(),
1111 },
12− .errstr = "invalid BPF_LD_IMM insn",
13− .errstr_unpriv = "R1 pointer comparison",
12+ .errstr = "jump into the middle of ldimm64 insn 1",
13+ .errstr_unpriv = "jump into the middle of ldimm64 insn 1",
1414 .result = REJECT,
1515 },
1616 {
@@ -23,8 +23,8 @@ · 6 unmodified lines
2323 BPF_LD_IMM64(BPF_REG_0, 1),
2424 BPF_EXIT_INSN(),
2525 },
26− .errstr = "invalid BPF_LD_IMM insn",
27− .errstr_unpriv = "R1 pointer comparison",
26+ .errstr = "jump into the middle of ldimm64 insn 1",
27+ .errstr_unpriv = "jump into the middle of ldimm64 insn 1",
2828 .result = REJECT,
2929 },
3030 {