summaryrefslogtreecommitdiff
path: root/net/socket.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/socket.c')
-rw-r--r--net/socket.c105
1 files changed, 73 insertions, 32 deletions
diff --git a/net/socket.c b/net/socket.c
index 89ad750ea536..22a412fdec07 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -77,6 +77,7 @@
#include <linux/mount.h>
#include <linux/pseudo_fs.h>
#include <linux/security.h>
+#include <linux/uio.h>
#include <linux/syscalls.h>
#include <linux/compat.h>
#include <linux/kmod.h>
@@ -280,23 +281,18 @@ static int move_addr_to_user(struct sockaddr_storage *kaddr, int klen,
BUG_ON(klen > sizeof(struct sockaddr_storage));
- if (can_do_masked_user_access())
- ulen = masked_user_access_begin(ulen);
- else if (!user_access_begin(ulen, 4))
- return -EFAULT;
-
- unsafe_get_user(len, ulen, efault_end);
-
- if (len > klen)
- len = klen;
- /*
- * "fromlen shall refer to the value before truncation.."
- * 1003.1g
- */
- if (len >= 0)
- unsafe_put_user(klen, ulen, efault_end);
+ scoped_user_rw_access_size(ulen, 4, efault_end) {
+ unsafe_get_user(len, ulen, efault_end);
- user_access_end();
+ if (len > klen)
+ len = klen;
+ /*
+ * "fromlen shall refer to the value before truncation.."
+ * 1003.1g
+ */
+ if (len >= 0)
+ unsafe_put_user(klen, ulen, efault_end);
+ }
if (len) {
if (len < 0)
@@ -309,7 +305,6 @@ static int move_addr_to_user(struct sockaddr_storage *kaddr, int klen,
return 0;
efault_end:
- user_access_end();
return -EFAULT;
}
@@ -977,11 +972,10 @@ void __sock_recv_timestamp(struct msghdr *msg, struct sock *sk,
{
int need_software_tstamp = sock_flag(sk, SOCK_RCVTSTAMP);
int new_tstamp = sock_flag(sk, SOCK_TSTAMP_NEW);
- struct scm_timestamping_internal tss;
- int empty = 1, false_tstamp = 0;
struct skb_shared_hwtstamps *shhwtstamps =
skb_hwtstamps(skb);
- int if_index;
+ struct scm_timestamping_internal tss;
+ int if_index, false_tstamp = 0;
ktime_t hwtstamp;
u32 tsflags;
@@ -1026,12 +1020,12 @@ void __sock_recv_timestamp(struct msghdr *msg, struct sock *sk,
memset(&tss, 0, sizeof(tss));
tsflags = READ_ONCE(sk->sk_tsflags);
- if ((tsflags & SOF_TIMESTAMPING_SOFTWARE &&
- (tsflags & SOF_TIMESTAMPING_RX_SOFTWARE ||
- skb_is_err_queue(skb) ||
- !(tsflags & SOF_TIMESTAMPING_OPT_RX_FILTER))) &&
- ktime_to_timespec64_cond(skb->tstamp, tss.ts + 0))
- empty = 0;
+ if (tsflags & SOF_TIMESTAMPING_SOFTWARE &&
+ (tsflags & SOF_TIMESTAMPING_RX_SOFTWARE ||
+ skb_is_err_queue(skb) ||
+ !(tsflags & SOF_TIMESTAMPING_OPT_RX_FILTER)))
+ tss.ts[0] = skb->tstamp;
+
if (shhwtstamps &&
(tsflags & SOF_TIMESTAMPING_RAW_HARDWARE &&
(tsflags & SOF_TIMESTAMPING_RX_HARDWARE ||
@@ -1048,15 +1042,15 @@ void __sock_recv_timestamp(struct msghdr *msg, struct sock *sk,
hwtstamp = ptp_convert_timestamp(&hwtstamp,
READ_ONCE(sk->sk_bind_phc));
- if (ktime_to_timespec64_cond(hwtstamp, tss.ts + 2)) {
- empty = 0;
+ if (hwtstamp) {
+ tss.ts[2] = hwtstamp;
if ((tsflags & SOF_TIMESTAMPING_OPT_PKTINFO) &&
!skb_is_err_queue(skb))
put_ts_pktinfo(msg, skb, if_index);
}
}
- if (!empty) {
+ if (tss.ts[0] | tss.ts[2]) {
if (sock_flag(sk, SOCK_TSTAMP_NEW))
put_cmsg_scm_timestamping64(msg, &tss);
else
@@ -2421,11 +2415,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);
@@ -2438,15 +2466,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)