Subject: [PATCH] net: Fix invalid ip_route_output_ports() call

From: Hyunwoo Kim <[email-masked]>
Date: Mon, 20 Mar 2023 19:49:46 -0700

If you pass the address of the struct flowi4 you declared as a
local variable as the fl4 argument to ip_route_output_ports(),
the subsequent call to xfrm_state_find() will read the local
variable by AF_INET6 rather than AF_INET as per policy,
which could cause it to go out of scope on the kernel stack.

Reported-by: [email-masked]
Signed-off-by: Hyunwoo Kim <[email-masked]>
---
 drivers/net/amt.c                                | 15 +++++++++------
 .../net/ethernet/chelsio/libcxgb/libcxgb_cm.c    |  5 +++--
 drivers/net/ppp/pptp.c                           | 14 ++++++++------
 net/ipv4/datagram.c                              |  5 +++--
 net/ipv4/igmp.c                                  | 16 +++++++++-------
 net/ipv4/ipmr.c                                  |  7 ++++---
 net/ipv6/ip6_tunnel.c                            |  7 ++++---
 net/ipv6/sit.c                                   |  5 +++--
 8 files changed, 43 insertions(+), 31 deletions(-)

diff --git a/drivers/net/amt.c b/drivers/net/amt.c
index 2d20be6ffb7e..4d8caaedc9d4 100644
--- a/drivers/net/amt.c
+++ b/drivers/net/amt.c
@@ -617,7 +617,8 @@ static void amt_send_discovery(struct amt_dev *amt)
 	struct sk_buff *skb;
 	struct iphdr *iph;
 	struct rtable *rt;
-	struct flowi4 fl4;
+	struct flowi fl;
+	struct flowi4 *fl4 = &fl.u.ip4;
 	u32 len;
 	int err;
 
@@ -629,7 +630,7 @@ static void amt_send_discovery(struct amt_dev *amt)
 	if (!netif_running(amt->stream_dev) || !netif_running(amt->dev))
 		goto out;
 
-	rt = ip_route_output_ports(amt->net, &fl4, sock->sk,
+	rt = ip_route_output_ports(amt->net, fl4, sock->sk,
 				   amt->discovery_ip, amt->local_ip,
 				   amt->gw_port, amt->relay_port,
 				   IPPROTO_UDP, 0,
@@ -706,7 +707,8 @@ static void amt_send_request(struct amt_dev *amt, bool v6)
 	struct sk_buff *skb;
 	struct iphdr *iph;
 	struct rtable *rt;
-	struct flowi4 fl4;
+	struct flowi fl;
+	struct flowi4 *fl4 = &fl.u.ip4;
 	u32 len;
 	int err;
 
@@ -718,7 +720,7 @@ static void amt_send_request(struct amt_dev *amt, bool v6)
 	if (!netif_running(amt->stream_dev) || !netif_running(amt->dev))
 		goto out;
 
-	rt = ip_route_output_ports(amt->net, &fl4, sock->sk,
+	rt = ip_route_output_ports(amt->net, fl4, sock->sk,
 				   amt->remote_ip, amt->local_ip,
 				   amt->gw_port, amt->relay_port,
 				   IPPROTO_UDP, 0,
@@ -2554,7 +2556,8 @@ static void amt_send_advertisement(struct amt_dev *amt, __be32 nonce,
 	struct sk_buff *skb;
 	struct iphdr *iph;
 	struct rtable *rt;
-	struct flowi4 fl4;
+	struct flowi fl;
+	struct flowi4 *fl4 = &fl.u.ip4;
 	u32 len;
 	int err;
 
@@ -2566,7 +2569,7 @@ static void amt_send_advertisement(struct amt_dev *amt, __be32 nonce,
 	if (!netif_running(amt->stream_dev) || !netif_running(amt->dev))
 		goto out;
 
-	rt = ip_route_output_ports(amt->net, &fl4, sock->sk,
+	rt = ip_route_output_ports(amt->net, fl4, sock->sk,
 				   daddr, amt->local_ip,
 				   dport, amt->relay_port,
 				   IPPROTO_UDP, 0,
diff --git a/drivers/net/ethernet/chelsio/libcxgb/libcxgb_cm.c b/drivers/net/ethernet/chelsio/libcxgb/libcxgb_cm.c
index da8d10475a08..e06a6d0a3595 100644
--- a/drivers/net/ethernet/chelsio/libcxgb/libcxgb_cm.c
+++ b/drivers/net/ethernet/chelsio/libcxgb/libcxgb_cm.c
@@ -95,10 +95,11 @@ cxgb_find_route(struct cxgb4_lld_info *lldi,
 		__be16 peer_port, u8 tos)
 {
 	struct rtable *rt;
-	struct flowi4 fl4;
+	struct flowi fl;
+	struct flowi4 *fl4 = &fl.u.ip4;
 	struct neighbour *n;
 
-	rt = ip_route_output_ports(&init_net, &fl4, NULL, peer_ip, local_ip,
+	rt = ip_route_output_ports(&init_net, fl4, NULL, peer_ip, local_ip,
 				   peer_port, local_port, IPPROTO_TCP,
 				   tos & ~INET_ECN_MASK, 0);
 	if (IS_ERR(rt))
diff --git a/drivers/net/ppp/pptp.c b/drivers/net/ppp/pptp.c
index 0fe78826c8fa..6e4422ce89b5 100644
--- a/drivers/net/ppp/pptp.c
+++ b/drivers/net/ppp/pptp.c
@@ -136,7 +136,8 @@ static int pptp_xmit(struct ppp_channel *chan, struct sk_buff *skb)
 	struct pptp_opt *opt = &po->proto.pptp;
 	struct pptp_gre_header *hdr;
 	unsigned int header_len = sizeof(*hdr);
-	struct flowi4 fl4;
+	struct flowi fl;
+	struct flowi4 *fl4 = &fl.u.ip4;
 	int islcp;
 	int len;
 	unsigned char *data;
@@ -151,7 +152,7 @@ static int pptp_xmit(struct ppp_channel *chan, struct sk_buff *skb)
 	if (sk_pppox(po)->sk_state & PPPOX_DEAD)
 		goto tx_error;
 
-	rt = ip_route_output_ports(net, &fl4, NULL,
+	rt = ip_route_output_ports(net, fl4, NULL,
 				   opt->dst_addr.sin_addr.s_addr,
 				   opt->src_addr.sin_addr.s_addr,
 				   0, 0, IPPROTO_GRE,
@@ -230,8 +231,8 @@ static int pptp_xmit(struct ppp_channel *chan, struct sk_buff *skb)
 		iph->frag_off	=	0;
 	iph->protocol = IPPROTO_GRE;
 	iph->tos      = 0;
-	iph->daddr    = fl4.daddr;
-	iph->saddr    = fl4.saddr;
+	iph->daddr    = fl4->daddr;
+	iph->saddr    = fl4->saddr;
 	iph->ttl      = ip4_dst_hoplimit(&rt->dst);
 	iph->tot_len  = htons(skb->len);
 
@@ -405,7 +406,8 @@ static int pptp_connect(struct socket *sock, struct sockaddr *uservaddr,
 	struct pppox_sock *po = pppox_sk(sk);
 	struct pptp_opt *opt = &po->proto.pptp;
 	struct rtable *rt;
-	struct flowi4 fl4;
+	struct flowi fl;
+	struct flowi4 *fl4 = &fl.u.ip4;
 	int error = 0;
 
 	if (sockaddr_len < sizeof(struct sockaddr_pppox))
@@ -438,7 +440,7 @@ static int pptp_connect(struct socket *sock, struct sockaddr *uservaddr,
 	po->chan.private = sk;
 	po->chan.ops = &pptp_chan_ops;
 
-	rt = ip_route_output_ports(sock_net(sk), &fl4, sk,
+	rt = ip_route_output_ports(sock_net(sk), fl4, sk,
 				   opt->dst_addr.sin_addr.s_addr,
 				   opt->src_addr.sin_addr.s_addr,
 				   0, 0,
diff --git a/net/ipv4/datagram.c b/net/ipv4/datagram.c
index 4d1af0cd7d99..12656eb49e50 100644
--- a/net/ipv4/datagram.c
+++ b/net/ipv4/datagram.c
@@ -103,7 +103,8 @@ void ip4_datagram_release_cb(struct sock *sk)
 	const struct ip_options_rcu *inet_opt;
 	__be32 daddr = inet->inet_daddr;
 	struct dst_entry *dst;
-	struct flowi4 fl4;
+	struct flowi fl;
+	struct flowi4 *fl4 = &fl.u.ip4;
 	struct rtable *rt;
 
 	rcu_read_lock();
@@ -116,7 +117,7 @@ void ip4_datagram_release_cb(struct sock *sk)
 	inet_opt = rcu_dereference(inet->inet_opt);
 	if (inet_opt && inet_opt->opt.srr)
 		daddr = inet_opt->opt.faddr;
-	rt = ip_route_output_ports(sock_net(sk), &fl4, sk, daddr,
+	rt = ip_route_output_ports(sock_net(sk), fl4, sk, daddr,
 				   inet->inet_saddr, inet->inet_dport,
 				   inet->inet_sport, sk->sk_protocol,
 				   RT_CONN_FLAGS(sk), sk->sk_bound_dev_if);
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
index c920aa9a62a9..b7de65708b37 100644
--- a/net/ipv4/igmp.c
+++ b/net/ipv4/igmp.c
@@ -350,7 +350,8 @@ static struct sk_buff *igmpv3_newpack(struct net_device *dev, unsigned int mtu)
 	struct iphdr *pip;
 	struct igmpv3_report *pig;
 	struct net *net = dev_net(dev);
-	struct flowi4 fl4;
+	struct flowi fl;
+	struct flowi4 *fl4 = &fl.u.ip4;
 	int hlen = LL_RESERVED_SPACE(dev);
 	int tlen = dev->needed_tailroom;
 	unsigned int size = mtu;
@@ -366,7 +367,7 @@ static struct sk_buff *igmpv3_newpack(struct net_device *dev, unsigned int mtu)
 	}
 	skb->priority = TC_PRIO_CONTROL;
 
-	rt = ip_route_output_ports(net, &fl4, NULL, IGMPV3_ALL_MCR, 0,
+	rt = ip_route_output_ports(net, fl4, NULL, IGMPV3_ALL_MCR, 0,
 				   0, 0,
 				   IPPROTO_IGMP, 0, dev->ifindex);
 	if (IS_ERR(rt)) {
@@ -389,10 +390,10 @@ static struct sk_buff *igmpv3_newpack(struct net_device *dev, unsigned int mtu)
 	pip->tos      = 0xc0;
 	pip->frag_off = htons(IP_DF);
 	pip->ttl      = 1;
-	pip->daddr    = fl4.daddr;
+	pip->daddr    = fl4->daddr;
 
 	rcu_read_lock();
-	pip->saddr    = igmpv3_get_srcaddr(dev, &fl4);
+	pip->saddr    = igmpv3_get_srcaddr(dev, fl4);
 	rcu_read_unlock();
 
 	pip->protocol = IPPROTO_IGMP;
@@ -730,7 +731,8 @@ static int igmp_send_report(struct in_device *in_dev, struct ip_mc_list *pmc,
 	struct net_device *dev = in_dev->dev;
 	struct net *net = dev_net(dev);
 	__be32	group = pmc ? pmc->multiaddr : 0;
-	struct flowi4 fl4;
+	struct flowi fl;
+	struct flowi4 *fl4 = &fl.u.ip4;
 	__be32	dst;
 	int hlen, tlen;
 
@@ -746,7 +748,7 @@ static int igmp_send_report(struct in_device *in_dev, struct ip_mc_list *pmc,
 	else
 		dst = group;
 
-	rt = ip_route_output_ports(net, &fl4, NULL, dst, 0,
+	rt = ip_route_output_ports(net, fl4, NULL, dst, 0,
 				   0, 0,
 				   IPPROTO_IGMP, 0, dev->ifindex);
 	if (IS_ERR(rt))
@@ -775,7 +777,7 @@ static int igmp_send_report(struct in_device *in_dev, struct ip_mc_list *pmc,
 	iph->frag_off = htons(IP_DF);
 	iph->ttl      = 1;
 	iph->daddr    = dst;
-	iph->saddr    = fl4.saddr;
+	iph->saddr    = fl4->saddr;
 	iph->protocol = IPPROTO_IGMP;
 	ip_select_ident(net, skb, NULL);
 	((u8 *)&iph[1])[0] = IPOPT_RA;
diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c
index eec1f6df80d8..994dd589835c 100644
--- a/net/ipv4/ipmr.c
+++ b/net/ipv4/ipmr.c
@@ -1829,7 +1829,8 @@ static void ipmr_queue_xmit(struct net *net, struct mr_table *mrt,
 	struct net_device *vif_dev;
 	struct net_device *dev;
 	struct rtable *rt;
-	struct flowi4 fl4;
+	struct flowi fl;
+	struct flowi4 *fl4 = &fl.u.ip4;
 	int    encap = 0;
 
 	vif_dev = vif_dev_read(vif);
@@ -1849,7 +1850,7 @@ static void ipmr_queue_xmit(struct net *net, struct mr_table *mrt,
 		goto out_free;
 
 	if (vif->flags & VIFF_TUNNEL) {
-		rt = ip_route_output_ports(net, &fl4, NULL,
+		rt = ip_route_output_ports(net, fl4, NULL,
 					   vif->remote, vif->local,
 					   0, 0,
 					   IPPROTO_IPIP,
@@ -1858,7 +1859,7 @@ static void ipmr_queue_xmit(struct net *net, struct mr_table *mrt,
 			goto out_free;
 		encap = sizeof(struct iphdr);
 	} else {
-		rt = ip_route_output_ports(net, &fl4, NULL, iph->daddr, 0,
+		rt = ip_route_output_ports(net, fl4, NULL, iph->daddr, 0,
 					   0, 0,
 					   IPPROTO_IPIP,
 					   RT_TOS(iph->tos), vif->link);
diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c
index 47b6607a1370..078a97742b7d 100644
--- a/net/ipv6/ip6_tunnel.c
+++ b/net/ipv6/ip6_tunnel.c
@@ -567,7 +567,8 @@ ip4ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
 	u8 rel_type = type;
 	u8 rel_code = code;
 	struct rtable *rt;
-	struct flowi4 fl4;
+	struct flowi fl;
+	struct flowi4 *fl4 = &fl.u.ip4;
 
 	err = ip6_tnl_err(skb, IPPROTO_IPIP, opt, &rel_type, &rel_code,
 			  &rel_msg, &rel_info, offset);
@@ -608,7 +609,7 @@ ip4ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
 	eiph = ip_hdr(skb2);
 
 	/* Try to guess incoming interface */
-	rt = ip_route_output_ports(dev_net(skb->dev), &fl4, NULL, eiph->saddr,
+	rt = ip_route_output_ports(dev_net(skb->dev), fl4, NULL, eiph->saddr,
 				   0, 0, 0, IPPROTO_IPIP, RT_TOS(eiph->tos), 0);
 	if (IS_ERR(rt))
 		goto out;
@@ -618,7 +619,7 @@ ip4ip6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
 
 	/* route "incoming" packet */
 	if (rt->rt_flags & RTCF_LOCAL) {
-		rt = ip_route_output_ports(dev_net(skb->dev), &fl4, NULL,
+		rt = ip_route_output_ports(dev_net(skb->dev), fl4, NULL,
 					   eiph->daddr, eiph->saddr, 0, 0,
 					   IPPROTO_IPIP, RT_TOS(eiph->tos), 0);
 		if (IS_ERR(rt) || rt->dst.dev->type != ARPHRD_TUNNEL6) {
diff --git a/net/ipv6/sit.c b/net/ipv6/sit.c
index 70d81bba5093..fc2fb6bae588 100644
--- a/net/ipv6/sit.c
+++ b/net/ipv6/sit.c
@@ -1098,13 +1098,14 @@ static void ipip6_tunnel_bind_dev(struct net_device *dev)
 	struct net_device *tdev = NULL;
 	struct ip_tunnel *tunnel;
 	const struct iphdr *iph;
-	struct flowi4 fl4;
+	struct flowi fl;
+	struct flowi4 *fl4 = &fl.u.ip4;
 
 	tunnel = netdev_priv(dev);
 	iph = &tunnel->parms.iph;
 
 	if (iph->daddr) {
-		struct rtable *rt = ip_route_output_ports(tunnel->net, &fl4,
+		struct rtable *rt = ip_route_output_ports(tunnel->net, fl4,
 							  NULL,
 							  iph->daddr, iph->saddr,
 							  0, 0,
-- 
2.25.1