diff options
author | Daniel Borkmann <daniel@iogearbox.net> | 2016-09-07 19:03:42 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2016-09-08 20:28:37 -0400 |
commit | 2d2be8cab26ed918e94d2deae89580003242a123 (patch) | |
tree | 78371d83d8f4f43a4c24268b93bbbbcb3813d901 /kernel/bpf/verifier.c | |
parent | 9f5afeae51526b3ad7b7cb21ee8b145ce6ea7a7a (diff) |
bpf: fix range propagation on direct packet access
LLVM can generate code that tests for direct packet access via
skb->data/data_end in a way that currently gets rejected by the
verifier, example:
[...]
7: (61) r3 = *(u32 *)(r6 +80)
8: (61) r9 = *(u32 *)(r6 +76)
9: (bf) r2 = r9
10: (07) r2 += 54
11: (3d) if r3 >= r2 goto pc+12
R1=inv R2=pkt(id=0,off=54,r=0) R3=pkt_end R4=inv R6=ctx
R9=pkt(id=0,off=0,r=0) R10=fp
12: (18) r4 = 0xffffff7a
14: (05) goto pc+430
[...]
from 11 to 24: R1=inv R2=pkt(id=0,off=54,r=0) R3=pkt_end R4=inv
R6=ctx R9=pkt(id=0,off=0,r=0) R10=fp
24: (7b) *(u64 *)(r10 -40) = r1
25: (b7) r1 = 0
26: (63) *(u32 *)(r6 +56) = r1
27: (b7) r2 = 40
28: (71) r8 = *(u8 *)(r9 +20)
invalid access to packet, off=20 size=1, R9(id=0,off=0,r=0)
The reason why this gets rejected despite a proper test is that we
currently call find_good_pkt_pointers() only in case where we detect
tests like rX > pkt_end, where rX is of type pkt(id=Y,off=Z,r=0) and
derived, for example, from a register of type pkt(id=Y,off=0,r=0)
pointing to skb->data. find_good_pkt_pointers() then fills the range
in the current branch to pkt(id=Y,off=0,r=Z) on success.
For above case, we need to extend that to recognize pkt_end >= rX
pattern and mark the other branch that is taken on success with the
appropriate pkt(id=Y,off=0,r=Z) type via find_good_pkt_pointers().
Since eBPF operates on BPF_JGT (>) and BPF_JGE (>=), these are the
only two practical options to test for from what LLVM could have
generated, since there's no such thing as BPF_JLT (<) or BPF_JLE (<=)
that we would need to take into account as well.
After the fix:
[...]
7: (61) r3 = *(u32 *)(r6 +80)
8: (61) r9 = *(u32 *)(r6 +76)
9: (bf) r2 = r9
10: (07) r2 += 54
11: (3d) if r3 >= r2 goto pc+12
R1=inv R2=pkt(id=0,off=54,r=0) R3=pkt_end R4=inv R6=ctx
R9=pkt(id=0,off=0,r=0) R10=fp
12: (18) r4 = 0xffffff7a
14: (05) goto pc+430
[...]
from 11 to 24: R1=inv R2=pkt(id=0,off=54,r=54) R3=pkt_end R4=inv
R6=ctx R9=pkt(id=0,off=0,r=54) R10=fp
24: (7b) *(u64 *)(r10 -40) = r1
25: (b7) r1 = 0
26: (63) *(u32 *)(r6 +56) = r1
27: (b7) r2 = 40
28: (71) r8 = *(u8 *)(r9 +20)
29: (bf) r1 = r8
30: (25) if r8 > 0x3c goto pc+47
R1=inv56 R2=imm40 R3=pkt_end R4=inv R6=ctx R8=inv56
R9=pkt(id=0,off=0,r=54) R10=fp
31: (b7) r1 = 1
[...]
Verifier test cases are also added in this work, one that demonstrates
the mentioned example here and one that tries a bad packet access for
the current/fall-through branch (the one with types pkt(id=X,off=Y,r=0),
pkt(id=X,off=0,r=0)), then a case with good and bad accesses, and two
with both test variants (>, >=).
Fixes: 969bf05eb3ce ("bpf: direct packet access")
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Acked-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'kernel/bpf/verifier.c')
-rw-r--r-- | kernel/bpf/verifier.c | 55 |
1 files changed, 40 insertions, 15 deletions
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 48c2705db22c..90493a66dddd 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c | |||
@@ -1637,21 +1637,42 @@ static int check_alu_op(struct verifier_env *env, struct bpf_insn *insn) | |||
1637 | return 0; | 1637 | return 0; |
1638 | } | 1638 | } |
1639 | 1639 | ||
1640 | static void find_good_pkt_pointers(struct verifier_env *env, | 1640 | static void find_good_pkt_pointers(struct verifier_state *state, |
1641 | struct reg_state *dst_reg) | 1641 | const struct reg_state *dst_reg) |
1642 | { | 1642 | { |
1643 | struct verifier_state *state = &env->cur_state; | ||
1644 | struct reg_state *regs = state->regs, *reg; | 1643 | struct reg_state *regs = state->regs, *reg; |
1645 | int i; | 1644 | int i; |
1646 | /* r2 = r3; | 1645 | |
1647 | * r2 += 8 | 1646 | /* LLVM can generate two kind of checks: |
1648 | * if (r2 > pkt_end) goto somewhere | 1647 | * |
1649 | * r2 == dst_reg, pkt_end == src_reg, | 1648 | * Type 1: |
1650 | * r2=pkt(id=n,off=8,r=0) | 1649 | * |
1651 | * r3=pkt(id=n,off=0,r=0) | 1650 | * r2 = r3; |
1652 | * find register r3 and mark its range as r3=pkt(id=n,off=0,r=8) | 1651 | * r2 += 8; |
1653 | * so that range of bytes [r3, r3 + 8) is safe to access | 1652 | * if (r2 > pkt_end) goto <handle exception> |
1653 | * <access okay> | ||
1654 | * | ||
1655 | * Where: | ||
1656 | * r2 == dst_reg, pkt_end == src_reg | ||
1657 | * r2=pkt(id=n,off=8,r=0) | ||
1658 | * r3=pkt(id=n,off=0,r=0) | ||
1659 | * | ||
1660 | * Type 2: | ||
1661 | * | ||
1662 | * r2 = r3; | ||
1663 | * r2 += 8; | ||
1664 | * if (pkt_end >= r2) goto <access okay> | ||
1665 | * <handle exception> | ||
1666 | * | ||
1667 | * Where: | ||
1668 | * pkt_end == dst_reg, r2 == src_reg | ||
1669 | * r2=pkt(id=n,off=8,r=0) | ||
1670 | * r3=pkt(id=n,off=0,r=0) | ||
1671 | * | ||
1672 | * Find register r3 and mark its range as r3=pkt(id=n,off=0,r=8) | ||
1673 | * so that range of bytes [r3, r3 + 8) is safe to access. | ||
1654 | */ | 1674 | */ |
1675 | |||
1655 | for (i = 0; i < MAX_BPF_REG; i++) | 1676 | for (i = 0; i < MAX_BPF_REG; i++) |
1656 | if (regs[i].type == PTR_TO_PACKET && regs[i].id == dst_reg->id) | 1677 | if (regs[i].type == PTR_TO_PACKET && regs[i].id == dst_reg->id) |
1657 | regs[i].range = dst_reg->off; | 1678 | regs[i].range = dst_reg->off; |
@@ -1668,8 +1689,8 @@ static void find_good_pkt_pointers(struct verifier_env *env, | |||
1668 | static int check_cond_jmp_op(struct verifier_env *env, | 1689 | static int check_cond_jmp_op(struct verifier_env *env, |
1669 | struct bpf_insn *insn, int *insn_idx) | 1690 | struct bpf_insn *insn, int *insn_idx) |
1670 | { | 1691 | { |
1671 | struct reg_state *regs = env->cur_state.regs, *dst_reg; | 1692 | struct verifier_state *other_branch, *this_branch = &env->cur_state; |
1672 | struct verifier_state *other_branch; | 1693 | struct reg_state *regs = this_branch->regs, *dst_reg; |
1673 | u8 opcode = BPF_OP(insn->code); | 1694 | u8 opcode = BPF_OP(insn->code); |
1674 | int err; | 1695 | int err; |
1675 | 1696 | ||
@@ -1750,13 +1771,17 @@ static int check_cond_jmp_op(struct verifier_env *env, | |||
1750 | } else if (BPF_SRC(insn->code) == BPF_X && opcode == BPF_JGT && | 1771 | } else if (BPF_SRC(insn->code) == BPF_X && opcode == BPF_JGT && |
1751 | dst_reg->type == PTR_TO_PACKET && | 1772 | dst_reg->type == PTR_TO_PACKET && |
1752 | regs[insn->src_reg].type == PTR_TO_PACKET_END) { | 1773 | regs[insn->src_reg].type == PTR_TO_PACKET_END) { |
1753 | find_good_pkt_pointers(env, dst_reg); | 1774 | find_good_pkt_pointers(this_branch, dst_reg); |
1775 | } else if (BPF_SRC(insn->code) == BPF_X && opcode == BPF_JGE && | ||
1776 | dst_reg->type == PTR_TO_PACKET_END && | ||
1777 | regs[insn->src_reg].type == PTR_TO_PACKET) { | ||
1778 | find_good_pkt_pointers(other_branch, ®s[insn->src_reg]); | ||
1754 | } else if (is_pointer_value(env, insn->dst_reg)) { | 1779 | } else if (is_pointer_value(env, insn->dst_reg)) { |
1755 | verbose("R%d pointer comparison prohibited\n", insn->dst_reg); | 1780 | verbose("R%d pointer comparison prohibited\n", insn->dst_reg); |
1756 | return -EACCES; | 1781 | return -EACCES; |
1757 | } | 1782 | } |
1758 | if (log_level) | 1783 | if (log_level) |
1759 | print_verifier_state(&env->cur_state); | 1784 | print_verifier_state(this_branch); |
1760 | return 0; | 1785 | return 0; |
1761 | } | 1786 | } |
1762 | 1787 | ||