Subject: [PATCH net] ipv4: clamp MCAST_MSFILTER getsockopt to optlen, not gf_numsrc

From: Greg Kroah-Hartman <[email-masked]>
Date: Mon, 20 Apr 2026 21:26:55 +0200

ip_get_mcast_msfilter() and its compat sibling read gf_numsrc from
the user's buffer header and pass it to ip_mc_gsfget(), which writes:

    min(actual_sources, gf_numsrc) * sizeof(struct sockaddr_storage)

bytes back into the user's optval starting at the gf_slist_flex offset.
The only optlen check is len >= size0 (the header), so a user can pass
optlen = 144 (header only) with gf_numsrc = 4.  If the socket has at
least 4 sources joined, the kernel writes 4*128 = 512 bytes via
copy_to_sockptr_offset() past the end of the user buffer.

This is a kernel-driven userspace heap overflow: the user told the
kernel their buffer size via optlen, the kernel ignored it and used a
field inside the buffer instead.  On a real system the writes go into
adjacent userspace heap and copy_to_user does not fault on mapped heap
pages.

Clamp gf_numsrc to (len - size0) / sizeof(sockaddr_storage) before the
call so the kernel never writes past what the user provided.  The
setsockopt path already has the equivalent check
(GROUP_FILTER_SIZE(gf_numsrc) > optlen at line 790).

Cc: "David S. Miller" <[email-masked]>
Cc: David Ahern <[email-masked]>
Cc: Eric Dumazet <[email-masked]>
Cc: Jakub Kicinski <[email-masked]>
Cc: Paolo Abeni <[email-masked]>
Cc: Simon Horman <[email-masked]>
Reported-by: Anthropic
Assisted-by: gkh_clanker_t1000
Cc: stable <[email-masked]>
Signed-off-by: Greg Kroah-Hartman <[email-masked]>
---
 net/ipv4/ip_sockglue.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/net/ipv4/ip_sockglue.c b/net/ipv4/ip_sockglue.c
index a55ef327ec93..c9bf5d223f21 100644
--- a/net/ipv4/ip_sockglue.c
+++ b/net/ipv4/ip_sockglue.c
@@ -1456,6 +1456,11 @@ static int ip_get_mcast_msfilter(struct sock *sk, sockptr_t optval,
 		return -EFAULT;
 
 	num = gsf.gf_numsrc;
+
+	if (num > (len - size0) / sizeof(struct sockaddr_storage))
+		num = (len - size0) / sizeof(struct sockaddr_storage);
+	gsf.gf_numsrc = num;
+
 	err = ip_mc_gsfget(sk, &gsf, optval,
 			   offsetof(struct group_filter, gf_slist_flex));
 	if (err)
@@ -1486,8 +1491,12 @@ static int compat_ip_get_mcast_msfilter(struct sock *sk, sockptr_t optval,
 	gf.gf_interface = gf32.gf_interface;
 	gf.gf_fmode = gf32.gf_fmode;
 	num = gf.gf_numsrc = gf32.gf_numsrc;
-	gf.gf_group = gf32.gf_group;
 
+	if (num > (len - size0) / sizeof(struct sockaddr_storage))
+		num = (len - size0) / sizeof(struct sockaddr_storage);
+	gf.gf_numsrc = num;
+
+	gf.gf_group = gf32.gf_group;
 	err = ip_mc_gsfget(sk, &gf, optval,
 			   offsetof(struct compat_group_filter, gf_slist_flex));
 	if (err)
-- 
2.53.0