[PATCH net-next v2 6/7] ipv6: convert do_ipv6_getsockopt() to sockopt_t
flat view
HOTtoday
From: Breno Leitao <leitao@debian.org>
Date: 2026-10-09 09:09:30
Also in:
bpf, linux-kselftest, lkml
Subsystem:
bpf [general] (safe dynamic programs and tools), bpf [networking] (tcx & tc bpf, sock_addr), networking [general], networking [ipv4/ipv6], the rest · Maintainers:
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Ahern, Ido Schimmel, Linus Torvalds
Convert the SOL_IPV6 switch and its two remaining sockptr_t leaves, ip6_mroute_getsockopt() and ipv6_getsockopt_sticky(). ipv6_getsockopt() writes optlen back unconditionally, and sol_ipv6_sockopt() builds the sockopt_t with sockptr_to_sockopt() and stores optlen back as well. ipv6_getsockopt() cannot use sockopt_init_user() as its IPv4 sibling does, since that rejects a negative optlen and this level answers it as 4. See discussion in the previous patch/commit. The write-back also happens before the netfilter fallback, so an unwritable optlen turns any error into -EFAULT and skips the fallback. The extra code in sol_ipv6_sockopt() will go away when sol_ipv6_sockopt() receives sockopt_t, but, for now, it only moves the leaves. Signed-off-by: Breno Leitao <leitao@debian.org> --- include/linux/mroute6.h | 5 ++- include/linux/net.h | 17 +++++++-- include/net/ipv6.h | 2 +- net/core/filter.c | 18 +++++++--- net/ipv6/ip6mr.c | 13 +++---- net/ipv6/ipv6_sockglue.c | 90 ++++++++++++++++++++---------------------------- 6 files changed, 73 insertions(+), 72 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/linux/net.h b/include/linux/net.h
index 461137bdc8817e..3fe84daaa5e2e2 100644
--- a/include/linux/net.h
+++ b/include/linux/net.h@@ -50,6 +50,19 @@ typedef struct sockopt { int sockptr_to_sockopt(sockopt_t *opt, sockptr_t optval, sockptr_t optlen, struct kvec *kvec); +/* + * Point a sockopt_t at a user-backed (optval, len) pair whose length is + * already known, e.g. because the caller applied its own optlen quirks + * before this point. + */ +static inline void sockopt_set_user(sockopt_t *opt, char __user *optval, + int len) +{ + iov_iter_ubuf(&opt->iter_out, ITER_DEST, optval, len); + iov_iter_ubuf(&opt->iter_in, ITER_SOURCE, optval, len); + opt->optlen = len; +} + /* * Initialize a user-backed sockopt_t from the (optval, optlen) __user pair of * a getsockopt() callback. Used by transitional __user getsockopt wrappers
@@ -66,9 +79,7 @@ static inline int sockopt_init_user(sockopt_t *opt, char __user *optval, if (len < 0) return -EINVAL; - iov_iter_ubuf(&opt->iter_out, ITER_DEST, optval, len); - iov_iter_ubuf(&opt->iter_in, ITER_SOURCE, optval, len); - opt->optlen = len; + sockopt_set_user(opt, optval, len); return 0; }
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 1bc194a32337ac..8606d5d9a3181b 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 b488391496af3c..c9787579531f64 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; }
@@ -998,42 +998,21 @@ 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; - /* Historic bug compatibility: the int options have always taken a - * negative optlen as 4, so take it as 4 everywhere. - */ - if (len < 0) - len = 4; + len = sopt->optlen; 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;
@@ -1042,12 +1021,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;
@@ -1098,8 +1077,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: {
@@ -1154,12 +1133,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:
@@ -1213,9 +1193,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;
@@ -1292,7 +1271,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)
@@ -1307,9 +1287,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;
@@ -1367,9 +1346,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; }
@@ -1377,7 +1355,8 @@ 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) { - int err; + sockopt_t sopt; + int len, err; if (level == SOL_IP && sk->sk_type != SOCK_RAW) return ip_getsockopt(sk, level, optname, optval, optlen);
@@ -1385,15 +1364,22 @@ 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)); + if (get_user(len, optlen)) + return -EFAULT; + /* Historic bug compatibility: the int options have always taken a + * negative optlen as 4, so take it as 4 everywhere. + */ + if (len < 0) + len = 4; + sockopt_set_user(&sopt, optval, len); + + 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