From ff3fe1f489e51c293b29b6400139aac3f2b1c8a2 Mon Sep 17 00:00:00 2001 From: sg Date: Fri, 17 Feb 2017 21:58:18 +0100 Subject: [PATCH] Fixed bug #50231 (nd6: several option size related issues in Router Advertisement message processing) --- src/core/ipv6/nd6.c | 101 +++++++++++++++++++++++++----------- src/include/lwip/prot/nd6.h | 9 ++-- 2 files changed, 73 insertions(+), 37 deletions(-) diff --git a/src/core/ipv6/nd6.c b/src/core/ipv6/nd6.c index 4eb6c115..df57df12 100644 --- a/src/core/ipv6/nd6.c +++ b/src/core/ipv6/nd6.c @@ -89,8 +89,16 @@ static u8_t nd6_cached_destination_index; /* Multicast address holder. */ static ip6_addr_t multicast_address; -/* Static buffer to parse RA packet options (size of a prefix option, biggest option) */ -static u8_t nd6_ra_buffer[sizeof(struct prefix_option)]; +/* Static buffer to parse RA packet options */ +union ra_options { + struct lladdr_option lladdr; + struct mtu_option mtu; + struct prefix_option prefix; +#if LWIP_ND6_RDNSS_MAX_DNS_SERVERS + struct rdnss_option rdnss; +#endif +}; +static union ra_options nd6_ra_buffer; /* Forward declarations. */ static s8_t nd6_find_neighbor_cache_entry(const ip6_addr_t *ip6addr); @@ -617,30 +625,44 @@ nd6_input(struct pbuf *p, struct netif *inp) offset = sizeof(struct ra_header); /* Process each option. */ - while ((p->tot_len - offset) > 0) { + while ((p->tot_len - offset) >= 2) { + u8_t option_type; + u16_t option_len; + int option_len8 = pbuf_try_get_at(p, offset + 1); + if (option_len8 <= 0) { + /* read beyond end or zero length */ + goto lenerr_drop_free_return; + } + option_len = ((u8_t)option_len8) << 3; + if (option_len > p->tot_len - offset) { + /* short packet (option does not fit in) */ + goto lenerr_drop_free_return; + } if (p->len == p->tot_len) { /* no need to copy from contiguous pbuf */ buffer = &((u8_t*)p->payload)[offset]; } else { - buffer = nd6_ra_buffer; - if (pbuf_copy_partial(p, buffer, sizeof(struct prefix_option), offset) != sizeof(struct prefix_option)) { - pbuf_free(p); - ND6_STATS_INC(nd6.lenerr); - ND6_STATS_INC(nd6.drop); - return; + /* check if this option fits into our buffer */ + if (option_len > sizeof(nd6_ra_buffer)) { + option_type = pbuf_get_at(p, offset); + /* invalid option length */ + if (option_type != ND6_OPTION_TYPE_RDNSS) { + goto lenerr_drop_free_return; + } + /* we allow RDNSS option to be longer - we'll just drop some servers */ + option_len = sizeof(nd6_ra_buffer); } + buffer = (u8_t*)&nd6_ra_buffer; + option_len = pbuf_copy_partial(p, &nd6_ra_buffer, option_len, offset); } - if (buffer[1] == 0) { - /* zero-length extension. drop packet */ - pbuf_free(p); - ND6_STATS_INC(nd6.lenerr); - ND6_STATS_INC(nd6.drop); - return; - } - switch (buffer[0]) { + option_type = buffer[0]; + switch (option_type) { case ND6_OPTION_TYPE_SOURCE_LLADDR: { struct lladdr_option *lladdr_opt; + if (option_len < sizeof(struct lladdr_option)) { + goto lenerr_drop_free_return; + } lladdr_opt = (struct lladdr_option *)buffer; if ((default_router_list[i].neighbor_entry != NULL) && (default_router_list[i].neighbor_entry->state == ND6_INCOMPLETE)) { @@ -653,6 +675,9 @@ nd6_input(struct pbuf *p, struct netif *inp) case ND6_OPTION_TYPE_MTU: { struct mtu_option *mtu_opt; + if (option_len < sizeof(struct mtu_option)) { + goto lenerr_drop_free_return; + } mtu_opt = (struct mtu_option *)buffer; if (lwip_htonl(mtu_opt->mtu) >= 1280) { #if LWIP_ND6_ALLOW_RA_UPDATES @@ -665,6 +690,9 @@ nd6_input(struct pbuf *p, struct netif *inp) { struct prefix_option *prefix_opt; ip6_addr_t prefix_addr; + if (option_len < sizeof(struct prefix_option)) { + goto lenerr_drop_free_return; + } prefix_opt = (struct prefix_option *)buffer; @@ -711,27 +739,33 @@ nd6_input(struct pbuf *p, struct netif *inp) case ND6_OPTION_TYPE_RDNSS: { u8_t num, n; + u16_t copy_offset = offset + SIZEOF_RDNSS_OPTION_BASE; struct rdnss_option * rdnss_opt; + if (option_len < SIZEOF_RDNSS_OPTION_BASE) { + goto lenerr_drop_free_return; + } rdnss_opt = (struct rdnss_option *)buffer; num = (rdnss_opt->length - 1) / 2; for (n = 0; (rdnss_server_idx < DNS_MAX_SERVERS) && (n < num); n++) { ip_addr_t rdnss_address; - /* Get a memory-aligned, zoned copy of the prefix. */ - ip_addr_copy_from_ip6_packed(rdnss_address, rdnss_opt->rdnss_address[n]); - ip6_addr_assign_zone(ip_2_ip6(&rdnss_address), IP6_UNKNOWN, inp); + /* Copy directly from pbuf to get an aligned, zoned copy of the prefix. */ + if (pbuf_copy_partial(p, &rdnss_address, sizeof(ip6_addr_p_t), copy_offset) == sizeof(ip6_addr_p_t)) { + IP_SET_TYPE_VAL(rdnss_address, IPADDR_TYPE_V6); + ip6_addr_assign_zone(ip_2_ip6(&rdnss_address), IP6_UNKNOWN, inp); - if (htonl(rdnss_opt->lifetime) > 0) { - /* TODO implement Lifetime > 0 */ - dns_setserver(rdnss_server_idx++, &rdnss_address); - } else { - /* TODO implement DNS removal in dns.c */ - u8_t s; - for (s = 0; s < DNS_MAX_SERVERS; s++) { - const ip_addr_t *addr = dns_getserver(s); - if(ip_addr_cmp(addr, &rdnss_address)) { - dns_setserver(s, NULL); + if (htonl(rdnss_opt->lifetime) > 0) { + /* TODO implement Lifetime > 0 */ + dns_setserver(rdnss_server_idx++, &rdnss_address); + } else { + /* TODO implement DNS removal in dns.c */ + u8_t s; + for (s = 0; s < DNS_MAX_SERVERS; s++) { + const ip_addr_t *addr = dns_getserver(s); + if(ip_addr_cmp(addr, &rdnss_address)) { + dns_setserver(s, NULL); + } } } } @@ -745,7 +779,7 @@ nd6_input(struct pbuf *p, struct netif *inp) break; } /* option length is checked earlier to be non-zero to make sure loop ends */ - offset += 8 * ((u16_t)buffer[1]); + offset += 8 * (u8_t)option_len8; } break; /* ICMP6_TYPE_RA */ @@ -884,6 +918,11 @@ nd6_input(struct pbuf *p, struct netif *inp) } pbuf_free(p); + return; +lenerr_drop_free_return: + ND6_STATS_INC(nd6.lenerr); + ND6_STATS_INC(nd6.drop); + pbuf_free(p); } diff --git a/src/include/lwip/prot/nd6.h b/src/include/lwip/prot/nd6.h index 2d4903d1..c270d07c 100644 --- a/src/include/lwip/prot/nd6.h +++ b/src/include/lwip/prot/nd6.h @@ -248,11 +248,6 @@ PACK_STRUCT_END #endif /** Recursive DNS Server Option. */ -#if LWIP_ND6_RDNSS_MAX_DNS_SERVERS -#define LWIP_RDNSS_OPTION_MAX_SERVERS LWIP_ND6_RDNSS_MAX_DNS_SERVERS -#else -#define LWIP_RDNSS_OPTION_MAX_SERVERS 1 -#endif #define ND6_OPTION_TYPE_RDNSS (25) #ifdef PACK_STRUCT_USE_INCLUDES # include "arch/bpstruct.h" @@ -263,13 +258,15 @@ struct rdnss_option { PACK_STRUCT_FLD_8(u8_t length); PACK_STRUCT_FIELD(u16_t reserved); PACK_STRUCT_FIELD(u32_t lifetime); - PACK_STRUCT_FLD_S(ip6_addr_p_t rdnss_address[LWIP_RDNSS_OPTION_MAX_SERVERS]); + PACK_STRUCT_FLD_S(ip6_addr_p_t rdnss_address[1]); } PACK_STRUCT_STRUCT; PACK_STRUCT_END #ifdef PACK_STRUCT_USE_INCLUDES # include "arch/epstruct.h" #endif +#define SIZEOF_RDNSS_OPTION_BASE 8 /* size without addresses */ + #ifdef __cplusplus } #endif