[PATCH net-next 3/6] ipv4: igmp: convert ip_mc_gsfget() to sockopt_t

From: Breno Leitao

Date: Fri Sep 25 2026 - 12:02:41 EST


MCAST_MSFILTER reads its reply through ip_mc_gsfget(), reached from
do_ip_getsockopt() and from nowhere else. Convert it the same way as
the ipv6 side, and build the sockopt_t at the call site for as long as
the caller still carries a sockptr_t pair.

optlen here only has to cover the fixed part, and the real reply size
comes from the gf_numsrc field inside it. This is nasty, but userspace
relies on it, so sockopt_expand_out() preserves the same mechanism: it
grows optval only for a user address, and only far enough for the
sources the socket has.

ip_mc_gsfget() writes the source list, and its two callers write the
fixed part afterwards, at the head of optval. iter_out only moves
forward, so the callee advances over the fixed part rather than
addressing each source by offset.

The callers then rewind. The reply length they already compute is
exactly what the callee consumed, ss_offset plus the sources it wrote,
so both land back where they used to write: offset 0 for the native
reply, gf_fmode for the compat one. The bytes are the same.

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 <leitao@xxxxxxxxxx>
---
include/linux/igmp.h | 2 +-
net/ipv4/igmp.c | 20 +++++++++++++-----
net/ipv4/ip_sockglue.c | 57 +++++++++++++++++++++++++++++++-------------------
3 files changed, 52 insertions(+), 27 deletions(-)

diff --git a/include/linux/igmp.h b/include/linux/igmp.h
index e075611344ef3b..0a1abf3552d2bb 100644
--- a/include/linux/igmp.h
+++ b/include/linux/igmp.h
@@ -276,7 +276,7 @@ extern int ip_mc_msfilter(struct sock *sk, struct ip_msfilter *msf,int ifindex);
extern int ip_mc_msfget(struct sock *sk, struct ip_msfilter *msf,
sockopt_t *opt);
extern int ip_mc_gsfget(struct sock *sk, struct group_filter *gsf,
- sockptr_t optval, size_t offset);
+ sockopt_t *opt, size_t offset);
extern int ip_mc_sf_allow(const struct sock *sk, __be32 local, __be32 rmt,
int dif, int sdif);
extern void ip_mc_init_dev(struct in_device *);
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
index 144fca158adcb0..d573c5bf8f038b 100644
--- a/net/ipv4/igmp.c
+++ b/net/ipv4/igmp.c
@@ -2775,9 +2775,9 @@ int ip_mc_msfget(struct sock *sk, struct ip_msfilter *msf, sockopt_t *opt)
}

int ip_mc_gsfget(struct sock *sk, struct group_filter *gsf,
- sockptr_t optval, size_t ss_offset)
+ sockopt_t *opt, size_t ss_offset)
{
- int i, count, copycount;
+ int i, count, copycount, err;
struct sockaddr_in *psin;
__be32 addr;
struct ip_mc_socklist *pmc;
@@ -2805,6 +2805,18 @@ int ip_mc_gsfget(struct sock *sk, struct group_filter *gsf,
count = psl ? psl->sl_count : 0;
copycount = count < gsf->gf_numsrc ? 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_storage ss;

@@ -2812,10 +2824,8 @@ int ip_mc_gsfget(struct sock *sk, struct group_filter *gsf,
memset(&ss, 0, sizeof(ss));
psin->sin_family = AF_INET;
psin->sin_addr.s_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;
}
diff --git a/net/ipv4/ip_sockglue.c b/net/ipv4/ip_sockglue.c
index e06c1f48ecad6e..1f452b6ea86e9d 100644
--- a/net/ipv4/ip_sockglue.c
+++ b/net/ipv4/ip_sockglue.c
@@ -1442,45 +1442,46 @@ static bool getsockopt_needs_rtnl(int optname)
return false;
}

-static int ip_get_mcast_msfilter(struct sock *sk, sockptr_t optval,
- sockptr_t optlen, int len)
+static int ip_get_mcast_msfilter(struct sock *sk, sockopt_t *opt)
{
const int size0 = offsetof(struct group_filter, gf_slist_flex);
struct group_filter gsf;
int num, gsf_size;
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;

num = gsf.gf_numsrc;
- err = ip_mc_gsfget(sk, &gsf, optval,
+ err = ip_mc_gsfget(sk, &gsf, opt,
offsetof(struct group_filter, gf_slist_flex));
if (err)
return err;
if (gsf.gf_numsrc < num)
num = gsf.gf_numsrc;
gsf_size = GROUP_FILTER_SIZE(num);
- if (copy_to_sockptr(optlen, &gsf_size, sizeof(int)) ||
- copy_to_sockptr(optval, &gsf, size0))
+ opt->optlen = gsf_size;
+
+ /* ip_mc_gsfget() consumed the whole reply; rewind to the fixed part. */
+ iov_iter_revert(&opt->iter_out, gsf_size);
+ if (copy_to_iter(&gsf, size0, &opt->iter_out) != size0)
return -EFAULT;
return 0;
}

-static int compat_ip_get_mcast_msfilter(struct sock *sk, sockptr_t optval,
- sockptr_t optlen, int len)
+static int compat_ip_get_mcast_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 num;
+ int num, len;
int err;

- 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;
@@ -1488,18 +1489,22 @@ static int compat_ip_get_mcast_msfilter(struct sock *sk, sockptr_t optval,
num = gf.gf_numsrc = gf32.gf_numsrc;
gf.gf_group = gf32.gf_group;

- err = ip_mc_gsfget(sk, &gf, optval,
+ err = ip_mc_gsfget(sk, &gf, opt,
offsetof(struct compat_group_filter, gf_slist_flex));
if (err)
return err;
if (gf.gf_numsrc < num)
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(gf.gf_fmode)) ||
- copy_to_sockptr_offset(optval, offsetof(struct compat_group_filter, gf_numsrc),
- &gf.gf_numsrc, sizeof(gf.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;
}
@@ -1727,12 +1732,22 @@ int do_ip_getsockopt(struct sock *sk, int level, int optname,
goto out;
}
case MCAST_MSFILTER:
+ {
+ struct kvec kvec;
+ sockopt_t opt;
+
+ err = sockptr_to_sockopt(&opt, optval, optlen, &kvec);
+ if (err)
+ goto out;
+
if (in_compat_syscall())
- err = compat_ip_get_mcast_msfilter(sk, optval, optlen,
- len);
+ err = compat_ip_get_mcast_msfilter(sk, &opt);
else
- err = ip_get_mcast_msfilter(sk, optval, optlen, len);
+ err = ip_get_mcast_msfilter(sk, &opt);
+ if (!err && copy_to_sockptr(optlen, &opt.optlen, sizeof(int)))
+ err = -EFAULT;
goto out;
+ }
case IP_PROTOCOL:
val = inet_sk(sk)->inet_num;
break;

--
2.53.0-Meta