Skip to content

Commit 6d94e74

Browse files
Alexei Starovoitovborkmann
authored andcommitted
bpf: Support for pointers beyond pkt_end.
This patch adds the verifier support to recognize inlined branch conditions. The LLVM knows that the branch evaluates to the same value, but the verifier couldn't track it. Hence causing valid programs to be rejected. The potential LLVM workaround: https://reviews.llvm.org/D87428 can have undesired side effects, since LLVM doesn't know that skb->data/data_end are being compared. LLVM has to introduce extra boolean variable and use inline_asm trick to force easier for the verifier assembly. Instead teach the verifier to recognize that r1 = skb->data; r1 += 10; r2 = skb->data_end; if (r1 > r2) { here r1 points beyond packet_end and subsequent if (r1 > r2) // always evaluates to "true". } Signed-off-by: Alexei Starovoitov <[email protected]> Signed-off-by: Daniel Borkmann <[email protected]> Tested-by: Jiri Olsa <[email protected]> Acked-by: John Fastabend <[email protected]> Link: https://lore.kernel.org/bpf/[email protected]
1 parent c365387 commit 6d94e74

File tree

2 files changed

+108
-23
lines changed

2 files changed

+108
-23
lines changed

include/linux/bpf_verifier.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ struct bpf_reg_state {
4545
enum bpf_reg_type type;
4646
union {
4747
/* valid when type == PTR_TO_PACKET */
48-
u16 range;
48+
int range;
4949

5050
/* valid when type == CONST_PTR_TO_MAP | PTR_TO_MAP_VALUE |
5151
* PTR_TO_MAP_VALUE_OR_NULL

kernel/bpf/verifier.c

Lines changed: 107 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2739,7 +2739,9 @@ static int check_packet_access(struct bpf_verifier_env *env, u32 regno, int off,
27392739
regno);
27402740
return -EACCES;
27412741
}
2742-
err = __check_mem_access(env, regno, off, size, reg->range,
2742+
2743+
err = reg->range < 0 ? -EINVAL :
2744+
__check_mem_access(env, regno, off, size, reg->range,
27432745
zero_size_allowed);
27442746
if (err) {
27452747
verbose(env, "R%d offset is outside of the packet\n", regno);
@@ -4697,6 +4699,32 @@ static void clear_all_pkt_pointers(struct bpf_verifier_env *env)
46974699
__clear_all_pkt_pointers(env, vstate->frame[i]);
46984700
}
46994701

4702+
enum {
4703+
AT_PKT_END = -1,
4704+
BEYOND_PKT_END = -2,
4705+
};
4706+
4707+
static void mark_pkt_end(struct bpf_verifier_state *vstate, int regn, bool range_open)
4708+
{
4709+
struct bpf_func_state *state = vstate->frame[vstate->curframe];
4710+
struct bpf_reg_state *reg = &state->regs[regn];
4711+
4712+
if (reg->type != PTR_TO_PACKET)
4713+
/* PTR_TO_PACKET_META is not supported yet */
4714+
return;
4715+
4716+
/* The 'reg' is pkt > pkt_end or pkt >= pkt_end.
4717+
* How far beyond pkt_end it goes is unknown.
4718+
* if (!range_open) it's the case of pkt >= pkt_end
4719+
* if (range_open) it's the case of pkt > pkt_end
4720+
* hence this pointer is at least 1 byte bigger than pkt_end
4721+
*/
4722+
if (range_open)
4723+
reg->range = BEYOND_PKT_END;
4724+
else
4725+
reg->range = AT_PKT_END;
4726+
}
4727+
47004728
static void release_reg_references(struct bpf_verifier_env *env,
47014729
struct bpf_func_state *state,
47024730
int ref_obj_id)
@@ -6708,7 +6736,7 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
67086736

67096737
static void __find_good_pkt_pointers(struct bpf_func_state *state,
67106738
struct bpf_reg_state *dst_reg,
6711-
enum bpf_reg_type type, u16 new_range)
6739+
enum bpf_reg_type type, int new_range)
67126740
{
67136741
struct bpf_reg_state *reg;
67146742
int i;
@@ -6733,8 +6761,7 @@ static void find_good_pkt_pointers(struct bpf_verifier_state *vstate,
67336761
enum bpf_reg_type type,
67346762
bool range_right_open)
67356763
{
6736-
u16 new_range;
6737-
int i;
6764+
int new_range, i;
67386765

67396766
if (dst_reg->off < 0 ||
67406767
(dst_reg->off == 0 && range_right_open))
@@ -6985,6 +7012,67 @@ static int is_branch_taken(struct bpf_reg_state *reg, u64 val, u8 opcode,
69857012
return is_branch64_taken(reg, val, opcode);
69867013
}
69877014

7015+
static int flip_opcode(u32 opcode)
7016+
{
7017+
/* How can we transform "a <op> b" into "b <op> a"? */
7018+
static const u8 opcode_flip[16] = {
7019+
/* these stay the same */
7020+
[BPF_JEQ >> 4] = BPF_JEQ,
7021+
[BPF_JNE >> 4] = BPF_JNE,
7022+
[BPF_JSET >> 4] = BPF_JSET,
7023+
/* these swap "lesser" and "greater" (L and G in the opcodes) */
7024+
[BPF_JGE >> 4] = BPF_JLE,
7025+
[BPF_JGT >> 4] = BPF_JLT,
7026+
[BPF_JLE >> 4] = BPF_JGE,
7027+
[BPF_JLT >> 4] = BPF_JGT,
7028+
[BPF_JSGE >> 4] = BPF_JSLE,
7029+
[BPF_JSGT >> 4] = BPF_JSLT,
7030+
[BPF_JSLE >> 4] = BPF_JSGE,
7031+
[BPF_JSLT >> 4] = BPF_JSGT
7032+
};
7033+
return opcode_flip[opcode >> 4];
7034+
}
7035+
7036+
static int is_pkt_ptr_branch_taken(struct bpf_reg_state *dst_reg,
7037+
struct bpf_reg_state *src_reg,
7038+
u8 opcode)
7039+
{
7040+
struct bpf_reg_state *pkt;
7041+
7042+
if (src_reg->type == PTR_TO_PACKET_END) {
7043+
pkt = dst_reg;
7044+
} else if (dst_reg->type == PTR_TO_PACKET_END) {
7045+
pkt = src_reg;
7046+
opcode = flip_opcode(opcode);
7047+
} else {
7048+
return -1;
7049+
}
7050+
7051+
if (pkt->range >= 0)
7052+
return -1;
7053+
7054+
switch (opcode) {
7055+
case BPF_JLE:
7056+
/* pkt <= pkt_end */
7057+
fallthrough;
7058+
case BPF_JGT:
7059+
/* pkt > pkt_end */
7060+
if (pkt->range == BEYOND_PKT_END)
7061+
/* pkt has at last one extra byte beyond pkt_end */
7062+
return opcode == BPF_JGT;
7063+
break;
7064+
case BPF_JLT:
7065+
/* pkt < pkt_end */
7066+
fallthrough;
7067+
case BPF_JGE:
7068+
/* pkt >= pkt_end */
7069+
if (pkt->range == BEYOND_PKT_END || pkt->range == AT_PKT_END)
7070+
return opcode == BPF_JGE;
7071+
break;
7072+
}
7073+
return -1;
7074+
}
7075+
69887076
/* Adjusts the register min/max values in the case that the dst_reg is the
69897077
* variable register that we are working on, and src_reg is a constant or we're
69907078
* simply doing a BPF_K check.
@@ -7148,23 +7236,7 @@ static void reg_set_min_max_inv(struct bpf_reg_state *true_reg,
71487236
u64 val, u32 val32,
71497237
u8 opcode, bool is_jmp32)
71507238
{
7151-
/* How can we transform "a <op> b" into "b <op> a"? */
7152-
static const u8 opcode_flip[16] = {
7153-
/* these stay the same */
7154-
[BPF_JEQ >> 4] = BPF_JEQ,
7155-
[BPF_JNE >> 4] = BPF_JNE,
7156-
[BPF_JSET >> 4] = BPF_JSET,
7157-
/* these swap "lesser" and "greater" (L and G in the opcodes) */
7158-
[BPF_JGE >> 4] = BPF_JLE,
7159-
[BPF_JGT >> 4] = BPF_JLT,
7160-
[BPF_JLE >> 4] = BPF_JGE,
7161-
[BPF_JLT >> 4] = BPF_JGT,
7162-
[BPF_JSGE >> 4] = BPF_JSLE,
7163-
[BPF_JSGT >> 4] = BPF_JSLT,
7164-
[BPF_JSLE >> 4] = BPF_JSGE,
7165-
[BPF_JSLT >> 4] = BPF_JSGT
7166-
};
7167-
opcode = opcode_flip[opcode >> 4];
7239+
opcode = flip_opcode(opcode);
71687240
/* This uses zero as "not present in table"; luckily the zero opcode,
71697241
* BPF_JA, can't get here.
71707242
*/
@@ -7346,13 +7418,15 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn,
73467418
/* pkt_data' > pkt_end, pkt_meta' > pkt_data */
73477419
find_good_pkt_pointers(this_branch, dst_reg,
73487420
dst_reg->type, false);
7421+
mark_pkt_end(other_branch, insn->dst_reg, true);
73497422
} else if ((dst_reg->type == PTR_TO_PACKET_END &&
73507423
src_reg->type == PTR_TO_PACKET) ||
73517424
(reg_is_init_pkt_pointer(dst_reg, PTR_TO_PACKET) &&
73527425
src_reg->type == PTR_TO_PACKET_META)) {
73537426
/* pkt_end > pkt_data', pkt_data > pkt_meta' */
73547427
find_good_pkt_pointers(other_branch, src_reg,
73557428
src_reg->type, true);
7429+
mark_pkt_end(this_branch, insn->src_reg, false);
73567430
} else {
73577431
return false;
73587432
}
@@ -7365,13 +7439,15 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn,
73657439
/* pkt_data' < pkt_end, pkt_meta' < pkt_data */
73667440
find_good_pkt_pointers(other_branch, dst_reg,
73677441
dst_reg->type, true);
7442+
mark_pkt_end(this_branch, insn->dst_reg, false);
73687443
} else if ((dst_reg->type == PTR_TO_PACKET_END &&
73697444
src_reg->type == PTR_TO_PACKET) ||
73707445
(reg_is_init_pkt_pointer(dst_reg, PTR_TO_PACKET) &&
73717446
src_reg->type == PTR_TO_PACKET_META)) {
73727447
/* pkt_end < pkt_data', pkt_data > pkt_meta' */
73737448
find_good_pkt_pointers(this_branch, src_reg,
73747449
src_reg->type, false);
7450+
mark_pkt_end(other_branch, insn->src_reg, true);
73757451
} else {
73767452
return false;
73777453
}
@@ -7384,13 +7460,15 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn,
73847460
/* pkt_data' >= pkt_end, pkt_meta' >= pkt_data */
73857461
find_good_pkt_pointers(this_branch, dst_reg,
73867462
dst_reg->type, true);
7463+
mark_pkt_end(other_branch, insn->dst_reg, false);
73877464
} else if ((dst_reg->type == PTR_TO_PACKET_END &&
73887465
src_reg->type == PTR_TO_PACKET) ||
73897466
(reg_is_init_pkt_pointer(dst_reg, PTR_TO_PACKET) &&
73907467
src_reg->type == PTR_TO_PACKET_META)) {
73917468
/* pkt_end >= pkt_data', pkt_data >= pkt_meta' */
73927469
find_good_pkt_pointers(other_branch, src_reg,
73937470
src_reg->type, false);
7471+
mark_pkt_end(this_branch, insn->src_reg, true);
73947472
} else {
73957473
return false;
73967474
}
@@ -7403,13 +7481,15 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn,
74037481
/* pkt_data' <= pkt_end, pkt_meta' <= pkt_data */
74047482
find_good_pkt_pointers(other_branch, dst_reg,
74057483
dst_reg->type, false);
7484+
mark_pkt_end(this_branch, insn->dst_reg, true);
74067485
} else if ((dst_reg->type == PTR_TO_PACKET_END &&
74077486
src_reg->type == PTR_TO_PACKET) ||
74087487
(reg_is_init_pkt_pointer(dst_reg, PTR_TO_PACKET) &&
74097488
src_reg->type == PTR_TO_PACKET_META)) {
74107489
/* pkt_end <= pkt_data', pkt_data <= pkt_meta' */
74117490
find_good_pkt_pointers(this_branch, src_reg,
74127491
src_reg->type, true);
7492+
mark_pkt_end(other_branch, insn->src_reg, false);
74137493
} else {
74147494
return false;
74157495
}
@@ -7509,6 +7589,10 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
75097589
src_reg->var_off.value,
75107590
opcode,
75117591
is_jmp32);
7592+
} else if (reg_is_pkt_pointer_any(dst_reg) &&
7593+
reg_is_pkt_pointer_any(src_reg) &&
7594+
!is_jmp32) {
7595+
pred = is_pkt_ptr_branch_taken(dst_reg, src_reg, opcode);
75127596
}
75137597

75147598
if (pred >= 0) {
@@ -7517,7 +7601,8 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
75177601
*/
75187602
if (!__is_pointer_value(false, dst_reg))
75197603
err = mark_chain_precision(env, insn->dst_reg);
7520-
if (BPF_SRC(insn->code) == BPF_X && !err)
7604+
if (BPF_SRC(insn->code) == BPF_X && !err &&
7605+
!__is_pointer_value(false, src_reg))
75217606
err = mark_chain_precision(env, insn->src_reg);
75227607
if (err)
75237608
return err;

0 commit comments

Comments
 (0)