Skip to content

Commit 3776c35

Browse files
aspskKernel Patches Daemon
authored andcommitted
bpf, x86: add support for indirect jumps
Add support for a new instruction BPF_JMP|BPF_X|BPF_JA, SRC=0, DST=Rx, off=0, imm=0 which does an indirect jump to a location stored in Rx. The register Rx should have type PTR_TO_INSN. This new type assures that the Rx register contains a value (or a range of values) loaded from a correct jump table – map of type instruction array. For example, for a C switch LLVM will generate the following code: 0: r3 = r1 # "switch (r3)" 1: if r3 > 0x13 goto +0x666 # check r3 boundaries 2: r3 <<= 0x3 # adjust to an index in array of addresses 3: r1 = 0xbeef ll # r1 is PTR_TO_MAP_VALUE, r1->map_ptr=M 5: r1 += r3 # r1 inherits boundaries from r3 6: r1 = *(u64 *)(r1 + 0x0) # r1 now has type INSN_TO_PTR 7: gotox r1 # jit will generate proper code Here the gotox instruction corresponds to one particular map. This is possible however to have a gotox instruction which can be loaded from different maps, e.g. 0: r1 &= 0x1 1: r2 <<= 0x3 2: r3 = 0x0 ll # load from map M_1 4: r3 += r2 5: if r1 == 0x0 goto +0x4 6: r1 <<= 0x3 7: r3 = 0x0 ll # load from map M_2 9: r3 += r1 A: r1 = *(u64 *)(r3 + 0x0) B: gotox r1 # jump to target loaded from M_1 or M_2 During check_cfg stage the verifier will collect all the maps which point to inside the subprog being verified. When building the config, the high 16 bytes of the insn_state are used, so this patch (theoretically) supports jump tables of up to 2^16 slots. During the later stage, in check_indirect_jump, it is checked that the register Rx was loaded from a particular instruction array. Signed-off-by: Anton Protopopov <[email protected]> Acked-by: Eduard Zingerman <[email protected]>
1 parent a7e7ab4 commit 3776c35

File tree

8 files changed

+391
-6
lines changed

8 files changed

+391
-6
lines changed

arch/x86/net/bpf_jit_comp.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2628,6 +2628,9 @@ st: if (is_imm8(insn->off))
26282628

26292629
break;
26302630

2631+
case BPF_JMP | BPF_JA | BPF_X:
2632+
emit_indirect_jump(&prog, insn->dst_reg, image + addrs[i - 1]);
2633+
break;
26312634
case BPF_JMP | BPF_JA:
26322635
case BPF_JMP32 | BPF_JA:
26332636
if (BPF_CLASS(insn->code) == BPF_JMP) {

include/linux/bpf.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,7 @@ enum bpf_reg_type {
10011001
PTR_TO_ARENA,
10021002
PTR_TO_BUF, /* reg points to a read/write buffer */
10031003
PTR_TO_FUNC, /* reg points to a bpf program function */
1004+
PTR_TO_INSN, /* reg points to a bpf program instruction */
10041005
CONST_PTR_TO_DYNPTR, /* reg points to a const struct bpf_dynptr */
10051006
__BPF_REG_TYPE_MAX,
10061007

include/linux/bpf_verifier.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,7 @@ struct bpf_insn_aux_data {
527527
struct {
528528
u32 map_index; /* index into used_maps[] */
529529
u32 map_off; /* offset from value base address */
530+
struct bpf_iarray *jt; /* jump table for gotox instruction */
530531
};
531532
struct {
532533
enum bpf_reg_type reg_type; /* type of pseudo_btf_id */
@@ -840,6 +841,7 @@ struct bpf_verifier_env {
840841
struct bpf_scc_info **scc_info;
841842
u32 scc_cnt;
842843
struct bpf_iarray *succ;
844+
struct bpf_iarray *gotox_tmp_buf;
843845
};
844846

845847
static inline struct bpf_func_info_aux *subprog_aux(struct bpf_verifier_env *env, int subprog)
@@ -1050,6 +1052,13 @@ static inline bool bpf_stack_narrow_access_ok(int off, int fill_size, int spill_
10501052
return !(off % BPF_REG_SIZE);
10511053
}
10521054

1055+
static inline bool insn_is_gotox(struct bpf_insn *insn)
1056+
{
1057+
return BPF_CLASS(insn->code) == BPF_JMP &&
1058+
BPF_OP(insn->code) == BPF_JA &&
1059+
BPF_SRC(insn->code) == BPF_X;
1060+
}
1061+
10531062
const char *reg_type_str(struct bpf_verifier_env *env, enum bpf_reg_type type);
10541063
const char *dynptr_type_str(enum bpf_dynptr_type type);
10551064
const char *iter_type_str(const struct btf *btf, u32 btf_id);

kernel/bpf/bpf_insn_array.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,20 @@ static u64 insn_array_mem_usage(const struct bpf_map *map)
119119
return insn_array_alloc_size(map->max_entries);
120120
}
121121

122+
static int insn_array_map_direct_value_addr(const struct bpf_map *map, u64 *imm, u32 off)
123+
{
124+
struct bpf_insn_array *insn_array = cast_insn_array(map);
125+
126+
if ((off % sizeof(long)) != 0 ||
127+
(off / sizeof(long)) >= map->max_entries)
128+
return -EINVAL;
129+
130+
/* from BPF's point of view, this map is a jump table */
131+
*imm = (unsigned long)insn_array->ips + off;
132+
133+
return 0;
134+
}
135+
122136
BTF_ID_LIST_SINGLE(insn_array_btf_ids, struct, bpf_insn_array)
123137

124138
const struct bpf_map_ops insn_array_map_ops = {
@@ -131,6 +145,7 @@ const struct bpf_map_ops insn_array_map_ops = {
131145
.map_delete_elem = insn_array_delete_elem,
132146
.map_check_btf = insn_array_check_btf,
133147
.map_mem_usage = insn_array_mem_usage,
148+
.map_direct_value_addr = insn_array_map_direct_value_addr,
134149
.map_btf_id = &insn_array_btf_ids[0],
135150
};
136151

kernel/bpf/core.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1708,6 +1708,7 @@ bool bpf_opcode_in_insntable(u8 code)
17081708
[BPF_LD | BPF_IND | BPF_B] = true,
17091709
[BPF_LD | BPF_IND | BPF_H] = true,
17101710
[BPF_LD | BPF_IND | BPF_W] = true,
1711+
[BPF_JMP | BPF_JA | BPF_X] = true,
17111712
[BPF_JMP | BPF_JCOND] = true,
17121713
};
17131714
#undef BPF_INSN_3_TBL

kernel/bpf/liveness.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,9 @@ bpf_insn_successors(struct bpf_verifier_env *env, u32 idx)
485485
struct bpf_iarray *succ;
486486
int insn_sz;
487487

488+
if (unlikely(insn_is_gotox(insn)))
489+
return env->insn_aux_data[idx].jt;
490+
488491
/* pre-allocated array of size up to 2; reset cnt, as it may have been used already */
489492
succ = env->succ;
490493
succ->cnt = 0;

kernel/bpf/log.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@ const char *reg_type_str(struct bpf_verifier_env *env, enum bpf_reg_type type)
461461
[PTR_TO_ARENA] = "arena",
462462
[PTR_TO_BUF] = "buf",
463463
[PTR_TO_FUNC] = "func",
464+
[PTR_TO_INSN] = "insn",
464465
[PTR_TO_MAP_KEY] = "map_key",
465466
[CONST_PTR_TO_DYNPTR] = "dynptr_ptr",
466467
};

0 commit comments

Comments
 (0)