mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-30 14:33:24 -04:00
Merge branch 'net-convert-udp-getsockopt-to-sockopt_t'
Breno Leitao says:
====================
net: convert UDP getsockopt to sockopt_t
The leaf proto_ops getsockopt callbacks have been moving to the new
getsockopt_iter()/sockopt_t interface.
I was trying to get SMC into getsockop and retire .getsockopt, but,
I found the best approach is to keep converting other protocols.
This series starts the same conversion one layer down, at the struct proto
getsockopt path, beginning with UDP.
Example of the current code.
static int udp_getsockopt(struct sock *sk, int level, int optname,
char __user *optval, int __user *optlen)
{
if (level == SOL_UDP)
return udp_lib_getsockopt(sk, level, optname, optval, optlen);
return ip_getsockopt(sk, level, optname, optval, optlen);
}
We want udp_getsockopt to go to .getsockopt_iter, and there are two
approaches in this case:
1) Create a patchset that moves both of them to getsockopt_iter, which
is will be a huge change (ip_getsockopt() is used in many places)
2) Break this down, and transform from bottoms up. First
udp_lib_getsockopt() up to the point we can easily convert
others, such as ip_getsockopt().
I am taking the approach 2), so, the intermediate code will be something
like:
static int udp_getsockopt(struct sock *sk, int level, int optname,
char __user *optval, int __user *optlen)
{
sockopt_t opt;
int err;
if (level != SOL_UDP)
return ip_getsockopt(sk, level, optname, optval, optlen);
// Convert optlen/optval in sockopt // (first patch)
err = udp_lib_getsockopt(sk, level, optname, &opt);
}
The work is bottom-up and mergeable in small steps: a protocol's inner
getsockopt helper is switched to sockopt_t behind its existing thin
__user wrapper, one patch at a time.
Once every inner helper speaks sockopt_t, a later series flips the shared
struct proto.getsockopt and inet_connection_sock_af_ops.getsockopt signatures
and drops the transitional wrappers.
Signed-off-by: Breno Leitao <leitao@debian.org>
====================
Link: https://patch.msgid.link/20260630-getsockopt_phase2-v2-0-193335f3d4d1@debian.org
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
@@ -47,6 +47,29 @@ typedef struct sockopt {
|
||||
int optlen;
|
||||
} sockopt_t;
|
||||
|
||||
/*
|
||||
* Initialize a user-backed sockopt_t from the (optval, optlen) __user pair of
|
||||
* a getsockopt() callback. Used by transitional __user getsockopt wrappers
|
||||
* while the proto-layer callbacks are converted to take a sockopt_t; the
|
||||
* caller writes opt->optlen back to the user optlen after the callback.
|
||||
*/
|
||||
static inline int sockopt_init_user(sockopt_t *opt, char __user *optval,
|
||||
int __user *optlen)
|
||||
{
|
||||
int len;
|
||||
|
||||
if (get_user(len, optlen))
|
||||
return -EFAULT;
|
||||
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;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct poll_table_struct;
|
||||
struct pipe_inode_info;
|
||||
struct inode;
|
||||
|
||||
@@ -430,7 +430,7 @@ struct sk_buff *skb_udp_tunnel_segment(struct sk_buff *skb,
|
||||
netdev_features_t features,
|
||||
bool is_ipv6);
|
||||
int udp_lib_getsockopt(struct sock *sk, int level, int optname,
|
||||
char __user *optval, int __user *optlen);
|
||||
sockopt_t *opt);
|
||||
int udp_lib_setsockopt(struct sock *sk, int level, int optname,
|
||||
sockptr_t optval, unsigned int optlen,
|
||||
int (*push_pending_frames)(struct sock *));
|
||||
|
||||
@@ -809,23 +809,18 @@ static int raw_seticmpfilter(struct sock *sk, sockptr_t optval, int optlen)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int raw_geticmpfilter(struct sock *sk, char __user *optval, int __user *optlen)
|
||||
static int raw_geticmpfilter(struct sock *sk, sockopt_t *opt)
|
||||
{
|
||||
int len, ret = -EFAULT;
|
||||
int len = opt->optlen;
|
||||
|
||||
if (get_user(len, optlen))
|
||||
goto out;
|
||||
ret = -EINVAL;
|
||||
if (len < 0)
|
||||
goto out;
|
||||
return -EINVAL;
|
||||
if (len > sizeof(struct icmp_filter))
|
||||
len = sizeof(struct icmp_filter);
|
||||
ret = -EFAULT;
|
||||
if (put_user(len, optlen) ||
|
||||
copy_to_user(optval, &raw_sk(sk)->filter, len))
|
||||
goto out;
|
||||
ret = 0;
|
||||
out: return ret;
|
||||
opt->optlen = len;
|
||||
if (copy_to_iter(&raw_sk(sk)->filter, len, &opt->iter_out) != len)
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_raw_setsockopt(struct sock *sk, int optname,
|
||||
@@ -848,14 +843,13 @@ static int raw_setsockopt(struct sock *sk, int level, int optname,
|
||||
return do_raw_setsockopt(sk, optname, optval, optlen);
|
||||
}
|
||||
|
||||
static int do_raw_getsockopt(struct sock *sk, int optname,
|
||||
char __user *optval, int __user *optlen)
|
||||
static int do_raw_getsockopt(struct sock *sk, int optname, sockopt_t *opt)
|
||||
{
|
||||
if (optname == ICMP_FILTER) {
|
||||
if (inet_sk(sk)->inet_num != IPPROTO_ICMP)
|
||||
return -EOPNOTSUPP;
|
||||
else
|
||||
return raw_geticmpfilter(sk, optval, optlen);
|
||||
return raw_geticmpfilter(sk, opt);
|
||||
}
|
||||
return -ENOPROTOOPT;
|
||||
}
|
||||
@@ -863,9 +857,24 @@ static int do_raw_getsockopt(struct sock *sk, int optname,
|
||||
static int raw_getsockopt(struct sock *sk, int level, int optname,
|
||||
char __user *optval, int __user *optlen)
|
||||
{
|
||||
sockopt_t opt;
|
||||
int err;
|
||||
|
||||
if (level != SOL_RAW)
|
||||
return ip_getsockopt(sk, level, optname, optval, optlen);
|
||||
return do_raw_getsockopt(sk, optname, optval, optlen);
|
||||
|
||||
err = sockopt_init_user(&opt, optval, optlen);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = do_raw_getsockopt(sk, optname, &opt);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (put_user(opt.optlen, optlen))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int raw_ioctl(struct sock *sk, int cmd, int *karg)
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
|
||||
#include <linux/bpf-cgroup.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/uio.h>
|
||||
#include <asm/ioctls.h>
|
||||
#include <linux/memblock.h>
|
||||
#include <linux/highmem.h>
|
||||
@@ -2995,14 +2996,13 @@ static int udp_setsockopt(struct sock *sk, int level, int optname, sockptr_t opt
|
||||
}
|
||||
|
||||
int udp_lib_getsockopt(struct sock *sk, int level, int optname,
|
||||
char __user *optval, int __user *optlen)
|
||||
sockopt_t *opt)
|
||||
{
|
||||
struct udp_sock *up = udp_sk(sk);
|
||||
int val, len;
|
||||
|
||||
if (get_user(len, optlen))
|
||||
return -EFAULT;
|
||||
|
||||
len = opt->optlen;
|
||||
/* keep the check so direct sockopt_t callers stay covered. */
|
||||
if (len < 0)
|
||||
return -EINVAL;
|
||||
|
||||
@@ -3037,9 +3037,8 @@ int udp_lib_getsockopt(struct sock *sk, int level, int optname,
|
||||
return -ENOPROTOOPT;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -3047,9 +3046,29 @@ int udp_lib_getsockopt(struct sock *sk, int level, int optname,
|
||||
static int udp_getsockopt(struct sock *sk, int level, int optname,
|
||||
char __user *optval, int __user *optlen)
|
||||
{
|
||||
if (level == SOL_UDP)
|
||||
return udp_lib_getsockopt(sk, level, optname, optval, optlen);
|
||||
return ip_getsockopt(sk, level, optname, optval, optlen);
|
||||
sockopt_t opt;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* keep the old __user pointers, until ip_getsockopt() moves
|
||||
* to sockopt_t
|
||||
*/
|
||||
if (level != SOL_UDP)
|
||||
return ip_getsockopt(sk, level, optname, optval, optlen);
|
||||
|
||||
err = sockopt_init_user(&opt, optval, optlen);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = udp_lib_getsockopt(sk, level, optname, &opt);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* optval was written by copy_to_iter() in udp_lib_getsockopt() */
|
||||
if (put_user(opt.optlen, optlen))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1826,9 +1826,22 @@ static int udpv6_setsockopt(struct sock *sk, int level, int optname,
|
||||
static int udpv6_getsockopt(struct sock *sk, int level, int optname,
|
||||
char __user *optval, int __user *optlen)
|
||||
{
|
||||
if (level == SOL_UDP)
|
||||
return udp_lib_getsockopt(sk, level, optname, optval, optlen);
|
||||
return ipv6_getsockopt(sk, level, optname, optval, optlen);
|
||||
sockopt_t opt;
|
||||
int err;
|
||||
|
||||
if (level != SOL_UDP)
|
||||
return ipv6_getsockopt(sk, level, optname, optval, optlen);
|
||||
|
||||
err = sockopt_init_user(&opt, optval, optlen);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = udp_lib_getsockopt(sk, level, optname, &opt);
|
||||
if (err)
|
||||
return err;
|
||||
if (put_user(opt.optlen, optlen))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
* that always reports the required buffer length back via optlen,
|
||||
* even when the user buffer is too small to receive any group bits.
|
||||
* - vsock: SO_VM_SOCKETS_BUFFER_SIZE covers the u64 path.
|
||||
* - raw: ICMP_FILTER covers a fixed-size struct payload that clamps
|
||||
* the length down on a short buffer instead of failing.
|
||||
*
|
||||
* Author: Breno Leitao <leitao@debian.org>
|
||||
*/
|
||||
@@ -24,12 +26,20 @@
|
||||
#include <linux/rtnetlink.h>
|
||||
#include <linux/time_types.h>
|
||||
#include <linux/vm_sockets.h>
|
||||
#include <linux/icmp.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include "kselftest_harness.h"
|
||||
|
||||
#ifndef AF_VSOCK
|
||||
#define AF_VSOCK 40
|
||||
#endif
|
||||
#ifndef SOL_RAW
|
||||
#define SOL_RAW 255
|
||||
#endif
|
||||
#ifndef ICMP_FILTER
|
||||
#define ICMP_FILTER 1
|
||||
#endif
|
||||
|
||||
/* ---------- netlink ---------- */
|
||||
|
||||
@@ -297,4 +307,91 @@ TEST_F(vsock, connect_timeout_old_exact)
|
||||
ASSERT_EQ(sizeof(tv), optlen);
|
||||
}
|
||||
|
||||
/* ---------- raw (ipv4) ---------- */
|
||||
|
||||
FIXTURE(raw)
|
||||
{
|
||||
int fd;
|
||||
};
|
||||
|
||||
FIXTURE_SETUP(raw)
|
||||
{
|
||||
struct icmp_filter filt = { .data = 0xdeadbeef };
|
||||
|
||||
self->fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
|
||||
if (self->fd < 0)
|
||||
SKIP(return, "SOCK_RAW/ICMP socket: %s", strerror(errno));
|
||||
|
||||
if (setsockopt(self->fd, SOL_RAW, ICMP_FILTER, &filt, sizeof(filt)) < 0)
|
||||
SKIP(return, "set ICMP_FILTER: %s", strerror(errno));
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(raw)
|
||||
{
|
||||
if (self->fd >= 0)
|
||||
close(self->fd);
|
||||
}
|
||||
|
||||
TEST_F(raw, icmpfilter_exact)
|
||||
{
|
||||
struct icmp_filter filt = {};
|
||||
socklen_t optlen = sizeof(filt);
|
||||
|
||||
ASSERT_EQ(0, getsockopt(self->fd, SOL_RAW, ICMP_FILTER,
|
||||
&filt, &optlen));
|
||||
ASSERT_EQ(sizeof(filt), optlen);
|
||||
ASSERT_EQ(0xdeadbeef, filt.data);
|
||||
}
|
||||
|
||||
TEST_F(raw, icmpfilter_oversize_clamped)
|
||||
{
|
||||
char buf[16] = {};
|
||||
socklen_t optlen = sizeof(buf);
|
||||
|
||||
ASSERT_EQ(0, getsockopt(self->fd, SOL_RAW, ICMP_FILTER,
|
||||
buf, &optlen));
|
||||
ASSERT_EQ(sizeof(struct icmp_filter), optlen);
|
||||
}
|
||||
|
||||
/* Unlike the int/u64 options above, ICMP_FILTER clamps the length down
|
||||
* to the user buffer instead of returning EINVAL: a short buffer
|
||||
* succeeds and reports the truncated length back via optlen.
|
||||
*/
|
||||
TEST_F(raw, icmpfilter_undersize_clamped)
|
||||
{
|
||||
char buf[2] = {};
|
||||
socklen_t optlen = sizeof(buf);
|
||||
|
||||
ASSERT_EQ(0, getsockopt(self->fd, SOL_RAW, ICMP_FILTER,
|
||||
buf, &optlen));
|
||||
ASSERT_EQ(sizeof(buf), optlen);
|
||||
}
|
||||
|
||||
TEST_F(raw, icmpfilter_wrong_proto)
|
||||
{
|
||||
struct icmp_filter filt;
|
||||
socklen_t optlen = sizeof(filt);
|
||||
int fd;
|
||||
|
||||
fd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
|
||||
if (fd < 0)
|
||||
SKIP(return, "SOCK_RAW/UDP socket: %s", strerror(errno));
|
||||
|
||||
ASSERT_EQ(-1, getsockopt(fd, SOL_RAW, ICMP_FILTER, &filt, &optlen));
|
||||
ASSERT_EQ(EOPNOTSUPP, errno);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
TEST_F(raw, bad_optname)
|
||||
{
|
||||
socklen_t optlen;
|
||||
int val;
|
||||
|
||||
optlen = sizeof(val);
|
||||
|
||||
ASSERT_EQ(-1, getsockopt(self->fd, SOL_RAW, 0x7fff, &val, &optlen));
|
||||
ASSERT_EQ(ENOPROTOOPT, errno);
|
||||
ASSERT_EQ(sizeof(val), optlen);
|
||||
}
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
|
||||
Reference in New Issue
Block a user