Use sa6_checkzone() and sa6_checkzone_opts() to check user's input. Remove prison_remote_ip6() call. in6_pcbconnect will check this. --- sys/netinet6/udp6_usrreq.c (svn+ssh://svn.freebsd.org/base/head) (revision 261548) +++ sys/netinet6/udp6_usrreq.c (working copy) @@ -550,7 +550,9 @@ udp6_getcred(SYSCTL_HANDLER_ARGS) { struct xucred xuc; struct sockaddr_in6 addrs[2]; + struct ifnet *ifp; struct inpcb *inp; + uint32_t zoneid; int error; error = priv_check(req->td, PRIV_NETINET_GETCRED); @@ -564,13 +566,30 @@ udp6_getcred(SYSCTL_HANDLER_ARGS) error = SYSCTL_IN(req, addrs, sizeof(addrs)); if (error) return (error); - if ((error = sa6_embedscope(&addrs[0], V_ip6_use_defzone)) != 0 || - (error = sa6_embedscope(&addrs[1], V_ip6_use_defzone)) != 0) { + error = sa6_checkzone(&addrs[0]); + if (error) return (error); + error = sa6_checkzone(&addrs[1]); + if (error) + return (error); + + ifp = NULL; + zoneid = 0; + if (addrs[0].sin6_scope_id != 0) + zoneid = addrs[0].sin6_scope_id; + if (addrs[1].sin6_scope_id != 0) { + if (zoneid != 0 && zoneid != addrs[1].sin6_scope_id) + return (EINVAL); + zoneid = addrs[1].sin6_scope_id; } + if (zoneid != 0) { + ifp = in6_getlinkifnet(zoneid); + if (ifp == NULL) + return (ENOENT); + } inp = in6_pcblookup(&V_udbinfo, &addrs[1].sin6_addr, addrs[1].sin6_port, &addrs[0].sin6_addr, addrs[0].sin6_port, - INPLOOKUP_WILDCARD | INPLOOKUP_RLOCKPCB, NULL); + INPLOOKUP_WILDCARD | INPLOOKUP_RLOCKPCB, ifp); if (inp != NULL) { INP_RLOCK_ASSERT(inp); if (inp->inp_socket == NULL) @@ -595,6 +614,7 @@ static int udp6_output(struct inpcb *inp, struct mbuf *m, struct sockaddr *addr6, struct mbuf *control, struct thread *td) { + struct route_in6 ro; u_int32_t ulen = m->m_pkthdr.len; u_int32_t plen = sizeof(struct udphdr) + ulen; struct ip6_hdr *ip6; @@ -602,39 +622,15 @@ udp6_output(struct inpcb *inp, struct mbuf *m, str struct in6_addr *laddr, *faddr, in6a; struct sockaddr_in6 *sin6 = NULL; struct ifnet *oifp = NULL; - int scope_ambiguous = 0; u_short fport; int error = 0; struct ip6_pktopts *optp, opt; int af = AF_INET6, hlen = sizeof(struct ip6_hdr); int flags; - struct sockaddr_in6 tmp; INP_WLOCK_ASSERT(inp); INP_HASH_WLOCK_ASSERT(inp->inp_pcbinfo); - if (addr6) { - /* addr6 has been validated in udp6_send(). */ - sin6 = (struct sockaddr_in6 *)addr6; - - /* protect *sin6 from overwrites */ - tmp = *sin6; - sin6 = &tmp; - - /* - * Application should provide a proper zone ID or the use of - * default zone IDs should be enabled. Unfortunately, some - * applications do not behave as it should, so we need a - * workaround. Even if an appropriate ID is not determined, - * we'll see if we can determine the outgoing interface. If we - * can, determine the zone ID based on the interface below. - */ - if (sin6->sin6_scope_id == 0 && !V_ip6_use_defzone) - scope_ambiguous = 1; - if ((error = sa6_embedscope(sin6, V_ip6_use_defzone)) != 0) - return (error); - } - if (control) { if ((error = ip6_setpktopts(control, &opt, inp->in6p_outputopts, td->td_ucred, IPPROTO_UDP)) != 0) @@ -643,6 +639,18 @@ udp6_output(struct inpcb *inp, struct mbuf *m, str } else optp = inp->in6p_outputopts; + bzero(&ro, sizeof(ro)); + if (addr6 != NULL) { + /* + * Application must provide a proper zone ID or the use of + * default zone IDs should be enabled. + */ + ro.ro_dst = *(struct sockaddr_in6 *)addr6; + sin6 = &ro.ro_dst; + error = sa6_checkzone_opts(optp, inp->in6p_moptions, sin6); + if (error != 0) + goto release; + } if (sin6) { faddr = &sin6->sin6_addr; @@ -696,15 +704,10 @@ udp6_output(struct inpcb *inp, struct mbuf *m, str } if (!IN6_IS_ADDR_V4MAPPED(faddr)) { - error = in6_selectsrc(sin6, optp, inp, NULL, + error = in6_selectsrc(sin6, optp, inp, &ro, td->td_ucred, &oifp, &in6a); if (error) goto release; - if (oifp && scope_ambiguous && - (error = in6_setscope(&sin6->sin6_addr, - oifp, NULL))) { - goto release; - } laddr = &in6a; } else laddr = &inp->in6p_laddr; /* XXX */ @@ -743,6 +746,8 @@ udp6_output(struct inpcb *inp, struct mbuf *m, str laddr = &inp->in6p_laddr; faddr = &inp->in6p_faddr; fport = inp->inp_fport; + if (inp->in6p_zoneid != 0) + oifp = in6_getlinkifnet(inp->in6p_zoneid); } if (af == AF_INET) @@ -786,12 +791,12 @@ udp6_output(struct inpcb *inp, struct mbuf *m, str m->m_pkthdr.csum_flags = CSUM_UDP_IPV6; m->m_pkthdr.csum_data = offsetof(struct udphdr, uh_sum); - flags = 0; + flags = oifp ? IPV6_USEROIF: 0; UDP_PROBE(send, NULL, inp, ip6, inp, udp6); UDPSTAT_INC(udps_opackets); - error = ip6_output(m, optp, NULL, flags, inp->in6p_moptions, - NULL, inp); + error = ip6_output(m, optp, &ro, flags, inp->in6p_moptions, + &oifp, inp); break; case AF_INET: error = EAFNOSUPPORT; @@ -807,6 +812,7 @@ releaseopt: ip6_clearpktopts(&opt, -1); m_freem(control); } + RO_RTFREE(&ro); return (error); } @@ -1004,9 +1010,6 @@ udp6_connect(struct socket *so, struct sockaddr *n } inp->inp_vflag &= ~INP_IPV4; inp->inp_vflag |= INP_IPV6; - error = prison_remote_ip6(td->td_ucred, &sin6->sin6_addr); - if (error != 0) - goto out; INP_HASH_WLOCK(&V_udbinfo); error = in6_pcbconnect(inp, nam, td->td_ucred); INP_HASH_WUNLOCK(&V_udbinfo);