MCAST_MSFILTER reads its reply through ip6_mc_msfget(), reached from
do_ipv6_getsockopt() and from nowhere else. Convert it, and build the
sockopt_t at the call site for as long as the caller still carries a
sockptr_t pair.

optlen only has to cover the fixed part, and the real reply size comes
from the gf_numsrc field inside it. Userspace relies on that, so
sockopt_expand_out() grows optval past optlen, for a user address only
and only far enough for the sources the socket has.

ip6_mc_msfget() now advances over the fixed part and writes the source
list through iter_out. Its callers rewind by the reply length they
already compute, which is exactly what the callee consumed, and land
back where they used to write: offset 0 for the native reply, gf_fmode
for the compat one.

The *optlen store moves out to the call site, guarded by !err so the
-EINVAL, -EADDRNOTAVAIL and -EFAULT returns still leave the caller's
optlen word untouched.

Signed-off-by: Breno Leitao <[email protected]>
---
 include/net/ipv6.h       |  2 +-
 net/ipv6/ipv6_sockglue.c | 63 ++++++++++++++++++++++++++++++++----------------
 net/ipv6/mcast.c         | 19 ++++++++++++---
 3 files changed, 58 insertions(+), 26 deletions(-)

diff --git a/include/net/ipv6.h b/include/net/ipv6.h
index 3de07e738538f7..9bb68d75890364 100644
--- a/include/net/ipv6.h
+++ b/include/net/ipv6.h
@@ -1195,7 +1195,7 @@ int ip6_mc_source(int add, int omode, struct sock *sk,
 int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf,
                  struct sockaddr_storage *list);
 int ip6_mc_msfget(struct sock *sk, struct group_filter *gsf,
-                 sockptr_t optval, size_t ss_offset);
+                 sockopt_t *opt, size_t ss_offset);
 
 #ifdef CONFIG_PROC_FS
 int ac6_proc_init(struct net *net);
diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c
index 5c6a0819a2aaff..1bdb3e001e4fe6 100644
--- a/net/ipv6/ipv6_sockglue.c
+++ b/net/ipv6/ipv6_sockglue.c
@@ -922,48 +922,51 @@ static int ipv6_getsockopt_sticky(struct sock *sk, struct 
ipv6_txoptions *opt,
        return len;
 }
 
-static int ipv6_get_msfilter(struct sock *sk, sockptr_t optval,
-                            sockptr_t optlen, int len)
+static int ipv6_get_msfilter(struct sock *sk, sockopt_t *opt)
 {
        const int size0 = offsetof(struct group_filter, gf_slist_flex);
        struct group_filter gsf;
-       int num;
+       int num, len;
        int err;
 
-       if (len < size0)
+       if (opt->optlen < size0)
                return -EINVAL;
-       if (copy_from_sockptr(&gsf, optval, size0))
+       if (copy_from_iter(&gsf, size0, &opt->iter_in) != size0)
                return -EFAULT;
        if (gsf.gf_group.ss_family != AF_INET6)
                return -EADDRNOTAVAIL;
        num = gsf.gf_numsrc;
        sockopt_lock_sock(sk);
-       err = ip6_mc_msfget(sk, &gsf, optval, size0);
+       err = ip6_mc_msfget(sk, &gsf, opt, size0);
        if (!err) {
                if (num > gsf.gf_numsrc)
                        num = gsf.gf_numsrc;
                len = GROUP_FILTER_SIZE(num);
-               if (copy_to_sockptr(optlen, &len, sizeof(int)) ||
-                   copy_to_sockptr(optval, &gsf, size0))
+               opt->optlen = len;
+
+               /* ip6_mc_msfget() consumed the whole reply; rewind to the
+                * fixed part.
+                */
+               iov_iter_revert(&opt->iter_out, len);
+               if (copy_to_iter(&gsf, size0, &opt->iter_out) != size0)
                        err = -EFAULT;
        }
        sockopt_release_sock(sk);
        return err;
 }
 
-static int compat_ipv6_get_msfilter(struct sock *sk, sockptr_t optval,
-                                   sockptr_t optlen, int len)
+static int compat_ipv6_get_msfilter(struct sock *sk, sockopt_t *opt)
 {
        const int size0 = offsetof(struct compat_group_filter, gf_slist_flex);
        struct compat_group_filter gf32;
        struct group_filter gf;
        int err;
-       int num;
+       int num, len;
 
-       if (len < size0)
+       if (opt->optlen < size0)
                return -EINVAL;
 
-       if (copy_from_sockptr(&gf32, optval, size0))
+       if (copy_from_iter(&gf32, size0, &opt->iter_in) != size0)
                return -EFAULT;
        gf.gf_interface = gf32.gf_interface;
        gf.gf_fmode = gf32.gf_fmode;
@@ -974,18 +977,22 @@ static int compat_ipv6_get_msfilter(struct sock *sk, 
sockptr_t optval,
                return -EADDRNOTAVAIL;
 
        sockopt_lock_sock(sk);
-       err = ip6_mc_msfget(sk, &gf, optval, size0);
+       err = ip6_mc_msfget(sk, &gf, opt, size0);
        sockopt_release_sock(sk);
        if (err)
                return err;
        if (num > gf.gf_numsrc)
                num = gf.gf_numsrc;
        len = GROUP_FILTER_SIZE(num) - (sizeof(gf)-sizeof(gf32));
-       if (copy_to_sockptr(optlen, &len, sizeof(int)) ||
-           copy_to_sockptr_offset(optval, offsetof(struct compat_group_filter, 
gf_fmode),
-                                  &gf.gf_fmode, sizeof(gf32.gf_fmode)) ||
-           copy_to_sockptr_offset(optval, offsetof(struct compat_group_filter, 
gf_numsrc),
-                                  &gf.gf_numsrc, sizeof(gf32.gf_numsrc)))
+       opt->optlen = len;
+
+       /* Rewind to gf_fmode, which gf_numsrc follows. */
+       iov_iter_revert(&opt->iter_out,
+                       len - offsetof(struct compat_group_filter, gf_fmode));
+       if (copy_to_iter(&gf.gf_fmode, sizeof(gf32.gf_fmode),
+                        &opt->iter_out) != sizeof(gf32.gf_fmode) ||
+           copy_to_iter(&gf.gf_numsrc, sizeof(gf32.gf_numsrc),
+                        &opt->iter_out) != sizeof(gf32.gf_numsrc))
                return -EFAULT;
        return 0;
 }
@@ -1006,9 +1013,23 @@ int do_ipv6_getsockopt(struct sock *sk, int level, int 
optname,
                return -EINVAL;
        switch (optname) {
        case MCAST_MSFILTER:
+       {
+               struct kvec kvec;
+               sockopt_t opt;
+               int err;
+
+               err = sockptr_to_sockopt(&opt, optval, optlen, &kvec);
+               if (err)
+                       return err;
+
                if (in_compat_syscall())
-                       return compat_ipv6_get_msfilter(sk, optval, optlen, 
len);
-               return ipv6_get_msfilter(sk, optval, optlen, len);
+                       err = compat_ipv6_get_msfilter(sk, &opt);
+               else
+                       err = ipv6_get_msfilter(sk, &opt);
+               if (!err && copy_to_sockptr(optlen, &opt.optlen, sizeof(int)))
+                       err = -EFAULT;
+               return err;
+       }
        case IPV6_2292PKTOPTIONS:
        {
                struct msghdr msg;
diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c
index ecef55f261890c..4ca2d77811f4eb 100644
--- a/net/ipv6/mcast.c
+++ b/net/ipv6/mcast.c
@@ -600,14 +600,14 @@ int ip6_mc_msfilter(struct sock *sk, struct group_filter 
*gsf,
 }
 
 int ip6_mc_msfget(struct sock *sk, struct group_filter *gsf,
-                 sockptr_t optval, size_t ss_offset)
+                 sockopt_t *opt, size_t ss_offset)
 {
        struct ipv6_pinfo *inet6 = inet6_sk(sk);
        const struct in6_addr *group;
        struct ipv6_mc_socklist *pmc;
        struct ip6_sf_socklist *psl;
+       int i, copycount, err;
        unsigned int count;
-       int i, copycount;
 
        group = &((struct sockaddr_in6 *)&gsf->gf_group)->sin6_addr;
 
@@ -629,6 +629,18 @@ int ip6_mc_msfget(struct sock *sk, struct group_filter 
*gsf,
 
        copycount = min(count, gsf->gf_numsrc);
        gsf->gf_numsrc = count;
+
+       /* The source list is sized by the gf_numsrc the caller left in optval,
+        * not by optlen, which only has to cover the fixed part.
+        */
+       err = sockopt_expand_out(opt, ss_offset +
+                                copycount * sizeof(struct sockaddr_storage));
+       if (err)
+               return err;
+
+       /* The caller fills the fixed part in once it knows gf_numsrc. */
+       iov_iter_advance(&opt->iter_out, ss_offset);
+
        for (i = 0; i < copycount; i++) {
                struct sockaddr_in6 *psin6;
                struct sockaddr_storage ss;
@@ -637,9 +649,8 @@ int ip6_mc_msfget(struct sock *sk, struct group_filter *gsf,
                memset(&ss, 0, sizeof(ss));
                psin6->sin6_family = AF_INET6;
                psin6->sin6_addr = psl->sl_addr[i];
-               if (copy_to_sockptr_offset(optval, ss_offset, &ss, sizeof(ss)))
+               if (copy_to_iter(&ss, sizeof(ss), &opt->iter_out) != sizeof(ss))
                        return -EFAULT;
-               ss_offset += sizeof(ss);
        }
        return 0;
 }

-- 
2.53.0-Meta


Reply via email to