sctp: fix possibly using a bad saddr with a given dst

[ Upstream commit 582eea230536a6f104097dd46205822005d5fe3a ]

Under certain circumstances, depending on the order of addresses on the
interfaces, it could be that sctp_v[46]_get_dst() would return a dst
with a mismatched struct flowi.

For example, if when walking through the bind addresses and the first
one is not a match, it saves the dst as a fallback (added in
410f03831c), but not the flowi. Then if the next one is also not a
match, the previous dst will be returned but with the flowi information
for the 2nd address, which is wrong.

The fix is to use a locally stored flowi that can be used for such
attempts, and copy it to the parameter only in case it is a possible
match, together with the corresponding dst entry.

The patch updates IPv6 code mostly just to be in sync. Even though the issue
is also present there, it fallback is not expected to work with IPv6.

Fixes: 410f03831c ("sctp: add routing output fallback")
Reported-by: Jin Meng <meng.a.jin@nokia-sbell.com>
Signed-off-by: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
Tested-by: Xin Long <lucien.xin@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Marcelo Ricardo Leitner 2020-03-26 20:47:46 -03:00 committed by Greg Kroah-Hartman
parent 6ce6aea362
commit e2ed7b117f
2 changed files with 33 additions and 15 deletions

View file

@ -240,7 +240,8 @@ static void sctp_v6_get_dst(struct sctp_transport *t, union sctp_addr *saddr,
{ {
struct sctp_association *asoc = t->asoc; struct sctp_association *asoc = t->asoc;
struct dst_entry *dst = NULL; struct dst_entry *dst = NULL;
struct flowi6 *fl6 = &fl->u.ip6; struct flowi _fl;
struct flowi6 *fl6 = &_fl.u.ip6;
struct sctp_bind_addr *bp; struct sctp_bind_addr *bp;
struct ipv6_pinfo *np = inet6_sk(sk); struct ipv6_pinfo *np = inet6_sk(sk);
struct sctp_sockaddr_entry *laddr; struct sctp_sockaddr_entry *laddr;
@ -250,7 +251,7 @@ static void sctp_v6_get_dst(struct sctp_transport *t, union sctp_addr *saddr,
enum sctp_scope scope; enum sctp_scope scope;
__u8 matchlen = 0; __u8 matchlen = 0;
memset(fl6, 0, sizeof(struct flowi6)); memset(&_fl, 0, sizeof(_fl));
fl6->daddr = daddr->v6.sin6_addr; fl6->daddr = daddr->v6.sin6_addr;
fl6->fl6_dport = daddr->v6.sin6_port; fl6->fl6_dport = daddr->v6.sin6_port;
fl6->flowi6_proto = IPPROTO_SCTP; fl6->flowi6_proto = IPPROTO_SCTP;
@ -288,8 +289,11 @@ static void sctp_v6_get_dst(struct sctp_transport *t, union sctp_addr *saddr,
rcu_read_unlock(); rcu_read_unlock();
dst = ip6_dst_lookup_flow(sk, fl6, final_p); dst = ip6_dst_lookup_flow(sk, fl6, final_p);
if (!asoc || saddr) if (!asoc || saddr) {
t->dst = dst;
memcpy(fl, &_fl, sizeof(_fl));
goto out; goto out;
}
bp = &asoc->base.bind_addr; bp = &asoc->base.bind_addr;
scope = sctp_scope(daddr); scope = sctp_scope(daddr);
@ -312,6 +316,8 @@ static void sctp_v6_get_dst(struct sctp_transport *t, union sctp_addr *saddr,
if ((laddr->a.sa.sa_family == AF_INET6) && if ((laddr->a.sa.sa_family == AF_INET6) &&
(sctp_v6_cmp_addr(&dst_saddr, &laddr->a))) { (sctp_v6_cmp_addr(&dst_saddr, &laddr->a))) {
rcu_read_unlock(); rcu_read_unlock();
t->dst = dst;
memcpy(fl, &_fl, sizeof(_fl));
goto out; goto out;
} }
} }
@ -350,6 +356,8 @@ static void sctp_v6_get_dst(struct sctp_transport *t, union sctp_addr *saddr,
if (!IS_ERR_OR_NULL(dst)) if (!IS_ERR_OR_NULL(dst))
dst_release(dst); dst_release(dst);
dst = bdst; dst = bdst;
t->dst = dst;
memcpy(fl, &_fl, sizeof(_fl));
break; break;
} }
@ -363,6 +371,8 @@ static void sctp_v6_get_dst(struct sctp_transport *t, union sctp_addr *saddr,
dst_release(dst); dst_release(dst);
dst = bdst; dst = bdst;
matchlen = bmatchlen; matchlen = bmatchlen;
t->dst = dst;
memcpy(fl, &_fl, sizeof(_fl));
} }
rcu_read_unlock(); rcu_read_unlock();
@ -371,14 +381,12 @@ static void sctp_v6_get_dst(struct sctp_transport *t, union sctp_addr *saddr,
struct rt6_info *rt; struct rt6_info *rt;
rt = (struct rt6_info *)dst; rt = (struct rt6_info *)dst;
t->dst = dst;
t->dst_cookie = rt6_get_cookie(rt); t->dst_cookie = rt6_get_cookie(rt);
pr_debug("rt6_dst:%pI6/%d rt6_src:%pI6\n", pr_debug("rt6_dst:%pI6/%d rt6_src:%pI6\n",
&rt->rt6i_dst.addr, rt->rt6i_dst.plen, &rt->rt6i_dst.addr, rt->rt6i_dst.plen,
&fl6->saddr); &fl->u.ip6.saddr);
} else { } else {
t->dst = NULL; t->dst = NULL;
pr_debug("no route\n"); pr_debug("no route\n");
} }
} }

View file

@ -424,7 +424,8 @@ static void sctp_v4_get_dst(struct sctp_transport *t, union sctp_addr *saddr,
{ {
struct sctp_association *asoc = t->asoc; struct sctp_association *asoc = t->asoc;
struct rtable *rt; struct rtable *rt;
struct flowi4 *fl4 = &fl->u.ip4; struct flowi _fl;
struct flowi4 *fl4 = &_fl.u.ip4;
struct sctp_bind_addr *bp; struct sctp_bind_addr *bp;
struct sctp_sockaddr_entry *laddr; struct sctp_sockaddr_entry *laddr;
struct dst_entry *dst = NULL; struct dst_entry *dst = NULL;
@ -434,7 +435,7 @@ static void sctp_v4_get_dst(struct sctp_transport *t, union sctp_addr *saddr,
if (t->dscp & SCTP_DSCP_SET_MASK) if (t->dscp & SCTP_DSCP_SET_MASK)
tos = t->dscp & SCTP_DSCP_VAL_MASK; tos = t->dscp & SCTP_DSCP_VAL_MASK;
memset(fl4, 0x0, sizeof(struct flowi4)); memset(&_fl, 0x0, sizeof(_fl));
fl4->daddr = daddr->v4.sin_addr.s_addr; fl4->daddr = daddr->v4.sin_addr.s_addr;
fl4->fl4_dport = daddr->v4.sin_port; fl4->fl4_dport = daddr->v4.sin_port;
fl4->flowi4_proto = IPPROTO_SCTP; fl4->flowi4_proto = IPPROTO_SCTP;
@ -453,8 +454,11 @@ static void sctp_v4_get_dst(struct sctp_transport *t, union sctp_addr *saddr,
&fl4->saddr); &fl4->saddr);
rt = ip_route_output_key(sock_net(sk), fl4); rt = ip_route_output_key(sock_net(sk), fl4);
if (!IS_ERR(rt)) if (!IS_ERR(rt)) {
dst = &rt->dst; dst = &rt->dst;
t->dst = dst;
memcpy(fl, &_fl, sizeof(_fl));
}
/* If there is no association or if a source address is passed, no /* If there is no association or if a source address is passed, no
* more validation is required. * more validation is required.
@ -517,27 +521,33 @@ static void sctp_v4_get_dst(struct sctp_transport *t, union sctp_addr *saddr,
odev = __ip_dev_find(sock_net(sk), laddr->a.v4.sin_addr.s_addr, odev = __ip_dev_find(sock_net(sk), laddr->a.v4.sin_addr.s_addr,
false); false);
if (!odev || odev->ifindex != fl4->flowi4_oif) { if (!odev || odev->ifindex != fl4->flowi4_oif) {
if (!dst) if (!dst) {
dst = &rt->dst; dst = &rt->dst;
else t->dst = dst;
memcpy(fl, &_fl, sizeof(_fl));
} else {
dst_release(&rt->dst); dst_release(&rt->dst);
}
continue; continue;
} }
dst_release(dst); dst_release(dst);
dst = &rt->dst; dst = &rt->dst;
t->dst = dst;
memcpy(fl, &_fl, sizeof(_fl));
break; break;
} }
out_unlock: out_unlock:
rcu_read_unlock(); rcu_read_unlock();
out: out:
t->dst = dst; if (dst) {
if (dst)
pr_debug("rt_dst:%pI4, rt_src:%pI4\n", pr_debug("rt_dst:%pI4, rt_src:%pI4\n",
&fl4->daddr, &fl4->saddr); &fl->u.ip4.daddr, &fl->u.ip4.saddr);
else } else {
t->dst = NULL;
pr_debug("no route\n"); pr_debug("no route\n");
}
} }
/* For v4, the source address is cached in the route entry(dst). So no need /* For v4, the source address is cached in the route entry(dst). So no need