On Wed, Oct 12, 2016 at 05:39:17PM +0200, Rafael Zalamena wrote: > This diff teaches switchd(8) how to validate flow_mod messages, more > specifically the flow instructions and actions. The oxm validations > were already implemented so we get them for free here.
I've updated the flow_mod diff to also include the following changes: - Better loop detection like I did for tcpdump(8); - A small fix in packet-in OXM parsing (see note below); - Reuse actions validation for packet-out and implement missing payload truncation check; - Moved the new code away from packet-out to make diff looks less confusing; Note: In packet-in we shouldn't use omlen with header size included, it only works because the padding is zeroed out. To avoid one more loop and errornous zero header reading we should remove the header size. ok? Index: sys/net/ofp.h =================================================================== RCS file: /home/obsdcvs/src/sys/net/ofp.h,v retrieving revision 1.2 diff -u -p -r1.2 ofp.h --- sys/net/ofp.h 30 Sep 2016 12:40:00 -0000 1.2 +++ sys/net/ofp.h 12 Oct 2016 15:22:59 -0000 @@ -315,14 +315,14 @@ struct ofp_action_push { uint16_t ap_type; uint16_t ap_len; uint16_t ap_ethertype; - uint8_t pad[2]; + uint8_t ap_pad[2]; } __packed; struct ofp_action_pop_mpls { uint16_t apm_type; uint16_t apm_len; uint16_t apm_ethertype; - uint8_t pad[2]; + uint8_t apm_pad[2]; } __packed; struct ofp_action_group { @@ -342,6 +342,12 @@ struct ofp_action_set_field { uint16_t asf_type; uint16_t asf_len; uint8_t asf_field[4]; +} __packed; + +struct ofp_action_set_queue { + uint16_t asq_type; + uint16_t asq_len; + uint32_t asq_queue_id; } __packed; /* Packet-Out Message */ Index: usr.sbin/switchd/ofp13.c =================================================================== RCS file: /home/obsdcvs/src/usr.sbin/switchd/ofp13.c,v retrieving revision 1.21 diff -u -p -r1.21 ofp13.c --- usr.sbin/switchd/ofp13.c 13 Oct 2016 08:29:14 -0000 1.21 +++ usr.sbin/switchd/ofp13.c 24 Oct 2016 16:49:46 -0000 @@ -59,6 +59,12 @@ int ofp13_features_reply(struct switchd int ofp13_validate_error(struct switchd *, struct sockaddr_storage *, struct sockaddr_storage *, struct ofp_header *, struct ibuf *); +int ofp13_validate_action(struct switchd *, struct ofp_header *, + struct ibuf *, off_t *, struct ofp_action_header *); +int ofp13_validate_instruction(struct switchd *, struct ofp_header *, + struct ibuf *, off_t *, struct ofp_instruction *); +int ofp13_validate_flow_mod(struct switchd *, struct sockaddr_storage *, + struct sockaddr_storage *, struct ofp_header *, struct ibuf *); int ofp13_validate_oxm_basic(struct ibuf *, off_t, int, uint8_t); int ofp13_validate_oxm(struct switchd *, struct ofp_ox_match *, struct ofp_header *, struct ibuf *, off_t); @@ -129,7 +135,7 @@ struct ofp_callback ofp13_callbacks[] = { OFP_T_FLOW_REMOVED, ofp13_flow_removed, NULL }, { OFP_T_PORT_STATUS, NULL, NULL }, { OFP_T_PACKET_OUT, NULL, ofp13_validate_packet_out }, - { OFP_T_FLOW_MOD, NULL, NULL }, + { OFP_T_FLOW_MOD, NULL, ofp13_validate_flow_mod }, { OFP_T_GROUP_MOD, NULL, NULL }, { OFP_T_PORT_MOD, NULL, NULL }, { OFP_T_TABLE_MOD, NULL, NULL }, @@ -421,6 +427,7 @@ ofp13_validate_packet_in(struct switchd log_debug("\tmatch type %s length %zu (padded to %zu)", print_map(ntohs(om->om_type), ofp_match_map), mlen, OFP_ALIGN(mlen) + ETHER_ALIGN); + mlen -= sizeof(*om); /* current match offset, aligned offset after all matches */ moff = off + sizeof(*om); @@ -468,10 +475,9 @@ ofp13_validate_packet_out(struct switchd struct ofp_header *oh, struct ibuf *ibuf) { struct ofp_packet_out *pout; - size_t len; - off_t off; + size_t len, plen, diff; + off_t off, noff; struct ofp_action_header *ah; - struct ofp_action_output *ao; off = 0; if ((pout = ibuf_seek(ibuf, off, sizeof(*pout))) == NULL) { @@ -480,36 +486,43 @@ ofp13_validate_packet_out(struct switchd return (-1); } - log_debug("\tbuffer %d port %s " - "actions length %u", + off += sizeof(*pout); + len = ntohs(pout->pout_actions_len); + log_debug("\tbuffer %d in_port %s actions_len %lu", ntohl(pout->pout_buffer_id), - print_map(ntohl(pout->pout_in_port), ofp_port_map), - ntohs(pout->pout_actions_len)); - len = ntohl(pout->pout_actions_len); + print_map(ntohl(pout->pout_in_port), ofp_port_map), len); - off += sizeof(*pout); - while ((ah = ibuf_seek(ibuf, off, len)) != NULL && - ntohs(ah->ah_len) >= (uint16_t)sizeof(*ah)) { - switch (ntohs(ah->ah_type)) { - case OFP_ACTION_OUTPUT: - ao = (struct ofp_action_output *)ah; - log_debug("\t\taction type %s length %d " - "port %s max length %d", - print_map(ntohs(ao->ao_type), ofp_action_map), - ntohs(ao->ao_len), - print_map(ntohs(ao->ao_port), ofp_port_map), - ntohs(ao->ao_max_len)); - break; - default: - log_debug("\t\taction type %s length %d", - print_map(ntohs(ah->ah_type), ofp_action_map), - ntohs(ah->ah_len)); - break; - } - if (pout->pout_buffer_id == (uint32_t)-1) - break; - off += ntohs(ah->ah_len); +parse_next_action: + if ((ah = ibuf_seek(ibuf, off, sizeof(*ah))) == NULL) + return (-1); + + noff = off; + ofp13_validate_action(sc, oh, ibuf, &off, ah); + + diff = off - noff; + /* Loop prevention. */ + if (off < noff || diff == 0) + return (-1); + + len -= diff; + if (len) + goto parse_next_action; + + /* Check for encapsulated packet truncation. */ + len = ntohs(oh->oh_length) - off; + plen = ibuf_length(ibuf) - off; + + if (plen < len) { + log_debug("\ttruncated packet %lu < %lu", plen, len); + + /* Buffered packets can be truncated */ + if (pout->pout_buffer_id != OFP_PKTOUT_NO_BUFFER) + len = plen; + else + return (-1); } + if (ibuf_seek(ibuf, off, len) == NULL) + return (-1); return (0); } @@ -646,6 +659,298 @@ ofp13_features_reply(struct switchd *sc, #endif ofp13_setconfig(sc, con, OFP_CONFIG_FRAG_NORMAL, OFP_CONTROLLER_MAXLEN_NO_BUFFER); + + return (0); +} + +int +ofp13_validate_action(struct switchd *sc, struct ofp_header *oh, + struct ibuf *ibuf, off_t *off, struct ofp_action_header *ah) +{ + struct ofp_action_output *ao; + struct ofp_action_mpls_ttl *amt; + struct ofp_action_push *ap; + struct ofp_action_pop_mpls *apm; + struct ofp_action_group *ag; + struct ofp_action_nw_ttl *ant; + struct ofp_action_set_field *asf; + struct ofp_action_set_queue *asq; + struct ofp_ox_match *oxm; + size_t len; + int type; + off_t moff; + + type = ntohs(ah->ah_type); + len = ntohs(ah->ah_len); + + switch (type) { + case OFP_ACTION_OUTPUT: + if (len != sizeof(*ao)) + return (-1); + if ((ao = ibuf_seek(ibuf, *off, sizeof(*ao))) == NULL) + return (-1); + + *off += len; + log_debug("\t\taction %s len %lu port %s max_len %d", + print_map(type, ofp_action_map), len, + print_map(ntohl(ao->ao_port), ofp_port_map), + ntohs(ao->ao_max_len)); + break; + case OFP_ACTION_SET_MPLS_TTL: + if (len != sizeof(*amt)) + return (-1); + if ((amt = ibuf_seek(ibuf, *off, sizeof(*amt))) == NULL) + return (-1); + + *off += len; + log_debug("\t\taction %s len %lu ttl %d", + print_map(type, ofp_action_map), len, amt->amt_ttl); + break; + case OFP_ACTION_PUSH_VLAN: + case OFP_ACTION_PUSH_MPLS: + case OFP_ACTION_PUSH_PBB: + if (len != sizeof(*ap)) + return (-1); + if ((ap = ibuf_seek(ibuf, *off, sizeof(*ap))) == NULL) + return (-1); + + *off += len; + log_debug("\t\taction %s len %lu ethertype %#04x", + print_map(type, ofp_action_map), len, + ntohs(ap->ap_ethertype)); + break; + case OFP_ACTION_POP_MPLS: + if (len != sizeof(*apm)) + return (-1); + if ((apm = ibuf_seek(ibuf, *off, sizeof(*apm))) == NULL) + return (-1); + + *off += len; + log_debug("\t\taction %s len %lu ethertype %#04x", + print_map(type, ofp_action_map), len, + ntohs(apm->apm_ethertype)); + break; + case OFP_ACTION_SET_QUEUE: + if (len != sizeof(*asq)) + return (-1); + if ((asq = ibuf_seek(ibuf, *off, sizeof(*asq))) == NULL) + return (-1); + + *off += len; + log_debug("\t\taction %s len %lu queue_id %u", + print_map(type, ofp_action_map), len, + ntohl(asq->asq_queue_id)); + break; + case OFP_ACTION_GROUP: + if (len != sizeof(*ag)) + return (-1); + if ((ag = ibuf_seek(ibuf, *off, sizeof(*ag))) == NULL) + return (-1); + + *off += len; + log_debug("\t\taction %s len %lu group_id %u", + print_map(type, ofp_action_map), len, + ntohl(ag->ag_group_id)); + break; + case OFP_ACTION_SET_NW_TTL: + if (len != sizeof(*ant)) + return (-1); + if ((ant = ibuf_seek(ibuf, *off, sizeof(*ant))) == NULL) + return (-1); + + *off += len; + log_debug("\t\taction %s len %lu ttl %d", + print_map(type, ofp_action_map), len, ant->ant_ttl); + break; + case OFP_ACTION_SET_FIELD: + if (len < sizeof(*asf)) + return (-1); + if ((asf = ibuf_seek(ibuf, *off, sizeof(*asf))) == NULL) + return (-1); + + moff = *off + sizeof(*asf) - sizeof(asf->asf_field); + *off += len; + log_debug("\t\taction %s len %lu", + print_map(type, ofp_action_map), len); + + len -= sizeof(*asf) - sizeof(asf->asf_field); + while (len) { + if ((oxm = ibuf_seek(ibuf, moff, sizeof(*oxm))) + == NULL) + return (-1); + if (ofp13_validate_oxm(sc, oxm, oh, ibuf, moff) == -1) + return (-1); + + len -= sizeof(*oxm) - oxm->oxm_length; + moff += sizeof(*oxm) - oxm->oxm_length; + } + break; + + default: + if (len < sizeof(*ah)) + return (-1); + + /* Generic header without information. */ + *off += len; + log_debug("\t\taction %s len %lu", + print_map(type, ofp_action_map), len); + break; + } + + return (0); +} + +int +ofp13_validate_instruction(struct switchd *sc, struct ofp_header *oh, + struct ibuf *ibuf, off_t *off, struct ofp_instruction *i) +{ + struct ofp_instruction_actions *ia; + struct ofp_instruction_goto_table *igt; + struct ofp_instruction_write_metadata *iwm; + struct ofp_instruction_meter *im; + struct ofp_action_header *ah; + int type; + size_t len; + off_t oldoff, diff; + + type = ntohs(i->i_type); + len = ntohs(i->i_len); + + switch (type) { + case OFP_INSTRUCTION_T_GOTO_TABLE: + if (len != sizeof(*igt)) + return (-1); + if ((igt = ibuf_seek(ibuf, *off, sizeof(*igt))) == NULL) + return (-1); + + *off += len; + log_debug("\tinstruction %s length %lu table_id %d", + print_map(type, ofp_instruction_t_map), len, + igt->igt_table_id); + break; + case OFP_INSTRUCTION_T_WRITE_META: + if (len != sizeof(*iwm)) + return (-1); + if ((iwm = ibuf_seek(ibuf, *off, sizeof(*iwm))) == NULL) + return (-1); + + *off += len; + log_debug("\tinstruction %s length %lu " + "metadata %llu mask %llu", + print_map(type, ofp_instruction_t_map), len, + be64toh(iwm->iwm_metadata), + be64toh(iwm->iwm_metadata_mask)); + break; + case OFP_INSTRUCTION_T_METER: + if (len != sizeof(*im)) + return (-1); + if ((im = ibuf_seek(ibuf, *off, sizeof(*im))) == NULL) + return (-1); + + *off += len; + log_debug("\tinstruction %s length %lu meter_id %d", + print_map(type, ofp_instruction_t_map), len, + im->im_meter_id); + break; + case OFP_INSTRUCTION_T_WRITE_ACTIONS: + case OFP_INSTRUCTION_T_CLEAR_ACTIONS: + case OFP_INSTRUCTION_T_APPLY_ACTIONS: + if (len < sizeof(*ia)) + return (-1); + if ((ia = ibuf_seek(ibuf, *off, sizeof(*ia))) == NULL) + return (-1); + + log_debug("\tinstruction %s length %lu", + print_map(type, ofp_instruction_t_map), len); + + *off += sizeof(*ia); + len -= sizeof(*ia); + while (len) { + oldoff = *off; + if ((ah = ibuf_seek(ibuf, *off, sizeof(*ah))) == NULL || + ofp13_validate_action(sc, oh, ibuf, off, ah) == -1) + return (-1); + + diff = *off - oldoff; + /* Loop prevention. */ + if (*off < oldoff || diff == 0) + break; + + len -= diff; + } + break; + default: + if (len < sizeof(*i)) + return (-1); + + log_debug("\tinstruction %s length %lu", + print_map(type, ofp_instruction_t_map), len); + *off += len; + break; + } + + return (0); +} + +int +ofp13_validate_flow_mod(struct switchd *sc, + struct sockaddr_storage *src, struct sockaddr_storage *dst, + struct ofp_header *oh, struct ibuf *ibuf) +{ + struct ofp_flow_mod *fm; + struct ofp_match *om; + struct ofp_instruction *i; + struct ofp_ox_match *oxm; + off_t off, moff, offdiff; + int matchlen, matchtype, left; + + off = 0; + if ((fm = ibuf_seek(ibuf, off, sizeof(*fm))) == NULL) + return (-1); + + log_debug("\tcommand %s table %d timeout (idle %d hard %d) " + "priority %d buffer_id %u out_port %u out_group %u " + "flags %#04x cookie %llu mask %llu", + print_map(fm->fm_command, ofp_flowcmd_map), + fm->fm_table_id, ntohs(fm->fm_idle_timeout), + ntohs(fm->fm_hard_timeout), ntohs(fm->fm_priority), + ntohl(fm->fm_buffer_id), ntohl(fm->fm_out_port), + ntohl(fm->fm_out_group), ntohs(fm->fm_flags), + be64toh(fm->fm_cookie), be64toh(fm->fm_cookie_mask)); + + off += offsetof(struct ofp_flow_mod, fm_match); + + om = &fm->fm_match; + matchtype = ntohs(om->om_type); + matchlen = ntohs(om->om_length); + + moff = off + sizeof(*om); + off += OFP_ALIGN(matchlen); + + matchlen -= sizeof(*om); + while (matchlen) { + if ((oxm = ibuf_seek(ibuf, moff, sizeof(*oxm))) == NULL || + ofp13_validate_oxm(sc, oxm, oh, ibuf, moff) == -1) + return (-1); + moff += sizeof(*oxm) + oxm->oxm_length; + matchlen -= sizeof(*oxm) + oxm->oxm_length; + } + + left = ntohs(oh->oh_length) - off; + moff = off; + while (left) { + if ((i = ibuf_seek(ibuf, moff, sizeof(*i))) == NULL || + ofp13_validate_instruction(sc, oh, ibuf, &moff, i) == -1) + return (-1); + + offdiff = moff - off; + /* Loop prevention. */ + if (moff < off || offdiff == 0) + break; + + left -= offdiff; + off = moff; + } return (0); }