[PATCH 4/4] ipv6: symbol_get to access a sit symbol

From: Christoph Hellwig
Date: Thu May 14 2020 - 10:51:25 EST


Instead of going through the ioctl handler from kernel space, use
symbol_get to the newly factored out ipip6_set_dstaddr helper, bypassing
addrconf.c entirely.

Signed-off-by: Christoph Hellwig <hch@xxxxxx>
---
include/net/addrconf.h | 1 -
include/net/ipv6.h | 2 ++
net/ipv6/addrconf.c | 66 ------------------------------------------
net/ipv6/af_inet6.c | 20 ++++++++++++-
net/ipv6/sit.c | 41 ++++++++++++++++++++++++++
5 files changed, 62 insertions(+), 68 deletions(-)

diff --git a/include/net/addrconf.h b/include/net/addrconf.h
index fdb07105384ca..569eb03ae2440 100644
--- a/include/net/addrconf.h
+++ b/include/net/addrconf.h
@@ -76,7 +76,6 @@ void addrconf_cleanup(void);

int addrconf_add_ifaddr(struct net *net, void __user *arg);
int addrconf_del_ifaddr(struct net *net, void __user *arg);
-int addrconf_set_dstaddr(struct net *net, void __user *arg);

int ipv6_chk_addr(struct net *net, const struct in6_addr *addr,
const struct net_device *dev, int strict);
diff --git a/include/net/ipv6.h b/include/net/ipv6.h
index 955badd1e8ffc..1b983f32c87ce 100644
--- a/include/net/ipv6.h
+++ b/include/net/ipv6.h
@@ -1080,6 +1080,8 @@ struct in6_addr *fl6_update_dst(struct flowi6 *fl6,
const struct ipv6_txoptions *opt,
struct in6_addr *orig);

+int ipip6_set_dstaddr(struct net *net, void __user *arg);
+
/*
* socket options (ipv6_sockglue.c)
*/
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index fd885f06c4ed6..02186f00f91c5 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -2783,72 +2783,6 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
in6_dev_put(in6_dev);
}

-/*
- * Set destination address.
- * Special case for SIT interfaces where we create a new "virtual"
- * device.
- */
-int addrconf_set_dstaddr(struct net *net, void __user *arg)
-{
- struct in6_ifreq ireq;
- struct net_device *dev;
- int err = -EINVAL;
-
- rtnl_lock();
-
- err = -EFAULT;
- if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq)))
- goto err_exit;
-
- dev = __dev_get_by_index(net, ireq.ifr6_ifindex);
-
- err = -ENODEV;
- if (!dev)
- goto err_exit;
-
-#if IS_ENABLED(CONFIG_IPV6_SIT)
- if (dev->type == ARPHRD_SIT) {
- const struct net_device_ops *ops = dev->netdev_ops;
- struct ifreq ifr;
- struct ip_tunnel_parm p;
-
- err = -EADDRNOTAVAIL;
- if (!(ipv6_addr_type(&ireq.ifr6_addr) & IPV6_ADDR_COMPATv4))
- goto err_exit;
-
- memset(&p, 0, sizeof(p));
- p.iph.daddr = ireq.ifr6_addr.s6_addr32[3];
- p.iph.saddr = 0;
- p.iph.version = 4;
- p.iph.ihl = 5;
- p.iph.protocol = IPPROTO_IPV6;
- p.iph.ttl = 64;
- ifr.ifr_ifru.ifru_data = (__force void __user *)&p;
-
- if (ops->ndo_do_ioctl) {
- mm_segment_t oldfs = get_fs();
-
- set_fs(KERNEL_DS);
- err = ops->ndo_do_ioctl(dev, &ifr, SIOCADDTUNNEL);
- set_fs(oldfs);
- } else
- err = -EOPNOTSUPP;
-
- if (err == 0) {
- err = -ENOBUFS;
- dev = __dev_get_by_name(net, p.name);
- if (!dev)
- goto err_exit;
- err = dev_open(dev, NULL);
- }
- }
-#endif
-
-err_exit:
- rtnl_unlock();
- return err;
-}
-
static int ipv6_mc_config(struct sock *sk, bool join,
const struct in6_addr *addr, int ifindex)
{
diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
index 345baa0a754f4..3ec9734c7bb11 100644
--- a/net/ipv6/af_inet6.c
+++ b/net/ipv6/af_inet6.c
@@ -538,6 +538,19 @@ int inet6_getname(struct socket *sock, struct sockaddr *uaddr,
}
EXPORT_SYMBOL(inet6_getname);

+static int inet6_ioctl_set_dstaddr(struct net *net, void __user *arg)
+{
+ int (*set_dstaddr)(struct net *, void __user *);
+ int err;
+
+ set_dstaddr = symbol_get(ipip6_set_dstaddr);
+ if (!set_dstaddr)
+ return -EOPNOTSUPP;
+ err = set_dstaddr(net, arg);
+ symbol_put(ipip6_set_dstaddr);
+ return err;
+}
+
int inet6_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
{
struct sock *sk = sock->sk;
@@ -554,7 +567,12 @@ int inet6_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
case SIOCDIFADDR:
return addrconf_del_ifaddr(net, (void __user *) arg);
case SIOCSIFDSTADDR:
- return addrconf_set_dstaddr(net, (void __user *) arg);
+ /* Special case for SIT interfaces where we create a new
+ * "virtual" device.
+ */
+ if (!IS_ENABLED(CONFIG_IPV6_SIT))
+ return -ENODEV;
+ return inet6_ioctl_set_dstaddr(net, (void __user *) arg);
default:
if (!sk->sk_prot->ioctl)
return -ENOIOCTLCMD;
diff --git a/net/ipv6/sit.c b/net/ipv6/sit.c
index 98954830c40ba..cb2cfa297f72e 100644
--- a/net/ipv6/sit.c
+++ b/net/ipv6/sit.c
@@ -274,6 +274,47 @@ static struct ip_tunnel *ipip6_tunnel_locate(struct net *net,
return NULL;
}

+int ipip6_set_dstaddr(struct net *net, void __user *arg)
+{
+ struct ip_tunnel_parm p = { };
+ struct in6_ifreq ireq;
+ struct net_device *tunnel_dev, *new_dev;
+ int err;
+
+ rtnl_lock();
+ err = -EFAULT;
+ if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq)))
+ goto out_unlock;
+
+ err = -ENODEV;
+ tunnel_dev = __dev_get_by_index(net, ireq.ifr6_ifindex);
+ if (!tunnel_dev || tunnel_dev->type != ARPHRD_SIT)
+ goto out_unlock;
+
+ err = -EADDRNOTAVAIL;
+ if (!(ipv6_addr_type(&ireq.ifr6_addr) & IPV6_ADDR_COMPATv4))
+ goto out_unlock;
+
+ p.iph.daddr = ireq.ifr6_addr.s6_addr32[3];
+ p.iph.version = 4;
+ p.iph.ihl = 5;
+ p.iph.protocol = IPPROTO_IPV6;
+ p.iph.ttl = 64;
+ p.iph.frag_off |= htons(IP_DF);
+
+ err = -ENOBUFS;
+ if (!ipip6_tunnel_locate(dev_net(tunnel_dev), &p, true))
+ goto out_unlock;
+ new_dev = __dev_get_by_name(net, p.name);
+ if (!new_dev)
+ goto out_unlock;
+ err = dev_open(new_dev, NULL);
+out_unlock:
+ rtnl_unlock();
+ return err;
+}
+EXPORT_SYMBOL_GPL(ipip6_set_dstaddr);
+
#define for_each_prl_rcu(start) \
for (prl = rcu_dereference(start); \
prl; \
--
2.26.2