This is a backport of fixes from 0.99.20.1 to the following three CVEs: CVE-2012-0249 - Quagga ospfd DoS on malformed LS-Update packet CVE-2012-0250 - Quagga ospfd DoS on malformed Network-LSA data CVE-2012-0255 - Quagga bgpd DoS on malformed OPEN message commit 4ae6ff6d0148222da564d1d9f44e82367caf1adf (HEAD, security/0.99.17) Author: Denis Ovsienko Date: Sun Feb 26 17:59:43 2012 +0400 ospfd: bring ospf_check_auth() into focus The old ospf_check_auth() function did two different jobs depending on AuType. For Null and Simple cases it actually authenticated the packet, but for Cryptographic case it only checked declared packet size (not taking the actual number of bytes on wire into account). The calling function, ospf_verify_header(), had its own set of MD5/checksum checks dispatched depending on AuType. This commit makes the packet size check work against the real number of bytes and moves it to ospf_packet_examine(). All MD5/checksum verification is now performed in ospf_check_auth() function. * ospf_packet.c * ospf_packet_examin(): check length with MD5 bytes in mind * ospf_verify_header(): remove all AuType-specific code * ospf_check_auth(): completely rewrite ospfd/ospf_packet.c | 170 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 100 insertions(+), 70 deletions(-) commit 3e17d1d3bda600daff3f637c65c899bff47c7ea8 Author: Denis Ovsienko Date: Sun Feb 26 17:00:57 2012 +0400 ospfd: introduce ospf_auth_type_str[] ospfd/ospf_dump.c | 15 +++++++++++++-- ospfd/ospf_dump.h | 2 ++ 2 files changed, 15 insertions(+), 2 deletions(-) commit e83ad386658ccd0e075b76b24d52a0d6faaee787 Author: Denis Ovsienko Date: Mon Feb 20 23:08:10 2012 +0400 ospfd: fix packet length check for auth/LLS cases An OSPFv2 packet with trailing data blocks (authentication and/or link-local signaling) failed the recently implemented packet length check, because trailing data length isn't counted in the packet header "length" field. This commit fixes respective check conditions. * ospf_packet.c * ospf_packet_examin(): use "bytesdeclared" instead of "bytesonwire" ospfd/ospf_packet.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) commit 12341fb6e48dd125c52f0ce7833712e805782405 Author: Denis Ovsienko Date: Fri Feb 17 16:20:50 2012 +0400 ospfd: introduce ospf_lsa_minlen[] (BZ#705) This commit ports more packet checks to OSPFv2, in particular, LSA size verification and Router-LSA link blocks verification. * ospf_lsa.h: add LSA size macros * ospf_packet.h: add struct ospf_ls_update * ospf_packet.c * ospf_lsa_minlen[]: a direct equivalent of ospf6_lsa_minlen[] * ospf_router_lsa_links_examin(): new function, verifies trailing part of a Router-LSA * ospf_lsa_examin(): new function like ospf6_lsa_examin() * ospf_lsaseq_examin(): new function like ospf6_lsaseq_examin() * ospf_packet_examin(): add type-specific deeper level checks ospfd/ospf_lsa.h | 4 + ospfd/ospf_packet.c | 260 ++++++++++++++++++++++++++++++++++++++++++++++++++- ospfd/ospf_packet.h | 4 + 3 files changed, 267 insertions(+), 1 deletion(-) commit 9a076c690f3b5f1d8e7b2c25e381ffa4e3911395 Author: Denis Ovsienko Date: Mon Jan 30 20:32:39 2012 +0400 ospfd: review ospf_check_md5_digest() Rewrite some pointer arithmetics without the additional variables and move byte order conversion inside the function. ospfd/ospf_packet.c | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) commit cce06267ee2f07d28cd9826cee58f404a367d7f6 Author: Denis Ovsienko Date: Mon Jan 30 16:07:18 2012 +0400 ospfd: review ospf_check_auth() 1. The only purpose of "ibuf" argument was to get stream size, which was always equal to OSPF_MAX_PACKET_SIZE + 1, exactly as initialized in ospf_new(). 2. Fix the packet size check condition, which was incorrect for very large packets, at least in theory. ospfd/ospf_packet.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) commit db6c1b61de4b7dd8aee3463821742b83a97e2857 Author: Denis Ovsienko Date: Mon Jan 30 15:41:39 2012 +0400 ospfd: introduce ospf_packet_minlen[] (BZ#705) This commit ports some of the OSPFv3 packet reception checks to OSPFv2. * ospf_packet.c * ospf_packet_minlen[]: a direct equivalent of ospf6_packet_minlen[] * ospf_packet_examin(): new function designed after the first part of ospf6_packet_examin() * ospf_read(): verify received packet with ospf_packet_examin() * ospf_packet.h: add convenience macros ospfd/ospf_packet.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++--- ospfd/ospf_packet.h | 4 ++++ 2 files changed, 62 insertions(+), 3 deletions(-) commit 5bd6287d9d1700e4e5fdbbcead547460554623b0 Author: Denis Ovsienko Date: Sun Jan 15 19:12:19 2012 +0400 ospfd: use LOOKUP() for ospf_packet_type_str * ospf_packet.h: add proper str/max extern declarations * ospf_packet.c * ospf_packet_type_str: rewrite in "struct message", add max value * ospf_packet_add(): use LOOKUP() * ospf_write(): ditto * ospf_hello(): ditto * ospf_read(): ditto * ospf_dump.h: the declaration does not belong here * ospf_dump.c * ospf_header_dump(): use LOOKUP() * show_debugging_ospf(): ditto ospfd/ospf_dump.c | 8 ++++---- ospfd/ospf_dump.h | 1 - ospfd/ospf_packet.c | 25 +++++++++++++------------ ospfd/ospf_packet.h | 3 +++ 4 files changed, 20 insertions(+), 17 deletions(-) commit 8a687c3260279acf00ccb4c69fdf1a9ef9e91700 Author: Paul Jakma Date: Mon Jan 9 20:59:26 2012 +0000 bgpd: Open option parse errors don't NOTIFY, resulting in abort & DoS * bgp_packet.c: (bgp_open_receive) Errors from bgp_open_option_parse are detected, and the code will stop processing the OPEN and return. However it does so without calling bgp_notify_send to send a NOTIFY - which means the peer FSM doesn't get stopped, and bgp_read will be called again later. Because it returns, it doesn't go through the code near the end of the function that removes the current message from the peer input streaam. Thus the next call to bgp_read will try to parse a half-parsed stream as if it were a new BGP message, leading to an assert later in the code when it tries to read stuff that isn't there. Add the required call to bgp_notify_send before returning. * bgp_open.c: (bgp_capability_as4) Be a bit stricter, check the length field corresponds to the only value it can be, which is the amount we're going to read off the stream. And make sure the capability flag gets set, so callers can know this capability was read, regardless. (peek_for_as4_capability) Let bgp_capability_as4 do the length check. bgpd/bgp_open.c | 14 +++++++++----- bgpd/bgp_packet.c | 10 +++++++--- 2 files changed, 16 insertions(+), 8 deletions(-) Index: quagga-0.99.17/bgpd/bgp_open.c =================================================================== --- quagga-0.99.17.orig/bgpd/bgp_open.c 2010-08-19 11:32:24.000000000 +0200 +++ quagga-0.99.17/bgpd/bgp_open.c 2012-05-16 19:07:18.669620354 +0200 @@ -433,13 +433,20 @@ static as_t bgp_capability_as4 (struct peer *peer, struct capability_header *hdr) { + SET_FLAG (peer->cap, PEER_CAP_AS4_RCV); + + if (hdr->length != CAPABILITY_CODE_AS4_LEN) + { + zlog_err ("%s AS4 capability has incorrect data length %d", + peer->host, hdr->length); + return 0; + } + as_t as4 = stream_getl (BGP_INPUT(peer)); if (BGP_DEBUG (as4, AS4)) zlog_debug ("%s [AS4] about to set cap PEER_CAP_AS4_RCV, got as4 %u", peer->host, as4); - SET_FLAG (peer->cap, PEER_CAP_AS4_RCV); - return as4; } @@ -701,9 +708,6 @@ if (hdr.code == CAPABILITY_CODE_AS4) { - if (hdr.length != CAPABILITY_CODE_AS4_LEN) - goto end; - if (BGP_DEBUG (as4, AS4)) zlog_info ("[AS4] found AS4 capability, about to parse"); as4 = bgp_capability_as4 (peer, &hdr); Index: quagga-0.99.17/bgpd/bgp_packet.c =================================================================== --- quagga-0.99.17.orig/bgpd/bgp_packet.c 2010-08-19 11:32:24.000000000 +0200 +++ quagga-0.99.17/bgpd/bgp_packet.c 2012-05-16 19:03:22.405628712 +0200 @@ -1451,9 +1451,13 @@ /* Open option part parse. */ if (optlen != 0) { - ret = bgp_open_option_parse (peer, optlen, &capability); - if (ret < 0) - return ret; + if ((ret = bgp_open_option_parse (peer, optlen, &capability)) < 0) + { + bgp_notify_send (peer, + BGP_NOTIFY_OPEN_ERR, + BGP_NOTIFY_OPEN_UNACEP_HOLDTIME); + return ret; + } } else { Index: quagga-0.99.17/ospfd/ospf_dump.c =================================================================== --- quagga-0.99.17.orig/ospfd/ospf_dump.c 2010-08-19 11:32:24.000000000 +0200 +++ quagga-0.99.17/ospfd/ospf_dump.c 2012-05-16 19:03:22.409628712 +0200 @@ -115,6 +115,16 @@ }; const int ospf_network_type_msg_max = OSPF_IFTYPE_MAX; +/* AuType */ +const struct message ospf_auth_type_str[] = +{ + { OSPF_AUTH_NULL, "Null" }, + { OSPF_AUTH_SIMPLE, "Simple" }, + { OSPF_AUTH_CRYPTOGRAPHIC, "Cryptographic" }, +}; +const size_t ospf_auth_type_str_max = sizeof (ospf_auth_type_str) / + sizeof (ospf_auth_type_str[0]); + /* Configuration debug option variables. */ unsigned long conf_debug_ospf_packet[5] = {0, 0, 0, 0, 0}; unsigned long conf_debug_ospf_event = 0; @@ -657,18 +667,19 @@ ospf_header_dump (struct ospf_header *ospfh) { char buf[9]; + u_int16_t auth_type = ntohs (ospfh->auth_type); zlog_debug ("Header"); zlog_debug (" Version %d", ospfh->version); zlog_debug (" Type %d (%s)", ospfh->type, - ospf_packet_type_str[ospfh->type]); + LOOKUP (ospf_packet_type_str, ospfh->type)); zlog_debug (" Packet Len %d", ntohs (ospfh->length)); zlog_debug (" Router ID %s", inet_ntoa (ospfh->router_id)); zlog_debug (" Area ID %s", inet_ntoa (ospfh->area_id)); zlog_debug (" Checksum 0x%x", ntohs (ospfh->checksum)); - zlog_debug (" AuType %d", ntohs (ospfh->auth_type)); + zlog_debug (" AuType %s", LOOKUP (ospf_auth_type_str, auth_type)); - switch (ntohs (ospfh->auth_type)) + switch (auth_type) { case OSPF_AUTH_NULL: break; @@ -1457,7 +1468,7 @@ if (IS_DEBUG_OSPF_PACKET (i, SEND) && IS_DEBUG_OSPF_PACKET (i, RECV)) { vty_out (vty, " OSPF packet %s%s debugging is on%s", - ospf_packet_type_str[i + 1], + LOOKUP (ospf_packet_type_str, i + 1), IS_DEBUG_OSPF_PACKET (i, DETAIL) ? " detail" : "", VTY_NEWLINE); } @@ -1465,12 +1476,12 @@ { if (IS_DEBUG_OSPF_PACKET (i, SEND)) vty_out (vty, " OSPF packet %s send%s debugging is on%s", - ospf_packet_type_str[i + 1], + LOOKUP (ospf_packet_type_str, i + 1), IS_DEBUG_OSPF_PACKET (i, DETAIL) ? " detail" : "", VTY_NEWLINE); if (IS_DEBUG_OSPF_PACKET (i, RECV)) vty_out (vty, " OSPF packet %s receive%s debugging is on%s", - ospf_packet_type_str[i + 1], + LOOKUP (ospf_packet_type_str, i + 1), IS_DEBUG_OSPF_PACKET (i, DETAIL) ? " detail" : "", VTY_NEWLINE); } Index: quagga-0.99.17/ospfd/ospf_dump.h =================================================================== --- quagga-0.99.17.orig/ospfd/ospf_dump.h 2010-08-19 11:32:24.000000000 +0200 +++ quagga-0.99.17/ospfd/ospf_dump.h 2012-05-16 19:03:22.409628712 +0200 @@ -121,8 +121,9 @@ extern unsigned long term_debug_ospf_nssa; /* Message Strings. */ -extern const char *ospf_packet_type_str[]; extern char *ospf_lsa_type_str[]; +extern const struct message ospf_auth_type_str[]; +extern const size_t ospf_auth_type_str_max; /* Prototypes. */ extern const char *ospf_area_name_string (struct ospf_area *); Index: quagga-0.99.17/ospfd/ospf_lsa.h =================================================================== --- quagga-0.99.17.orig/ospfd/ospf_lsa.h 2010-08-19 11:32:24.000000000 +0200 +++ quagga-0.99.17/ospfd/ospf_lsa.h 2012-05-16 19:03:22.409628712 +0200 @@ -154,6 +154,7 @@ }; /* OSPF Router-LSAs structure. */ +#define OSPF_ROUTER_LSA_MIN_SIZE 16U /* w/1 link descriptor */ struct router_lsa { struct lsa_header header; @@ -171,6 +172,7 @@ }; /* OSPF Network-LSAs structure. */ +#define OSPF_NETWORK_LSA_MIN_SIZE 8U /* w/1 router-ID */ struct network_lsa { struct lsa_header header; @@ -179,6 +181,7 @@ }; /* OSPF Summary-LSAs structure. */ +#define OSPF_SUMMARY_LSA_MIN_SIZE 8U /* w/1 TOS metric block */ struct summary_lsa { struct lsa_header header; @@ -188,6 +191,7 @@ }; /* OSPF AS-external-LSAs structure. */ +#define OSPF_AS_EXTERNAL_LSA_MIN_SIZE 16U /* w/1 TOS forwarding block */ struct as_external_lsa { struct lsa_header header; Index: quagga-0.99.17/ospfd/ospf_packet.c =================================================================== --- quagga-0.99.17.orig/ospfd/ospf_packet.c 2010-08-19 11:32:24.000000000 +0200 +++ quagga-0.99.17/ospfd/ospf_packet.c 2012-05-16 19:06:42.929621620 +0200 @@ -50,15 +50,49 @@ #include "ospfd/ospf_dump.h" /* Packet Type String. */ -const char *ospf_packet_type_str[] = +const struct message ospf_packet_type_str[] = { - "unknown", - "Hello", - "Database Description", - "Link State Request", - "Link State Update", - "Link State Acknowledgment", + { OSPF_MSG_HELLO, "Hello" }, + { OSPF_MSG_DB_DESC, "Database Description" }, + { OSPF_MSG_LS_REQ, "Link State Request" }, + { OSPF_MSG_LS_UPD, "Link State Update" }, + { OSPF_MSG_LS_ACK, "Link State Acknowledgment" }, }; +const size_t ospf_packet_type_str_max = sizeof (ospf_packet_type_str) / + sizeof (ospf_packet_type_str[0]); + +/* Minimum (besides OSPF_HEADER_SIZE) lengths for OSPF packets of + particular types, offset is the "type" field of a packet. */ +static const u_int16_t ospf_packet_minlen[] = +{ + 0, + OSPF_HELLO_MIN_SIZE, + OSPF_DB_DESC_MIN_SIZE, + OSPF_LS_REQ_MIN_SIZE, + OSPF_LS_UPD_MIN_SIZE, + OSPF_LS_ACK_MIN_SIZE, +}; + +/* Minimum (besides OSPF_LSA_HEADER_SIZE) lengths for LSAs of particular + types, offset is the "LSA type" field. */ +static const u_int16_t ospf_lsa_minlen[] = +{ + 0, + OSPF_ROUTER_LSA_MIN_SIZE, + OSPF_NETWORK_LSA_MIN_SIZE, + OSPF_SUMMARY_LSA_MIN_SIZE, + OSPF_SUMMARY_LSA_MIN_SIZE, + OSPF_AS_EXTERNAL_LSA_MIN_SIZE, + 0, + OSPF_AS_EXTERNAL_LSA_MIN_SIZE, + 0, + 0, + 0, + 0, +}; + +/* for ospf_check_auth() */ +static int ospf_check_sum (struct ospf_header *); /* OSPF authentication checking function */ static int @@ -187,7 +221,7 @@ "destination %s) called with NULL obuf, ignoring " "(please report this bug)!\n", IF_NAME(oi), oi->state, LOOKUP (ospf_ism_state_msg, oi->state), - ospf_packet_type_str[stream_getc_from(op->s, 1)], + LOOKUP (ospf_packet_type_str, stream_getc_from(op->s, 1)), inet_ntoa (op->dst)); return; } @@ -256,24 +290,14 @@ static int -ospf_check_md5_digest (struct ospf_interface *oi, struct stream *s, - u_int16_t length) +ospf_check_md5_digest (struct ospf_interface *oi, struct ospf_header *ospfh) { - unsigned char *ibuf; MD5_CTX ctx; unsigned char digest[OSPF_AUTH_MD5_SIZE]; - unsigned char *pdigest; struct crypt_key *ck; - struct ospf_header *ospfh; struct ospf_neighbor *nbr; + u_int16_t length = ntohs (ospfh->length); - - ibuf = STREAM_PNT (s); - ospfh = (struct ospf_header *) ibuf; - - /* Get pointer to the end of the packet. */ - pdigest = ibuf + length; - /* Get secret key. */ ck = ospf_crypt_key_lookup (OSPF_IF_PARAM (oi, auth_crypt), ospfh->u.crypt.key_id); @@ -299,12 +323,12 @@ /* Generate a digest for the ospf packet - their digest + our digest. */ memset(&ctx, 0, sizeof(ctx)); MD5Init(&ctx); - MD5Update(&ctx, ibuf, length); + MD5Update(&ctx, ospfh, length); MD5Update(&ctx, ck->auth_key, OSPF_AUTH_MD5_SIZE); MD5Final(digest, &ctx); /* compare the two */ - if (memcmp (pdigest, digest, OSPF_AUTH_MD5_SIZE)) + if (memcmp ((caddr_t)ospfh + length, digest, OSPF_AUTH_MD5_SIZE)) { zlog_warn ("interface %s: ospf_check_md5 checksum mismatch", IF_NAME (oi)); @@ -713,7 +737,7 @@ } zlog_debug ("%s sent to [%s] via [%s].", - ospf_packet_type_str[type], inet_ntoa (op->dst), + LOOKUP (ospf_packet_type_str, type), inet_ntoa (op->dst), IF_NAME (oi)); if (IS_DEBUG_OSPF_PACKET (type - 1, DETAIL)) @@ -759,7 +783,7 @@ { zlog_debug ("ospf_header[%s/%s]: selforiginated, " "dropping.", - ospf_packet_type_str[ospfh->type], + LOOKUP (ospf_packet_type_str, ospfh->type), inet_ntoa (iph->ip_src)); } return; @@ -2178,45 +2202,91 @@ return 0; } +/* Return 1, if the packet is properly authenticated and checksummed, + 0 otherwise. In particular, check that AuType header field is valid and + matches the locally configured AuType, and that D.5 requirements are met. */ static int -ospf_check_auth (struct ospf_interface *oi, struct stream *ibuf, - struct ospf_header *ospfh) +ospf_check_auth (struct ospf_interface *oi, struct ospf_header *ospfh) { - int ret = 0; struct crypt_key *ck; + u_int16_t iface_auth_type; + u_int16_t pkt_auth_type = ntohs (ospfh->auth_type); - switch (ntohs (ospfh->auth_type)) + switch (pkt_auth_type) + { + case OSPF_AUTH_NULL: /* RFC2328 D.5.1 */ + if (OSPF_AUTH_NULL != (iface_auth_type = ospf_auth_type (oi))) { - case OSPF_AUTH_NULL: - ret = 1; - break; - case OSPF_AUTH_SIMPLE: - if (!memcmp (OSPF_IF_PARAM (oi, auth_simple), ospfh->u.auth_data, OSPF_AUTH_SIMPLE_SIZE)) - ret = 1; - else - ret = 0; - break; - case OSPF_AUTH_CRYPTOGRAPHIC: - if ((ck = listgetdata (listtail(OSPF_IF_PARAM (oi,auth_crypt)))) == NULL) - { - ret = 0; - break; - } - - /* This is very basic, the digest processing is elsewhere */ - if (ospfh->u.crypt.auth_data_len == OSPF_AUTH_MD5_SIZE && - ospfh->u.crypt.key_id == ck->key_id && - ntohs (ospfh->length) + OSPF_AUTH_SIMPLE_SIZE <= stream_get_size (ibuf)) - ret = 1; - else - ret = 0; - break; - default: - ret = 0; - break; + if (IS_DEBUG_OSPF_PACKET (ospfh->type - 1, RECV)) + zlog_warn ("interface %s: auth-type mismatch, local %s, rcvd Null", + IF_NAME (oi), LOOKUP (ospf_auth_type_str, iface_auth_type)); + return 0; } - - return ret; + if (! ospf_check_sum (ospfh)) + { + if (IS_DEBUG_OSPF_PACKET (ospfh->type - 1, RECV)) + zlog_warn ("interface %s: Null auth OK, but checksum error, Router-ID %s", + IF_NAME (oi), inet_ntoa (ospfh->router_id)); + return 0; + } + return 1; + case OSPF_AUTH_SIMPLE: /* RFC2328 D.5.2 */ + if (OSPF_AUTH_SIMPLE != (iface_auth_type = ospf_auth_type (oi))) + { + if (IS_DEBUG_OSPF_PACKET (ospfh->type - 1, RECV)) + zlog_warn ("interface %s: auth-type mismatch, local %s, rcvd Simple", + IF_NAME (oi), LOOKUP (ospf_auth_type_str, iface_auth_type)); + return 0; + } + if (memcmp (OSPF_IF_PARAM (oi, auth_simple), ospfh->u.auth_data, OSPF_AUTH_SIMPLE_SIZE)) + { + if (IS_DEBUG_OSPF_PACKET (ospfh->type - 1, RECV)) + zlog_warn ("interface %s: Simple auth failed", IF_NAME (oi)); + return 0; + } + if (! ospf_check_sum (ospfh)) + { + if (IS_DEBUG_OSPF_PACKET (ospfh->type - 1, RECV)) + zlog_warn ("interface %s: Simple auth OK, checksum error, Router-ID %s", + IF_NAME (oi), inet_ntoa (ospfh->router_id)); + return 0; + } + return 1; + case OSPF_AUTH_CRYPTOGRAPHIC: /* RFC2328 D.5.3 */ + if (OSPF_AUTH_CRYPTOGRAPHIC != (iface_auth_type = ospf_auth_type (oi))) + { + if (IS_DEBUG_OSPF_PACKET (ospfh->type - 1, RECV)) + zlog_warn ("interface %s: auth-type mismatch, local %s, rcvd Cryptographic", + IF_NAME (oi), LOOKUP (ospf_auth_type_str, iface_auth_type)); + return 0; + } + if (ospfh->checksum) + { + if (IS_DEBUG_OSPF_PACKET (ospfh->type - 1, RECV)) + zlog_warn ("interface %s: OSPF header checksum is not 0", IF_NAME (oi)); + return 0; + } + /* only MD5 crypto method can pass ospf_packet_examin() */ + if + ( + NULL == (ck = listgetdata (listtail(OSPF_IF_PARAM (oi,auth_crypt)))) || + ospfh->u.crypt.key_id != ck->key_id || + /* Condition above uses the last key ID on the list, which is + different from what ospf_crypt_key_lookup() does. A bug? */ + ! ospf_check_md5_digest (oi, ospfh) + ) + { + if (IS_DEBUG_OSPF_PACKET (ospfh->type - 1, RECV)) + zlog_warn ("interface %s: MD5 auth failed", IF_NAME (oi)); + return 0; + } + return 1; + default: + if (IS_DEBUG_OSPF_PACKET (ospfh->type - 1, RECV)) + zlog_warn ("interface %s: invalid packet auth-type (%02x)", + IF_NAME (oi), pkt_auth_type); + return 0; + } } static int @@ -2245,6 +2315,300 @@ return 1; } +/* Verify, that given link/TOS records are properly sized/aligned and match + Router-LSA "# links" and "# TOS" fields as specified in RFC2328 A.4.2. */ +static unsigned +ospf_router_lsa_links_examin +( + struct router_lsa_link * link, + u_int16_t linkbytes, + const u_int16_t num_links +) +{ + unsigned counted_links = 0, thislinklen; + + while (linkbytes) + { + thislinklen = OSPF_ROUTER_LSA_LINK_SIZE + 4 * link->m[0].tos_count; + if (thislinklen > linkbytes) + { + if (IS_DEBUG_OSPF_PACKET (0, RECV)) + zlog_debug ("%s: length error in link block #%u", __func__, counted_links); + return MSG_NG; + } + link = (struct router_lsa_link *)((caddr_t) link + thislinklen); + linkbytes -= thislinklen; + counted_links++; + } + if (counted_links != num_links) + { + if (IS_DEBUG_OSPF_PACKET (0, RECV)) + zlog_debug ("%s: %u link blocks declared, %u present", + __func__, num_links, counted_links); + return MSG_NG; + } + return MSG_OK; +} + +/* Verify, that the given LSA is properly sized/aligned (including type-specific + minimum length constraint). */ +static unsigned +ospf_lsa_examin (struct lsa_header * lsah, const u_int16_t lsalen, const u_char headeronly) +{ + unsigned ret; + struct router_lsa * rlsa; + if + ( + lsah->type < OSPF_MAX_LSA && + ospf_lsa_minlen[lsah->type] && + lsalen < OSPF_LSA_HEADER_SIZE + ospf_lsa_minlen[lsah->type] + ) + { + if (IS_DEBUG_OSPF_PACKET (0, RECV)) + zlog_debug ("%s: undersized (%u B) %s", + __func__, lsalen, LOOKUP (ospf_lsa_type_msg, lsah->type)); + return MSG_NG; + } + switch (lsah->type) + { + case OSPF_ROUTER_LSA: + /* RFC2328 A.4.2, LSA header + 4 bytes followed by N>=1 (12+)-byte link blocks */ + if (headeronly) + { + ret = (lsalen - OSPF_LSA_HEADER_SIZE - OSPF_ROUTER_LSA_MIN_SIZE) % 4 ? MSG_NG : MSG_OK; + break; + } + rlsa = (struct router_lsa *) lsah; + ret = ospf_router_lsa_links_examin + ( + (struct router_lsa_link *) rlsa->link, + lsalen - OSPF_LSA_HEADER_SIZE - 4, /* skip: basic header, "flags", 0, "# links" */ + ntohs (rlsa->links) /* 16 bits */ + ); + break; + case OSPF_AS_EXTERNAL_LSA: + /* RFC2328 A.4.5, LSA header + 4 bytes followed by N>=1 12-bytes long blocks */ + case OSPF_AS_NSSA_LSA: + /* RFC3101 C, idem */ + ret = (lsalen - OSPF_LSA_HEADER_SIZE - OSPF_AS_EXTERNAL_LSA_MIN_SIZE) % 12 ? MSG_NG : MSG_OK; + break; + /* Following LSA types are considered OK length-wise as soon as their minimum + * length constraint is met and length of the whole LSA is a multiple of 4 + * (basic LSA header size is already a multiple of 4). */ + case OSPF_NETWORK_LSA: + /* RFC2328 A.4.3, LSA header + 4 bytes followed by N>=1 router-IDs */ + case OSPF_SUMMARY_LSA: + case OSPF_ASBR_SUMMARY_LSA: + /* RFC2328 A.4.4, LSA header + 4 bytes followed by N>=1 4-bytes TOS blocks */ +#ifdef HAVE_OPAQUE_LSA + case OSPF_OPAQUE_LINK_LSA: + case OSPF_OPAQUE_AREA_LSA: + case OSPF_OPAQUE_AS_LSA: + /* RFC5250 A.2, "some number of octets (of application-specific + * data) padded to 32-bit alignment." This is considered equivalent + * to 4-byte alignment of all other LSA types, see OSPF-ALIGNMENT.txt + * file for the detailed analysis of this passage. */ +#endif + ret = lsalen % 4 ? MSG_NG : MSG_OK; + break; + default: + if (IS_DEBUG_OSPF_PACKET (0, RECV)) + zlog_debug ("%s: unsupported LSA type 0x%02x", __func__, lsah->type); + return MSG_NG; + } + if (ret != MSG_OK && IS_DEBUG_OSPF_PACKET (0, RECV)) + zlog_debug ("%s: alignment error in %s", + __func__, LOOKUP (ospf_lsa_type_msg, lsah->type)); + return ret; +} + +/* Verify if the provided input buffer is a valid sequence of LSAs. This + includes verification of LSA blocks length/alignment and dispatching + of deeper-level checks. */ +static unsigned +ospf_lsaseq_examin +( + struct lsa_header *lsah, /* start of buffered data */ + size_t length, + const u_char headeronly, + /* When declared_num_lsas is not 0, compare it to the real number of LSAs + and treat the difference as an error. */ + const u_int32_t declared_num_lsas +) +{ + u_int32_t counted_lsas = 0; + + while (length) + { + u_int16_t lsalen; + if (length < OSPF_LSA_HEADER_SIZE) + { + if (IS_DEBUG_OSPF_PACKET (0, RECV)) + zlog_debug ("%s: undersized (%zu B) trailing (#%u) LSA header", + __func__, length, counted_lsas); + return MSG_NG; + } + /* save on ntohs() calls here and in the LSA validator */ + lsalen = ntohs (lsah->length); + if (lsalen < OSPF_LSA_HEADER_SIZE) + { + if (IS_DEBUG_OSPF_PACKET (0, RECV)) + zlog_debug ("%s: malformed LSA header #%u, declared length is %u B", + __func__, counted_lsas, lsalen); + return MSG_NG; + } + if (headeronly) + { + /* less checks here and in ospf_lsa_examin() */ + if (MSG_OK != ospf_lsa_examin (lsah, lsalen, 1)) + { + if (IS_DEBUG_OSPF_PACKET (0, RECV)) + zlog_debug ("%s: malformed header-only LSA #%u", __func__, counted_lsas); + return MSG_NG; + } + lsah = (struct lsa_header *) ((caddr_t) lsah + OSPF_LSA_HEADER_SIZE); + length -= OSPF_LSA_HEADER_SIZE; + } + else + { + /* make sure the input buffer is deep enough before further checks */ + if (lsalen > length) + { + if (IS_DEBUG_OSPF_PACKET (0, RECV)) + zlog_debug ("%s: anomaly in LSA #%u: declared length is %u B, buffered length is %zu B", + __func__, counted_lsas, lsalen, length); + return MSG_NG; + } + if (MSG_OK != ospf_lsa_examin (lsah, lsalen, 0)) + { + if (IS_DEBUG_OSPF_PACKET (0, RECV)) + zlog_debug ("%s: malformed LSA #%u", __func__, counted_lsas); + return MSG_NG; + } + lsah = (struct lsa_header *) ((caddr_t) lsah + lsalen); + length -= lsalen; + } + counted_lsas++; + } + + if (declared_num_lsas && counted_lsas != declared_num_lsas) + { + if (IS_DEBUG_OSPF_PACKET (0, RECV)) + zlog_debug ("%s: #LSAs declared (%u) does not match actual (%u)", + __func__, declared_num_lsas, counted_lsas); + return MSG_NG; + } + return MSG_OK; +} + +/* Verify a complete OSPF packet for proper sizing/alignment. */ +static unsigned +ospf_packet_examin (struct ospf_header * oh, const unsigned bytesonwire) +{ + u_int16_t bytesdeclared, bytesauth; + unsigned ret; + struct ospf_ls_update * lsupd; + + /* Length, 1st approximation. */ + if (bytesonwire < OSPF_HEADER_SIZE) + { + if (IS_DEBUG_OSPF_PACKET (0, RECV)) + zlog_debug ("%s: undersized (%u B) packet", __func__, bytesonwire); + return MSG_NG; + } + /* Now it is safe to access header fields. Performing length check, allow + * for possible extra bytes of crypto auth/padding, which are not counted + * in the OSPF header "length" field. */ + bytesdeclared = ntohs (oh->length); + if (ntohs (oh->auth_type) != OSPF_AUTH_CRYPTOGRAPHIC) + bytesauth = 0; + else + { + if (oh->u.crypt.auth_data_len != OSPF_AUTH_MD5_SIZE) + { + if (IS_DEBUG_OSPF_PACKET (0, RECV)) + zlog_debug ("%s: unsupported crypto auth length (%u B)", + __func__, oh->u.crypt.auth_data_len); + return MSG_NG; + } + bytesauth = OSPF_AUTH_MD5_SIZE; + } + if (bytesdeclared + bytesauth > bytesonwire) + { + if (IS_DEBUG_OSPF_PACKET (0, RECV)) + zlog_debug ("%s: packet length error (%u real, %u+%u declared)", + __func__, bytesonwire, bytesdeclared, bytesauth); + return MSG_NG; + } + /* Length, 2nd approximation. The type-specific constraint is checked + against declared length, not amount of bytes on wire. */ + if + ( + oh->type >= OSPF_MSG_HELLO && + oh->type <= OSPF_MSG_LS_ACK && + bytesdeclared < OSPF_HEADER_SIZE + ospf_packet_minlen[oh->type] + ) + { + if (IS_DEBUG_OSPF_PACKET (0, RECV)) + zlog_debug ("%s: undersized (%u B) %s packet", __func__, + bytesdeclared, LOOKUP (ospf_packet_type_str, oh->type)); + return MSG_NG; + } + switch (oh->type) + { + case OSPF_MSG_HELLO: + /* RFC2328 A.3.2, packet header + OSPF_HELLO_MIN_SIZE bytes followed + by N>=0 router-IDs. */ + ret = (bytesdeclared - OSPF_HEADER_SIZE - OSPF_HELLO_MIN_SIZE) % 4 ? MSG_NG : MSG_OK; + break; + case OSPF_MSG_DB_DESC: + /* RFC2328 A.3.3, packet header + OSPF_DB_DESC_MIN_SIZE bytes followed + by N>=0 header-only LSAs. */ + ret = ospf_lsaseq_examin + ( + (struct lsa_header *) ((caddr_t) oh + OSPF_HEADER_SIZE + OSPF_DB_DESC_MIN_SIZE), + bytesdeclared - OSPF_HEADER_SIZE - OSPF_DB_DESC_MIN_SIZE, + 1, /* header-only LSAs */ + 0 + ); + break; + case OSPF_MSG_LS_REQ: + /* RFC2328 A.3.4, packet header followed by N>=0 12-bytes request blocks. */ + ret = (bytesdeclared - OSPF_HEADER_SIZE - OSPF_LS_REQ_MIN_SIZE) % + OSPF_LSA_KEY_SIZE ? MSG_NG : MSG_OK; + break; + case OSPF_MSG_LS_UPD: + /* RFC2328 A.3.5, packet header + OSPF_LS_UPD_MIN_SIZE bytes followed + by N>=0 full LSAs (with N declared beforehand). */ + lsupd = (struct ospf_ls_update *) ((caddr_t) oh + OSPF_HEADER_SIZE); + ret = ospf_lsaseq_examin + ( + (struct lsa_header *) ((caddr_t) lsupd + OSPF_LS_UPD_MIN_SIZE), + bytesdeclared - OSPF_HEADER_SIZE - OSPF_LS_UPD_MIN_SIZE, + 0, /* full LSAs */ + ntohl (lsupd->num_lsas) /* 32 bits */ + ); + break; + case OSPF_MSG_LS_ACK: + /* RFC2328 A.3.6, packet header followed by N>=0 header-only LSAs. */ + ret = ospf_lsaseq_examin + ( + (struct lsa_header *) ((caddr_t) oh + OSPF_HEADER_SIZE + OSPF_LS_ACK_MIN_SIZE), + bytesdeclared - OSPF_HEADER_SIZE - OSPF_LS_ACK_MIN_SIZE, + 1, /* header-only LSAs */ + 0 + ); + break; + default: + if (IS_DEBUG_OSPF_PACKET (0, RECV)) + zlog_debug ("%s: invalid packet type 0x%02x", __func__, oh->type); + return MSG_NG; + } + if (ret != MSG_OK && IS_DEBUG_OSPF_PACKET (0, RECV)) + zlog_debug ("%s: malformed %s packet", __func__, LOOKUP (ospf_packet_type_str, oh->type)); + return ret; +} + /* OSPF Header verification. */ static int ospf_verify_header (struct stream *ibuf, struct ospf_interface *oi, @@ -2274,42 +2638,9 @@ return -1; } - /* Check authentication. */ - if (ospf_auth_type (oi) != ntohs (ospfh->auth_type)) - { - zlog_warn ("interface %s: auth-type mismatch, local %d, rcvd %d", - IF_NAME (oi), ospf_auth_type (oi), ntohs (ospfh->auth_type)); - return -1; - } - - if (! ospf_check_auth (oi, ibuf, ospfh)) - { - zlog_warn ("interface %s: ospf_read authentication failed.", - IF_NAME (oi)); - return -1; - } - - /* if check sum is invalid, packet is discarded. */ - if (ntohs (ospfh->auth_type) != OSPF_AUTH_CRYPTOGRAPHIC) - { - if (! ospf_check_sum (ospfh)) - { - zlog_warn ("interface %s: ospf_read packet checksum error %s", - IF_NAME (oi), inet_ntoa (ospfh->router_id)); - return -1; - } - } - else - { - if (ospfh->checksum != 0) - return -1; - if (ospf_check_md5_digest (oi, ibuf, ntohs (ospfh->length)) == 0) - { - zlog_warn ("interface %s: ospf_read md5 authentication failed.", - IF_NAME (oi)); - return -1; - } - } + /* Check authentication. The function handles logging actions, where required. */ + if (! ospf_check_auth (oi, ospfh)) + return -1; return 0; } @@ -2333,10 +2664,10 @@ /* prepare for next packet. */ ospf->t_read = thread_add_read (master, ospf_read, ospf, ospf->fd); - /* read OSPF packet. */ stream_reset(ospf->ibuf); if (!(ibuf = ospf_recv_packet (ospf->fd, &ifp, ospf->ibuf))) return -1; + /* This raw packet is known to be at least as big as its IP header. */ /* Note that there should not be alignment problems with this assignment because this is at the beginning of the stream data buffer. */ @@ -2369,9 +2700,11 @@ /* Adjust size to message length. */ stream_forward_getp (ibuf, iph->ip_hl * 4); - - /* Get ospf packet header. */ + ospfh = (struct ospf_header *) STREAM_PNT (ibuf); + if (MSG_OK != ospf_packet_examin (ospfh, stream_get_endp (ibuf) - stream_get_getp (ibuf))) + return -1; + /* Now it is safe to access all fields of OSPF packet header. */ /* associate packet with ospf interface */ oi = ospf_if_lookup_recv_if (ospf, iph->ip_src, ifp); @@ -2476,7 +2809,7 @@ } zlog_debug ("%s received from [%s] via [%s]", - ospf_packet_type_str[ospfh->type], + LOOKUP (ospf_packet_type_str, ospfh->type), inet_ntoa (ospfh->router_id), IF_NAME (oi)); zlog_debug (" src [%s],", inet_ntoa (iph->ip_src)); zlog_debug (" dst [%s]", inet_ntoa (iph->ip_dst)); Index: quagga-0.99.17/ospfd/ospf_packet.h =================================================================== --- quagga-0.99.17.orig/ospfd/ospf_packet.h 2010-08-19 11:32:24.000000000 +0200 +++ quagga-0.99.17/ospfd/ospf_packet.h 2012-05-16 19:03:22.413628712 +0200 @@ -46,6 +46,10 @@ #define OSPF_HELLO_REPLY_DELAY 1 +/* Return values of functions involved in packet verification, see ospf6d. */ +#define MSG_OK 0 +#define MSG_NG 1 + struct ospf_packet { struct ospf_packet *next; @@ -117,6 +121,10 @@ u_int32_t dd_seqnum; }; +struct ospf_ls_update +{ + u_int32_t num_lsas; +}; /* Macros. */ /* XXX Perhaps obsolete; function in ospf_packet.c */ @@ -164,4 +172,7 @@ extern int ospf_hello_reply_timer (struct thread *); extern void ospf_hello_send_sub (struct ospf_interface *, struct in_addr *); +extern const struct message ospf_packet_type_str[]; +extern const size_t ospf_packet_type_str_max; + #endif /* _ZEBRA_OSPF_PACKET_H */