Skip to content

Commit 0a58a65

Browse files
committed
Merge branch 'bpf-ptrs-beyond-pkt-end'
Alexei Starovoitov says: ==================== v1->v2: - removed set-but-unused variable. - added Jiri's Tested-by. In some cases LLVM uses the knowledge that branch is taken to optimze the code which causes the verifier to reject valid programs. 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: Daniel Borkmann <[email protected]>
2 parents c365387 + cb62d34 commit 0a58a65

File tree

5 files changed

+245
-23
lines changed

5 files changed

+245
-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;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright (c) 2020 Facebook */
3+
#include <test_progs.h>
4+
#include <network_helpers.h>
5+
#include "skb_pkt_end.skel.h"
6+
7+
static int sanity_run(struct bpf_program *prog)
8+
{
9+
__u32 duration, retval;
10+
int err, prog_fd;
11+
12+
prog_fd = bpf_program__fd(prog);
13+
err = bpf_prog_test_run(prog_fd, 1, &pkt_v4, sizeof(pkt_v4),
14+
NULL, NULL, &retval, &duration);
15+
if (CHECK(err || retval != 123, "test_run",
16+
"err %d errno %d retval %d duration %d\n",
17+
err, errno, retval, duration))
18+
return -1;
19+
return 0;
20+
}
21+
22+
void test_test_skb_pkt_end(void)
23+
{
24+
struct skb_pkt_end *skb_pkt_end_skel = NULL;
25+
__u32 duration = 0;
26+
int err;
27+
28+
skb_pkt_end_skel = skb_pkt_end__open_and_load();
29+
if (CHECK(!skb_pkt_end_skel, "skb_pkt_end_skel_load", "skb_pkt_end skeleton failed\n"))
30+
goto cleanup;
31+
32+
err = skb_pkt_end__attach(skb_pkt_end_skel);
33+
if (CHECK(err, "skb_pkt_end_attach", "skb_pkt_end attach failed: %d\n", err))
34+
goto cleanup;
35+
36+
if (sanity_run(skb_pkt_end_skel->progs.main_prog))
37+
goto cleanup;
38+
39+
cleanup:
40+
skb_pkt_end__destroy(skb_pkt_end_skel);
41+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
#define BPF_NO_PRESERVE_ACCESS_INDEX
3+
#include <vmlinux.h>
4+
#include <bpf/bpf_core_read.h>
5+
#include <bpf/bpf_helpers.h>
6+
7+
#define NULL 0
8+
#define INLINE __always_inline
9+
10+
#define skb_shorter(skb, len) ((void *)(long)(skb)->data + (len) > (void *)(long)skb->data_end)
11+
12+
#define ETH_IPV4_TCP_SIZE (14 + sizeof(struct iphdr) + sizeof(struct tcphdr))
13+
14+
static INLINE struct iphdr *get_iphdr(struct __sk_buff *skb)
15+
{
16+
struct iphdr *ip = NULL;
17+
struct ethhdr *eth;
18+
19+
if (skb_shorter(skb, ETH_IPV4_TCP_SIZE))
20+
goto out;
21+
22+
eth = (void *)(long)skb->data;
23+
ip = (void *)(eth + 1);
24+
25+
out:
26+
return ip;
27+
}
28+
29+
SEC("classifier/cls")
30+
int main_prog(struct __sk_buff *skb)
31+
{
32+
struct iphdr *ip = NULL;
33+
struct tcphdr *tcp;
34+
__u8 proto = 0;
35+
36+
if (!(ip = get_iphdr(skb)))
37+
goto out;
38+
39+
proto = ip->protocol;
40+
41+
if (proto != IPPROTO_TCP)
42+
goto out;
43+
44+
tcp = (void*)(ip + 1);
45+
if (tcp->dest != 0)
46+
goto out;
47+
if (!tcp)
48+
goto out;
49+
50+
return tcp->urg_ptr;
51+
out:
52+
return -1;
53+
}
54+
char _license[] SEC("license") = "GPL";

tools/testing/selftests/bpf/verifier/ctx_skb.c

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,3 +1089,45 @@
10891089
.errstr_unpriv = "R1 leaks addr",
10901090
.result = REJECT,
10911091
},
1092+
{
1093+
"pkt > pkt_end taken check",
1094+
.insns = {
1095+
BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_1, // 0. r2 = *(u32 *)(r1 + data_end)
1096+
offsetof(struct __sk_buff, data_end)),
1097+
BPF_LDX_MEM(BPF_W, BPF_REG_4, BPF_REG_1, // 1. r4 = *(u32 *)(r1 + data)
1098+
offsetof(struct __sk_buff, data)),
1099+
BPF_MOV64_REG(BPF_REG_3, BPF_REG_4), // 2. r3 = r4
1100+
BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, 42), // 3. r3 += 42
1101+
BPF_MOV64_IMM(BPF_REG_1, 0), // 4. r1 = 0
1102+
BPF_JMP_REG(BPF_JGT, BPF_REG_3, BPF_REG_2, 2), // 5. if r3 > r2 goto 8
1103+
BPF_ALU64_IMM(BPF_ADD, BPF_REG_4, 14), // 6. r4 += 14
1104+
BPF_MOV64_REG(BPF_REG_1, BPF_REG_4), // 7. r1 = r4
1105+
BPF_JMP_REG(BPF_JGT, BPF_REG_3, BPF_REG_2, 1), // 8. if r3 > r2 goto 10
1106+
BPF_LDX_MEM(BPF_H, BPF_REG_2, BPF_REG_1, 9), // 9. r2 = *(u8 *)(r1 + 9)
1107+
BPF_MOV64_IMM(BPF_REG_0, 0), // 10. r0 = 0
1108+
BPF_EXIT_INSN(), // 11. exit
1109+
},
1110+
.result = ACCEPT,
1111+
.prog_type = BPF_PROG_TYPE_SK_SKB,
1112+
},
1113+
{
1114+
"pkt_end < pkt taken check",
1115+
.insns = {
1116+
BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_1, // 0. r2 = *(u32 *)(r1 + data_end)
1117+
offsetof(struct __sk_buff, data_end)),
1118+
BPF_LDX_MEM(BPF_W, BPF_REG_4, BPF_REG_1, // 1. r4 = *(u32 *)(r1 + data)
1119+
offsetof(struct __sk_buff, data)),
1120+
BPF_MOV64_REG(BPF_REG_3, BPF_REG_4), // 2. r3 = r4
1121+
BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, 42), // 3. r3 += 42
1122+
BPF_MOV64_IMM(BPF_REG_1, 0), // 4. r1 = 0
1123+
BPF_JMP_REG(BPF_JGT, BPF_REG_3, BPF_REG_2, 2), // 5. if r3 > r2 goto 8
1124+
BPF_ALU64_IMM(BPF_ADD, BPF_REG_4, 14), // 6. r4 += 14
1125+
BPF_MOV64_REG(BPF_REG_1, BPF_REG_4), // 7. r1 = r4
1126+
BPF_JMP_REG(BPF_JLT, BPF_REG_2, BPF_REG_3, 1), // 8. if r2 < r3 goto 10
1127+
BPF_LDX_MEM(BPF_H, BPF_REG_2, BPF_REG_1, 9), // 9. r2 = *(u8 *)(r1 + 9)
1128+
BPF_MOV64_IMM(BPF_REG_0, 0), // 10. r0 = 0
1129+
BPF_EXIT_INSN(), // 11. exit
1130+
},
1131+
.result = ACCEPT,
1132+
.prog_type = BPF_PROG_TYPE_SK_SKB,
1133+
},

0 commit comments

Comments
 (0)