Index: sys/netinet6/ip6_output.c =================================================================== --- sys/netinet6/ip6_output.c +++ sys/netinet6/ip6_output.c @@ -416,7 +416,7 @@ struct mbuf *mprev; struct route_in6 *ro_pmtu; struct nhop_object *nh; - struct sockaddr_in6 *dst, sin6, src_sa, dst_sa; + struct sockaddr_in6 *dst, sin6, dst_sa; struct in6_addr odst; u_char *nexthdrp; int tlen, len; @@ -427,8 +427,6 @@ int alwaysfrag, dontfrag; u_int32_t optlen, plen = 0, unfragpartlen; struct ip6_exthdrs exthdrs; - struct in6_addr src0, dst0; - u_int32_t zone; bool hdrsplit; int sw_csum, tso; int needfiblookup; @@ -689,7 +687,6 @@ ro->ro_dst.sin6_family == AF_INET6 && IN6_ARE_ADDR_EQUAL(&ro->ro_dst.sin6_addr, &ip6->ip6_dst)) { nh = ro->ro_nh; - ifp = nh->nh_ifp; } else { if (ro->ro_lle) LLE_FREE(ro->ro_lle); /* zeros ro_lle */ @@ -708,8 +705,6 @@ in6_ifstat_inc(ifp, ifs6_out_discard); goto bad; } - if (ifp != NULL) - mtu = ifp->if_mtu; } if (nh == NULL) { /* @@ -717,9 +712,12 @@ * dst may not have been updated. */ *dst = dst_sa; /* XXX */ + origifp = ifp; + mtu = ifp->if_mtu; } else { - if (nh->nh_flags & NHF_HOST) - mtu = nh->nh_mtu; + ifp = nh->nh_ifp; + origifp = nh->nh_aifp; + mtu = nh->nh_mtu; ia = (struct in6_ifaddr *)(nh->nh_ifa); counter_u64_add(nh->nh_pksent, 1); } @@ -740,6 +738,7 @@ (ifp = im6o->im6o_multicast_ifp) != NULL) { /* We do not need a route lookup. */ *dst = dst_sa; /* XXX */ + origifp = ifp; goto nonh6lookup; } @@ -754,6 +753,7 @@ goto bad; } *dst = dst_sa; /* XXX */ + origifp = ifp; goto nonh6lookup; } } @@ -768,6 +768,7 @@ } ifp = nh->nh_ifp; + origifp = nh->nh_aifp; mtu = nh->nh_mtu; ia = ifatoia6(nh->nh_ifa); if (nh->nh_flags & NHF_GATEWAY) @@ -784,65 +785,18 @@ in6_ifstat_inc(ifp, ifs6_out_request); } - /* Setup data structures for scope ID checks. */ - src0 = ip6->ip6_src; - bzero(&src_sa, sizeof(src_sa)); - src_sa.sin6_family = AF_INET6; - src_sa.sin6_len = sizeof(src_sa); - src_sa.sin6_addr = ip6->ip6_src; - - dst0 = ip6->ip6_dst; - /* Re-initialize to be sure. */ - bzero(&dst_sa, sizeof(dst_sa)); - dst_sa.sin6_family = AF_INET6; - dst_sa.sin6_len = sizeof(dst_sa); - dst_sa.sin6_addr = ip6->ip6_dst; - - /* Check for valid scope ID. */ - if (in6_setscope(&src0, ifp, &zone) == 0 && - sa6_recoverscope(&src_sa) == 0 && zone == src_sa.sin6_scope_id && - in6_setscope(&dst0, ifp, &zone) == 0 && - sa6_recoverscope(&dst_sa) == 0 && zone == dst_sa.sin6_scope_id) { - /* - * The outgoing interface is in the zone of the source - * and destination addresses. - * - * Because the loopback interface cannot receive - * packets with a different scope ID than its own, - * there is a trick to pretend the outgoing packet - * was received by the real network interface, by - * setting "origifp" different from "ifp". This is - * only allowed when "ifp" is a loopback network - * interface. Refer to code in nd6_output_ifp() for - * more details. - */ - origifp = ifp; - - /* - * We should use ia_ifp to support the case of sending - * packets to an address of our own. - */ - if (ia != NULL && ia->ia_ifp) - ifp = ia->ia_ifp; - - } else if ((ifp->if_flags & IFF_LOOPBACK) == 0 || - sa6_recoverscope(&src_sa) != 0 || - sa6_recoverscope(&dst_sa) != 0 || - dst_sa.sin6_scope_id == 0 || - (src_sa.sin6_scope_id != 0 && - src_sa.sin6_scope_id != dst_sa.sin6_scope_id) || - (origifp = ifnet_byindex(dst_sa.sin6_scope_id)) == NULL) { - /* - * If the destination network interface is not a - * loopback interface, or the destination network - * address has no scope ID, or the source address has - * a scope ID set which is different from the - * destination address one, or there is no network - * interface representing this scope ID, the address - * pair is considered invalid. - */ + /* + * Check scope for source/destination addresses. + * Per RFC 4007, clause 5, it is required to maintain "convex" property - + * do not allow packets routed outside their zone. + * Separately check the zone for source and destination, using _address interface_ - + * "normal" transmit interface if dst is non-local destination and ifp which dst + * belongs to otherwise. This allows to pass link-local packets via loopback interface. + */ + if (!in6_checkscope_embedded(&ip6->ip6_src, origifp) || + !in6_checkscope_embedded(&ip6->ip6_dst, origifp)) { IP6STAT_INC(ip6s_badscope); - in6_ifstat_inc(ifp, ifs6_out_discard); + in6_ifstat_inc(origifp, ifs6_out_discard); if (error == 0) error = EHOSTUNREACH; /* XXX */ goto bad; Index: sys/netinet6/scope6.c =================================================================== --- sys/netinet6/scope6.c +++ sys/netinet6/scope6.c @@ -542,6 +542,57 @@ *scopeid = zoneid; } +/* + * Checks that @addr_zone of @addr scope spans across @ifp. + * Returns true on success. + * + * @addr - Specified (non ::) valid IPv6 address, w/o embedded scope, used only to determine scope + * @addr_zone - zone id (host format) of an @addr zone + * @check_multi - if true, verify @addr_zone against the relevant @ifp zone, otherwise pass + * @ifp - interface to check scope against (if @addr is a local address, then @ifp is the addr iface) + */ +bool +in6_checkscope(const struct in6_addr *addr, uint32_t addr_zone, bool check_multi, const struct ifnet *ifp) +{ + NET_EPOCH_ASSERT(); + + if (IN6_IS_ADDR_LINKLOCAL(addr) || IN6_IS_ADDR_LOOPBACK(addr)) { + return (addr_zone == ifp->if_index); + } + if (__predict_false(IN6_IS_ADDR_MULTICAST(addr) && check_multi)) { + switch (IPV6_ADDR_MC_SCOPE(addr)) { + case 0x0F: + return (true); // IPV6_ADDR_SCOPE_GLOBAL + case IPV6_ADDR_SCOPE_INTFACELOCAL: + case IPV6_ADDR_SCOPE_LINKLOCAL: + return (addr_zone == ifp->if_index); + default: + if (ifp->if_afdata[AF_INET6] != NULL) { + return (addr_zone == SID(ifp)->s6id_list[IPV6_ADDR_MC_SCOPE(addr)]); + } + return false; + } + } + // Explicitly pass check for global unicast & site-local addresses + return (true); +} + +/* + * Checks that the zoneid potentially embedded in @addr spans across @ifp. + * Returns true on success. + */ +bool +in6_checkscope_embedded(const struct in6_addr *addr, const struct ifnet *ifp) +{ + uint32_t zone; + + if (!IN6_IS_ADDR_LOOPBACK(addr)) + zone = ntohs(addr->s6_addr16[1]); + else + zone = (ifp->if_flags & IFF_LOOPBACK) ? ifp->if_index : 0; + return (in6_checkscope(addr, zone, false, ifp)); +} + /* * This function is for checking sockaddr_in6 structure passed * from the application level (usually). Index: sys/netinet6/scope6_var.h =================================================================== --- sys/netinet6/scope6_var.h +++ sys/netinet6/scope6_var.h @@ -66,6 +66,9 @@ struct ifnet* in6_getlinkifnet(uint32_t); uint32_t in6_get_unicast_scopeid(const struct in6_addr *, const struct ifnet *); void in6_set_unicast_scopeid(struct in6_addr *, uint32_t); +bool in6_checkscope(const struct in6_addr *addr, uint32_t addr_zone, + bool check_multi, const struct ifnet *ifp); +bool in6_checkscope_embedded(const struct in6_addr *addr, const struct ifnet *ifp); #endif /* _KERNEL */ #endif /* _NETINET6_SCOPE6_VAR_H_ */