This patch introduces a compat version of the tcp_zerocopy_receive structure. Subsequently additional helper functions have been added to convert between compat and purecap strucutres.
Signed-off-by: Harry Ramsey harry.ramsey@arm.com --- include/linux/sockptr.h | 14 +++++++ include/uapi/linux/tcp.h | 15 +++++++ net/ipv4/tcp.c | 87 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 108 insertions(+), 8 deletions(-)
diff --git a/include/linux/sockptr.h b/include/linux/sockptr.h index c81355d61d07..fd270378e8c8 100644 --- a/include/linux/sockptr.h +++ b/include/linux/sockptr.h @@ -69,6 +69,20 @@ static inline int copy_from_sockptr(void *dst, sockptr_t src, size_t size) return copy_from_sockptr_offset(dst, src, 0, size); }
+static inline int copy_to_sockptr_offset_with_ptr(sockptr_t dst, size_t offset, + const void *src, size_t size) +{ + if (!sockptr_is_kernel(dst)) + return copy_to_user_with_ptr(dst.user + offset, src, size); + memcpy(dst.kernel + offset, src, size); + return 0; +} + +static inline int copy_to_sockptr_with_ptr(sockptr_t dst, const void *src, size_t size) +{ + return copy_to_sockptr_offset_with_ptr(dst, 0, src, size); +} + static inline int copy_to_sockptr_offset(sockptr_t dst, size_t offset, const void *src, size_t size) { diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h index 3854b7ace73e..631d5f3c11fd 100644 --- a/include/uapi/linux/tcp.h +++ b/include/uapi/linux/tcp.h @@ -365,4 +365,19 @@ struct tcp_zerocopy_receive { __u32 msg_flags; __u32 reserved; /* set to 0 for now */ }; + +struct tcp_zerocopy_receive_compat { + __u64 address; /* in: address of mapping */ + __u32 length; /* in/out: number of bytes to map/mapped */ + __u32 recv_skip_hint; /* out: amount of bytes to skip */ + __u32 inq; /* out: amount of bytes in read queue */ + __s32 err; /* out: socket error */ + __u64 copybuf_address; /* in: copybuf address (small reads) */ + __s32 copybuf_len; /* in/out: copybuf bytes avail/used or error */ + __u32 flags; /* in: flags */ + __u64 msg_control; /* ancillary data */ + __u64 msg_controllen; + __u32 msg_flags; + __u32 reserved; /* set to 0 for now */ +}; #endif /* _UAPI_LINUX_TCP_H */ diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 440b00e763e6..f4e7d2d3922b 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -1941,6 +1941,59 @@ static int find_next_mappable_frag(const skb_frag_t *frag, return offset; }
+static inline bool in_compat64(void) +{ + return IS_ENABLED(CONFIG_COMPAT64) && in_compat_syscall(); +} + +static int tcp_zerocopy_receive_len(void) +{ + if (in_compat64()) + return sizeof(struct tcp_zerocopy_receive_compat); + + return sizeof(struct tcp_zerocopy_receive); +} + +static void copy_tcp_zerocopy_from_user(struct tcp_zerocopy_receive *zc, + struct tcp_zerocopy_receive_compat *zcc) +{ + if (!in_compat64()) + return; + + zc->address = (__kernel_uintptr_t)compat_ptr(zcc->address); + zc->length = zcc->length; + zc->recv_skip_hint = zcc->recv_skip_hint; + zc->inq = zcc->inq; + zc->err = zcc->err; + zc->copybuf_address = (__kernel_uintptr_t)compat_ptr(zcc->copybuf_address); + zc->copybuf_len = zcc->copybuf_len; + zc->flags = zcc->flags; + zc->msg_control = (__kernel_uintptr_t)compat_ptr(zcc->msg_control); + zc->msg_controllen = zcc->msg_controllen; + zc->msg_flags = zcc->msg_flags; + zc->reserved = zcc->reserved; +} + +static void copy_tcp_zerocopy_to_user(struct tcp_zerocopy_receive *zc, + struct tcp_zerocopy_receive_compat *zcc) +{ + if (!in_compat64()) + return; + + zcc->address = zc->address; + zcc->length = zc->length; + zcc->recv_skip_hint = zc->recv_skip_hint; + zcc->inq = zc->inq; + zcc->err = zc->err; + zcc->copybuf_address = zc->copybuf_address; + zcc->copybuf_len = zc->copybuf_len; + zcc->flags = zc->flags; + zcc->msg_control = zc->msg_control; + zcc->msg_controllen = zc->msg_controllen; + zcc->msg_flags = zc->msg_flags; + zcc->reserved = zc->reserved; +} + static void tcp_zerocopy_set_hint_for_skb(struct sock *sk, struct tcp_zerocopy_receive *zc, struct sk_buff *skb, u32 offset) @@ -2234,6 +2287,7 @@ static int tcp_zerocopy_receive(struct sock *sk, mmap_read_unlock(current->mm); return -EINVAL; } + vma_len = min_t(unsigned long, zc->length, vma->vm_end - address); avail_len = min_t(u32, vma_len, inq); total_bytes_to_map = avail_len & ~(PAGE_SIZE - 1); @@ -4326,6 +4380,7 @@ int do_tcp_getsockopt(struct sock *sk, int level, case TCP_ZEROCOPY_RECEIVE: { struct scm_timestamping_internal tss; struct tcp_zerocopy_receive zc = {}; + struct tcp_zerocopy_receive_compat zcc = {}; int err;
if (copy_from_sockptr(&len, optlen, sizeof(int))) @@ -4333,17 +4388,24 @@ int do_tcp_getsockopt(struct sock *sk, int level, if (len < 0 || len < offsetofend(struct tcp_zerocopy_receive, length)) return -EINVAL; - if (unlikely(len > sizeof(zc))) { - err = check_zeroed_sockptr(optval, sizeof(zc), - len - sizeof(zc)); + if (unlikely(len > tcp_zerocopy_receive_len())) { + err = check_zeroed_sockptr(optval, + tcp_zerocopy_receive_len(), + len - tcp_zerocopy_receive_len()); if (err < 1) return err == 0 ? -EINVAL : err; - len = sizeof(zc); + len = tcp_zerocopy_receive_len(); if (copy_to_sockptr(optlen, &len, sizeof(int))) return -EFAULT; } - if (copy_from_sockptr_with_ptr(&zc, optval, len)) - return -EFAULT; + if (in_compat64()) { + if (copy_from_sockptr(&zcc, optval, len)) + return -EFAULT; + copy_tcp_zerocopy_from_user(&zc, &zcc); + } else { + if (copy_from_sockptr_with_ptr(&zc, optval, len)) + return -EFAULT; + } if (zc.reserved) return -EINVAL; if (zc.msg_flags & ~(TCP_VALID_ZC_MSG_FLAGS)) @@ -4382,8 +4444,17 @@ int do_tcp_getsockopt(struct sock *sk, int level, zerocopy_rcv_inq: zc.inq = tcp_inq_hint(sk); zerocopy_rcv_out: - if (!err && copy_to_sockptr(optval, &zc, len)) - err = -EFAULT; + if (!err) { + if (in_compat64()) { + copy_tcp_zerocopy_to_user(&zc, &zcc); + if (copy_to_sockptr(optval, &zcc, len)) + return -EFAULT; + } else { + // convert to capability copy. + if (copy_to_sockptr_with_ptr(optval, &zc, len)) + return -EFAULT; + } + } return err; } #endif