* [PATCH net-next 1/6] ipv6: reject a negative optlen in do_ipv6_getsockopt()
2026-09-25 15:55 [PATCH net-next 0/6] ipv4,ipv6: convert the getsockopt switches to sockopt_t Breno Leitao
@ 2026-09-25 15:55 ` Breno Leitao
2026-09-25 19:02 ` Stanislav Fomichev
2026-09-25 15:55 ` [PATCH net-next 2/6] ipv6: mcast: convert ip6_mc_msfget() to sockopt_t Breno Leitao
` (4 subsequent siblings)
5 siblings, 1 reply; 8+ messages in thread
From: Breno Leitao @ 2026-09-25 15:55 UTC (permalink / raw)
To: David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai,
John Fastabend, Stanislav Fomichev, Shuah Khan
Cc: netdev, linux-kernel, bpf, linux-kselftest, david.laight.linux,
Breno Leitao, kernel-team
IPv4's do_ip_getsockopt() rejects a negative optlen right after reading
it. do_ipv6_getsockopt() never has, and nothing downstream treats it as
an error either: len is an int, but every consumer compares it unsigned,
so -1 behaves as a huge value and each site clamps to its own reply
size.
len = min_t(unsigned int, sizeof(int), len);
So getsockopt(fd, SOL_IPV6, IPV6_TCLASS, buf, &len) with len set to -1
answers 4 bytes and reports 4, rather than failing.
This is a bug ready to bite us in the near future, let's get this fixed.
I've found this because testing the rest of the patch was returning
inconsistency when optlen = -1.
Later patches make the check unreachable, since sockopt_init_user() and
sockptr_to_sockopt() both reject a negative length before the switch
runs. Keep it as a guard; the fix stands on its own here.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
net/ipv6/ipv6_sockglue.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c
index 4b3536571c9804..5c6a0819a2aaff 100644
--- a/net/ipv6/ipv6_sockglue.c
+++ b/net/ipv6/ipv6_sockglue.c
@@ -1002,6 +1002,8 @@ int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
if (copy_from_sockptr(&len, optlen, sizeof(int)))
return -EFAULT;
+ if (len < 0)
+ return -EINVAL;
switch (optname) {
case MCAST_MSFILTER:
if (in_compat_syscall())
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH net-next 1/6] ipv6: reject a negative optlen in do_ipv6_getsockopt()
2026-09-25 15:55 ` [PATCH net-next 1/6] ipv6: reject a negative optlen in do_ipv6_getsockopt() Breno Leitao
@ 2026-09-25 19:02 ` Stanislav Fomichev
0 siblings, 0 replies; 8+ messages in thread
From: Stanislav Fomichev @ 2026-09-25 19:02 UTC (permalink / raw)
To: Breno Leitao
Cc: David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai,
John Fastabend, Stanislav Fomichev, Shuah Khan, netdev,
linux-kernel, bpf, linux-kselftest, david.laight.linux,
kernel-team
On 09/25, Breno Leitao wrote:
> IPv4's do_ip_getsockopt() rejects a negative optlen right after reading
> it. do_ipv6_getsockopt() never has, and nothing downstream treats it as
> an error either: len is an int, but every consumer compares it unsigned,
> so -1 behaves as a huge value and each site clamps to its own reply
> size.
>
> len = min_t(unsigned int, sizeof(int), len);
>
> So getsockopt(fd, SOL_IPV6, IPV6_TCLASS, buf, &len) with len set to -1
> answers 4 bytes and reports 4, rather than failing.
>
> This is a bug ready to bite us in the near future, let's get this fixed.
>
> I've found this because testing the rest of the patch was returning
> inconsistency when optlen = -1.
If I can do getsockopt with len=-1 today and get 4 bytes back, isn't
that a uapi and we are gonna break someone?
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH net-next 2/6] ipv6: mcast: convert ip6_mc_msfget() to sockopt_t
2026-09-25 15:55 [PATCH net-next 0/6] ipv4,ipv6: convert the getsockopt switches to sockopt_t Breno Leitao
2026-09-25 15:55 ` [PATCH net-next 1/6] ipv6: reject a negative optlen in do_ipv6_getsockopt() Breno Leitao
@ 2026-09-25 15:55 ` Breno Leitao
2026-09-25 15:55 ` [PATCH net-next 3/6] ipv4: igmp: convert ip_mc_gsfget() " Breno Leitao
` (3 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Breno Leitao @ 2026-09-25 15:55 UTC (permalink / raw)
To: David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai,
John Fastabend, Stanislav Fomichev, Shuah Khan
Cc: netdev, linux-kernel, bpf, linux-kselftest, david.laight.linux,
Breno Leitao, kernel-team
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 <leitao@debian.org>
---
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
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH net-next 3/6] ipv4: igmp: convert ip_mc_gsfget() to sockopt_t
2026-09-25 15:55 [PATCH net-next 0/6] ipv4,ipv6: convert the getsockopt switches to sockopt_t Breno Leitao
2026-09-25 15:55 ` [PATCH net-next 1/6] ipv6: reject a negative optlen in do_ipv6_getsockopt() Breno Leitao
2026-09-25 15:55 ` [PATCH net-next 2/6] ipv6: mcast: convert ip6_mc_msfget() to sockopt_t Breno Leitao
@ 2026-09-25 15:55 ` Breno Leitao
2026-09-25 15:55 ` [PATCH net-next 4/6] ipv4: convert do_ip_getsockopt() " Breno Leitao
` (2 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Breno Leitao @ 2026-09-25 15:55 UTC (permalink / raw)
To: David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai,
John Fastabend, Stanislav Fomichev, Shuah Khan
Cc: netdev, linux-kernel, bpf, linux-kselftest, david.laight.linux,
Breno Leitao, kernel-team
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@debian.org>
---
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
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH net-next 4/6] ipv4: convert do_ip_getsockopt() to sockopt_t
2026-09-25 15:55 [PATCH net-next 0/6] ipv4,ipv6: convert the getsockopt switches to sockopt_t Breno Leitao
` (2 preceding siblings ...)
2026-09-25 15:55 ` [PATCH net-next 3/6] ipv4: igmp: convert ip_mc_gsfget() " Breno Leitao
@ 2026-09-25 15:55 ` Breno Leitao
2026-09-25 15:55 ` [PATCH net-next 5/6] ipv6: convert do_ipv6_getsockopt() " Breno Leitao
2026-09-25 15:55 ` [PATCH net-next 6/6] selftests: net: getsockopt_iter: cover ip and ipv6 Breno Leitao
5 siblings, 0 replies; 8+ messages in thread
From: Breno Leitao @ 2026-09-25 15:55 UTC (permalink / raw)
To: David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai,
John Fastabend, Stanislav Fomichev, Shuah Khan
Cc: netdev, linux-kernel, bpf, linux-kselftest, david.laight.linux,
Breno Leitao, kernel-team
Convert the SOL_IP switch and ip_mroute_getsockopt(), its last leaf on
a sockptr_t pair. The IP_MSFILTER and MCAST_MSFILTER bridges go away.
ip_getsockopt() builds the sockopt_t with sockopt_init_user() and
writes optlen back unconditionally, because IP_OPTIONS and others
report a length then fail; ip_mc_msfget() therefore stores its length
last. IP_PKTOPTIONS takes its buffer off iter_out. The argument is
sopt, since IP_OPTIONS already has an ip_options local named opt.
Validating optlen now precedes the level check and the MRT_* dispatch,
so a negative optlen wins over -EOPNOTSUPP there.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
include/linux/mroute.h | 4 +--
include/net/ip.h | 3 +-
net/core/filter.c | 18 ++++++++---
net/ipv4/igmp.c | 3 +-
net/ipv4/ip_sockglue.c | 87 +++++++++++++++++++-------------------------------
net/ipv4/ipmr.c | 11 +++----
6 files changed, 56 insertions(+), 70 deletions(-)
diff --git a/include/linux/mroute.h b/include/linux/mroute.h
index 4c5003afee6c51..c1c21e61c81c37 100644
--- a/include/linux/mroute.h
+++ b/include/linux/mroute.h
@@ -17,7 +17,7 @@ static inline int ip_mroute_opt(int opt)
}
int ip_mroute_setsockopt(struct sock *, int, sockptr_t, unsigned int);
-int ip_mroute_getsockopt(struct sock *, int, sockptr_t, sockptr_t);
+int ip_mroute_getsockopt(struct sock *sk, int optname, sockopt_t *opt);
int ipmr_ioctl(struct sock *sk, int cmd, void *arg);
int ipmr_compat_ioctl(struct sock *sk, unsigned int cmd, void __user *arg);
int ip_mr_init(void);
@@ -31,7 +31,7 @@ static inline int ip_mroute_setsockopt(struct sock *sock, int optname,
}
static inline int ip_mroute_getsockopt(struct sock *sk, int optname,
- sockptr_t optval, sockptr_t optlen)
+ sockopt_t *opt)
{
return -ENOPROTOOPT;
}
diff --git a/include/net/ip.h b/include/net/ip.h
index 6f602df72ee621..81aef4b98430bf 100644
--- a/include/net/ip.h
+++ b/include/net/ip.h
@@ -823,8 +823,7 @@ int do_ip_setsockopt(struct sock *sk, int level, int optname, sockptr_t optval,
unsigned int optlen);
int ip_setsockopt(struct sock *sk, int level, int optname, sockptr_t optval,
unsigned int optlen);
-int do_ip_getsockopt(struct sock *sk, int level, int optname,
- sockptr_t optval, sockptr_t optlen);
+int do_ip_getsockopt(struct sock *sk, int level, int optname, sockopt_t *sopt);
int ip_getsockopt(struct sock *sk, int level, int optname, char __user *optval,
int __user *optlen);
int ip_ra_control(struct sock *sk, unsigned char on,
diff --git a/net/core/filter.c b/net/core/filter.c
index 70dc621672f2e9..9a706ec1f7f122 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -5737,10 +5737,20 @@ static int sol_ip_sockopt(struct sock *sk, int optname,
return -EINVAL;
}
- if (getopt)
- return do_ip_getsockopt(sk, SOL_IP, optname,
- KERNEL_SOCKPTR(optval),
- KERNEL_SOCKPTR(optlen));
+ if (getopt) {
+ struct kvec kvec;
+ sockopt_t opt;
+ int err;
+
+ err = sockptr_to_sockopt(&opt, KERNEL_SOCKPTR(optval),
+ KERNEL_SOCKPTR(optlen), &kvec);
+ if (err)
+ return err;
+
+ err = do_ip_getsockopt(sk, SOL_IP, optname, &opt);
+ *optlen = opt.optlen;
+ return err;
+ }
return do_ip_setsockopt(sk, SOL_IP, optname,
KERNEL_SOCKPTR(optval), *optlen);
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
index d573c5bf8f038b..bf00810cdd7a13 100644
--- a/net/ipv4/igmp.c
+++ b/net/ipv4/igmp.c
@@ -2763,12 +2763,13 @@ int ip_mc_msfget(struct sock *sk, struct ip_msfilter *msf, sockopt_t *opt)
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_iter(psl->sl_addr, len, &opt->iter_out) != len)
return -EFAULT;
+
+ opt->optlen = msf_size;
return 0;
done:
return err;
diff --git a/net/ipv4/ip_sockglue.c b/net/ipv4/ip_sockglue.c
index 1f452b6ea86e9d..2e78b11d294a9f 100644
--- a/net/ipv4/ip_sockglue.c
+++ b/net/ipv4/ip_sockglue.c
@@ -1509,8 +1509,7 @@ static int compat_ip_get_mcast_msfilter(struct sock *sk, sockopt_t *opt)
return 0;
}
-int do_ip_getsockopt(struct sock *sk, int level, int optname,
- sockptr_t optval, sockptr_t optlen)
+int do_ip_getsockopt(struct sock *sk, int level, int optname, sockopt_t *sopt)
{
struct inet_sock *inet = inet_sk(sk);
bool needs_rtnl = getsockopt_needs_rtnl(optname);
@@ -1521,10 +1520,9 @@ int do_ip_getsockopt(struct sock *sk, int level, int optname,
return -EOPNOTSUPP;
if (ip_mroute_opt(optname))
- return ip_mroute_getsockopt(sk, optname, optval, optlen);
+ return ip_mroute_getsockopt(sk, optname, sopt);
- if (copy_from_sockptr(&len, optlen, sizeof(int)))
- return -EFAULT;
+ len = sopt->optlen;
if (len < 0)
return -EINVAL;
@@ -1620,16 +1618,15 @@ int do_ip_getsockopt(struct sock *sk, int level, int optname,
rcu_read_unlock();
if (opt->optlen == 0) {
- len = 0;
- return copy_to_sockptr(optlen, &len, sizeof(int));
+ sopt->optlen = 0;
+ return 0;
}
ip_options_undo(opt);
len = min_t(unsigned int, len, opt->optlen);
- if (copy_to_sockptr(optlen, &len, sizeof(int)))
- return -EFAULT;
- if (copy_to_sockptr(optval, opt->__data, len))
+ sopt->optlen = len;
+ if (copy_to_iter(opt->__data, len, &sopt->iter_out) != len)
return -EFAULT;
return 0;
}
@@ -1653,12 +1650,12 @@ int do_ip_getsockopt(struct sock *sk, int level, int optname,
if (sk->sk_type != SOCK_STREAM)
return -ENOPROTOOPT;
- if (optval.is_kernel) {
+ if (iov_iter_is_kvec(&sopt->iter_out)) {
msg.msg_control_is_user = false;
- msg.msg_control = optval.kernel;
+ msg.msg_control = sopt->iter_out.kvec->iov_base;
} else {
msg.msg_control_is_user = true;
- msg.msg_control_user = optval.user;
+ msg.msg_control_user = sopt->iter_out.ubuf;
}
msg.msg_controllen = len;
msg.msg_flags = in_compat_syscall() ? MSG_CMSG_COMPAT : 0;
@@ -1680,8 +1677,8 @@ int do_ip_getsockopt(struct sock *sk, int level, int optname,
int tos = READ_ONCE(inet->rcv_tos);
put_cmsg(&msg, SOL_IP, IP_TOS, sizeof(tos), &tos);
}
- len -= msg.msg_controllen;
- return copy_to_sockptr(optlen, &len, sizeof(int));
+ sopt->optlen = len - msg.msg_controllen;
+ return 0;
}
case IP_UNICAST_IF:
val = (__force int)htonl((__u32) READ_ONCE(inet->uc_index));
@@ -1692,9 +1689,8 @@ int do_ip_getsockopt(struct sock *sk, int level, int optname,
len = min_t(unsigned int, len, sizeof(struct in_addr));
addr.s_addr = READ_ONCE(inet->mc_addr);
- if (copy_to_sockptr(optlen, &len, sizeof(int)))
- return -EFAULT;
- if (copy_to_sockptr(optval, &addr, len))
+ sopt->optlen = len;
+ if (copy_to_iter(&addr, len, &sopt->iter_out) != len)
return -EFAULT;
return 0;
}
@@ -1711,43 +1707,25 @@ 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;
goto out;
}
- if (copy_from_sockptr(&msf, optval, IP_MSFILTER_SIZE(0))) {
+ if (copy_from_iter(&msf, IP_MSFILTER_SIZE(0), &sopt->iter_in) !=
+ IP_MSFILTER_SIZE(0)) {
err = -EFAULT;
goto out;
}
- err = sockptr_to_sockopt(&opt, optval, optlen, &kvec);
- if (err)
- goto out;
-
- err = ip_mc_msfget(sk, &msf, &opt);
- if (!err && copy_to_sockptr(optlen, &opt.optlen, sizeof(int)))
- err = -EFAULT;
+ err = ip_mc_msfget(sk, &msf, sopt);
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, &opt);
+ err = compat_ip_get_mcast_msfilter(sk, sopt);
else
- err = ip_get_mcast_msfilter(sk, &opt);
- if (!err && copy_to_sockptr(optlen, &opt.optlen, sizeof(int)))
- err = -EFAULT;
+ err = ip_get_mcast_msfilter(sk, sopt);
goto out;
- }
case IP_PROTOCOL:
val = inet_sk(sk)->inet_num;
break;
@@ -1759,16 +1737,14 @@ int do_ip_getsockopt(struct sock *sk, int level, int optname,
copyval:
if (len < sizeof(int) && len > 0 && val >= 0 && val <= 255) {
unsigned char ucval = (unsigned char)val;
- len = 1;
- if (copy_to_sockptr(optlen, &len, sizeof(int)))
- return -EFAULT;
- if (copy_to_sockptr(optval, &ucval, 1))
+
+ sopt->optlen = 1;
+ if (copy_to_iter(&ucval, 1, &sopt->iter_out) != 1)
return -EFAULT;
} else {
len = min_t(unsigned int, sizeof(int), len);
- if (copy_to_sockptr(optlen, &len, sizeof(int)))
- return -EFAULT;
- if (copy_to_sockptr(optval, &val, len))
+ sopt->optlen = len;
+ if (copy_to_iter(&val, len, &sopt->iter_out) != len)
return -EFAULT;
}
return 0;
@@ -1783,19 +1759,22 @@ int do_ip_getsockopt(struct sock *sk, int level, int optname,
int ip_getsockopt(struct sock *sk, int level,
int optname, char __user *optval, int __user *optlen)
{
+ sockopt_t opt;
int err;
- err = do_ip_getsockopt(sk, level, optname,
- USER_SOCKPTR(optval), USER_SOCKPTR(optlen));
+ err = sockopt_init_user(&opt, optval, optlen);
+ if (err)
+ return err;
+
+ err = do_ip_getsockopt(sk, level, optname, &opt);
+ if (put_user(opt.optlen, optlen))
+ return -EFAULT;
#ifdef CONFIG_NETFILTER
/* we need to exclude all possible ENOPROTOOPTs except default case */
if (err == -ENOPROTOOPT && optname != IP_PKTOPTIONS &&
!ip_mroute_opt(optname)) {
- int len;
-
- if (get_user(len, optlen))
- return -EFAULT;
+ int len = opt.optlen;
err = nf_getsockopt(sk, PF_INET, optname, optval, &len);
if (err >= 0)
diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c
index e4c51ca473019c..f249569b4bf8b9 100644
--- a/net/ipv4/ipmr.c
+++ b/net/ipv4/ipmr.c
@@ -1638,8 +1638,7 @@ int ipmr_sk_ioctl(struct sock *sk, unsigned int cmd, void __user *arg)
}
/* Getsock opt support for the multicast routing system. */
-int ip_mroute_getsockopt(struct sock *sk, int optname, sockptr_t optval,
- sockptr_t optlen)
+int ip_mroute_getsockopt(struct sock *sk, int optname, sockopt_t *opt)
{
int olr;
int val;
@@ -1670,16 +1669,14 @@ int ip_mroute_getsockopt(struct sock *sk, int optname, sockptr_t optval,
return -ENOPROTOOPT;
}
- if (copy_from_sockptr(&olr, optlen, sizeof(int)))
- return -EFAULT;
+ olr = opt->optlen;
if (olr < 0)
return -EINVAL;
olr = min_t(unsigned int, olr, sizeof(int));
- if (copy_to_sockptr(optlen, &olr, sizeof(int)))
- return -EFAULT;
- if (copy_to_sockptr(optval, &val, olr))
+ opt->optlen = olr;
+ if (copy_to_iter(&val, olr, &opt->iter_out) != olr)
return -EFAULT;
return 0;
}
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH net-next 5/6] ipv6: convert do_ipv6_getsockopt() to sockopt_t
2026-09-25 15:55 [PATCH net-next 0/6] ipv4,ipv6: convert the getsockopt switches to sockopt_t Breno Leitao
` (3 preceding siblings ...)
2026-09-25 15:55 ` [PATCH net-next 4/6] ipv4: convert do_ip_getsockopt() " Breno Leitao
@ 2026-09-25 15:55 ` Breno Leitao
2026-09-25 15:55 ` [PATCH net-next 6/6] selftests: net: getsockopt_iter: cover ip and ipv6 Breno Leitao
5 siblings, 0 replies; 8+ messages in thread
From: Breno Leitao @ 2026-09-25 15:55 UTC (permalink / raw)
To: David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai,
John Fastabend, Stanislav Fomichev, Shuah Khan
Cc: netdev, linux-kernel, bpf, linux-kselftest, david.laight.linux,
Breno Leitao, kernel-team
Convert the SOL_IPV6 switch and its two remaining sockptr_t leaves,
ip6_mroute_getsockopt() and ipv6_getsockopt_sticky(). The
MCAST_MSFILTER bridge goes away.
ipv6_getsockopt() builds the sockopt_t with sockopt_init_user() after
its own level checks, and writes optlen back unconditionally.
IPV6_2292PKTOPTIONS takes its buffer off iter_out; IPV6_FLOWLABEL_MGR
reads through iter_in. The argument is sopt, since the sticky options
already have an ipv6_txoptions local named opt.
MRT6_* is still dispatched before optlen is validated, so a negative
optlen there wins over -EOPNOTSUPP.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
include/linux/mroute6.h | 5 ++--
include/net/ipv6.h | 2 +-
net/core/filter.c | 18 ++++++++---
net/ipv6/ip6mr.c | 13 +++-----
net/ipv6/ipv6_sockglue.c | 78 ++++++++++++++++++++----------------------------
5 files changed, 53 insertions(+), 63 deletions(-)
diff --git a/include/linux/mroute6.h b/include/linux/mroute6.h
index fddafdc168f733..ab4d206fb32065 100644
--- a/include/linux/mroute6.h
+++ b/include/linux/mroute6.h
@@ -27,7 +27,7 @@ struct sock;
#ifdef CONFIG_IPV6_MROUTE
extern int ip6_mroute_setsockopt(struct sock *, int, sockptr_t, unsigned int);
-extern int ip6_mroute_getsockopt(struct sock *, int, sockptr_t, sockptr_t);
+int ip6_mroute_getsockopt(struct sock *sk, int optname, sockopt_t *sopt);
extern int ip6_mr_input(struct sk_buff *skb);
extern int ip6mr_compat_ioctl(struct sock *sk, unsigned int cmd, void __user *arg);
extern int ip6_mr_init(void);
@@ -42,8 +42,7 @@ static inline int ip6_mroute_setsockopt(struct sock *sock, int optname,
}
static inline
-int ip6_mroute_getsockopt(struct sock *sock,
- int optname, sockptr_t optval, sockptr_t optlen)
+int ip6_mroute_getsockopt(struct sock *sock, int optname, sockopt_t *sopt)
{
return -ENOPROTOOPT;
}
diff --git a/include/net/ipv6.h b/include/net/ipv6.h
index 9bb68d75890364..a1e1de7da8c70d 100644
--- a/include/net/ipv6.h
+++ b/include/net/ipv6.h
@@ -1142,7 +1142,7 @@ int do_ipv6_setsockopt(struct sock *sk, int level, int optname, sockptr_t optval
int ipv6_setsockopt(struct sock *sk, int level, int optname, sockptr_t optval,
unsigned int optlen);
int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
- sockptr_t optval, sockptr_t optlen);
+ sockopt_t *sopt);
int ipv6_getsockopt(struct sock *sk, int level, int optname,
char __user *optval, int __user *optlen);
diff --git a/net/core/filter.c b/net/core/filter.c
index 9a706ec1f7f122..e0b79c78c9a9c8 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -5774,10 +5774,20 @@ static int sol_ipv6_sockopt(struct sock *sk, int optname,
return -EINVAL;
}
- if (getopt)
- return do_ipv6_getsockopt(sk, SOL_IPV6, optname,
- KERNEL_SOCKPTR(optval),
- KERNEL_SOCKPTR(optlen));
+ if (getopt) {
+ struct kvec kvec;
+ sockopt_t opt;
+ int err;
+
+ err = sockptr_to_sockopt(&opt, KERNEL_SOCKPTR(optval),
+ KERNEL_SOCKPTR(optlen), &kvec);
+ if (err)
+ return err;
+
+ err = do_ipv6_getsockopt(sk, SOL_IPV6, optname, &opt);
+ *optlen = opt.optlen;
+ return err;
+ }
return do_ipv6_setsockopt(sk, SOL_IPV6, optname,
KERNEL_SOCKPTR(optval), *optlen);
diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c
index 36f117ad367081..9513258b95fb88 100644
--- a/net/ipv6/ip6mr.c
+++ b/net/ipv6/ip6mr.c
@@ -1893,8 +1893,7 @@ int ip6_mroute_setsockopt(struct sock *sk, int optname, sockptr_t optval,
* Getsock opt support for the multicast routing system.
*/
-int ip6_mroute_getsockopt(struct sock *sk, int optname, sockptr_t optval,
- sockptr_t optlen)
+int ip6_mroute_getsockopt(struct sock *sk, int optname, sockopt_t *sopt)
{
int olr;
int val;
@@ -1925,16 +1924,12 @@ int ip6_mroute_getsockopt(struct sock *sk, int optname, sockptr_t optval,
return -ENOPROTOOPT;
}
- if (copy_from_sockptr(&olr, optlen, sizeof(int)))
- return -EFAULT;
-
- olr = min_t(int, olr, sizeof(int));
+ olr = min_t(int, sopt->optlen, sizeof(int));
if (olr < 0)
return -EINVAL;
- if (copy_to_sockptr(optlen, &olr, sizeof(int)))
- return -EFAULT;
- if (copy_to_sockptr(optval, &val, olr))
+ sopt->optlen = olr;
+ if (copy_to_iter(&val, olr, &sopt->iter_out) != olr)
return -EFAULT;
return 0;
}
diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c
index 1bdb3e001e4fe6..3a43e26234eea3 100644
--- a/net/ipv6/ipv6_sockglue.c
+++ b/net/ipv6/ipv6_sockglue.c
@@ -889,7 +889,7 @@ int ipv6_setsockopt(struct sock *sk, int level, int optname, sockptr_t optval,
EXPORT_SYMBOL(ipv6_setsockopt);
static int ipv6_getsockopt_sticky(struct sock *sk, struct ipv6_txoptions *opt,
- int optname, sockptr_t optval, int len)
+ int optname, sockopt_t *sopt, int len)
{
struct ipv6_opt_hdr *hdr;
@@ -917,7 +917,7 @@ static int ipv6_getsockopt_sticky(struct sock *sk, struct ipv6_txoptions *opt,
return 0;
len = min_t(unsigned int, len, ipv6_optlen(hdr));
- if (copy_to_sockptr(optval, hdr, len))
+ if (copy_to_iter(hdr, len, &sopt->iter_out) != len)
return -EFAULT;
return len;
}
@@ -997,39 +997,23 @@ static int compat_ipv6_get_msfilter(struct sock *sk, sockopt_t *opt)
return 0;
}
-int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
- sockptr_t optval, sockptr_t optlen)
+int do_ipv6_getsockopt(struct sock *sk, int level, int optname, sockopt_t *sopt)
{
struct ipv6_pinfo *np = inet6_sk(sk);
int len;
int val;
if (ip6_mroute_opt(optname))
- return ip6_mroute_getsockopt(sk, optname, optval, optlen);
+ return ip6_mroute_getsockopt(sk, optname, sopt);
- if (copy_from_sockptr(&len, optlen, sizeof(int)))
- return -EFAULT;
+ len = sopt->optlen;
if (len < 0)
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())
- 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;
- }
+ return compat_ipv6_get_msfilter(sk, sopt);
+ return ipv6_get_msfilter(sk, sopt);
case IPV6_2292PKTOPTIONS:
{
struct msghdr msg;
@@ -1038,12 +1022,12 @@ int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
if (sk->sk_type != SOCK_STREAM)
return -ENOPROTOOPT;
- if (optval.is_kernel) {
+ if (iov_iter_is_kvec(&sopt->iter_out)) {
msg.msg_control_is_user = false;
- msg.msg_control = optval.kernel;
+ msg.msg_control = sopt->iter_out.kvec->iov_base;
} else {
msg.msg_control_is_user = true;
- msg.msg_control_user = optval.user;
+ msg.msg_control_user = sopt->iter_out.ubuf;
}
msg.msg_controllen = len;
msg.msg_flags = 0;
@@ -1094,8 +1078,8 @@ int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
put_cmsg(&msg, SOL_IPV6, IPV6_FLOWINFO, sizeof(flowinfo), &flowinfo);
}
}
- len -= msg.msg_controllen;
- return copy_to_sockptr(optlen, &len, sizeof(int));
+ sopt->optlen = len - msg.msg_controllen;
+ return 0;
}
case IPV6_MTU:
{
@@ -1150,12 +1134,13 @@ int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
sockopt_lock_sock(sk);
opt = rcu_dereference_protected(np->opt,
lockdep_sock_is_held(sk));
- len = ipv6_getsockopt_sticky(sk, opt, optname, optval, len);
+ len = ipv6_getsockopt_sticky(sk, opt, optname, sopt, len);
sockopt_release_sock(sk);
/* check if ipv6_getsockopt_sticky() returns err code */
if (len < 0)
return len;
- return copy_to_sockptr(optlen, &len, sizeof(int));
+ sopt->optlen = len;
+ return 0;
}
case IPV6_RECVHOPOPTS:
@@ -1209,9 +1194,8 @@ int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
if (!mtuinfo.ip6m_mtu)
return -ENOTCONN;
- if (copy_to_sockptr(optlen, &len, sizeof(int)))
- return -EFAULT;
- if (copy_to_sockptr(optval, &mtuinfo, len))
+ sopt->optlen = len;
+ if (copy_to_iter(&mtuinfo, len, &sopt->iter_out) != len)
return -EFAULT;
return 0;
@@ -1288,7 +1272,8 @@ int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
if (len < sizeof(freq))
return -EINVAL;
- if (copy_from_sockptr(&freq, optval, sizeof(freq)))
+ if (copy_from_iter(&freq, sizeof(freq), &sopt->iter_in) !=
+ sizeof(freq))
return -EFAULT;
if (freq.flr_action != IPV6_FL_A_GET)
@@ -1303,9 +1288,8 @@ int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
if (val < 0)
return val;
- if (copy_to_sockptr(optlen, &len, sizeof(int)))
- return -EFAULT;
- if (copy_to_sockptr(optval, &freq, len))
+ sopt->optlen = len;
+ if (copy_to_iter(&freq, len, &sopt->iter_out) != len)
return -EFAULT;
return 0;
@@ -1363,9 +1347,8 @@ int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
return -ENOPROTOOPT;
}
len = min_t(unsigned int, sizeof(int), len);
- if (copy_to_sockptr(optlen, &len, sizeof(int)))
- return -EFAULT;
- if (copy_to_sockptr(optval, &val, len))
+ sopt->optlen = len;
+ if (copy_to_iter(&val, len, &sopt->iter_out) != len)
return -EFAULT;
return 0;
}
@@ -1373,6 +1356,7 @@ int do_ipv6_getsockopt(struct sock *sk, int level, int optname,
int ipv6_getsockopt(struct sock *sk, int level, int optname,
char __user *optval, int __user *optlen)
{
+ sockopt_t sopt;
int err;
if (level == SOL_IP && sk->sk_type != SOCK_RAW)
@@ -1381,15 +1365,17 @@ int ipv6_getsockopt(struct sock *sk, int level, int optname,
if (level != SOL_IPV6)
return -ENOPROTOOPT;
- err = do_ipv6_getsockopt(sk, level, optname,
- USER_SOCKPTR(optval), USER_SOCKPTR(optlen));
+ err = sockopt_init_user(&sopt, optval, optlen);
+ if (err)
+ return err;
+
+ err = do_ipv6_getsockopt(sk, level, optname, &sopt);
+ if (put_user(sopt.optlen, optlen))
+ return -EFAULT;
#ifdef CONFIG_NETFILTER
/* we need to exclude all possible ENOPROTOOPTs except default case */
if (err == -ENOPROTOOPT && optname != IPV6_2292PKTOPTIONS) {
- int len;
-
- if (get_user(len, optlen))
- return -EFAULT;
+ int len = sopt.optlen;
err = nf_getsockopt(sk, PF_INET6, optname, optval, &len);
if (err >= 0)
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH net-next 6/6] selftests: net: getsockopt_iter: cover ip and ipv6
2026-09-25 15:55 [PATCH net-next 0/6] ipv4,ipv6: convert the getsockopt switches to sockopt_t Breno Leitao
` (4 preceding siblings ...)
2026-09-25 15:55 ` [PATCH net-next 5/6] ipv6: convert do_ipv6_getsockopt() " Breno Leitao
@ 2026-09-25 15:55 ` Breno Leitao
5 siblings, 0 replies; 8+ messages in thread
From: Breno Leitao @ 2026-09-25 15:55 UTC (permalink / raw)
To: David Ahern, Ido Schimmel, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai,
John Fastabend, Stanislav Fomichev, Shuah Khan
Cc: netdev, linux-kernel, bpf, linux-kselftest, david.laight.linux,
Breno Leitao, kernel-team
Add an ip and an ipv6 fixture, pinning the returned length and errno
across buffer sizes, the branches that answer empty, an unknown
optname and the options dispatched before the switch.
SOL_IP answers a sub-int buffer with one byte where SOL_IPV6 clamps the
int. IP_PKTOPTIONS and IPV6_2292PKTOPTIONS want a stream socket;
MRT_*/MRT6_* want a raw one, and skip without CONFIG_IP_MROUTE or
CONFIG_IPV6_MROUTE, where the switch answers ENOPROTOOPT instead.
MRT_VERSION and MRT6_VERSION are spelled out; linux/mroute.h does not
coexist with netinet/in.h here.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
tools/testing/selftests/net/getsockopt_iter.c | 302 ++++++++++++++++++++++++++
1 file changed, 302 insertions(+)
diff --git a/tools/testing/selftests/net/getsockopt_iter.c b/tools/testing/selftests/net/getsockopt_iter.c
index 6c2408df461232..32b6e0a50f1f9c 100644
--- a/tools/testing/selftests/net/getsockopt_iter.c
+++ b/tools/testing/selftests/net/getsockopt_iter.c
@@ -55,6 +55,13 @@
#ifndef TCP_ULP
#define TCP_ULP 31
#endif
+/* linux/mroute.h does not coexist with netinet/in.h here. */
+#ifndef MRT_VERSION
+#define MRT_VERSION 206
+#endif
+#ifndef MRT6_VERSION
+#define MRT6_VERSION 206
+#endif
/* ---------- netlink ---------- */
@@ -492,6 +499,301 @@ TEST_F(rawv6, bad_optname)
ASSERT_EQ(sizeof(val), optlen);
}
+/* ---------- ip (SOL_IP) ---------- */
+
+FIXTURE(ip)
+{
+ int fd;
+};
+
+FIXTURE_SETUP(ip)
+{
+ /* a router alert option, so IP_OPTIONS has something to answer with */
+ static const unsigned char ipopts[4] = { 0x94, 0x04, 0x00, 0x00 };
+ int ttl = 42;
+
+ self->fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (self->fd < 0)
+ SKIP(return, "AF_INET dgram socket: %s", strerror(errno));
+
+ if (setsockopt(self->fd, SOL_IP, IP_TTL, &ttl, sizeof(ttl)) < 0)
+ SKIP(return, "set IP_TTL: %s", strerror(errno));
+
+ if (setsockopt(self->fd, SOL_IP, IP_OPTIONS, ipopts,
+ sizeof(ipopts)) < 0)
+ SKIP(return, "set IP_OPTIONS: %s", strerror(errno));
+}
+
+FIXTURE_TEARDOWN(ip)
+{
+ if (self->fd >= 0)
+ close(self->fd);
+}
+
+TEST_F(ip, ttl_exact)
+{
+ socklen_t optlen = sizeof(int);
+ int val = 0;
+
+ ASSERT_EQ(0, getsockopt(self->fd, SOL_IP, IP_TTL, &val, &optlen));
+ ASSERT_EQ(sizeof(int), optlen);
+ ASSERT_EQ(42, val);
+}
+
+TEST_F(ip, ttl_oversize_clamped)
+{
+ socklen_t optlen = 64;
+ char buf[64] = {};
+
+ ASSERT_EQ(0, getsockopt(self->fd, SOL_IP, IP_TTL, buf, &optlen));
+ ASSERT_EQ(sizeof(int), optlen);
+}
+
+/* SOL_IP answers a sub-int buffer with a single byte when the value fits
+ * in one, rather than clamping the int down.
+ */
+TEST_F(ip, ttl_single_byte)
+{
+ unsigned char buf[3] = {};
+ socklen_t optlen = sizeof(buf);
+
+ ASSERT_EQ(0, getsockopt(self->fd, SOL_IP, IP_TTL, buf, &optlen));
+ ASSERT_EQ(1, optlen);
+ ASSERT_EQ(42, buf[0]);
+}
+
+TEST_F(ip, ttl_zero_len)
+{
+ socklen_t optlen = 0;
+ int val;
+
+ ASSERT_EQ(0, getsockopt(self->fd, SOL_IP, IP_TTL, &val, &optlen));
+ ASSERT_EQ(0, optlen);
+}
+
+TEST_F(ip, negative_optlen)
+{
+ socklen_t optlen = (socklen_t)-1;
+ int val;
+
+ ASSERT_EQ(-1, getsockopt(self->fd, SOL_IP, IP_TTL, &val, &optlen));
+ ASSERT_EQ(EINVAL, errno);
+}
+
+TEST_F(ip, options_roundtrip)
+{
+ unsigned char buf[40] = {};
+ socklen_t optlen = sizeof(buf);
+
+ ASSERT_EQ(0, getsockopt(self->fd, SOL_IP, IP_OPTIONS, buf, &optlen));
+ ASSERT_EQ(4, optlen);
+ ASSERT_EQ(0x94, buf[0]);
+}
+
+TEST_F(ip, options_undersize_clamped)
+{
+ unsigned char buf[2] = {};
+ socklen_t optlen = sizeof(buf);
+
+ ASSERT_EQ(0, getsockopt(self->fd, SOL_IP, IP_OPTIONS, buf, &optlen));
+ ASSERT_EQ(sizeof(buf), optlen);
+}
+
+/* With no option set the reply is empty and the call still succeeds. */
+TEST_F(ip, options_absent)
+{
+ unsigned char buf[40] = {};
+ socklen_t optlen = sizeof(buf);
+ int fd;
+
+ fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0)
+ SKIP(return, "AF_INET dgram socket: %s", strerror(errno));
+
+ ASSERT_EQ(0, getsockopt(fd, SOL_IP, IP_OPTIONS, buf, &optlen));
+ ASSERT_EQ(0, optlen);
+ close(fd);
+}
+
+TEST_F(ip, multicast_if_oversize_clamped)
+{
+ socklen_t optlen = 64;
+ char buf[64] = {};
+
+ ASSERT_EQ(0, getsockopt(self->fd, SOL_IP, IP_MULTICAST_IF, buf,
+ &optlen));
+ ASSERT_EQ(sizeof(struct in_addr), optlen);
+}
+
+/* IP_PKTOPTIONS only answers on a stream socket. */
+TEST_F(ip, pktoptions_wrong_type)
+{
+ socklen_t optlen = 64;
+ char buf[64];
+
+ ASSERT_EQ(-1, getsockopt(self->fd, SOL_IP, IP_PKTOPTIONS, buf,
+ &optlen));
+ ASSERT_EQ(ENOPROTOOPT, errno);
+}
+
+/* The MRT_* options are dispatched ahead of the rest of the switch and
+ * want a raw IGMP socket. Without CONFIG_IP_MROUTE they are not
+ * dispatched at all and the switch answers ENOPROTOOPT instead.
+ */
+TEST_F(ip, mroute_wrong_type)
+{
+ socklen_t optlen = sizeof(int);
+ int val;
+
+ ASSERT_EQ(-1, getsockopt(self->fd, SOL_IP, MRT_VERSION, &val,
+ &optlen));
+ if (errno == ENOPROTOOPT)
+ SKIP(return, "CONFIG_IP_MROUTE disabled");
+ ASSERT_EQ(EOPNOTSUPP, errno);
+}
+
+TEST_F(ip, bad_optname)
+{
+ socklen_t optlen = sizeof(int);
+ int val;
+
+ ASSERT_EQ(-1, getsockopt(self->fd, SOL_IP, 0x7fff, &val, &optlen));
+ ASSERT_EQ(ENOPROTOOPT, errno);
+ ASSERT_EQ(sizeof(int), optlen);
+}
+
+/* ---------- ipv6 (SOL_IPV6) ---------- */
+
+FIXTURE(ipv6)
+{
+ int fd;
+};
+
+FIXTURE_SETUP(ipv6)
+{
+ /* an 8 byte hop-by-hop header, so the sticky options answer */
+ static const unsigned char hopopt[8] = { 0, 0, 1, 4, 0, 0, 0, 0 };
+ int hops = 42;
+
+ self->fd = socket(AF_INET6, SOCK_DGRAM, 0);
+ if (self->fd < 0)
+ SKIP(return, "AF_INET6 dgram socket: %s", strerror(errno));
+
+ if (setsockopt(self->fd, SOL_IPV6, IPV6_UNICAST_HOPS, &hops,
+ sizeof(hops)) < 0)
+ SKIP(return, "set IPV6_UNICAST_HOPS: %s", strerror(errno));
+
+ if (setsockopt(self->fd, SOL_IPV6, IPV6_HOPOPTS, hopopt,
+ sizeof(hopopt)) < 0)
+ SKIP(return, "set IPV6_HOPOPTS: %s", strerror(errno));
+}
+
+FIXTURE_TEARDOWN(ipv6)
+{
+ if (self->fd >= 0)
+ close(self->fd);
+}
+
+TEST_F(ipv6, hops_exact)
+{
+ socklen_t optlen = sizeof(int);
+ int val = 0;
+
+ ASSERT_EQ(0, getsockopt(self->fd, SOL_IPV6, IPV6_UNICAST_HOPS, &val,
+ &optlen));
+ ASSERT_EQ(sizeof(int), optlen);
+ ASSERT_EQ(42, val);
+}
+
+TEST_F(ipv6, hops_oversize_clamped)
+{
+ socklen_t optlen = 64;
+ char buf[64] = {};
+
+ ASSERT_EQ(0, getsockopt(self->fd, SOL_IPV6, IPV6_UNICAST_HOPS, buf,
+ &optlen));
+ ASSERT_EQ(sizeof(int), optlen);
+}
+
+TEST_F(ipv6, hopopts_roundtrip)
+{
+ unsigned char buf[64] = {};
+ socklen_t optlen = sizeof(buf);
+
+ ASSERT_EQ(0, getsockopt(self->fd, SOL_IPV6, IPV6_HOPOPTS, buf,
+ &optlen));
+ ASSERT_EQ(8, optlen);
+}
+
+TEST_F(ipv6, hopopts_undersize_clamped)
+{
+ unsigned char buf[4] = {};
+ socklen_t optlen = sizeof(buf);
+
+ ASSERT_EQ(0, getsockopt(self->fd, SOL_IPV6, IPV6_HOPOPTS, buf,
+ &optlen));
+ ASSERT_EQ(sizeof(buf), optlen);
+}
+
+/* With no header set the reply is empty and the call still succeeds. */
+TEST_F(ipv6, hopopts_absent)
+{
+ unsigned char buf[64] = {};
+ socklen_t optlen = sizeof(buf);
+ int fd;
+
+ fd = socket(AF_INET6, SOCK_DGRAM, 0);
+ if (fd < 0)
+ SKIP(return, "AF_INET6 dgram socket: %s", strerror(errno));
+
+ ASSERT_EQ(0, getsockopt(fd, SOL_IPV6, IPV6_HOPOPTS, buf, &optlen));
+ ASSERT_EQ(0, optlen);
+ close(fd);
+}
+
+/* IPV6_PATHMTU wants room for the whole struct ip6_mtuinfo. */
+TEST_F(ipv6, pathmtu_undersize)
+{
+ socklen_t optlen = 8;
+ char buf[8];
+
+ ASSERT_EQ(-1, getsockopt(self->fd, SOL_IPV6, IPV6_PATHMTU, buf,
+ &optlen));
+ ASSERT_EQ(EINVAL, errno);
+}
+
+TEST_F(ipv6, pktoptions_wrong_type)
+{
+ socklen_t optlen = 64;
+ char buf[64];
+
+ ASSERT_EQ(-1, getsockopt(self->fd, SOL_IPV6, IPV6_2292PKTOPTIONS, buf,
+ &optlen));
+ ASSERT_EQ(ENOPROTOOPT, errno);
+}
+
+TEST_F(ipv6, mroute_wrong_type)
+{
+ socklen_t optlen = sizeof(int);
+ int val;
+
+ ASSERT_EQ(-1, getsockopt(self->fd, SOL_IPV6, MRT6_VERSION, &val,
+ &optlen));
+ if (errno == ENOPROTOOPT)
+ SKIP(return, "CONFIG_IPV6_MROUTE disabled");
+ ASSERT_EQ(EOPNOTSUPP, errno);
+}
+
+TEST_F(ipv6, bad_optname)
+{
+ socklen_t optlen = sizeof(int);
+ int val;
+
+ ASSERT_EQ(-1, getsockopt(self->fd, SOL_IPV6, 0x7fff, &val, &optlen));
+ ASSERT_EQ(ENOPROTOOPT, errno);
+ ASSERT_EQ(sizeof(int), optlen);
+}
+
/* ---------- tls ---------- */
FIXTURE(tls)
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 8+ messages in thread