From: Breno Leitao Date: Wed, 8 Apr 2026 10:30:30 +0000 (-0700) Subject: net: call getsockopt_iter if available X-Git-Url: http://git.ipfire.org/index.cgi?a=commitdiff_plain;h=5bd0dec150f56b6307d599132dcb7c01007bbecc;p=thirdparty%2Fkernel%2Flinux.git net: call getsockopt_iter if available Update do_sock_getsockopt() to use the new getsockopt_iter callback when available. Add do_sock_getsockopt_iter() helper that: 1. Reads optlen from user/kernel space 2. Initializes a sockopt_t with the appropriate iov_iter (kvec for kernel, ubuf for user buffers) and sets opt.optlen 3. Calls the protocol's getsockopt_iter callback 4. Writes opt.optlen back to user/kernel space The optlen is always written back, even on failure. Some protocols (e.g. CAN raw) return -ERANGE and set optlen to the required buffer size so userspace knows how much to allocate. The callback is responsible for setting opt.optlen to indicate the returned data size. Important to say that iov_out does not need to be copied back in do_sock_getsockopt(). When optval is not kernel (the userspace path), sockptr_to_sockopt() sets up opt->iter_out as a ITER_DEST ubuf iterator pointing directly at the userspace buffer (optval.user). So when getsockopt_iter implementations call copy_to_iter(..., &opt->iter_out), the data is written directly to userspace — no intermediate kernel buffer is involved. When optval.is_kernel is true (the in-kernel path, e.g. from io_uring), the kvec points at the already-provided kernel buffer (optval.kernel), so the data lands in the caller's buffer directly via the kvec-backed iterator. In both cases the iterator writes to the final destination in-place at protocol callback. There's nothing to copy back — only optlen needs to be written back. Signed-off-by: Breno Leitao Acked-by: Stanislav Fomichev Link: https://patch.msgid.link/20260408-getsockopt-v3-2-061bb9cb355d@debian.org Signed-off-by: Jakub Kicinski --- diff --git a/net/socket.c b/net/socket.c index ade2ff5845a0c..a25e513cf0f47 100644 --- a/net/socket.c +++ b/net/socket.c @@ -77,6 +77,7 @@ #include #include #include +#include #include #include #include @@ -2349,11 +2350,45 @@ SYSCALL_DEFINE5(setsockopt, int, fd, int, level, int, optname, INDIRECT_CALLABLE_DECLARE(bool tcp_bpf_bypass_getsockopt(int level, int optname)); +/* + * Initialize a sockopt_t from sockptr optval/optlen, setting up iov_iter + * for both input and output directions. + * 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 koptlen; + + if (copy_from_sockptr(&koptlen, optlen, sizeof(int))) + return -EFAULT; + + if (koptlen < 0) + return -EINVAL; + + if (optval.is_kernel) { + kvec->iov_base = optval.kernel; + kvec->iov_len = koptlen; + iov_iter_kvec(&opt->iter_out, ITER_DEST, kvec, 1, koptlen); + iov_iter_kvec(&opt->iter_in, ITER_SOURCE, kvec, 1, koptlen); + } else { + iov_iter_ubuf(&opt->iter_out, ITER_DEST, optval.user, koptlen); + iov_iter_ubuf(&opt->iter_in, ITER_SOURCE, optval.user, + koptlen); + } + opt->optlen = koptlen; + + return 0; +} + int do_sock_getsockopt(struct socket *sock, bool compat, int level, int optname, sockptr_t optval, sockptr_t optlen) { int max_optlen __maybe_unused = 0; const struct proto_ops *ops; + struct kvec kvec; + sockopt_t opt; int err; err = security_socket_getsockopt(sock, level, optname); @@ -2366,15 +2401,28 @@ int do_sock_getsockopt(struct socket *sock, bool compat, int level, ops = READ_ONCE(sock->ops); if (level == SOL_SOCKET) { err = sk_getsockopt(sock->sk, level, optname, optval, optlen); - } else if (unlikely(!ops->getsockopt)) { - err = -EOPNOTSUPP; - } else { + } else if (ops->getsockopt_iter) { + err = sockptr_to_sockopt(&opt, optval, optlen, &kvec); + if (err) + return err; + + err = ops->getsockopt_iter(sock, level, optname, &opt); + + /* Always write back optlen, even on failure. Some protocols + * (e.g. CAN raw) return -ERANGE and set optlen to the + * required buffer size so userspace can discover it. + */ + if (copy_to_sockptr(optlen, &opt.optlen, sizeof(int))) + return -EFAULT; + } else if (ops->getsockopt) { if (WARN_ONCE(optval.is_kernel || optlen.is_kernel, "Invalid argument type")) return -EOPNOTSUPP; err = ops->getsockopt(sock, level, optname, optval.user, optlen.user); + } else { + err = -EOPNOTSUPP; } if (!compat)