+ return error;
+}
+
+int
+sendmsg_x(struct proc *p, struct sendmsg_x_args *uap, user_ssize_t *retval)
+{
+ int error = 0;
+ struct user_msghdr_x *user_msg_x = NULL;
+ struct uio **uiop = NULL;
+ struct socket *so;
+ u_int i;
+ struct sockaddr *to = NULL;
+ user_ssize_t len_before = 0, len_after;
+ int need_drop = 0;
+ size_t size_of_msghdr;
+ void *umsgp = NULL;
+ u_int uiocnt;
+ int has_addr_or_ctl = 0;
+
+ KERNEL_DEBUG(DBG_FNC_SENDMSG_X | DBG_FUNC_START, 0, 0, 0, 0, 0);
+
+ size_of_msghdr = IS_64BIT_PROCESS(p) ?
+ sizeof(struct user64_msghdr_x) : sizeof(struct user32_msghdr_x);
+
+ if (uap->flags & MSG_SKIPCFIL) {
+ error = EPERM;
+ goto out;
+ }
+
+ error = file_socket(uap->s, &so);
+ if (error) {
+ goto out;
+ }
+ need_drop = 1;
+ if (so == NULL) {
+ error = EBADF;
+ goto out;
+ }
+
+ /*
+ * Input parameter range check
+ */
+ if (uap->cnt == 0 || uap->cnt > UIO_MAXIOV) {
+ error = EINVAL;
+ goto out;
+ }
+ /*
+ * Clip to max currently allowed
+ */
+ if (uap->cnt > somaxsendmsgx) {
+ uap->cnt = somaxsendmsgx;
+ }
+
+ user_msg_x = kheap_alloc(KHEAP_TEMP,
+ uap->cnt * sizeof(struct user_msghdr_x), Z_WAITOK | Z_ZERO);
+ if (user_msg_x == NULL) {
+ DBG_PRINTF("%s kheap_alloc user_msg_x failed\n", __func__);
+ error = ENOMEM;
+ goto out;
+ }
+ uiop = kheap_alloc(KHEAP_TEMP,
+ uap->cnt * sizeof(struct uio *), Z_WAITOK | Z_ZERO);
+ if (uiop == NULL) {
+ DBG_PRINTF("%s kheap_alloc uiop failed\n", __func__);
+ error = ENOMEM;
+ goto out;
+ }
+
+ umsgp = kheap_alloc(KHEAP_TEMP,
+ uap->cnt * size_of_msghdr, Z_WAITOK | Z_ZERO);
+ if (umsgp == NULL) {
+ printf("%s kheap_alloc user_msg_x failed\n", __func__);
+ error = ENOMEM;
+ goto out;
+ }
+ error = copyin(uap->msgp, umsgp, uap->cnt * size_of_msghdr);
+ if (error) {
+ DBG_PRINTF("%s copyin() failed\n", __func__);
+ goto out;
+ }
+ error = internalize_user_msghdr_array(umsgp,
+ IS_64BIT_PROCESS(p) ? UIO_USERSPACE64 : UIO_USERSPACE32,
+ UIO_WRITE, uap->cnt, user_msg_x, uiop);
+ if (error) {
+ DBG_PRINTF("%s copyin_user_msghdr_array() failed\n", __func__);
+ goto out;
+ }
+ /*
+ * Make sure the size of each message iovec and
+ * the aggregate size of all the iovec is valid
+ */
+ if (uio_array_is_valid(uiop, uap->cnt) == false) {
+ error = EINVAL;
+ goto out;
+ }
+
+ /*
+ * Sanity check on passed arguments
+ */
+ for (i = 0; i < uap->cnt; i++) {
+ struct user_msghdr_x *mp = user_msg_x + i;
+
+ /*
+ * No flags on send message
+ */
+ if (mp->msg_flags != 0) {
+ error = EINVAL;
+ goto out;
+ }
+ /*
+ * No support for address or ancillary data (yet)
+ */
+ if (mp->msg_name != USER_ADDR_NULL || mp->msg_namelen != 0) {
+ has_addr_or_ctl = 1;
+ }
+
+ if (mp->msg_control != USER_ADDR_NULL ||
+ mp->msg_controllen != 0) {
+ has_addr_or_ctl = 1;
+ }
+
+#if CONFIG_MACF_SOCKET_SUBSET
+ /*
+ * We check the state without holding the socket lock;
+ * if a race condition occurs, it would simply result
+ * in an extra call to the MAC check function.
+ *
+ * Note: The following check is never true taken with the
+ * current limitation that we do not accept to pass an address,
+ * this is effectively placeholder code. If we add support for
+ * addresses, we will have to check every address.
+ */
+ if (to != NULL &&
+ !(so->so_state & SS_DEFUNCT) &&
+ (error = mac_socket_check_send(kauth_cred_get(), so, to))
+ != 0) {
+ goto out;
+ }
+#endif /* MAC_SOCKET_SUBSET */
+ }
+
+ len_before = uio_array_resid(uiop, uap->cnt);
+
+ /*
+ * Feed list of packets at once only for connected socket without
+ * control message
+ */
+ if (so->so_proto->pr_usrreqs->pru_sosend_list !=
+ pru_sosend_list_notsupp &&
+ has_addr_or_ctl == 0 && somaxsendmsgx == 0) {
+ error = so->so_proto->pr_usrreqs->pru_sosend_list(so, uiop,
+ uap->cnt, uap->flags);
+ } else {
+ for (i = 0; i < uap->cnt; i++) {
+ struct user_msghdr_x *mp = user_msg_x + i;
+ struct user_msghdr user_msg;
+ uio_t auio = uiop[i];
+ int32_t tmpval;
+
+ user_msg.msg_flags = mp->msg_flags;
+ user_msg.msg_controllen = mp->msg_controllen;
+ user_msg.msg_control = mp->msg_control;
+ user_msg.msg_iovlen = mp->msg_iovlen;
+ user_msg.msg_iov = mp->msg_iov;
+ user_msg.msg_namelen = mp->msg_namelen;
+ user_msg.msg_name = mp->msg_name;
+
+ error = sendit(p, so, &user_msg, auio, uap->flags,
+ &tmpval);
+ if (error != 0) {
+ break;
+ }
+ }
+ }
+ len_after = uio_array_resid(uiop, uap->cnt);
+
+ VERIFY(len_after <= len_before);
+
+ if (error != 0) {
+ if (len_after != len_before && (error == ERESTART ||
+ error == EINTR || error == EWOULDBLOCK ||
+ error == ENOBUFS)) {
+ error = 0;
+ }
+ /* Generation of SIGPIPE can be controlled per socket */
+ if (error == EPIPE && !(so->so_flags & SOF_NOSIGPIPE) &&
+ !(uap->flags & MSG_NOSIGNAL)) {
+ psignal(p, SIGPIPE);
+ }
+ }
+ if (error == 0) {
+ uiocnt = externalize_user_msghdr_array(umsgp,
+ IS_64BIT_PROCESS(p) ? UIO_USERSPACE64 : UIO_USERSPACE32,
+ UIO_WRITE, uap->cnt, user_msg_x, uiop);
+
+ *retval = (int)(uiocnt);
+ }
+out:
+ if (need_drop) {
+ file_drop(uap->s);
+ }
+ kheap_free(KHEAP_TEMP, umsgp, uap->cnt * size_of_msghdr);
+ if (uiop != NULL) {
+ free_uio_array(uiop, uap->cnt);
+ kheap_free(KHEAP_TEMP, uiop,
+ uap->cnt * sizeof(struct uio *));
+ }
+ kheap_free(KHEAP_TEMP, user_msg_x,
+ uap->cnt * sizeof(struct user_msghdr_x));
+
+ KERNEL_DEBUG(DBG_FNC_SENDMSG_X | DBG_FUNC_END, error, 0, 0, 0, 0);
+
+ return error;
+}
+
+
+static int
+copyout_sa(struct sockaddr *fromsa, user_addr_t name, socklen_t *namelen)
+{
+ int error = 0;
+ socklen_t sa_len = 0;
+ ssize_t len;
+
+ len = *namelen;
+ if (len <= 0 || fromsa == 0) {
+ len = 0;
+ } else {
+#ifndef MIN
+#define MIN(a, b) ((a) > (b) ? (b) : (a))
+#endif
+ sa_len = fromsa->sa_len;
+ len = MIN((unsigned int)len, sa_len);
+ error = copyout(fromsa, name, (unsigned)len);
+ if (error) {
+ goto out;
+ }
+ }
+ *namelen = sa_len;
+out:
+ return 0;
+}
+
+static int
+copyout_control(struct proc *p, struct mbuf *m, user_addr_t control,
+ socklen_t *controllen, int *flags, struct socket *so)
+{
+ int error = 0;
+ socklen_t len;
+ user_addr_t ctlbuf;
+ struct inpcb *inp = so ? sotoinpcb(so) : NULL;
+
+ len = *controllen;
+ *controllen = 0;
+ ctlbuf = control;
+
+ while (m && len > 0) {
+ socklen_t tocopy;
+ struct cmsghdr *cp = mtod(m, struct cmsghdr *);
+ socklen_t cp_size = CMSG_ALIGN(cp->cmsg_len);
+ socklen_t buflen = m->m_len;
+
+ while (buflen > 0 && len > 0) {
+ /*
+ * SCM_TIMESTAMP hack because struct timeval has a
+ * different size for 32 bits and 64 bits processes
+ */
+ if (cp->cmsg_level == SOL_SOCKET && cp->cmsg_type == SCM_TIMESTAMP) {
+ unsigned char tmp_buffer[CMSG_SPACE(sizeof(struct user64_timeval))] = {};
+ struct cmsghdr *tmp_cp = (struct cmsghdr *)(void *)tmp_buffer;
+ socklen_t tmp_space;
+ struct timeval *tv = (struct timeval *)(void *)CMSG_DATA(cp);
+
+ tmp_cp->cmsg_level = SOL_SOCKET;
+ tmp_cp->cmsg_type = SCM_TIMESTAMP;
+
+ if (proc_is64bit(p)) {
+ struct user64_timeval *tv64 = (struct user64_timeval *)(void *)CMSG_DATA(tmp_cp);
+
+ os_unaligned_deref(&tv64->tv_sec) = tv->tv_sec;
+ os_unaligned_deref(&tv64->tv_usec) = tv->tv_usec;
+
+ tmp_cp->cmsg_len = CMSG_LEN(sizeof(struct user64_timeval));
+ tmp_space = CMSG_SPACE(sizeof(struct user64_timeval));
+ } else {
+ struct user32_timeval *tv32 = (struct user32_timeval *)(void *)CMSG_DATA(tmp_cp);
+
+ tv32->tv_sec = (user32_time_t)tv->tv_sec;
+ tv32->tv_usec = tv->tv_usec;
+
+ tmp_cp->cmsg_len = CMSG_LEN(sizeof(struct user32_timeval));
+ tmp_space = CMSG_SPACE(sizeof(struct user32_timeval));
+ }
+ if (len >= tmp_space) {
+ tocopy = tmp_space;
+ } else {
+ *flags |= MSG_CTRUNC;
+ tocopy = len;
+ }
+ error = copyout(tmp_buffer, ctlbuf, tocopy);
+ if (error) {
+ goto out;
+ }
+ } else {
+#if CONTENT_FILTER
+ /* If socket is attached to Content Filter and socket did not request address, ignore it */
+ if ((so != NULL) && (so->so_cfil_db != NULL) &&
+ ((cp->cmsg_level == IPPROTO_IP && cp->cmsg_type == IP_RECVDSTADDR && inp &&
+ !(inp->inp_flags & INP_RECVDSTADDR)) ||
+ (cp->cmsg_level == IPPROTO_IPV6 && (cp->cmsg_type == IPV6_PKTINFO || cp->cmsg_type == IPV6_2292PKTINFO) && inp &&
+ !(inp->inp_flags & IN6P_PKTINFO)))) {
+ tocopy = 0;
+ } else
+#endif
+ {
+ if (cp_size > buflen) {
+ panic("cp_size > buflen, something"
+ "wrong with alignment!");
+ }
+ if (len >= cp_size) {
+ tocopy = cp_size;
+ } else {
+ *flags |= MSG_CTRUNC;
+ tocopy = len;
+ }
+ error = copyout((caddr_t) cp, ctlbuf, tocopy);
+ if (error) {
+ goto out;
+ }
+ }
+ }
+
+ ctlbuf += tocopy;
+ len -= tocopy;
+
+ buflen -= cp_size;
+ cp = (struct cmsghdr *)(void *)
+ ((unsigned char *) cp + cp_size);
+ cp_size = CMSG_ALIGN(cp->cmsg_len);
+ }
+
+ m = m->m_next;
+ }
+ *controllen = (socklen_t)(ctlbuf - control);
+out:
+ return error;