diff --git a/net/socket/CMakeLists.txt b/net/socket/CMakeLists.txt index 776b190c7255d..fde54729e5b1c 100644 --- a/net/socket/CMakeLists.txt +++ b/net/socket/CMakeLists.txt @@ -44,6 +44,12 @@ set(SRCS net_poll.c net_fstat.c) +# User-space address environment bounce buffering for msghdr + +if(CONFIG_BUILD_KERNEL) + list(APPEND SRCS msg_copyusr.c) +endif() + # Socket options if(CONFIG_NET_SOCKOPTS) diff --git a/net/socket/Make.defs b/net/socket/Make.defs index 84f9527633fa6..3e0aae3650a84 100644 --- a/net/socket/Make.defs +++ b/net/socket/Make.defs @@ -27,6 +27,12 @@ SOCK_CSRCS += listen.c recv.c recvfrom.c send.c sendto.c socket.c SOCK_CSRCS += socketpair.c net_close.c recvmsg.c sendmsg.c shutdown.c SOCK_CSRCS += net_dup2.c net_sockif.c net_poll.c net_fstat.c +# User-space address environment bounce buffering for msghdr + +ifeq ($(CONFIG_BUILD_KERNEL),y) +SOCK_CSRCS += msg_copyusr.c +endif + # Socket options ifeq ($(CONFIG_NET_SOCKOPTS),y) diff --git a/net/socket/msg_copyusr.c b/net/socket/msg_copyusr.c new file mode 100644 index 0000000000000..ff3f4c9b1d884 --- /dev/null +++ b/net/socket/msg_copyusr.c @@ -0,0 +1,309 @@ +/**************************************************************************** + * net/socket/msg_copyusr.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#if defined(CONFIG_BUILD_KERNEL) && defined(CONFIG_ARCH_ADDRENV) + +#include +#include +#include + +#include +#include + +#include "socket/socket.h" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: msg_alloc_kbuf + * + * Description: + * In CONFIG_BUILD_KERNEL + CONFIG_ARCH_ADDRENV builds each user process + * has its own MMU page table. When a blocking socket call yields, the + * scheduler may switch to a different user process, changing the active + * address environment (SATP on RISC-V, TTBR on ARM). Network callbacks + * that fire in that context and copy data to/from the original user-space + * iov_base / msg_name / msg_control pointers would access the wrong + * physical memory. + * + * This function creates a kernel-owned copy of the relevant msghdr + * fields (iov payload, msg_name, msg_control) so that all internal + * network stack accesses go through kernel virtual addresses, which are + * mapped identically in every address environment. + * + * Only a single iovec (msg_iovlen == 1) is handled. All protocols + * that reach blocking code already enforce this restriction. + * + * Input Parameters: + * src - Original user-space msghdr (read-only after this call). + * + * Returned Value: + * Pointer to the kernel msghdr on success; NULL on failure. + * + ****************************************************************************/ + +FAR struct msghdr *msg_alloc_kbuf(FAR const struct msghdr *src) +{ + FAR struct msg_kbuf_s *kbuf; + FAR struct iob_s *meta_iob; + FAR uint8_t *meta; + size_t payload_len; + size_t cmsg_len; + size_t name_len; + size_t fixed_len; + size_t payload_room; + bool has_payload; + bool has_cmsg; + bool has_name; + + static_assert(sizeof(struct msg_kbuf_s) <= CONFIG_IOB_BUFSIZE, + "msg_kbuf_s does not fit in one IOB"); + + has_payload = (src->msg_iovlen >= 1 && src->msg_iov != NULL && + src->msg_iov->iov_base != NULL && + src->msg_iov->iov_len > 0); + has_cmsg = (src->msg_controllen > 0 && src->msg_control != NULL); + has_name = (src->msg_namelen > 0 && src->msg_name != NULL); + + payload_len = has_payload ? src->msg_iov->iov_len : 0; + cmsg_len = has_cmsg ? src->msg_controllen : 0; + name_len = has_name ? src->msg_namelen : 0; + + /* Keep all fixed-size state in the first IOB: + * [msg_kbuf_s][msg_name][msg_control]. Place payload there too when it + * fits; otherwise allocate payload from kmm. + */ + + fixed_len = sizeof(struct msg_kbuf_s) + name_len + cmsg_len; + if (fixed_len > CONFIG_IOB_BUFSIZE) + { + /* CONFIG_IOB_BUFSIZE must include room for msg_kbuf_s plus all + * fixed per-message data (msg_name + msg_control). + */ + + return NULL; + } + + meta_iob = iob_alloc(false); + if (meta_iob == NULL) + { + return NULL; + } + + iob_reserve(meta_iob, sizeof(struct msg_kbuf_s)); + kbuf = (FAR struct msg_kbuf_s *)meta_iob->io_data; + meta = IOB_DATA(meta_iob); + + /* Start with a shallow copy so non-pointer fields are correct. */ + + memcpy(&kbuf->kmsg, src, sizeof(struct msghdr)); + kbuf->iob_meta = meta_iob; + kbuf->payload_kmem = NULL; + + if (has_name) + { + memset(meta, 0, name_len); + kbuf->kmsg.msg_name = meta; + meta += name_len; + } + + if (has_cmsg) + { + memset(meta, 0, cmsg_len); + kbuf->kmsg.msg_control = meta; + meta += cmsg_len; + } + + payload_room = CONFIG_IOB_BUFSIZE - fixed_len; + + if (has_payload) + { + kbuf->kmsg.msg_iov = &kbuf->kiov; + kbuf->kmsg.msg_iovlen = 1; + + if (payload_len <= payload_room) + { + kbuf->kiov.iov_base = meta; + kbuf->kiov.iov_len = payload_len; + } + else + { + kbuf->payload_kmem = kmm_malloc(payload_len); + if (kbuf->payload_kmem == NULL) + { + iob_free(meta_iob); + return NULL; + } + + kbuf->kiov.iov_base = kbuf->payload_kmem; + kbuf->kiov.iov_len = payload_len; + } + } + + return &kbuf->kmsg; +} + +/**************************************************************************** + * Name: msg_copy_from_user + * + * Description: + * Copy user-space send data into the kernel bounce buffers allocated by + * msg_alloc_kbuf(). Call this after alloc and before psock_sendmsg. + * + * Input Parameters: + * src - Original user msghdr whose payload and control data to copy. + * kbuf - Kernel msghdr state filled by msg_alloc_kbuf(). + * + ****************************************************************************/ + +void msg_copy_from_user(FAR const struct msghdr *src, + FAR struct msg_kbuf_s *kbuf) +{ + /* Copy iov payload from user to kernel IOB. */ + + if (src->msg_iovlen > 0 && src->msg_iov != NULL && + src->msg_iov->iov_len > 0 && kbuf->kmsg.msg_iov != NULL) + { + memcpy(kbuf->kmsg.msg_iov->iov_base, src->msg_iov->iov_base, + src->msg_iov->iov_len); + } + + /* Copy ancillary control data from user to kernel IOB. */ + + if (src->msg_controllen > 0 && src->msg_control != NULL && + kbuf->kmsg.msg_control != NULL) + { + memcpy(kbuf->kmsg.msg_control, src->msg_control, + src->msg_controllen); + } +} + +/**************************************************************************** + * Name: msg_copy_to_user + * + * Description: + * Copy received data from the kernel bounce buffers back to the original + * user-space msghdr fields. Call this after psock_recvmsg() returns + * successfully, while the correct address environment is active. + * + * Input Parameters: + * dst - Original user-space msghdr to update. + * ksrc - Kernel msghdr state filled and used by psock_recvmsg(). + * recvlen - Number of payload bytes returned by psock_recvmsg(). + * + ****************************************************************************/ + +void msg_copy_to_user(FAR struct msghdr *dst, + FAR const struct msghdr *src, + ssize_t recvlen) +{ + FAR const struct msg_kbuf_s *ksrc = (FAR const struct msg_kbuf_s *)src; + + /* Only copy results back on success; on error the user buffers + * must not be touched as they may contain partial or undefined data. + */ + + if (recvlen < 0) + { + return; + } + + /* Copy received payload back to the user iov buffer. */ + + if (recvlen > 0 && dst->msg_iov != NULL && dst->msg_iovlen > 0 && + ksrc->kmsg.msg_iov != NULL) + { + memcpy(dst->msg_iov->iov_base, ksrc->kmsg.msg_iov->iov_base, recvlen); + } + + /* Copy source address back. */ + + if (ksrc->kmsg.msg_name != NULL && dst->msg_name != NULL) + { + memcpy(dst->msg_name, ksrc->kmsg.msg_name, + ksrc->kmsg.msg_namelen); + } + + /* Copy ancillary data back. + * psock_recvmsg() decrements msg_controllen by the amount consumed via + * cmsg_append(); the difference is the number of bytes written. + */ + + if (ksrc->kmsg.msg_control != NULL && dst->msg_control != NULL) + { + size_t written = ksrc->kmsg.msg_controllen; + if (written > 0) + { + memcpy(dst->msg_control, ksrc->kmsg.msg_control, written); + } + } + + /* Propagate output fields. */ + + dst->msg_namelen = ksrc->kmsg.msg_namelen; + dst->msg_controllen = ksrc->kmsg.msg_controllen; + dst->msg_flags = ksrc->kmsg.msg_flags; +} + +/**************************************************************************** + * Name: msg_free_kbuf + * + * Description: + * Release all kernel memory allocated by msg_alloc_kbuf(). + * Safe to call with partially-initialised state (NULL pointers are + * ignored by iob_free). + * + * Input Parameters: + * kbuf - Kernel msghdr state to release. + * + ****************************************************************************/ + +void msg_free_kbuf(FAR struct msghdr *msg) +{ + FAR struct msg_kbuf_s *kbuf = (FAR struct msg_kbuf_s *)msg; + + if (kbuf == NULL) + { + return; + } + + if (kbuf->payload_kmem != NULL) + { + kmm_free(kbuf->payload_kmem); + kbuf->payload_kmem = NULL; + } + + if (kbuf->iob_meta != NULL) + { + iob_free(kbuf->iob_meta); + } +} + +#endif /* CONFIG_BUILD_KERNEL && CONFIG_ARCH_ADDRENV */ diff --git a/net/socket/recvmsg.c b/net/socket/recvmsg.c index 9543c6b6f6e5b..281fb3735dcb1 100644 --- a/net/socket/recvmsg.c +++ b/net/socket/recvmsg.c @@ -178,7 +178,40 @@ ssize_t recvmsg(int sockfd, FAR struct msghdr *msg, int flags) if (ret == OK) { - ret = psock_recvmsg(psock, msg, flags); +#if defined(CONFIG_BUILD_KERNEL) && defined(CONFIG_ARCH_ADDRENV) + FAR struct msghdr *umsg = msg; + + /* In kernel build with address environments each user process has its + * own MMU page table (SATP on RISC-V, TTBR on ARM). When this task + * blocks waiting for data, the scheduler may switch to a different + * user process, changing the active address environment. The network + * callback that eventually copies received data fires in that context + * and would write to the wrong physical memory if it used the original + * user-space iov_base pointer directly. + * + * Fix: allocate kernel-side IOB bounce buffers and redirect the msghdr + * to them before the shared psock_recvmsg() call below. After the + * call (back in the correct address environment) copy results back. + * See net/socket/msg_copyusr.c for the helpers. + */ + + msg = msg_alloc_kbuf(msg); + if (msg == NULL) + { + ret = -ENOMEM; + } +#endif + + if (ret == OK) + { + ret = psock_recvmsg(psock, msg, flags); + +#if defined(CONFIG_BUILD_KERNEL) && defined(CONFIG_ARCH_ADDRENV) + msg_copy_to_user(umsg, msg, ret); + msg_free_kbuf(msg); +#endif + } + file_put(filep); } diff --git a/net/socket/sendmsg.c b/net/socket/sendmsg.c index cbcc647ebbfc0..7af75918ec488 100644 --- a/net/socket/sendmsg.c +++ b/net/socket/sendmsg.c @@ -157,7 +157,39 @@ ssize_t sendmsg(int sockfd, FAR struct msghdr *msg, int flags) if (ret == OK) { - ret = psock_sendmsg(psock, msg, flags); +#if defined(CONFIG_BUILD_KERNEL) && defined(CONFIG_ARCH_ADDRENV) + FAR struct msghdr *umsg = msg; + + /* In kernel build with address environments the TX callback that + * calls devif_send() may fire while a different user process is + * active (different SATP/TTBR), causing reads from the wrong + * physical memory if the original user-space iov_base is used. + * + * Fix: copy user payload and control data into kernel-owned IOB + * bounce buffers and redirect the msghdr to them before the shared + * psock_sendmsg() call below so the callback always reads from + * kernel addresses regardless of the active address environment. + * See net/socket/msg_copyusr.c for the helpers. + */ + + msg = msg_alloc_kbuf(msg); + if (msg == NULL) + { + ret = -ENOMEM; + } + + if (ret == OK) + { + msg_copy_from_user(umsg, (FAR struct msg_kbuf_s *)msg); +#endif + + ret = psock_sendmsg(psock, msg, flags); + +#if defined(CONFIG_BUILD_KERNEL) && defined(CONFIG_ARCH_ADDRENV) + msg_free_kbuf(msg); + } +#endif + file_put(filep); } diff --git a/net/socket/socket.h b/net/socket/socket.h index d05c66c83bec9..e437b83a5bd76 100644 --- a/net/socket/socket.h +++ b/net/socket/socket.h @@ -34,6 +34,8 @@ #include #include +#include + #include #include #include @@ -160,6 +162,40 @@ extern "C" FAR const struct sock_intf_s * net_sockif(sa_family_t family, int type, int protocol); +/**************************************************************************** + * Name: msg_kbuf_s + * + * Description: + * Kernel-side msghdr bounce-buffer state used by msg_alloc_kbuf(), + * msg_copy_from_user(), msg_copy_to_user() and msg_free_kbuf(). + * + * Only relevant when CONFIG_BUILD_KERNEL && CONFIG_ARCH_ADDRENV because + * that is the only configuration where the MMU address environment can + * change while a blocking socket call is in flight. + * + ****************************************************************************/ + +#if defined(CONFIG_BUILD_KERNEL) && defined(CONFIG_ARCH_ADDRENV) +struct msg_kbuf_s +{ + struct msghdr kmsg; /* Kernel copy of user msghdr */ + struct iovec kiov; /* Kernel iovec entry */ + FAR struct iob_s *iob_meta; /* IOB containing this structure */ + + /* Heap payload when not in iob_meta */ + + FAR void *payload_kmem; +}; + +FAR struct msghdr *msg_alloc_kbuf(FAR const struct msghdr *src); +void msg_copy_from_user(FAR const struct msghdr *src, + FAR struct msg_kbuf_s *kbuf); +void msg_copy_to_user(FAR struct msghdr *dst, + FAR const struct msghdr *src, + ssize_t recvlen); +void msg_free_kbuf(FAR struct msghdr *msg); +#endif /* CONFIG_BUILD_KERNEL && CONFIG_ARCH_ADDRENV */ + /**************************************************************************** * Name: net_timeo *