Skip to content

Commit 011832b

Browse files
Alexei Starovoitovanakryiko
authored andcommitted
bpf: Introduce may_goto instruction
Introduce may_goto instruction that from the verifier pov is similar to open coded iterators bpf_for()/bpf_repeat() and bpf_loop() helper, but it doesn't iterate any objects. In assembly 'may_goto' is a nop most of the time until bpf runtime has to terminate the program for whatever reason. In the current implementation may_goto has a hidden counter, but other mechanisms can be used. For programs written in C the later patch introduces 'cond_break' macro that combines 'may_goto' with 'break' statement and has similar semantics: cond_break is a nop until bpf runtime has to break out of this loop. It can be used in any normal "for" or "while" loop, like for (i = zero; i < cnt; cond_break, i++) { The verifier recognizes that may_goto is used in the program, reserves additional 8 bytes of stack, initializes them in subprog prologue, and replaces may_goto instruction with: aux_reg = *(u64 *)(fp - 40) if aux_reg == 0 goto pc+off aux_reg -= 1 *(u64 *)(fp - 40) = aux_reg may_goto instruction can be used by LLVM to implement __builtin_memcpy, __builtin_strcmp. may_goto is not a full substitute for bpf_for() macro. bpf_for() doesn't have induction variable that verifiers sees, so 'i' in bpf_for(i, 0, 100) is seen as imprecise and bounded. But when the code is written as: for (i = 0; i < 100; cond_break, i++) the verifier see 'i' as precise constant zero, hence cond_break (aka may_goto) doesn't help to converge the loop. A static or global variable can be used as a workaround: static int zero = 0; for (i = zero; i < 100; cond_break, i++) // works! may_goto works well with arena pointers that don't need to be bounds checked on access. Load/store from arena returns imprecise unbounded scalar and loops with may_goto pass the verifier. Reserve new opcode BPF_JMP | BPF_JCOND for may_goto insn. JCOND stands for conditional pseudo jump. Since goto_or_nop insn was proposed, it may use the same opcode. may_goto vs goto_or_nop can be distinguished by src_reg: code = BPF_JMP | BPF_JCOND src_reg = 0 - may_goto src_reg = 1 - goto_or_nop Signed-off-by: Alexei Starovoitov <[email protected]> Signed-off-by: Andrii Nakryiko <[email protected]> Acked-by: Andrii Nakryiko <[email protected]> Acked-by: Eduard Zingerman <[email protected]> Acked-by: John Fastabend <[email protected]> Tested-by: John Fastabend <[email protected]> Link: https://lore.kernel.org/bpf/[email protected]
1 parent 9a9d1d3 commit 011832b

File tree

6 files changed

+150
-30
lines changed

6 files changed

+150
-30
lines changed

include/linux/bpf_verifier.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@ struct bpf_verifier_state {
449449
u32 jmp_history_cnt;
450450
u32 dfs_depth;
451451
u32 callback_unroll_depth;
452+
u32 may_goto_depth;
452453
};
453454

454455
#define bpf_get_spilled_reg(slot, frame, mask) \
@@ -619,6 +620,7 @@ struct bpf_subprog_info {
619620
u32 start; /* insn idx of function entry point */
620621
u32 linfo_idx; /* The idx to the main_prog->aux->linfo */
621622
u16 stack_depth; /* max. stack depth used by this function */
623+
u16 stack_extra;
622624
bool has_tail_call: 1;
623625
bool tail_call_reachable: 1;
624626
bool has_ld_abs: 1;

include/uapi/linux/bpf.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#define BPF_JSGE 0x70 /* SGE is signed '>=', GE in x86 */
4343
#define BPF_JSLT 0xc0 /* SLT is signed, '<' */
4444
#define BPF_JSLE 0xd0 /* SLE is signed, '<=' */
45+
#define BPF_JCOND 0xe0 /* conditional pseudo jumps: may_goto, goto_or_nop */
4546
#define BPF_CALL 0x80 /* function call */
4647
#define BPF_EXIT 0x90 /* function return */
4748

@@ -50,6 +51,10 @@
5051
#define BPF_XCHG (0xe0 | BPF_FETCH) /* atomic exchange */
5152
#define BPF_CMPXCHG (0xf0 | BPF_FETCH) /* atomic compare-and-write */
5253

54+
enum bpf_cond_pseudo_jmp {
55+
BPF_MAY_GOTO = 0,
56+
};
57+
5358
/* Register numbers */
5459
enum {
5560
BPF_REG_0 = 0,

kernel/bpf/core.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1675,6 +1675,7 @@ bool bpf_opcode_in_insntable(u8 code)
16751675
[BPF_LD | BPF_IND | BPF_B] = true,
16761676
[BPF_LD | BPF_IND | BPF_H] = true,
16771677
[BPF_LD | BPF_IND | BPF_W] = true,
1678+
[BPF_JMP | BPF_JCOND] = true,
16781679
};
16791680
#undef BPF_INSN_3_TBL
16801681
#undef BPF_INSN_2_TBL

kernel/bpf/disasm.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,10 @@ void print_bpf_insn(const struct bpf_insn_cbs *cbs,
322322
} else if (insn->code == (BPF_JMP | BPF_JA)) {
323323
verbose(cbs->private_data, "(%02x) goto pc%+d\n",
324324
insn->code, insn->off);
325+
} else if (insn->code == (BPF_JMP | BPF_JCOND) &&
326+
insn->src_reg == BPF_MAY_GOTO) {
327+
verbose(cbs->private_data, "(%02x) may_goto pc%+d\n",
328+
insn->code, insn->off);
325329
} else if (insn->code == (BPF_JMP32 | BPF_JA)) {
326330
verbose(cbs->private_data, "(%02x) gotol pc%+d\n",
327331
insn->code, insn->imm);

0 commit comments

Comments
 (0)