NetBSD-Bugs archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
kern/37820: stf security and nat
>Number: 37820
>Category: kern
>Synopsis: stf security and nat
>Confidential: no
>Severity: non-critical
>Priority: medium
>Responsible: kern-bug-people
>State: open
>Class: change-request
>Submitter-Id: net
>Arrival-Date: Sun Jan 20 13:55:00 +0000 2008
>Originator: de SAINT LEGER Rodolphe
>Release: current
>Organization:
none
>Environment:
NetBSD hitomi.ipv6.roroland.net 4.99.49 NetBSD 4.99.49 (build) #1 Jan 18
11:11:22 CET 2008 root%hitomi.ipv6.roroland.net@localhost:/root/build/sparc
>Description:
The stf interface may have some security problems and can't work behind a nat.
- you may receive a packet without any 6to4 address into it,
- you may bounce packets if you do not have real connectivity,
- you may send (and receive) packets to one of your unconfigured prefix network,
>How-To-Repeat:
for nat, just configure your stf interface with your external address, even if
your machine is configured as a dmz, packets won't flow.
ifconfig stf0 create
ifconfig stf0 inet6 2002:<your>:<ipv4>::1
>Fix:
add the 49 bit to your stf alias and apply the following patch, packet should
be handled without any problem (you can also make real public prefixes and
translated prefixes cohabit).
security fixes will be also applied with the patch.
example:
ifconfig stf0 create
ifconfig stf0 inet6 2002:<your>:<ipv4>:c000::1
Index: if_stf.c
===================================================================
RCS file: /cvsroot/src/sys/net/if_stf.c,v
retrieving revision 1.63
diff -u -4 -r1.63 if_stf.c
--- if_stf.c 20 Dec 2007 19:53:30 -0000 1.63
+++ if_stf.c 19 Jan 2008 13:27:54 -0000
@@ -1,5 +1,5 @@
-/* $NetBSD: if_stf.c,v 1.63 2007/12/20 19:53:30 dyoung Exp $ */
+/* $NetBSD: if_stf.c,v 1.63 2007/12/20 19:53:30 dyoung Exp $ */
/* $KAME: if_stf.c,v 1.62 2001/06/07 22:32:16 itojun Exp $ */
/*
* Copyright (C) 2000 WIDE Project.
@@ -130,9 +130,10 @@
#include <net/if_gif.h>
#endif
#define IN6_IS_ADDR_6TO4(x) (ntohs((x)->s6_addr16[0]) == 0x2002)
-#define GET_V4(x) ((const struct in_addr *)(&(x)->s6_addr16[1]))
+#define CHECK_NAT(x) ((ntohs((x)->s6_addr16[3]) & 0xc000) == 0xc000)
+#define GET_V4(x) ((const struct in_addr *)(&(x)->s6_addr16[1]))
struct stf_softc {
struct ifnet sc_if; /* common area */
struct route sc_ro;
@@ -164,9 +165,9 @@
void stfattach(int);
static int stf_encapcheck(struct mbuf *, int, int, void *);
-static struct in6_ifaddr *stf_getsrcifa6(struct ifnet *);
+static struct in_ifaddr *stf_getsrcifa4(struct ifnet *, const struct ip *ip,
const struct in6_addr *);
static int stf_output(struct ifnet *, struct mbuf *, const struct sockaddr *,
struct rtentry *);
static int isrfc1918addr(const struct in_addr *);
static int stf_checkaddr4(struct stf_softc *, const struct in_addr *,
@@ -179,9 +180,8 @@
/* ARGSUSED */
void
stfattach(int count)
{
-
LIST_INIT(&stf_softc_list);
if_clone_attach(&stf_cloner);
}
@@ -244,13 +244,13 @@
static int
stf_encapcheck(struct mbuf *m, int off, int proto, void *arg)
{
struct ip ip;
- struct in6_ifaddr *ia6;
struct stf_softc *sc;
- struct in_addr a, b;
+ struct ifnet *inifp;
sc = (struct stf_softc *)arg;
+
if (sc == NULL)
return 0;
if ((sc->sc_if.if_flags & IFF_UP) == 0)
@@ -262,68 +262,173 @@
if (proto != IPPROTO_IPV6)
return 0;
+ /* Bail on short packets (stolen from if_gif.c) */
+ KASSERT(m->m_flags & M_PKTHDR);
+ if (m->m_pkthdr.len < sizeof(ip))
+ return 0;
+
m_copydata(m, 0, sizeof(ip), (void *)&ip);
if (ip.ip_v != 4)
return 0;
- ia6 = stf_getsrcifa6(&sc->sc_if);
- if (ia6 == NULL)
- return 0;
+ /* also stolen from if_gif.c */
+ inifp = ((m->m_flags & M_PKTHDR) != 0) ? m->m_pkthdr.rcvif : NULL;
/*
- * check if IPv4 dst matches the IPv4 address derived from the
- * local 6to4 address.
- * success on: dst = 10.1.1.1, ia6->ia_addr = 2002:0a01:0101:...
+ * perform sanity check against outer src.
+ * perform ingress filter as well.
*/
- if (bcmp(GET_V4(&ia6->ia_addr.sin6_addr), &ip.ip_dst,
- sizeof(ip.ip_dst)) != 0)
+ if (stf_checkaddr4(sc, &ip.ip_src, inifp) < 0)
return 0;
- /*
- * check if IPv4 src matches the IPv4 address derived from the
- * local 6to4 address masked by prefixmask.
- * success on: src = 10.1.1.1, ia6->ia_addr = 2002:0a00:.../24
- * fail on: src = 10.1.1.1, ia6->ia_addr = 2002:0b00:.../24
- */
- memset(&a, 0, sizeof(a));
- a.s_addr = GET_V4(&ia6->ia_addr.sin6_addr)->s_addr;
- a.s_addr &= GET_V4(&ia6->ia_prefixmask.sin6_addr)->s_addr;
- b = ip.ip_src;
- b.s_addr &= GET_V4(&ia6->ia_prefixmask.sin6_addr)->s_addr;
- if (a.s_addr != b.s_addr)
+ /* search a 6to4 address which match our packet */
+ if (stf_getsrcifa4(&sc->sc_if, &ip, NULL) == NULL)
return 0;
- /* stf interface makes single side match only */
+ /*
+ * outer dst match our 6to4 prefix (which is a valid global ipv4
address)
+ * outer dst is one of our local address (which may be private in dmz
case),
+ * src is a valid global ipv4 addresss,
+ * ingress filtering is ok (if enabled).
+ *
+ * so no more sanity checks will be done on ip header in stf_input().
+ * more checks will be done against 6to4 inner addresses.
+ *
+ * report matched bits of dst
+ * (see encap4_lookup() in netinet ip_encap.c)
+ * matched bits must not reflect the src test
+ * (other tunnels which match exactly src and dst like gif can be used)
+ */
return 32;
}
-static struct in6_ifaddr *
-stf_getsrcifa6(struct ifnet *ifp)
+static struct in_ifaddr *
+stf_getsrcifa4(struct ifnet *ifp, const struct ip *ip, const struct in6_addr
*dst6)
{
- struct ifaddr *ifa;
+ const struct in_addr *in;
+ struct in6_ifaddr *ia6;
struct in_ifaddr *ia4;
- struct sockaddr_in6 *sin6;
- struct in_addr in;
+ struct ifaddr *ia;
+ in_addr_t pfilter;
- IFADDR_FOREACH(ifa, ifp)
- {
- if (ifa->ifa_addr == NULL)
+ /*
+ * prefix matching is done below to avoid most code replication
+ * ip must be an incoming packet (call from stf_encapcheck()) or
+ * a null value (call from stf_output())
+ * dst6 must be the ipv6 destination of a decapsulated packet
+ * (call from stf_input()) or a null value.
+ */
+ IFADDR_FOREACH(ia, ifp) {
+ ia6 = (struct in6_ifaddr *) ia;
+
+ if (ia6->ia_ifa.ifa_addr == NULL)
continue;
- if (ifa->ifa_addr->sa_family != AF_INET6)
+ if (ia6->ia_ifa.ifa_addr->sa_family != AF_INET6)
continue;
- sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;
- if (!IN6_IS_ADDR_6TO4(&sin6->sin6_addr))
+ if (!IN6_IS_ADDR_6TO4(&ia6->ia_addr.sin6_addr))
continue;
- bcopy(GET_V4(&sin6->sin6_addr), &in, sizeof(in));
- INADDR_TO_IA(in, ia4);
- if (ia4 == NULL)
+ /* get global ipv4 address */
+ in = (const struct in_addr *) GET_V4(&ia6->ia_addr.sin6_addr);
+
+ /* get the IPv4 interface associated with the 6to4 address */
+ INADDR_TO_IA(*in, ia4);
+
+ /*
+ * XXX the rfc 3056 allows packets to be received from any ipv4
+ * source, but it forbids to send traffic from a private
address.
+ * according to the rfc, the nat router should handle 6to4
traffic.
+ * this part violates the rfc because it allows to emit packets
+ * from a private address.
+ *
+ * The bit 49 of the address is used to indicate that nat
processing
+ * has to be done.
+ */
+ if (CHECK_NAT(&ia6->ia_addr.sin6_addr)) {
+ struct sockaddr_in sin;
+ struct rtentry *rt;
+
+ if (ia4 != NULL)
+ continue;
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_len = sizeof(struct sockaddr_in);
+ sin.sin_addr = *in;
+
+ /* search for a route to our 6to4 address */
+ rt = rtalloc1((struct sockaddr *)&sin, 0);
+
+ if (rt == NULL)
+ continue;
+
+ /* get outgoing address */
+ ia4 = (struct in_ifaddr *) rt->rt_ifa;
+
+ /* free the route */
+ rtfree(rt);
+
+ if (ia4 != NULL) {
+ /* and get internal address for match */
+ in = (const struct in_addr *)
&IA_SIN(ia4)->sin_addr;
+
+ /*
+ * only accept rfc1918 addresses
+ * (reject link-local and multicast)
+ */
+ if (!isrfc1918addr(in))
+ continue;
+ }
+ }
+
+ /* any matching interface ? */
+ if (ia4 == NULL) {
continue;
+ }
- return (struct in6_ifaddr *)ifa;
+ /* is there any encapsulated packet to match ? */
+ if (ip != NULL) {
+ /*
+ * check if IPv4 dst matches the IPv4 address derived
from the
+ * local 6to4 address (or derived from nat).
+ * success on: dst = 10.1.1.1, ia6->ia_addr =
2002:0a01:0101:...
+ */
+ if (bcmp(in, &ip->ip_dst, sizeof(ip->ip_dst)) != 0)
+ continue;
+
+ /* Get the prefix mask */
+ pfilter = GET_V4(&ia6->ia_prefixmask.sin6_addr)->s_addr;
+
+ /*
+ * check if IPv4 src matches the IPv4 address derived
from the
+ * local 6to4 address masked by prefixmask.
+ * success on: src = 10.1.1.1, ia6->ia_addr =
2002:0a00:.../24
+ * fail on: src = 10.1.1.1, ia6->ia_addr =
2002:0b00:.../24
+ */
+ if ((GET_V4(&ia6->ia_addr.sin6_addr)->s_addr & pfilter)
!=
+ (ip->ip_src.s_addr & pfilter))
+ continue;
+ }
+
+ /*
+ * is there any IPv6 dst address to match ?
+ */
+ if (dst6 != NULL) {
+ /*
+ * match the two IPv6 addresses derived from 6to4
prefixes
+ */
+ if ((IN6_IS_ADDR_6TO4(dst6)) && (bcmp(in, GET_V4(dst6),
sizeof(in)) != 0))
+ continue;
+ }
+
+ /*
+ * either we have an exact match for our 6to4 address
+ * or we have a valid dmz interface
+ */
+ return ia4;
}
return NULL;
}
@@ -334,34 +439,35 @@
{
struct rtentry *rt;
struct stf_softc *sc;
const struct sockaddr_in6 *dst6;
- const struct in_addr *in4;
+ const struct in6_addr *in6;
u_int8_t tos;
struct ip *ip;
struct ip6_hdr *ip6;
- struct in6_ifaddr *ia6;
+ struct in_ifaddr *ia4;
union {
struct sockaddr dst;
struct sockaddr_in dst4;
} u;
- sc = (struct stf_softc*)ifp;
+ sc = (struct stf_softc *)ifp;
dst6 = (const struct sockaddr_in6 *)dst;
/* just in case */
if ((ifp->if_flags & IFF_UP) == 0) {
m_freem(m);
return ENETDOWN;
}
+ ia4 = stf_getsrcifa4(ifp, NULL, NULL);
+
/*
* If we don't have an ip4 address that match my inner ip6 address,
- * we shouldn't generate output. Without this check, we'll end up
- * using wrong IPv4 source.
+ * we shouldn't generate output. Without this check, we'll may
+ * end up using wrong IPv4 source.
*/
- ia6 = stf_getsrcifa6(ifp);
- if (ia6 == NULL) {
+ if (ia4 == NULL) {
m_freem(m);
ifp->if_oerrors++;
return ENETDOWN;
}
@@ -376,20 +482,65 @@
ip6 = mtod(m, struct ip6_hdr *);
tos = (ntohl(ip6->ip6_flow) >> 20) & 0xff;
/*
+ * these tests avoid stf to send packet that may be
+ * discarded when received by a 6to4 router.
+ */
+
+ /* check ipv6 addresses (src and dst) */
+ if (stf_checkaddr6(NULL, &ip6->ip6_dst, NULL) < 0 ||
+ stf_checkaddr6(NULL, &ip6->ip6_src, NULL) < 0) {
+ m_freem(m);
+ ifp->if_oerrors++;
+
+ /* packet will be dropped, so do it now */
+ return 0;
+ }
+ /* either ipv6 source or destination must be a 6to4 address */
+ if (!(IN6_IS_ADDR_6TO4(&ip6->ip6_dst) ||
+ IN6_IS_ADDR_6TO4(&ip6->ip6_src))) {
+ m_freem(m);
+ ifp->if_oerrors++;
+
+ /* packet will be dropped, so do it now */
+ return 0;
+ }
+
+ /*
* Pickup the right outer dst addr from the list of candidates.
* ip6_dst has priority as it may be able to give us shorter IPv4 hops.
*/
- if (IN6_IS_ADDR_6TO4(&ip6->ip6_dst))
- in4 = GET_V4(&ip6->ip6_dst);
- else if (IN6_IS_ADDR_6TO4(&dst6->sin6_addr))
- in4 = GET_V4(&dst6->sin6_addr);
- else {
+ if (IN6_IS_ADDR_6TO4(&ip6->ip6_dst)) {
+ in6 = &ip6->ip6_dst;
+ } else {
+ if (IN6_IS_ADDR_6TO4(&dst6->sin6_addr)) {
+ in6 = &dst6->sin6_addr;
+ } else {
+ m_freem(m);
+ ifp->if_oerrors++;
+
+ return ENETUNREACH;
+ }
+ }
+
+ /* check the router address if needed */
+ if ((in6 != &ip6->ip6_dst) &&
+ (stf_checkaddr6(NULL, in6, NULL) < 0)) {
m_freem(m);
ifp->if_oerrors++;
+
return ENETUNREACH;
}
+ /* check that we are not sending packet to ourselves */
+ if (IN6_IS_ADDR_6TO4(&ip6->ip6_src) &&
+ (bcmp(GET_V4(in6), GET_V4(&ip6->ip6_src), sizeof(struct in_addr))
== 0)) {
+ m_freem(m);
+ ifp->if_oerrors++;
+
+ /* just drop packet to avoid topology guess */
+ return 0;
+ }
#if NBPFILTER > 0
if (ifp->if_bpf)
bpf_mtap_af(ifp->if_bpf, AF_INET6, m);
@@ -405,11 +556,10 @@
ip = mtod(m, struct ip *);
memset(ip, 0, sizeof(*ip));
- bcopy(GET_V4(&((struct sockaddr_in6 *)&ia6->ia_addr)->sin6_addr),
- &ip->ip_src, sizeof(ip->ip_src));
- bcopy(in4, &ip->ip_dst, sizeof(ip->ip_dst));
+ bcopy(&IA_SIN(ia4)->sin_addr, &ip->ip_src, sizeof(ip->ip_src));
+ bcopy(GET_V4(in6), &ip->ip_dst, sizeof(ip->ip_dst));
ip->ip_p = IPPROTO_IPV6;
ip->ip_ttl = ip_gif_ttl; /*XXX*/
ip->ip_len = htons(m->m_pkthdr.len);
if (ifp->if_flags & IFF_LINK1)
@@ -555,8 +705,31 @@
*/
if (IN6_IS_ADDR_MC_NODELOCAL(in6) || IN6_IS_ADDR_MC_LINKLOCAL(in6))
return -1;
+ /*
+ * perform ingress filter
+ */
+ if (sc && (sc->sc_if.if_flags & IFF_LINK2) == 0) {
+ struct sockaddr_in6 sin6;
+ struct rtentry *rt;
+
+ /* source ipv6 interface should be stf */
+ inifp = &sc->sc_if;
+
+ memset(&sin6, 0, sizeof(sin6));
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_len = sizeof(sin6);
+ bcopy(in6, &sin6.sin6_addr, sizeof(sin6.sin6_addr));
+ rt = rtalloc1((struct sockaddr *)&sin6, 0);
+ if (!rt || rt->rt_ifp != inifp) {
+ if (rt)
+ rtfree(rt);
+ return -1;
+ }
+ rtfree(rt);
+ }
+
return 0;
}
void
@@ -568,9 +741,10 @@
struct ip6_hdr *ip6;
u_int8_t otos, itos;
int s, isr;
struct ifqueue *ifq = NULL;
- struct ifnet *ifp;
+ struct ifnet *ifp, *inifp;
+ struct in_addr in;
va_list ap;
va_start(ap, m);
off = va_arg(ap, int);
@@ -591,18 +765,16 @@
return;
}
ifp = &sc->sc_if;
+ inifp = ((m->m_flags & M_PKTHDR) != 0) ? m->m_pkthdr.rcvif : NULL;
- /*
- * perform sanity check against outer src/dst.
- * for source, perform ingress filter as well.
- */
- if (stf_checkaddr4(sc, &ip->ip_dst, NULL) < 0 ||
- stf_checkaddr4(sc, &ip->ip_src, m->m_pkthdr.rcvif) < 0) {
- m_freem(m);
- return;
- }
+ /* incoming interface check has already been done in stf_encapcheck() */
+
+ /* outer src/dst sanity check has been done by stf_encapcheck() */
+
+ /* save v4 src for strict checking */
+ bcopy(&ip->ip_src, &in, sizeof(in));
otos = ip->ip_tos;
m_adj(m, off);
@@ -613,14 +785,101 @@
}
ip6 = mtod(m, struct ip6_hdr *);
/*
+ * we should only accept for incoming 6to4 tunneled traffic:
+ * - 6to4(wan) to native if the src ipv4 match the 6to4 src,
+ * - native to 6to4(wan) if we are 6to4 dst,
+ * - 6to4(wan) to 6to4(wan) if both conditions are correct.
+ */
+
+ /* is src a 6to4 address ? */
+ if (IN6_IS_ADDR_6TO4(&ip6->ip6_src)) {
+ /* then ipv4 src must match 6to4 src */
+ if (bcmp(&in, GET_V4(&ip6->ip6_src), sizeof(in)) != 0) {
+ m_freem(m);
+ ifp->if_ierrors++;
+
+ return;
+ }
+ /* don't bother checking ingress v4 filter two times... */
+ inifp = NULL;
+ }
+
+ /* some more checks for dst */
+ if (IN6_IS_ADDR_6TO4(&ip6->ip6_dst)) {
+ /*
+ * check that packet does not come from us
+ */
+ if (bcmp(&in, GET_V4(&ip6->ip6_dst), sizeof(in)) == 0) {
+ m_freem(m);
+ ifp->if_oerrors++;
+
+ /* just drop packet */
+ return;
+ }
+
+ /*
+ * if we are the 6to4 dst, then discard ingress v6 filtering
+ * even if we have native ipv6, packets may come from this
interface
+ */
+ if (stf_getsrcifa4(&sc->sc_if, NULL, &ip6->ip6_dst) != NULL)
+ inifp = NULL;
+ }
+
+
+ /*
* perform sanity check against inner src/dst.
- * for source, perform ingress filter as well.
+ * ingress filter is now done against ipv6 part (if needed).
*/
if (stf_checkaddr6(sc, &ip6->ip6_dst, NULL) < 0 ||
- stf_checkaddr6(sc, &ip6->ip6_src, m->m_pkthdr.rcvif) < 0) {
+ stf_checkaddr6(sc, &ip6->ip6_src, inifp) < 0) {
m_freem(m);
+ ifp->if_ierrors++;
+
+ return;
+ }
+
+ /* at least one address should be 6to4 */
+ if (IN6_IS_ADDR_6TO4(&ip6->ip6_dst) ||
+ IN6_IS_ADDR_6TO4(&ip6->ip6_src)) {
+ /*
+ * this packet must be sent elsewhere, check if we can
+ * handle it without resending the packet on stf
+ */
+ struct sockaddr_in6 sin6;
+ struct rtentry *rt;
+
+ /* copy the ipv6 address */
+ memset(&sin6, 0, sizeof(sin6));
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_len = sizeof(sin6);
+ bcopy(&ip6->ip6_dst, &sin6.sin6_addr, sizeof(sin6.sin6_addr));
+
+ /* ask for the route */
+ rt = rtalloc1((struct sockaddr *)&sin6, 0);
+
+ /* check the interface */
+ if (!rt || rt->rt_ifp == ifp) {
+ if (rt)
+ rtfree(rt);
+ /*
+ * if dst is a 6to4 address, it means the packet is
+ * not for us. if dst is a native address, it means
+ * we do not have a native ipv6 connectivity.
+ *
+ * drop packet to avoid bounce abuse on us.
+ */
+ m_freem(m);
+ ifp->if_ierrors++;
+
+ return;
+ }
+ rtfree(rt);
+ } else {
+ m_freem(m);
+ ifp->if_ierrors++;
+
return;
}
itos = (ntohl(ip6->ip6_flow) >> 20) & 0xff;
@@ -677,55 +936,47 @@
static int
stf_ioctl(struct ifnet *ifp, u_long cmd, void *data)
{
struct lwp *l = curlwp; /* XXX */
- struct ifaddr *ifa;
- struct ifreq *ifr;
+ struct ifaddr *ifa = (struct ifaddr *)data;
+ struct ifreq *ifr = (struct ifreq *)data;
struct sockaddr_in6 *sin6;
- int error;
u_long mtu;
+ int error = 0;
- error = 0;
switch (cmd) {
case SIOCSIFADDR:
- ifa = (struct ifaddr *)data;
if (ifa == NULL || ifa->ifa_addr->sa_family != AF_INET6) {
error = EAFNOSUPPORT;
break;
}
sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;
if (IN6_IS_ADDR_6TO4(&sin6->sin6_addr) &&
- !isrfc1918addr(GET_V4(&sin6->sin6_addr))) {
+ (stf_checkaddr6(NULL, &sin6->sin6_addr, NULL) == 0)) {
ifa->ifa_rtrequest = stf_rtrequest;
ifp->if_flags |= IFF_UP;
} else
error = EINVAL;
break;
-
case SIOCADDMULTI:
case SIOCDELMULTI:
- ifr = (struct ifreq *)data;
- if (ifr != NULL &&
- ifreq_getaddr(cmd, ifr)->sa_family == AF_INET6)
- ;
- else
+ if (ifr == NULL || ifreq_getaddr(cmd, ifr)->sa_family !=
AF_INET6)
error = EAFNOSUPPORT;
break;
-
case SIOCSIFMTU:
if ((error = kauth_authorize_generic(l->l_cred,
KAUTH_GENERIC_ISSUSER, NULL)) != 0)
break;
- ifr = (struct ifreq *)data;
+
mtu = ifr->ifr_mtu;
if (mtu < STF_MTU_MIN || mtu > STF_MTU_MAX)
- return EINVAL;
- ifp->if_mtu = mtu;
+ error = EINVAL;
+ else
+ ifp->if_mtu = mtu;
break;
-
default:
error = EINVAL;
break;
}
-
return error;
}
+
Home |
Main Index |
Thread Index |
Old Index