Convert SMC socket's getsockopt implementation to use the new getsockopt_iter callback with sockopt_t. Key changes: - Replace (char __user *optval, int __user *optlen) with sockopt_t *opt - Use opt->optlen for buffer length (input) and returned size (output) - Use copy_to_iter() instead of put_user()/copy_to_user() - Add linux/uio.h for copy_to_iter() SMC is a proxy socket: only the SOL_SMC level is handled locally, while all other levels are forwarded to the underlying CLC (TCP) socket. That socket's getsockopt() still operates on __user buffers, so the pass-through is limited to user-backed iters: optval is reconstructed from iter_out, the original optlen pointer (preserved in sockopt_t) is forwarded, and the length reported by the clcsock is mirrored back into opt->optlen so the core writes the correct value to userspace. Signed-off-by: Breno Leitao --- net/smc/af_smc.c | 41 +++++++++++++++++++++++++++++------------ net/smc/smc.h | 2 +- net/smc/smc_inet.c | 4 ++-- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c index b5db69073e20..064d752388d2 100644 --- a/net/smc/af_smc.c +++ b/net/smc/af_smc.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -3017,17 +3018,14 @@ int smc_shutdown(struct socket *sock, int how) } static int __smc_getsockopt(struct socket *sock, int level, int optname, - char __user *optval, int __user *optlen) + sockopt_t *opt) { struct smc_sock *smc; int val, len; smc = smc_sk(sock->sk); - if (get_user(len, optlen)) - return -EFAULT; - - len = min_t(int, len, sizeof(int)); + len = min_t(int, opt->optlen, sizeof(int)); if (len < 0) return -EINVAL; @@ -3040,9 +3038,8 @@ static int __smc_getsockopt(struct socket *sock, int level, int optname, return -EOPNOTSUPP; } - if (put_user(len, optlen)) - return -EFAULT; - if (copy_to_user(optval, &val, len)) + opt->optlen = len; + if (copy_to_iter(&val, len, &opt->iter_out) != len) return -EFAULT; return 0; @@ -3168,13 +3165,26 @@ int smc_setsockopt(struct socket *sock, int level, int optname, } int smc_getsockopt(struct socket *sock, int level, int optname, - char __user *optval, int __user *optlen) + sockopt_t *opt) { struct smc_sock *smc; int rc; if (level == SOL_SMC) - return __smc_getsockopt(sock, level, optname, optval, optlen); + return __smc_getsockopt(sock, level, optname, opt); + + /* Other levels apply to the CLC socket, whose getsockopt() still + * operates on __user buffers. Reconstruct the userspace pointers and + * forward the call; kernel-backed callers (e.g. io_uring) are not + * supported for this pass-through. + * + * TODO: this pass-through is limited to user-backed iters because the + * underlying protocols (TCP/IP) have not been converted to + * getsockopt_iter() yet. Once they are, forward the sockopt_t directly + * and drop this restriction so all iov_iter types are supported. + */ + if (!iter_is_ubuf(&opt->iter_out) || !opt->optlen_user) + return -EOPNOTSUPP; smc = smc_sk(sock->sk); mutex_lock(&smc->clcsock_release_lock); @@ -3188,8 +3198,15 @@ int smc_getsockopt(struct socket *sock, int level, int optname, return -EOPNOTSUPP; } rc = smc->clcsock->ops->getsockopt(smc->clcsock, level, optname, - optval, optlen); + opt->iter_out.ubuf, opt->optlen_user); mutex_unlock(&smc->clcsock_release_lock); + + /* The clcsock wrote the resulting length to the user optlen pointer; + * mirror it into opt->optlen so the core writes the same value back. + */ + if (get_user(opt->optlen, opt->optlen_user)) + return -EFAULT; + return rc; } @@ -3341,7 +3358,7 @@ static const struct proto_ops smc_sock_ops = { .listen = smc_listen, .shutdown = smc_shutdown, .setsockopt = smc_setsockopt, - .getsockopt = smc_getsockopt, + .getsockopt_iter = smc_getsockopt, .sendmsg = smc_sendmsg, .recvmsg = smc_recvmsg, .mmap = sock_no_mmap, diff --git a/net/smc/smc.h b/net/smc/smc.h index 52145df83f6e..e62549067b67 100644 --- a/net/smc/smc.h +++ b/net/smc/smc.h @@ -59,7 +59,7 @@ int smc_shutdown(struct socket *sock, int how); int smc_setsockopt(struct socket *sock, int level, int optname, sockptr_t optval, unsigned int optlen); int smc_getsockopt(struct socket *sock, int level, int optname, - char __user *optval, int __user *optlen); + sockopt_t *opt); int smc_sendmsg(struct socket *sock, struct msghdr *msg, size_t len); int smc_recvmsg(struct socket *sock, struct msghdr *msg, size_t len, int flags); diff --git a/net/smc/smc_inet.c b/net/smc/smc_inet.c index a94084b4a498..419240fedbc3 100644 --- a/net/smc/smc_inet.c +++ b/net/smc/smc_inet.c @@ -44,7 +44,7 @@ static const struct proto_ops smc_inet_stream_ops = { .listen = smc_listen, .shutdown = smc_shutdown, .setsockopt = smc_setsockopt, - .getsockopt = smc_getsockopt, + .getsockopt_iter = smc_getsockopt, .sendmsg = smc_sendmsg, .recvmsg = smc_recvmsg, .mmap = sock_no_mmap, @@ -91,7 +91,7 @@ static const struct proto_ops smc_inet6_stream_ops = { .listen = smc_listen, .shutdown = smc_shutdown, .setsockopt = smc_setsockopt, - .getsockopt = smc_getsockopt, + .getsockopt_iter = smc_getsockopt, .sendmsg = smc_sendmsg, .recvmsg = smc_recvmsg, .mmap = sock_no_mmap, -- 2.53.0-Meta Add a kselftest that exercises the SMC getsockopt() paths converted to the getsockopt_iter() / sockopt_t callback: - SOL_SMC options (SMC_LIMIT_HS), handled directly by smc_getsockopt(), which returns the int value through copy_to_iter() and reports the written length in opt->optlen. - The CLC pass-through (e.g. SOL_TCP), where smc_getsockopt() forwards to the underlying TCP socket: optval is reconstructed from iter_out, the optlen pointer is forwarded, and the clamped length is mirrored back through opt->optlen. The oversized-buffer case (input optlen differs from output) specifically guards against a missing writeback sync. Signed-off-by: Breno Leitao --- tools/testing/selftests/net/Makefile | 1 + tools/testing/selftests/net/getsockopt_smc.c | 175 +++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile index 5ca6c557fc3f..5b50f718dbde 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -177,6 +177,7 @@ TEST_GEN_PROGS := \ bind_wildcard \ epoll_busy_poll \ getsockopt_iter \ + getsockopt_smc \ icmp_rfc4884 \ ipv6_fragmentation \ proc_net_pktgen \ diff --git a/tools/testing/selftests/net/getsockopt_smc.c b/tools/testing/selftests/net/getsockopt_smc.c new file mode 100644 index 000000000000..239deefb3187 --- /dev/null +++ b/tools/testing/selftests/net/getsockopt_smc.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Exercise the SMC getsockopt() paths that were converted to the + * getsockopt_iter() / sockopt_t callback. + * + * Two distinct paths are covered: + * + * - SOL_SMC options (SMC_LIMIT_HS) are handled directly by + * smc_getsockopt(), which returns the int value through copy_to_iter() + * and reports the written length in opt->optlen. + * + * - Other levels (e.g. SOL_TCP) are forwarded to the underlying CLC (TCP) + * socket, whose getsockopt() still operates on __user buffers. The + * converted smc_getsockopt() reconstructs the userspace optval from + * iter_out, forwards the original optlen pointer, and mirrors the length + * the clcsock reported back into opt->optlen so the core writes the right + * value to userspace. + * + * The kernel-buffer (kvec) path of the CLC pass-through returns -EOPNOTSUPP + * and is not reachable from a userspace getsockopt(), so it is not tested + * here. + * + * Author: Breno Leitao + */ +#include +#include +#include +#include +#include +#include +#include + +#include "kselftest_harness.h" + +#ifndef AF_SMC +#define AF_SMC 43 +#endif +#ifndef SMCPROTO_SMC +#define SMCPROTO_SMC 0 +#endif +#ifndef SOL_SMC +#define SOL_SMC 286 +#endif +#ifndef SMC_LIMIT_HS +#define SMC_LIMIT_HS 1 +#endif + +FIXTURE(smc) { + int fd; +}; + +FIXTURE_SETUP(smc) +{ + self->fd = socket(AF_SMC, SOCK_STREAM, SMCPROTO_SMC); + if (self->fd < 0) + SKIP(return, "AF_SMC unavailable (errno %d) - load the smc module", + errno); +} + +FIXTURE_TEARDOWN(smc) +{ + if (self->fd >= 0) + close(self->fd); +} + +/* ---------- SOL_SMC: handled directly by smc_getsockopt() ---------- */ + +/* SMC_LIMIT_HS is reported back as a 4-byte int via copy_to_iter(). */ +TEST_F(smc, limit_hs_default) +{ + socklen_t optlen = sizeof(int); + int val = 0xdeadbeef; + + ASSERT_EQ(0, getsockopt(self->fd, SOL_SMC, SMC_LIMIT_HS, &val, &optlen)); + EXPECT_EQ(sizeof(int), optlen); + EXPECT_TRUE(val == 0 || val == 1); +} + +/* A value set via setsockopt() must be readable back unchanged. */ +TEST_F(smc, limit_hs_set_get) +{ + socklen_t optlen = sizeof(int); + int val = 1; + + ASSERT_EQ(0, setsockopt(self->fd, SOL_SMC, SMC_LIMIT_HS, &val, optlen)); + + val = -1; + ASSERT_EQ(0, getsockopt(self->fd, SOL_SMC, SMC_LIMIT_HS, &val, &optlen)); + EXPECT_EQ(sizeof(int), optlen); + EXPECT_EQ(1, val); +} + +/* setsockopt() stores !!val, so a non-1 truthy value reads back as 1. */ +TEST_F(smc, limit_hs_set_get_clear) +{ + socklen_t optlen = sizeof(int); + int val = 0; + + ASSERT_EQ(0, setsockopt(self->fd, SOL_SMC, SMC_LIMIT_HS, &val, optlen)); + + val = -1; + ASSERT_EQ(0, getsockopt(self->fd, SOL_SMC, SMC_LIMIT_HS, &val, &optlen)); + EXPECT_EQ(sizeof(int), optlen); + EXPECT_EQ(0, val); +} + +/* An oversized buffer is clamped: optlen is reported back as sizeof(int). */ +TEST_F(smc, limit_hs_oversize_clamped) +{ + socklen_t optlen; + char buf[16] = {}; + + optlen = sizeof(buf); + ASSERT_EQ(0, getsockopt(self->fd, SOL_SMC, SMC_LIMIT_HS, buf, &optlen)); + EXPECT_EQ(sizeof(int), optlen); +} + +/* An unknown SOL_SMC option is rejected with -EOPNOTSUPP. */ +TEST_F(smc, bad_optname) +{ + socklen_t optlen = sizeof(int); + int val; + + ASSERT_EQ(-1, getsockopt(self->fd, SOL_SMC, 0x7fff, &val, &optlen)); + EXPECT_EQ(EOPNOTSUPP, errno); +} + +/* ---------- CLC pass-through: forwarded to the underlying TCP socket ------ */ + +/* A TCP option set on the SMC socket is applied to the CLC socket and must be + * readable back through the pass-through, exercising optval reconstruction. + */ +TEST_F(smc, clc_tcp_nodelay_set_get) +{ + socklen_t optlen = sizeof(int); + int val = 1; + + ASSERT_EQ(0, setsockopt(self->fd, IPPROTO_TCP, TCP_NODELAY, + &val, optlen)); + + val = -1; + ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_TCP, TCP_NODELAY, + &val, &optlen)); + EXPECT_EQ(sizeof(int), optlen); + EXPECT_EQ(1, val); +} + +/* With an oversized buffer the clcsock clamps the reported length to + * sizeof(int). That length is produced by the clcsock writing the user optlen + * pointer, and must be mirrored back through opt->optlen; since the input + * optlen (16) differs from the output (4), this fails if the writeback sync + * in smc_getsockopt() is missing. + */ +TEST_F(smc, clc_tcp_nodelay_oversize_clamped) +{ + socklen_t optlen; + char buf[16] = {}; + + optlen = sizeof(buf); + ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_TCP, TCP_NODELAY, + buf, &optlen)); + EXPECT_EQ(sizeof(int), optlen); +} + +/* An error from the clcsock (unknown TCP option) is propagated unchanged. */ +TEST_F(smc, clc_bad_optname) +{ + socklen_t optlen = sizeof(int); + int val; + + ASSERT_EQ(-1, getsockopt(self->fd, IPPROTO_TCP, 0x7fff, &val, &optlen)); + EXPECT_EQ(ENOPROTOOPT, errno); +} + +TEST_HARNESS_MAIN -- 2.53.0-Meta