summaryrefslogtreecommitdiff
path: root/sys/net/pf.c
diff options
context:
space:
mode:
authorAlexander Bluhm <bluhm@cvs.openbsd.org>2011-07-07 20:46:38 +0000
committerAlexander Bluhm <bluhm@cvs.openbsd.org>2011-07-07 20:46:38 +0000
commitc672c28f271b32e8c50ee8a8ce8adbb36fc79420 (patch)
tree8d4454eca8b92e4f3b3370d5a3992d0fa9c89627 /sys/net/pf.c
parent6dcc67f5a65c901e09cc8c82acd1da6ed4a6ac3a (diff)
There were two loops in pf_setup_pdesc() and pf_normalize_ip6()
walking over the IPv6 header chain. Merge them into one loop, adjust some length checks and fix IPv6 jumbo option handling. Also allow strange but legal IPv6 packets with plen=0 passing through pf. IPv6 jumbo packets still get dropped. testing dhill@; ok mcbride@ henning@
Diffstat (limited to 'sys/net/pf.c')
-rw-r--r--sys/net/pf.c269
1 files changed, 193 insertions, 76 deletions
diff --git a/sys/net/pf.c b/sys/net/pf.c
index 376569c9047..b6afb0c5ce4 100644
--- a/sys/net/pf.c
+++ b/sys/net/pf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: pf.c,v 1.761 2011/07/07 00:47:18 mcbride Exp $ */
+/* $OpenBSD: pf.c,v 1.762 2011/07/07 20:46:36 bluhm Exp $ */
/*
* Copyright (c) 2001 Daniel Hartmeier
@@ -224,6 +224,10 @@ void pf_set_rt_ifp(struct pf_state *,
int pf_check_proto_cksum(struct mbuf *, int, int,
u_int8_t, sa_family_t);
struct pf_divert *pf_get_divert(struct mbuf *);
+int pf_walk_option6(struct mbuf *, int, int, u_int32_t *,
+ u_short *);
+int pf_walk_header6(struct mbuf *, u_int8_t *, int *,
+ int *, u_int32_t *, u_short *);
void pf_print_state_parts(struct pf_state *,
struct pf_state_key *, struct pf_state_key *);
int pf_addr_wrap_neq(struct pf_addr_wrap *,
@@ -5429,6 +5433,142 @@ pf_get_divert(struct mbuf *m)
}
int
+pf_walk_option6(struct mbuf *m, int off, int end, u_int32_t *jumbolen,
+ u_short *reason)
+{
+ struct ip6_opt opt;
+ struct ip6_opt_jumbo jumbo;
+ struct ip6_hdr *h = mtod(m, struct ip6_hdr *);
+
+ while (off < end) {
+ if (!pf_pull_hdr(m, off, &opt.ip6o_type, sizeof(opt.ip6o_type),
+ NULL, reason, AF_INET6)) {
+ DPFPRINTF(LOG_NOTICE, "IPv6 short opt type");
+ return (PF_DROP);
+ }
+ if (opt.ip6o_type == IP6OPT_PAD1) {
+ off++;
+ continue;
+ }
+ if (!pf_pull_hdr(m, off, &opt, sizeof(opt),
+ NULL, reason, AF_INET6)) {
+ DPFPRINTF(LOG_NOTICE, "IPv6 short opt");
+ return (PF_DROP);
+ }
+ if (off + sizeof(opt) + opt.ip6o_len > end) {
+ DPFPRINTF(LOG_NOTICE, "IPv6 long opt");
+ REASON_SET(reason, PFRES_IPOPTIONS);
+ return (PF_DROP);
+ }
+ switch (opt.ip6o_type) {
+ case IP6OPT_JUMBO:
+ if (*jumbolen != 0) {
+ DPFPRINTF(LOG_NOTICE, "IPv6 multiple jumbo");
+ REASON_SET(reason, PFRES_IPOPTIONS);
+ return (PF_DROP);
+ }
+ if (ntohs(h->ip6_plen) != 0) {
+ DPFPRINTF(LOG_NOTICE, "IPv6 bad jumbo plen");
+ REASON_SET(reason, PFRES_IPOPTIONS);
+ return (PF_DROP);
+ }
+ if (!pf_pull_hdr(m, off, &jumbo, sizeof(jumbo),
+ NULL, reason, AF_INET6)) {
+ DPFPRINTF(LOG_NOTICE, "IPv6 short jumbo");
+ return (PF_DROP);
+ }
+ memcpy(jumbolen, jumbo.ip6oj_jumbo_len,
+ sizeof(*jumbolen));
+ *jumbolen = ntohl(*jumbolen);
+ if (*jumbolen < IPV6_MAXPACKET) {
+ DPFPRINTF(LOG_NOTICE, "IPv6 short jumbolen");
+ REASON_SET(reason, PFRES_IPOPTIONS);
+ return (PF_DROP);
+ }
+ break;
+ default:
+ break;
+ }
+ off += sizeof(opt) + opt.ip6o_len;
+ }
+
+ return (PF_PASS);
+}
+
+int
+pf_walk_header6(struct mbuf *m, u_int8_t *nxt, int *off, int *extoff,
+ u_int32_t *jumbolen, u_short *reason)
+{
+ struct ip6_ext ext;
+ struct ip6_rthdr rthdr;
+ struct ip6_hdr *h = mtod(m, struct ip6_hdr *);
+ int rthdr_cnt = 0;
+
+ *nxt = h->ip6_nxt;
+ *off = sizeof(struct ip6_hdr);
+ *extoff = 0;
+ *jumbolen = 0;
+ for (;;) {
+ switch (*nxt) {
+ case IPPROTO_FRAGMENT:
+ /* jumbo payload packets cannot be fragmented */
+ if (*jumbolen != 0) {
+ DPFPRINTF(LOG_NOTICE, "IPv6 fragmented jumbo");
+ REASON_SET(reason, PFRES_FRAG);
+ return (PF_DROP);
+ }
+ return (PF_PASS);
+ case IPPROTO_ROUTING:
+ if (rthdr_cnt++) {
+ DPFPRINTF(LOG_NOTICE, "IPv6 multiple rthdr");
+ REASON_SET(reason, PFRES_IPOPTIONS);
+ return (PF_DROP);
+ }
+ if (!pf_pull_hdr(m, *off, &rthdr, sizeof(rthdr),
+ NULL, reason, AF_INET6)) {
+ DPFPRINTF(LOG_NOTICE, "IPv6 short rthdr");
+ return (PF_DROP);
+ }
+ if (rthdr.ip6r_type == IPV6_RTHDR_TYPE_0) {
+ DPFPRINTF(LOG_NOTICE, "IPv6 rthdr0");
+ REASON_SET(reason, PFRES_IPOPTIONS);
+ return (PF_DROP);
+ }
+ /* FALLTHROUGH */
+ case IPPROTO_AH:
+ case IPPROTO_HOPOPTS:
+ case IPPROTO_DSTOPTS:
+ if (!pf_pull_hdr(m, *off, &ext, sizeof(ext),
+ NULL, reason, AF_INET6)) {
+ DPFPRINTF(LOG_NOTICE, "IPv6 short exthdr");
+ return (PF_DROP);
+ }
+ *extoff = *off;
+ if (*nxt == IPPROTO_HOPOPTS) {
+ if (pf_walk_option6(m, *off + sizeof(ext),
+ *off + (ext.ip6e_len + 1) * 8, jumbolen,
+ reason) != PF_PASS)
+ return (PF_DROP);
+ if (ntohs(h->ip6_plen) == 0 && *jumbolen != 0) {
+ DPFPRINTF(LOG_NOTICE,
+ "IPv6 missing jumbo");
+ REASON_SET(reason, PFRES_IPOPTIONS);
+ return (PF_DROP);
+ }
+ }
+ if (*nxt == IPPROTO_AH)
+ *off += (ext.ip6e_len + 2) * 4;
+ else
+ *off += (ext.ip6e_len + 1) * 8;
+ *nxt = ext.ip6e_nxt;
+ break;
+ default:
+ return (PF_PASS);
+ }
+ }
+}
+
+int
pf_setup_pdesc(sa_family_t af, int dir, struct pf_pdesc *pd, struct mbuf **m0,
u_short *action, u_short *reason, struct pfi_kif *kif, struct pf_rule **a,
struct pf_rule **r, struct pf_state **s, struct pf_ruleset **ruleset,
@@ -5516,7 +5656,9 @@ pf_setup_pdesc(sa_family_t af, int dir, struct pf_pdesc *pd, struct mbuf **m0,
#ifdef INET6
case AF_INET6: {
struct ip6_hdr *h;
- int terminal = 0;
+ int extoff;
+ u_int32_t jumbolen;
+ u_int8_t nxt;
/* Check for illegal packets */
if (m->m_pkthdr.len < (int)sizeof(struct ip6_hdr)) {
@@ -5525,25 +5667,50 @@ pf_setup_pdesc(sa_family_t af, int dir, struct pf_pdesc *pd, struct mbuf **m0,
return (-1);
}
- /* packet reassembly */
- if (pf_normalize_ip6(m0, dir, reason) != PF_PASS) {
+ h = mtod(m, struct ip6_hdr *);
+
+ if (m->m_pkthdr.len <
+ sizeof(struct ip6_hdr) + ntohs(h->ip6_plen)) {
*action = PF_DROP;
+ REASON_SET(reason, PFRES_SHORT);
return (-1);
}
- m = *m0;
- if (m == NULL) {
- /* packet sits in reassembly queue, no error */
- *action = PF_PASS;
+
+ if (pf_walk_header6(m, &nxt, off, &extoff, &jumbolen, reason)
+ != PF_PASS) {
+ *action = PF_DROP;
return (-1);
}
- h = mtod(m, struct ip6_hdr *);
+ if (pf_status.reass && nxt == IPPROTO_FRAGMENT) {
+ /* packet reassembly */
+ if (pf_normalize_ip6(m0, dir, *off, extoff, reason) !=
+ PF_PASS) {
+ *action = PF_DROP;
+ return (-1);
+ }
+ m = *m0;
+ if (m == NULL) {
+ /* packet sits in reassembly queue, no error */
+ *action = PF_PASS;
+ return (-1);
+ }
+
+ /* recalc offset, refetch header, then update pd */
+ if (pf_walk_header6(m, &nxt, off, &extoff, &jumbolen,
+ reason) != PF_PASS) {
+ *action = PF_DROP;
+ return (-1);
+ }
+ h = mtod(m, struct ip6_hdr *);
+ }
+
#if 1
/*
* we do not support jumbogram yet. if we keep going, zero
* ip6_plen will do something bad, so drop the packet for now.
*/
- if (htons(h->ip6_plen) == 0) {
+ if (jumbolen != 0) {
*action = PF_DROP;
REASON_SET(reason, PFRES_NORM);
return (-1);
@@ -5560,73 +5727,23 @@ pf_setup_pdesc(sa_family_t af, int dir, struct pf_pdesc *pd, struct mbuf **m0,
pd->didx = (dir == PF_IN) ? 1 : 0;
pd->tos = 0;
pd->tot_len = ntohs(h->ip6_plen) + sizeof(struct ip6_hdr);
- *off = ((caddr_t)h - m->m_data) + sizeof(struct ip6_hdr);
- pd->virtual_proto = pd->proto = h->ip6_nxt;
- do {
- switch (pd->proto) {
- case IPPROTO_FRAGMENT:
- pd->virtual_proto = PF_VPROTO_FRAGMENT;
- if (kif == NULL || r == NULL) /* pflog */
- *action = PF_DROP;
- else
- *action = pf_test_rule(r, s, dir, kif,
- m, *off, pd, a, ruleset, *hdrlen);
- if (*action != PF_PASS)
- REASON_SET(reason, PFRES_FRAG);
- return (-1);
- case IPPROTO_ROUTING: {
- struct ip6_rthdr rthdr;
+ pd->virtual_proto = pd->proto = nxt;
- if (pd->badopts++) {
- DPFPRINTF(LOG_NOTICE,
- "IPv6 more than one rthdr");
- *action = PF_DROP;
- REASON_SET(reason, PFRES_IPOPTIONS);
- return (-1);
- }
- if (!pf_pull_hdr(m, *off, &rthdr, sizeof(rthdr),
- NULL, reason, pd->af)) {
- DPFPRINTF(LOG_NOTICE,
- "IPv6 short rthdr");
- *action = PF_DROP;
- REASON_SET(reason, PFRES_SHORT);
- return (-1);
- }
- if (rthdr.ip6r_type == IPV6_RTHDR_TYPE_0) {
- DPFPRINTF(LOG_NOTICE,
- "IPv6 rthdr0");
- *action = PF_DROP;
- REASON_SET(reason, PFRES_IPOPTIONS);
- return (-1);
- }
- /* FALLTHROUGH */
- }
- case IPPROTO_AH:
- case IPPROTO_HOPOPTS:
- case IPPROTO_DSTOPTS: {
- /* get next header and header length */
- struct ip6_ext opt6;
-
- if (!pf_pull_hdr(m, *off, &opt6, sizeof(opt6),
- NULL, reason, pd->af)) {
- DPFPRINTF(LOG_NOTICE,
- "IPv6 short opt");
- *action = PF_DROP;
- return (-1);
- }
- if (pd->proto == IPPROTO_AH)
- *off += (opt6.ip6e_len + 2) * 4;
- else
- *off += (opt6.ip6e_len + 1) * 8;
- pd->virtual_proto = pd->proto = opt6.ip6e_nxt;
- /* goto the next header */
- break;
- }
- default:
- terminal++;
- break;
- }
- } while (!terminal);
+ if (pd->proto == IPPROTO_FRAGMENT) {
+ /*
+ * handle fragments that aren't reassembled by
+ * normalization
+ */
+ pd->virtual_proto = PF_VPROTO_FRAGMENT;
+ if (kif == NULL || r == NULL) /* pflog */
+ *action = PF_DROP;
+ else
+ *action = pf_test_rule(r, s, dir, kif,
+ m, *off, pd, a, ruleset, *hdrlen);
+ if (*action != PF_PASS)
+ REASON_SET(reason, PFRES_FRAG);
+ return (-1);
+ }
break;
}
#endif