* [PATCH net-next 0/2] net: a sockopt_t quirk for the options that write past optlen
@ 2026-09-10 9:47 Breno Leitao
2026-09-10 9:47 ` [PATCH net-next 1/2] net: add sockopt_expand_out() Breno Leitao
2026-09-10 9:47 ` [PATCH net-next 2/2] ipv4: igmp: convert ip_mc_msfget() to sockopt_t Breno Leitao
0 siblings, 2 replies; 8+ messages in thread
From: Breno Leitao @ 2026-09-10 9:47 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Kuniyuki Iwashima, Willem de Bruijn, David Ahern,
Ido Schimmel
Cc: netdev, linux-kernel, david.laight.linux, Breno Leitao, kernel-team
This series continues the migration of our protocols to sockopt_t, as
described in [1].
There are some protocols that use optlen as the header size, and the real
buffer size comes from a field inside the header. This means a bug, given
that optlen should be the full buffer size, but there are indications [2]
that we have programs that use the bad behaviour above, and we want to
avoid breaking them (or, honestly, avoid being cursed by Linus).
That said, create a quirk helper that preserves the same behaviour, even
using sockopt_t. The way to do it is simple:
1) Only do it for userspace callers (ubuf), otherwise a bug here will
corrupt the kernel instead of a simple SIGSEGV.
2) Expand optval mid-air based on the header field.
IP_MSFILTER is the first user, and the smallest one: a single caller, no
compat variant, and a reply written front to back. MCAST_MSFILTER on ipv4
and ipv6 comes next, and TCP_AO_GET_KEYS has the same shape. Do this
quirk on IP_MSFILTER to make sure the dynamic is ok, so, we can expand
it later.
None of this is meant to change what userspace sees.
Link: https://lore.kernel.org/all/20260401-getsockopt-v2-0-611df6771aff@debian.org/ [1]
Link: https://lore.kernel.org/all/20260806-mcast_fix-v1-0-bed0a5518e57@debian.org/ [2]
Signed-off-by: Breno Leitao <leitao@debian.org>
---
Breno Leitao (2):
net: add sockopt_expand_out()
ipv4: igmp: convert ip_mc_msfget() to sockopt_t
include/linux/igmp.h | 3 ++-
include/linux/net.h | 25 +++++++++++++++++++++++++
net/ipv4/igmp.c | 23 ++++++++++++++---------
net/ipv4/ip_sockglue.c | 10 +++++++++-
net/socket.c | 4 ++--
5 files changed, 52 insertions(+), 13 deletions(-)
---
base-commit: 548b86839f7fb819a4d6c83b71c73ec378d24275
change-id: 20260909-getsockopt_phase6-c7c96a21b062
Best regards,
--
Breno Leitao <leitao@debian.org>
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH net-next 1/2] net: add sockopt_expand_out()
2026-09-10 9:47 [PATCH net-next 0/2] net: a sockopt_t quirk for the options that write past optlen Breno Leitao
@ 2026-09-10 9:47 ` Breno Leitao
2026-09-11 15:56 ` Stanislav Fomichev
2026-09-10 9:47 ` [PATCH net-next 2/2] ipv4: igmp: convert ip_mc_msfget() to sockopt_t Breno Leitao
1 sibling, 1 reply; 8+ messages in thread
From: Breno Leitao @ 2026-09-10 9:47 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Kuniyuki Iwashima, Willem de Bruijn, David Ahern,
Ido Schimmel
Cc: netdev, linux-kernel, david.laight.linux, Breno Leitao, kernel-team
Add sockopt_expand_out() to grow opt->iter_out mid-air.
It is a no-op unless the proper size outruns optlen (i.e, some
not-well-behaved userspace program calling it).
In this case, only a user buffer can be longer than optlen says, so
a kernel-backed optval keeps the bounded iterator and the callback gets
-EINVAL if it asks to grow.
This whole quirk is added to:
1) Avoid breaking userspace
2) Making the quirk explict
* Instead of protocol doing implict assumping like this.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
include/linux/net.h | 25 +++++++++++++++++++++++++
net/socket.c | 4 ++--
2 files changed, 27 insertions(+), 2 deletions(-)
diff --git a/include/linux/net.h b/include/linux/net.h
index 470100ae710773..de0ed362b37794 100644
--- a/include/linux/net.h
+++ b/include/linux/net.h
@@ -70,6 +70,31 @@ static inline int sockopt_init_user(sockopt_t *opt, char __user *optval,
return 0;
}
+/*
+ * Grow optval to @size, for the options whose reply is sized by a count the
+ * caller left in optval rather than by optlen. Those write past optlen today
+ * and userspace relies on it.
+ *
+ * Call it before writing through opt->iter_out: it re-anchors the iterator at
+ * the head of optval. Only a user buffer can be longer than the optlen the
+ * caller declared, so a kernel-backed optval is refused with -EINVAL.
+ */
+static inline int sockopt_expand_out(sockopt_t *opt, size_t size)
+{
+ if (size <= iov_iter_count(&opt->iter_out))
+ return 0;
+
+ if (WARN_ON_ONCE(!iter_is_ubuf(&opt->iter_out)))
+ return -EINVAL;
+
+ iov_iter_ubuf(&opt->iter_out, ITER_DEST, opt->iter_out.ubuf, size);
+
+ return 0;
+}
+
+int sockptr_to_sockopt(sockopt_t *opt, sockptr_t optval, sockptr_t optlen,
+ struct kvec *kvec);
+
struct poll_table_struct;
struct pipe_inode_info;
struct inode;
diff --git a/net/socket.c b/net/socket.c
index c05d86e63abf7d..29a0f7f8e2cabe 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -2437,8 +2437,8 @@ INDIRECT_CALLABLE_DECLARE(bool tcp_bpf_bypass_getsockopt(int level,
* It is important to remember that both iov points to the same data, but,
* .iter_in is read-only and .iter_out is write-only by the protocol callbacks
*/
-static int sockptr_to_sockopt(sockopt_t *opt, sockptr_t optval,
- sockptr_t optlen, struct kvec *kvec)
+int sockptr_to_sockopt(sockopt_t *opt, sockptr_t optval,
+ sockptr_t optlen, struct kvec *kvec)
{
int koptlen;
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH net-next 2/2] ipv4: igmp: convert ip_mc_msfget() to sockopt_t
2026-09-10 9:47 [PATCH net-next 0/2] net: a sockopt_t quirk for the options that write past optlen Breno Leitao
2026-09-10 9:47 ` [PATCH net-next 1/2] net: add sockopt_expand_out() Breno Leitao
@ 2026-09-10 9:47 ` Breno Leitao
2026-09-11 15:56 ` Stanislav Fomichev
1 sibling, 1 reply; 8+ messages in thread
From: Breno Leitao @ 2026-09-10 9:47 UTC (permalink / raw)
To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Kuniyuki Iwashima, Willem de Bruijn, David Ahern,
Ido Schimmel
Cc: netdev, linux-kernel, david.laight.linux, Breno Leitao, kernel-team
IP_MSFILTER reads its reply through ip_mc_msfget(), reached from
do_ip_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.
This is a special case, where optlen might only point
to the header, and the real structure size is inside the header.
This is nasty, but, in order to avoid breaking userspace, we need to
preserve the same mechanism, by:
1) Only applying it for userspace address, otherwise it is too risky
2) Assume there is room to support the new size (in userspace)
The source list also moves from copy_to_sockptr_offset() to a sequential
copy_to_iter(). IP_MSFILTER_SIZE(0) and offsetof(struct ip_msfilter,
imsf_slist_flex) are both 16, so the bytes land where they did.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
include/linux/igmp.h | 3 ++-
net/ipv4/igmp.c | 23 ++++++++++++++---------
net/ipv4/ip_sockglue.c | 10 +++++++++-
3 files changed, 25 insertions(+), 11 deletions(-)
diff --git a/include/linux/igmp.h b/include/linux/igmp.h
index a0cf0398519fd7..e075611344ef3b 100644
--- a/include/linux/igmp.h
+++ b/include/linux/igmp.h
@@ -14,6 +14,7 @@
#include <linux/timer.h>
#include <linux/in.h>
#include <linux/ip.h>
+#include <linux/net.h>
#include <linux/refcount.h>
#include <linux/sockptr.h>
#include <uapi/linux/igmp.h>
@@ -273,7 +274,7 @@ extern int ip_mc_source(int add, int omode, struct sock *sk,
struct ip_mreq_source *mreqs, int ifindex);
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,
- sockptr_t optval, sockptr_t optlen);
+ sockopt_t *opt);
extern int ip_mc_gsfget(struct sock *sk, struct group_filter *gsf,
sockptr_t optval, size_t offset);
extern int ip_mc_sf_allow(const struct sock *sk, __be32 local, __be32 rmt,
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
index d56355aca79776..144fca158adcb0 100644
--- a/net/ipv4/igmp.c
+++ b/net/ipv4/igmp.c
@@ -2709,8 +2709,8 @@ int ip_mc_msfilter(struct sock *sk, struct ip_msfilter *msf, int ifindex)
err = ip_mc_leave_group(sk, &imr);
return err;
}
-int ip_mc_msfget(struct sock *sk, struct ip_msfilter *msf,
- sockptr_t optval, sockptr_t optlen)
+
+int ip_mc_msfget(struct sock *sk, struct ip_msfilter *msf, sockopt_t *opt)
{
int err, len, count, copycount, msf_size;
struct ip_mreqn imr;
@@ -2755,14 +2755,19 @@ int ip_mc_msfget(struct sock *sk, struct ip_msfilter *msf,
len = flex_array_size(psl, sl_addr, copycount);
msf->imsf_numsrc = count;
msf_size = IP_MSFILTER_SIZE(copycount);
- if (copy_to_sockptr(optlen, &msf_size, sizeof(int)) ||
- copy_to_sockptr(optval, msf, IP_MSFILTER_SIZE(0))) {
+
+ /* The source list is sized by the imsf_numsrc the caller left in
+ * optval, not by optlen, which only has to cover the fixed part.
+ */
+ err = sockopt_expand_out(opt, msf_size);
+ if (err)
+ return err;
+
+ opt->optlen = msf_size;
+ if (copy_to_iter(msf, IP_MSFILTER_SIZE(0), &opt->iter_out) !=
+ IP_MSFILTER_SIZE(0))
return -EFAULT;
- }
- if (len &&
- copy_to_sockptr_offset(optval,
- offsetof(struct ip_msfilter, imsf_slist_flex),
- psl->sl_addr, len))
+ if (len && copy_to_iter(psl->sl_addr, len, &opt->iter_out) != len)
return -EFAULT;
return 0;
done:
diff --git a/net/ipv4/ip_sockglue.c b/net/ipv4/ip_sockglue.c
index a55ef327ec932c..c58e565f2a5aa7 100644
--- a/net/ipv4/ip_sockglue.c
+++ b/net/ipv4/ip_sockglue.c
@@ -1706,6 +1706,8 @@ int do_ip_getsockopt(struct sock *sk, int level, int optname,
case IP_MSFILTER:
{
struct ip_msfilter msf;
+ struct kvec kvec;
+ sockopt_t opt;
if (len < IP_MSFILTER_SIZE(0)) {
err = -EINVAL;
@@ -1715,7 +1717,13 @@ int do_ip_getsockopt(struct sock *sk, int level, int optname,
err = -EFAULT;
goto out;
}
- err = ip_mc_msfget(sk, &msf, optval, optlen);
+ err = sockptr_to_sockopt(&opt, optval, optlen, &kvec);
+ if (err)
+ goto out;
+
+ err = ip_mc_msfget(sk, &msf, &opt);
+ if (copy_to_sockptr(optlen, &opt.optlen, sizeof(int)))
+ err = -EFAULT;
goto out;
}
case MCAST_MSFILTER:
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net-next 1/2] net: add sockopt_expand_out()
2026-09-10 9:47 ` [PATCH net-next 1/2] net: add sockopt_expand_out() Breno Leitao
@ 2026-09-11 15:56 ` Stanislav Fomichev
2026-09-11 16:09 ` Breno Leitao
0 siblings, 1 reply; 8+ messages in thread
From: Stanislav Fomichev @ 2026-09-11 15:56 UTC (permalink / raw)
To: Breno Leitao
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Kuniyuki Iwashima, Willem de Bruijn, David Ahern,
Ido Schimmel, netdev, linux-kernel, david.laight.linux,
kernel-team
On 09/10, Breno Leitao wrote:
> Add sockopt_expand_out() to grow opt->iter_out mid-air.
>
> It is a no-op unless the proper size outruns optlen (i.e, some
> not-well-behaved userspace program calling it).
>
> In this case, only a user buffer can be longer than optlen says, so
> a kernel-backed optval keeps the bounded iterator and the callback gets
> -EINVAL if it asks to grow.
>
> This whole quirk is added to:
>
> 1) Avoid breaking userspace
> 2) Making the quirk explict
> * Instead of protocol doing implict assumping like this.
>
> Signed-off-by: Breno Leitao <leitao@debian.org>
> ---
> include/linux/net.h | 25 +++++++++++++++++++++++++
> net/socket.c | 4 ++--
> 2 files changed, 27 insertions(+), 2 deletions(-)
>
> diff --git a/include/linux/net.h b/include/linux/net.h
> index 470100ae710773..de0ed362b37794 100644
> --- a/include/linux/net.h
> +++ b/include/linux/net.h
> @@ -70,6 +70,31 @@ static inline int sockopt_init_user(sockopt_t *opt, char __user *optval,
> return 0;
> }
>
> +/*
> + * Grow optval to @size, for the options whose reply is sized by a count the
> + * caller left in optval rather than by optlen. Those write past optlen today
> + * and userspace relies on it.
> + *
> + * Call it before writing through opt->iter_out: it re-anchors the iterator at
> + * the head of optval. Only a user buffer can be longer than the optlen the
> + * caller declared, so a kernel-backed optval is refused with -EINVAL.
> + */
> +static inline int sockopt_expand_out(sockopt_t *opt, size_t size)
> +{
[..]
> + if (size <= iov_iter_count(&opt->iter_out))
> + return 0;
> +
> + if (WARN_ON_ONCE(!iter_is_ubuf(&opt->iter_out)))
> + return -EINVAL;
nit: if you end up re-spinning for some reason, maybe swap these two?
I always get confused by the count vs len of iov (iov_iter_count vs
iter_iov_len). Because I think count for ubuf is len because of the
aliasing? (and then, if !iter_is_ubuf check is first, at least iov_iter_count
will 100% be ubuf specific)
Acked-by: Stanislav Fomichev <sdf@fomichev.me>
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net-next 2/2] ipv4: igmp: convert ip_mc_msfget() to sockopt_t
2026-09-10 9:47 ` [PATCH net-next 2/2] ipv4: igmp: convert ip_mc_msfget() to sockopt_t Breno Leitao
@ 2026-09-11 15:56 ` Stanislav Fomichev
0 siblings, 0 replies; 8+ messages in thread
From: Stanislav Fomichev @ 2026-09-11 15:56 UTC (permalink / raw)
To: Breno Leitao
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Kuniyuki Iwashima, Willem de Bruijn, David Ahern,
Ido Schimmel, netdev, linux-kernel, david.laight.linux,
kernel-team
On 09/10, Breno Leitao wrote:
> IP_MSFILTER reads its reply through ip_mc_msfget(), reached from
> do_ip_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.
>
> This is a special case, where optlen might only point
> to the header, and the real structure size is inside the header.
>
> This is nasty, but, in order to avoid breaking userspace, we need to
> preserve the same mechanism, by:
>
> 1) Only applying it for userspace address, otherwise it is too risky
> 2) Assume there is room to support the new size (in userspace)
>
> The source list also moves from copy_to_sockptr_offset() to a sequential
> copy_to_iter(). IP_MSFILTER_SIZE(0) and offsetof(struct ip_msfilter,
> imsf_slist_flex) are both 16, so the bytes land where they did.
>
> Signed-off-by: Breno Leitao <leitao@debian.org>
Acked-by: Stanislav Fomichev <sdf@fomichev.me>
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net-next 1/2] net: add sockopt_expand_out()
2026-09-11 15:56 ` Stanislav Fomichev
@ 2026-09-11 16:09 ` Breno Leitao
2026-09-11 18:10 ` David Laight
2026-09-11 21:17 ` Stanislav Fomichev
0 siblings, 2 replies; 8+ messages in thread
From: Breno Leitao @ 2026-09-11 16:09 UTC (permalink / raw)
To: Stanislav Fomichev
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Kuniyuki Iwashima, Willem de Bruijn, David Ahern,
Ido Schimmel, netdev, linux-kernel, david.laight.linux,
kernel-team
On Fri, Sep 11, 2026 at 08:56:23AM -0700, Stanislav Fomichev wrote:
> On 09/10, Breno Leitao wrote:
> >
> > + if (size <= iov_iter_count(&opt->iter_out))
> > + return 0;
> > +
> > + if (WARN_ON_ONCE(!iter_is_ubuf(&opt->iter_out)))
> > + return -EINVAL;
>
> nit: if you end up re-spinning for some reason, maybe swap these two?
I am not sure we want to swap these two. This is the reason:
1) The first check (size <= iov_iter_count(&opt->iter_out)), check if we
need the "hack" or not.
a) If we don't need the tack, then we are fine and this could be called
from user or kernel.
b) for the hack (which happens in the next line -- iov_iter_ubuf()), it
needs to be ubuf.
So, this force kernel callers to use the right optlen, avoiding hitting
the same issue as userspace.
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net-next 1/2] net: add sockopt_expand_out()
2026-09-11 16:09 ` Breno Leitao
@ 2026-09-11 18:10 ` David Laight
2026-09-11 21:17 ` Stanislav Fomichev
1 sibling, 0 replies; 8+ messages in thread
From: David Laight @ 2026-09-11 18:10 UTC (permalink / raw)
To: Breno Leitao
Cc: Stanislav Fomichev, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Kuniyuki Iwashima,
Willem de Bruijn, David Ahern, Ido Schimmel, netdev,
linux-kernel, kernel-team
On Fri, 11 Sep 2026 09:09:22 -0700
Breno Leitao <leitao@debian.org> wrote:
> On Fri, Sep 11, 2026 at 08:56:23AM -0700, Stanislav Fomichev wrote:
> > On 09/10, Breno Leitao wrote:
> > >
> > > + if (size <= iov_iter_count(&opt->iter_out))
> > > + return 0;
> > > +
> > > + if (WARN_ON_ONCE(!iter_is_ubuf(&opt->iter_out)))
> > > + return -EINVAL;
> >
> > nit: if you end up re-spinning for some reason, maybe swap these two?
>
> I am not sure we want to swap these two. This is the reason:
>
> 1) The first check (size <= iov_iter_count(&opt->iter_out)), check if we
> need the "hack" or not.
>
> a) If we don't need the tack, then we are fine and this could be called
> from user or kernel.
>
> b) for the hack (which happens in the next line -- iov_iter_ubuf()), it
> needs to be ubuf.
>
> So, this force kernel callers to use the right optlen, avoiding hitting
> the same issue as userspace.
>
kernel users might need to specify both lengths.
That is the value of optlen and the length of the buffer.
For 99.9% of the calls they will be the same.
David
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net-next 1/2] net: add sockopt_expand_out()
2026-09-11 16:09 ` Breno Leitao
2026-09-11 18:10 ` David Laight
@ 2026-09-11 21:17 ` Stanislav Fomichev
1 sibling, 0 replies; 8+ messages in thread
From: Stanislav Fomichev @ 2026-09-11 21:17 UTC (permalink / raw)
To: Breno Leitao
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Kuniyuki Iwashima, Willem de Bruijn, David Ahern,
Ido Schimmel, netdev, linux-kernel, david.laight.linux,
kernel-team
On 09/11, Breno Leitao wrote:
> On Fri, Sep 11, 2026 at 08:56:23AM -0700, Stanislav Fomichev wrote:
> > On 09/10, Breno Leitao wrote:
> > >
> > > + if (size <= iov_iter_count(&opt->iter_out))
> > > + return 0;
> > > +
> > > + if (WARN_ON_ONCE(!iter_is_ubuf(&opt->iter_out)))
> > > + return -EINVAL;
> >
> > nit: if you end up re-spinning for some reason, maybe swap these two?
>
> I am not sure we want to swap these two. This is the reason:
>
> 1) The first check (size <= iov_iter_count(&opt->iter_out)), check if we
> need the "hack" or not.
>
> a) If we don't need the tack, then we are fine and this could be called
> from user or kernel.
>
> b) for the hack (which happens in the next line -- iov_iter_ubuf()), it
> needs to be ubuf.
>
> So, this force kernel callers to use the right optlen, avoiding hitting
> the same issue as userspace.
Hmm, that feels a bit too magical :-/ Who are the kernel callers? Do we
have any? Is it reachable via bpf or something else?
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-09-11 21:17 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-10 9:47 [PATCH net-next 0/2] net: a sockopt_t quirk for the options that write past optlen Breno Leitao
2026-09-10 9:47 ` [PATCH net-next 1/2] net: add sockopt_expand_out() Breno Leitao
2026-09-11 15:56 ` Stanislav Fomichev
2026-09-11 16:09 ` Breno Leitao
2026-09-11 18:10 ` David Laight
2026-09-11 21:17 ` Stanislav Fomichev
2026-09-10 9:47 ` [PATCH net-next 2/2] ipv4: igmp: convert ip_mc_msfget() to sockopt_t Breno Leitao
2026-09-11 15:56 ` Stanislav Fomichev
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®