Merge branch 'net-convert-rawv6-ieee802154-phonet-and-tls-getsockopt-to-sockopt_t'

Breno Leitao says:

====================
net: convert rawv6, ieee802154, phonet and tls getsockopt to sockopt_t

Now that sockopt_init_user() was already merged, builds a user-backed
sockopt_t from the __user pair. A getsockopt leaf can then take
a sockopt_t behind a thin __user wrapper: the wrapper builds it, calls
the leaf, and writes the length back to optlen. The leaf copies with
copy_to_iter() instead of copy_to_user().

Convert four more leaves the way udp and raw already were: ipv6 raw
(do_rawv6_getsockopt), ieee802154 dgram, phonet pep, and tls
(do_tls_getsockopt and its per-option helpers).

Converting phonet surfaced a pre-existing bug: pep_getsockopt() clamps the
length it reports but writes a full int with put_user(), overrunning an
optval buffer shorter than sizeof(int). It is fixed in its own patch, with
a Fixes: tag, before the phonet conversion, so it can be backported alone.

The last patch adds getsockopt_iter selftest fixtures for rawv6,
ieee802154, phonet and tls, checking the returned length and errno across
exact, oversized and short buffers, an unknown optname and a bad level.

For full motivation about these changes, please check the initial thread
at link
https://lore.kernel.org/all/20260401-getsockopt-v2-0-611df6771aff@debian.org/#t
====================

Link: https://patch.msgid.link/20260729-getsockopt_phase4-v4-0-c44576757c17@debian.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-08-03 16:55:25 -07:00
commit 958f2f4b1d
5 changed files with 349 additions and 73 deletions

View File

@ -831,20 +831,12 @@ static int ieee802154_dgram_deliver(struct net_device *dev, struct sk_buff *skb)
return ret;
}
static int dgram_getsockopt(struct sock *sk, int level, int optname,
char __user *optval, int __user *optlen)
static int do_dgram_getsockopt(struct sock *sk, int optname, sockopt_t *opt)
{
struct dgram_sock *ro = dgram_sk(sk);
int val, len;
if (level != SOL_IEEE802154)
return -EOPNOTSUPP;
if (get_user(len, optlen))
return -EFAULT;
len = min_t(unsigned int, len, sizeof(int));
len = umin(sizeof(int), opt->optlen);
switch (optname) {
case WPAN_WANTACK:
@ -871,10 +863,32 @@ static int dgram_getsockopt(struct sock *sk, int level, int optname,
return -ENOPROTOOPT;
}
if (put_user(len, optlen))
opt->optlen = len;
if (copy_to_iter(&val, len, &opt->iter_out) != len)
return -EFAULT;
if (copy_to_user(optval, &val, len))
return 0;
}
static int dgram_getsockopt(struct sock *sk, int level, int optname,
char __user *optval, int __user *optlen)
{
sockopt_t opt;
int err;
if (level != SOL_IEEE802154)
return -EOPNOTSUPP;
err = sockopt_init_user(&opt, optval, optlen);
if (err)
return err;
err = do_dgram_getsockopt(sk, optname, &opt);
if (err)
return err;
if (put_user(opt.optlen, optlen))
return -EFAULT;
return 0;
}

View File

@ -1051,14 +1051,12 @@ static int rawv6_setsockopt(struct sock *sk, int level, int optname,
return do_rawv6_setsockopt(sk, level, optname, optval, optlen);
}
static int do_rawv6_getsockopt(struct sock *sk, int level, int optname,
char __user *optval, int __user *optlen)
static int do_rawv6_getsockopt(struct sock *sk, int optname, sockopt_t *opt)
{
struct raw6_sock *rp = raw6_sk(sk);
int val, len;
if (get_user(len, optlen))
return -EFAULT;
len = opt->optlen;
switch (optname) {
case IPV6_HDRINCL:
@ -1080,11 +1078,10 @@ static int do_rawv6_getsockopt(struct sock *sk, int level, int optname,
return -ENOPROTOOPT;
}
len = min_t(unsigned int, sizeof(int), len);
len = umin(sizeof(int), len);
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;
}
@ -1092,6 +1089,9 @@ static int do_rawv6_getsockopt(struct sock *sk, int level, int optname,
static int rawv6_getsockopt(struct sock *sk, int level, int optname,
char __user *optval, int __user *optlen)
{
sockopt_t opt;
int err;
switch (level) {
case SOL_RAW:
break;
@ -1109,7 +1109,18 @@ static int rawv6_getsockopt(struct sock *sk, int level, int optname,
return ipv6_getsockopt(sk, level, optname, optval, optlen);
}
return do_rawv6_getsockopt(sk, level, optname, optval, optlen);
err = sockopt_init_user(&opt, optval, optlen);
if (err)
return err;
err = do_rawv6_getsockopt(sk, optname, &opt);
if (err)
return err;
if (put_user(opt.optlen, optlen))
return -EFAULT;
return 0;
}
static int rawv6_ioctl(struct sock *sk, int cmd, int *karg)

View File

@ -1080,17 +1080,11 @@ static int pep_setsockopt(struct sock *sk, int level, int optname,
return err;
}
static int pep_getsockopt(struct sock *sk, int level, int optname,
char __user *optval, int __user *optlen)
static int do_pep_getsockopt(struct sock *sk, int optname, sockopt_t *opt)
{
struct pep_sock *pn = pep_sk(sk);
int len, val;
if (level != SOL_PNPIPE)
return -ENOPROTOOPT;
if (get_user(len, optlen))
return -EFAULT;
switch (optname) {
case PNPIPE_ENCAP:
val = pn->ifindex ? PNPIPE_ENCAP_IP : PNPIPE_ENCAP_NONE;
@ -1114,11 +1108,33 @@ static int pep_getsockopt(struct sock *sk, int level, int optname,
return -ENOPROTOOPT;
}
len = min_t(unsigned int, sizeof(int), len);
if (put_user(len, optlen))
len = umin(sizeof(int), opt->optlen);
opt->optlen = len;
if (copy_to_iter(&val, len, &opt->iter_out) != len)
return -EFAULT;
if (put_user(val, (int __user *) optval))
return 0;
}
static int pep_getsockopt(struct sock *sk, int level, int optname,
char __user *optval, int __user *optlen)
{
sockopt_t opt;
int err;
if (level != SOL_PNPIPE)
return -ENOPROTOOPT;
err = sockopt_init_user(&opt, optval, optlen);
if (err)
return err;
err = do_pep_getsockopt(sk, optname, &opt);
if (err)
return err;
if (put_user(opt.optlen, optlen))
return -EFAULT;
return 0;
}

View File

@ -424,20 +424,16 @@ static __poll_t tls_sk_poll(struct file *file, struct socket *sock,
return mask;
}
static int do_tls_getsockopt_conf(struct sock *sk, char __user *optval,
int __user *optlen, int tx)
static int do_tls_getsockopt_conf(struct sock *sk, sockopt_t *opt, int tx)
{
int rc = 0;
const struct tls_cipher_desc *cipher_desc;
struct tls_context *ctx = tls_get_ctx(sk);
struct tls_crypto_info *crypto_info;
struct cipher_context *cctx;
int len;
int len = opt->optlen;
if (get_user(len, optlen))
return -EFAULT;
if (!optval || (len < sizeof(*crypto_info))) {
if (!opt->iter_out.ubuf || len < sizeof(*crypto_info)) {
rc = -EINVAL;
goto out;
}
@ -462,7 +458,8 @@ static int do_tls_getsockopt_conf(struct sock *sk, char __user *optval,
}
if (len == sizeof(*crypto_info)) {
if (copy_to_user(optval, crypto_info, sizeof(*crypto_info)))
if (copy_to_iter(crypto_info, sizeof(*crypto_info),
&opt->iter_out) != sizeof(*crypto_info))
rc = -EFAULT;
goto out;
}
@ -478,44 +475,38 @@ static int do_tls_getsockopt_conf(struct sock *sk, char __user *optval,
memcpy(crypto_info_rec_seq(crypto_info, cipher_desc),
cctx->rec_seq, cipher_desc->rec_seq);
if (copy_to_user(optval, crypto_info, cipher_desc->crypto_info))
if (copy_to_iter(crypto_info, cipher_desc->crypto_info,
&opt->iter_out) != cipher_desc->crypto_info)
rc = -EFAULT;
out:
return rc;
}
static int do_tls_getsockopt_tx_zc(struct sock *sk, char __user *optval,
int __user *optlen)
static int do_tls_getsockopt_tx_zc(struct sock *sk, sockopt_t *opt)
{
struct tls_context *ctx = tls_get_ctx(sk);
unsigned int value;
int len;
if (get_user(len, optlen))
return -EFAULT;
int len = opt->optlen;
if (len != sizeof(value))
return -EINVAL;
value = ctx->zerocopy_sendfile;
if (copy_to_user(optval, &value, sizeof(value)))
if (copy_to_iter(&value, sizeof(value), &opt->iter_out) != sizeof(value))
return -EFAULT;
return 0;
}
static int do_tls_getsockopt_no_pad(struct sock *sk, char __user *optval,
int __user *optlen)
static int do_tls_getsockopt_no_pad(struct sock *sk, sockopt_t *opt)
{
struct tls_context *ctx = tls_get_ctx(sk);
int value, len;
int value, len = opt->optlen;
if (ctx->prot_info.version != TLS_1_3_VERSION)
return -EINVAL;
if (get_user(len, optlen))
return -EFAULT;
if (len < sizeof(value))
return -EINVAL;
@ -525,38 +516,31 @@ static int do_tls_getsockopt_no_pad(struct sock *sk, char __user *optval,
if (value < 0)
return value;
if (put_user(sizeof(value), optlen))
return -EFAULT;
if (copy_to_user(optval, &value, sizeof(value)))
opt->optlen = sizeof(value);
if (copy_to_iter(&value, sizeof(value), &opt->iter_out) != sizeof(value))
return -EFAULT;
return 0;
}
static int do_tls_getsockopt_tx_payload_len(struct sock *sk, char __user *optval,
int __user *optlen)
static int do_tls_getsockopt_tx_payload_len(struct sock *sk, sockopt_t *opt)
{
struct tls_context *ctx = tls_get_ctx(sk);
u16 payload_len = ctx->tx_max_payload_len;
int len;
if (get_user(len, optlen))
return -EFAULT;
int len = opt->optlen;
if (len < sizeof(payload_len))
return -EINVAL;
if (put_user(sizeof(payload_len), optlen))
return -EFAULT;
if (copy_to_user(optval, &payload_len, sizeof(payload_len)))
opt->optlen = sizeof(payload_len);
if (copy_to_iter(&payload_len, sizeof(payload_len),
&opt->iter_out) != sizeof(payload_len))
return -EFAULT;
return 0;
}
static int do_tls_getsockopt(struct sock *sk, int optname,
char __user *optval, int __user *optlen)
static int do_tls_getsockopt(struct sock *sk, int optname, sockopt_t *opt)
{
int rc = 0;
@ -565,17 +549,16 @@ static int do_tls_getsockopt(struct sock *sk, int optname,
switch (optname) {
case TLS_TX:
case TLS_RX:
rc = do_tls_getsockopt_conf(sk, optval, optlen,
optname == TLS_TX);
rc = do_tls_getsockopt_conf(sk, opt, optname == TLS_TX);
break;
case TLS_TX_ZEROCOPY_RO:
rc = do_tls_getsockopt_tx_zc(sk, optval, optlen);
rc = do_tls_getsockopt_tx_zc(sk, opt);
break;
case TLS_RX_EXPECT_NO_PAD:
rc = do_tls_getsockopt_no_pad(sk, optval, optlen);
rc = do_tls_getsockopt_no_pad(sk, opt);
break;
case TLS_TX_MAX_PAYLOAD_LEN:
rc = do_tls_getsockopt_tx_payload_len(sk, optval, optlen);
rc = do_tls_getsockopt_tx_payload_len(sk, opt);
break;
default:
rc = -ENOPROTOOPT;
@ -591,12 +574,25 @@ static int tls_getsockopt(struct sock *sk, int level, int optname,
char __user *optval, int __user *optlen)
{
struct tls_context *ctx = tls_get_ctx(sk);
sockopt_t opt;
int err;
if (level != SOL_TLS)
return ctx->sk_proto->getsockopt(sk, level,
optname, optval, optlen);
return do_tls_getsockopt(sk, optname, optval, optlen);
err = sockopt_init_user(&opt, optval, optlen);
if (err)
return err;
err = do_tls_getsockopt(sk, optname, &opt);
if (err)
return err;
if (put_user(opt.optlen, optlen))
return -EFAULT;
return 0;
}
static int validate_crypto_info(const struct tls_crypto_info *crypto_info,

View File

@ -28,7 +28,10 @@
#include <linux/vm_sockets.h>
#include <linux/icmp.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <linux/tls.h>
#include "kselftest_harness.h"
#ifndef AF_VSOCK
@ -40,6 +43,18 @@
#ifndef ICMP_FILTER
#define ICMP_FILTER 1
#endif
#ifndef IPV6_HDRINCL
#define IPV6_HDRINCL 36
#endif
#ifndef IPV6_CHECKSUM
#define IPV6_CHECKSUM 7
#endif
#ifndef SOL_TLS
#define SOL_TLS 282
#endif
#ifndef TCP_ULP
#define TCP_ULP 31
#endif
/* ---------- netlink ---------- */
@ -394,4 +409,228 @@ TEST_F(raw, bad_optname)
ASSERT_EQ(sizeof(val), optlen);
}
/* ---------- raw (ipv6) ---------- */
FIXTURE(rawv6)
{
int fd;
};
FIXTURE_SETUP(rawv6)
{
self->fd = socket(AF_INET6, SOCK_RAW, IPPROTO_UDP);
if (self->fd < 0)
SKIP(return, "SOCK_RAW/IPv6 socket: %s", strerror(errno));
}
FIXTURE_TEARDOWN(rawv6)
{
if (self->fd >= 0)
close(self->fd);
}
TEST_F(rawv6, hdrincl_exact)
{
socklen_t optlen;
int val = -1;
optlen = sizeof(val);
ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_IPV6, IPV6_HDRINCL,
&val, &optlen));
ASSERT_EQ(sizeof(int), optlen);
ASSERT_TRUE(val == 0 || val == 1);
}
TEST_F(rawv6, hdrincl_oversize_clamped)
{
char buf[16] = {};
socklen_t optlen = sizeof(buf);
ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_IPV6, IPV6_HDRINCL,
buf, &optlen));
ASSERT_EQ(sizeof(int), optlen);
}
/* Raw int options clamp the reported length down to the user buffer
* instead of returning EINVAL on a short buffer.
*/
TEST_F(rawv6, hdrincl_undersize_clamped)
{
socklen_t optlen = 2;
int val = 0;
ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_IPV6, IPV6_HDRINCL,
&val, &optlen));
ASSERT_EQ(2, optlen);
}
TEST_F(rawv6, checksum_default)
{
socklen_t optlen;
int val = 0;
optlen = sizeof(val);
/* A non-ICMPv6 raw socket has the checksum disabled, reported as -1. */
ASSERT_EQ(0, getsockopt(self->fd, IPPROTO_IPV6, IPV6_CHECKSUM,
&val, &optlen));
ASSERT_EQ(sizeof(int), optlen);
ASSERT_EQ(-1, val);
}
TEST_F(rawv6, bad_optname)
{
socklen_t optlen;
int val;
optlen = sizeof(val);
/* SOL_RAW reaches do_rawv6_getsockopt() directly. */
ASSERT_EQ(-1, getsockopt(self->fd, SOL_RAW, 0x7fff, &val, &optlen));
ASSERT_EQ(ENOPROTOOPT, errno);
ASSERT_EQ(sizeof(val), optlen);
}
/* ---------- tls ---------- */
FIXTURE(tls)
{
int fd;
int sfd;
};
FIXTURE_SETUP(tls)
{
struct sockaddr_in a = {
.sin_family = AF_INET,
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
};
socklen_t alen = sizeof(a);
int lfd;
self->fd = -1;
self->sfd = -1;
lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd < 0)
SKIP(return, "TCP socket: %s", strerror(errno));
if (bind(lfd, (struct sockaddr *)&a, sizeof(a)) || listen(lfd, 1) ||
getsockname(lfd, (struct sockaddr *)&a, &alen)) {
close(lfd);
SKIP(return, "listener setup: %s", strerror(errno));
}
self->fd = socket(AF_INET, SOCK_STREAM, 0);
if (self->fd < 0) {
close(lfd);
SKIP(return, "TCP socket: %s", strerror(errno));
}
if (connect(self->fd, (struct sockaddr *)&a, sizeof(a))) {
close(lfd);
SKIP(return, "connect: %s", strerror(errno));
}
self->sfd = accept(lfd, NULL, NULL);
close(lfd);
if (setsockopt(self->fd, IPPROTO_TCP, TCP_ULP, "tls", sizeof("tls")))
SKIP(return, "TCP_ULP=tls: %s (built without TLS?)",
strerror(errno));
}
FIXTURE_TEARDOWN(tls)
{
if (self->fd >= 0)
close(self->fd);
if (self->sfd >= 0)
close(self->sfd);
}
/* do_tls_getsockopt_tx_zc(): fixed-size int, exact length required. */
TEST_F(tls, tx_zerocopy_exact)
{
socklen_t optlen = sizeof(int);
int val = -1;
ASSERT_EQ(0, getsockopt(self->fd, SOL_TLS, TLS_TX_ZEROCOPY_RO,
&val, &optlen));
ASSERT_EQ(sizeof(int), optlen);
ASSERT_TRUE(val == 0 || val == 1);
}
TEST_F(tls, tx_zerocopy_wrong_len)
{
socklen_t optlen = 2;
int val;
ASSERT_EQ(-1, getsockopt(self->fd, SOL_TLS, TLS_TX_ZEROCOPY_RO,
&val, &optlen));
ASSERT_EQ(EINVAL, errno);
}
/* do_tls_getsockopt_conf(): NULL optval still yields EINVAL -- the
* converted code tests opt->iter_out.ubuf in place of optval.
*/
TEST_F(tls, conf_null_optval)
{
socklen_t optlen = 64;
ASSERT_EQ(-1, getsockopt(self->fd, SOL_TLS, TLS_TX, NULL, &optlen));
ASSERT_EQ(EINVAL, errno);
}
TEST_F(tls, conf_short)
{
socklen_t optlen = 2;
char buf[2];
ASSERT_EQ(-1, getsockopt(self->fd, SOL_TLS, TLS_TX, buf, &optlen));
ASSERT_EQ(EINVAL, errno);
}
/* TLS_TX before crypto is set reports not-ready. */
TEST_F(tls, conf_not_ready)
{
struct tls_crypto_info info;
socklen_t optlen = sizeof(info);
ASSERT_EQ(-1, getsockopt(self->fd, SOL_TLS, TLS_TX, &info, &optlen));
ASSERT_EQ(EBUSY, errno);
}
/* Set TX crypto, then read it back at the base and full sizes, exercising
* both copy_to_iter() branches. SKIP if AES-GCM is unavailable.
*/
TEST_F(tls, conf_crypto_roundtrip)
{
struct tls12_crypto_info_aes_gcm_128 tx = {
.info.version = TLS_1_2_VERSION,
.info.cipher_type = TLS_CIPHER_AES_GCM_128,
};
struct tls12_crypto_info_aes_gcm_128 full;
struct tls_crypto_info base;
socklen_t optlen;
if (setsockopt(self->fd, SOL_TLS, TLS_TX, &tx, sizeof(tx)))
SKIP(return, "set TLS_TX aes_gcm_128: %s", strerror(errno));
optlen = sizeof(base);
ASSERT_EQ(0, getsockopt(self->fd, SOL_TLS, TLS_TX, &base, &optlen));
ASSERT_EQ(sizeof(base), optlen);
ASSERT_EQ(TLS_1_2_VERSION, base.version);
ASSERT_EQ(TLS_CIPHER_AES_GCM_128, base.cipher_type);
optlen = sizeof(full);
ASSERT_EQ(0, getsockopt(self->fd, SOL_TLS, TLS_TX, &full, &optlen));
ASSERT_EQ(sizeof(full), optlen);
ASSERT_EQ(TLS_CIPHER_AES_GCM_128, full.info.cipher_type);
}
TEST_F(tls, bad_optname)
{
socklen_t optlen = sizeof(int);
int val;
ASSERT_EQ(-1, getsockopt(self->fd, SOL_TLS, 0x7fff, &val, &optlen));
ASSERT_EQ(ENOPROTOOPT, errno);
}
TEST_HARNESS_MAIN