ssize_t NaClImcSendTypedMessage(struct NaClDesc *channel, const struct NaClImcTypedMsgHdr *nitmhp, int flags) { int supported_flags; ssize_t retval = -NACL_ABI_EINVAL; struct NaClMessageHeader kern_msg_hdr; /* xlated interface */ size_t i; /* * BEWARE: NaClImcMsgIoVec has the same layout as NaClIOVec, so * there will be type punning below to avoid copying from a struct * NaClImcMsgIoVec array to a struct NaClIOVec array in order to * call the IMC library code using kern_msg_hdr. */ struct NaClImcMsgIoVec kern_iov[NACL_ABI_IMC_IOVEC_MAX + 1]; struct NaClDesc **kern_desc; NaClHandle kern_handle[NACL_ABI_IMC_DESC_MAX]; size_t user_bytes; size_t sys_bytes; size_t sys_handles; size_t desc_bytes; size_t desc_handles; struct NaClInternalHeader *hdr; char *hdr_buf; struct NaClDescXferState xfer_state; static struct NaClInternalHeader const kNoHandles = { { NACL_HANDLE_TRANSFER_PROTOCOL, 0, }, /* and implicit zeros for pad bytes */ }; NaClLog(3, ("Entered" " NaClImcSendTypedMessage(0x%08"NACL_PRIxPTR", " "0x%08"NACL_PRIxPTR", 0x%x)\n"), (uintptr_t) channel, (uintptr_t) nitmhp, flags); /* * What flags do we know now? If a program was compiled using a * newer ABI than what this implementation knows, the extra flag * bits are ignored but will generate a warning. */ supported_flags = NACL_ABI_IMC_NONBLOCK; if (0 != (flags & ~supported_flags)) { NaClLog(LOG_WARNING, "WARNING: NaClImcSendTypedMessage: unknown IMC flag used: 0x%x\n", flags); flags &= supported_flags; } /* * use (ahem) RTTI -- or a virtual function that's been partially * evaluated/memoized -- to short circuit the error check, so that * cleanups are easier (rather than letting it fail at the * ExternalizeSize virtual function call). */ if (0 != nitmhp->ndesc_length && NACL_DESC_IMC_SOCKET != NACL_VTBL(NaClDesc, channel)->typeTag) { NaClLog(4, "not an IMC socket and trying to send descriptors!\n"); return -NACL_ABI_EINVAL; } if (nitmhp->iov_length > NACL_ABI_IMC_IOVEC_MAX) { NaClLog(4, "gather/scatter array too large\n"); return -NACL_ABI_EINVAL; } if (nitmhp->ndesc_length > NACL_ABI_IMC_USER_DESC_MAX) { NaClLog(4, "handle vector too long\n"); return -NACL_ABI_EINVAL; } memcpy(kern_iov + 1, (void *) nitmhp->iov, nitmhp->iov_length * sizeof *nitmhp->iov); /* * kern_iov[0] is the message header that tells the recipient where * the user data and capabilities data (e.g., NaClDescConnCap) * boundary is, as well as types of objects in the handles vector of * NaClHandle objects, which must be internalized. * * NB: we assume that communication is secure, and no external * entity can listen in or will inject bogus information. This is a * strong assumption, since falsification can cause loss of * type-safety. */ user_bytes = 0; for (i = 0; i < nitmhp->iov_length; ++i) { if (user_bytes > SIZE_T_MAX - kern_iov[i+1].length) { return -NACL_ABI_EINVAL; } user_bytes += kern_iov[i+1].length; } if (user_bytes > NACL_ABI_IMC_USER_BYTES_MAX) { return -NACL_ABI_EINVAL; } kern_desc = nitmhp->ndescv; hdr_buf = NULL; /* * NOTE: type punning w/ NaClImcMsgIoVec and NaClIOVec. * This breaks ansi type aliasing rules and hence we may use * soemthing like '-fno-strict-aliasing' */ kern_msg_hdr.iov = (struct NaClIOVec *) kern_iov; kern_msg_hdr.iov_length = nitmhp->iov_length + 1; /* header */ if (0 == nitmhp->ndesc_length) { kern_msg_hdr.handles = NULL; kern_msg_hdr.handle_count = 0; kern_iov[0].base = (void *) &kNoHandles; kern_iov[0].length = sizeof kNoHandles; } else { /* * \forall i \in [0, nitmhp->desc_length): kern_desc[i] != NULL. */ sys_bytes = 0; sys_handles = 0; /* nitmhp->desc_length <= NACL_ABI_IMC_USER_DESC_MAX */ for (i = 0; i < nitmhp->ndesc_length; ++i) { desc_bytes = 0; desc_handles = 0; retval = (*((struct NaClDescVtbl const *) kern_desc[i]->base.vtbl)-> ExternalizeSize)(kern_desc[i], &desc_bytes, &desc_handles); if (retval < 0) { NaClLog(1, ("NaClImcSendTypedMessage: ExternalizeSize" " returned %"NACL_PRIdS"\n"), retval); goto cleanup; } /* * No integer overflow should be possible given the max-handles * limits and actual resource use per handle involved. */ if (desc_bytes > NACL_ABI_SIZE_T_MAX - 1 || (desc_bytes + 1) > NACL_ABI_SIZE_T_MAX - sys_bytes || desc_handles > NACL_ABI_SIZE_T_MAX - sys_handles) { retval = -NACL_ABI_EOVERFLOW; goto cleanup; } sys_bytes += (1 + desc_bytes); sys_handles += desc_handles; } if (sys_handles > NACL_ABI_IMC_DESC_MAX) { NaClLog(LOG_FATAL, ("User had %"NACL_PRIdNACL_SIZE" descriptors," " which expanded into %"NACL_PRIdS "handles, more than" " the max of %d.\n"), nitmhp->ndesc_length, sys_handles, NACL_ABI_IMC_DESC_MAX); } /* a byte for NACL_DESC_TYPE_END_TAG, then rounded up to 0 mod 16 */ sys_bytes = (sys_bytes + 1 + 0xf) & ~0xf; /* bsy notes that sys_bytes should never exceed NACL_ABI_IMC_DESC_MAX * times the maximum protocol transfer size, which is currently set to * NACL_PATH_MAX, so it would be abnormal for this check to fail. * Including it anyway just in case something changes. */ if (sys_bytes > NACL_ABI_SIZE_T_MAX - sizeof *hdr) { NaClLog(LOG_FATAL, "NaClImcSendTypedMessage: " "Buffer size overflow (%"NACL_PRIdS" bytes)", sys_bytes); retval = -NACL_ABI_EOVERFLOW; goto cleanup; } hdr_buf = malloc(sys_bytes + sizeof *hdr); if (NULL == hdr_buf) { NaClLog(4, "NaClImcSendTypedMessage: out of memory for iov"); retval = -NACL_ABI_ENOMEM; goto cleanup; } kern_iov[0].base = (void *) hdr_buf; /* Checked above that sys_bytes <= NACL_ABI_SIZE_T_MAX - sizeof *hdr */ kern_iov[0].length = (nacl_abi_size_t)(sys_bytes + sizeof *hdr); hdr = (struct NaClInternalHeader *) hdr_buf; memset(hdr, 0, sizeof(*hdr)); /* Initilize the struct's padding bytes. */ hdr->h.xfer_protocol_version = NACL_HANDLE_TRANSFER_PROTOCOL; if (sys_bytes > UINT32_MAX) { /* * We really want: * sys_bytes > numeric_limits<typeof(hdr->h.descriptor_data_bytes)>:max() * in case the type changes. * * This should never occur in practice, since * NACL_ABI_IMC_DESC_MAX * NACL_PATH_MAX is far smaller than * UINT32_MAX. Furthermore, NACL_ABI_IMC_DESC_MAX * * NACL_PATH_MAX + user_bytes <= NACL_ABI_IMC_DESC_MAX * * NACL_PATH_MAX + NACL_ABI_IMC_USER_BYTES_MAX == max_xfer (so * max_xfer is upper bound on data transferred), and max_xfer <= * INT32_MAX = NACL_ABI_SSIZE_MAX <= SSIZE_T_MAX holds. */ retval = -NACL_ABI_EOVERFLOW; goto cleanup; } hdr->h.descriptor_data_bytes = (uint32_t) sys_bytes; xfer_state.next_byte = (char *) (hdr + 1); xfer_state.byte_buffer_end = xfer_state.next_byte + sys_bytes; xfer_state.next_handle = kern_handle; xfer_state.handle_buffer_end = xfer_state.next_handle + NACL_ABI_IMC_DESC_MAX; for (i = 0; i < nitmhp->ndesc_length; ++i) { retval = NaClDescExternalizeToXferBuffer(&xfer_state, kern_desc[i]); if (0 != retval) { NaClLog(4, ("NaClImcSendTypedMessage: Externalize for" " descriptor %"NACL_PRIdS" returned %"NACL_PRIdS"\n"), i, retval); goto cleanup; } } *xfer_state.next_byte++ = (char) NACL_DESC_TYPE_END_TAG; /* * zero fill the rest of memory to avoid leaking info from * otherwise uninitialized malloc'd memory. */ while (xfer_state.next_byte < xfer_state.byte_buffer_end) { *xfer_state.next_byte++ = '\0'; } kern_msg_hdr.handles = kern_handle; kern_msg_hdr.handle_count = (uint32_t) sys_handles; } NaClLog(4, "Invoking LowLevelSendMsg, flags 0x%x\n", flags); retval = (*((struct NaClDescVtbl const *) channel->base.vtbl)-> LowLevelSendMsg)(channel, &kern_msg_hdr, flags); NaClLog(4, "LowLevelSendMsg returned %"NACL_PRIdS"\n", retval); if (NaClSSizeIsNegErrno(&retval)) { /* * NaClWouldBlock uses TSD (for both the errno-based and * GetLastError()-based implementations), so this is threadsafe. */ if (0 != (flags & NACL_DONT_WAIT) && NaClWouldBlock()) { retval = -NACL_ABI_EAGAIN; } else if (-NACL_ABI_EMSGSIZE == retval) { /* * Allow the above layer to process when imc_sendmsg calls fail due * to the OS not supporting a large enough buffer. */ retval = -NACL_ABI_EMSGSIZE; } else { /* * TODO(bsy): the else case is some mysterious internal error. * should we destroy the channel? Was the failure atomic? Did * it send some partial data? Linux implementation appears * okay. * * We return EIO and let the caller deal with it. */ retval = -NACL_ABI_EIO; } } else if ((unsigned) retval < kern_iov[0].length) { /* * retval >= 0, so cast to unsigned is value preserving. */ retval = -NACL_ABI_ENOBUFS; } else { /* * The return value (number of bytes sent) should not include the * "out of band" additional information added by the service runtime. */ retval -= kern_iov[0].length; } cleanup: free(hdr_buf); NaClLog(4, "NaClImcSendTypedMessage: returning %"NACL_PRIdS"\n", retval); return retval; }
/* * This function converts addresses from user addresses to system * addresses, copying into kernel space as needed to avoid TOCvTOU * races, then invokes the descriptor's SendMsg() method. */ int32_t NaClSysImcSendmsg(struct NaClAppThread *natp, int d, uint32_t nanimhp, int flags) { struct NaClApp *nap = natp->nap; int32_t retval = -NACL_ABI_EINVAL; ssize_t ssize_retval; uintptr_t sysaddr; /* copy of user-space data for validation */ struct NaClAbiNaClImcMsgHdr kern_nanimh; struct NaClAbiNaClImcMsgIoVec kern_naiov[NACL_ABI_IMC_IOVEC_MAX]; struct NaClImcMsgIoVec kern_iov[NACL_ABI_IMC_IOVEC_MAX]; int32_t usr_desc[NACL_ABI_IMC_USER_DESC_MAX]; /* kernel-side representatin of descriptors */ struct NaClDesc *kern_desc[NACL_ABI_IMC_USER_DESC_MAX]; struct NaClImcTypedMsgHdr kern_msg_hdr; struct NaClDesc *ndp; size_t i; NaClLog(3, ("Entered NaClSysImcSendmsg(0x%08"NACL_PRIxPTR", %d," " 0x%08"NACL_PRIx32", 0x%x)\n"), (uintptr_t) natp, d, nanimhp, flags); if (!NaClCopyInFromUser(nap, &kern_nanimh, nanimhp, sizeof kern_nanimh)) { NaClLog(4, "NaClImcMsgHdr not in user address space\n"); retval = -NACL_ABI_EFAULT; goto cleanup_leave; } /* copy before validating contents */ /* * Some of these checks duplicate checks that will be done in the * nrd xfer library, but it is better to check before doing the * address translation of memory/descriptor vectors if those vectors * might be too long. Plus, we need to copy and validate vectors * for TOCvTOU race protection, and we must prevent overflows. The * nrd xfer library's checks should never fire when called from the * service runtime, but the nrd xfer library might be called from * other code. */ if (kern_nanimh.iov_length > NACL_ABI_IMC_IOVEC_MAX) { NaClLog(4, "gather/scatter array too large\n"); retval = -NACL_ABI_EINVAL; goto cleanup_leave; } if (kern_nanimh.desc_length > NACL_ABI_IMC_USER_DESC_MAX) { NaClLog(4, "handle vector too long\n"); retval = -NACL_ABI_EINVAL; goto cleanup_leave; } if (kern_nanimh.iov_length > 0) { if (!NaClCopyInFromUser(nap, kern_naiov, (uintptr_t) kern_nanimh.iov, (kern_nanimh.iov_length * sizeof kern_naiov[0]))) { NaClLog(4, "gather/scatter array not in user address space\n"); retval = -NACL_ABI_EFAULT; goto cleanup_leave; } for (i = 0; i < kern_nanimh.iov_length; ++i) { sysaddr = NaClUserToSysAddrRange(nap, (uintptr_t) kern_naiov[i].base, kern_naiov[i].length); if (kNaClBadAddress == sysaddr) { retval = -NACL_ABI_EFAULT; goto cleanup_leave; } kern_iov[i].base = (void *) sysaddr; kern_iov[i].length = kern_naiov[i].length; } } ndp = NaClAppGetDesc(nap, d); if (NULL == ndp) { retval = -NACL_ABI_EBADF; goto cleanup_leave; } /* * make things easier for cleaup exit processing */ memset(kern_desc, 0, sizeof kern_desc); retval = -NACL_ABI_EINVAL; kern_msg_hdr.iov = kern_iov; kern_msg_hdr.iov_length = kern_nanimh.iov_length; if (0 == kern_nanimh.desc_length) { kern_msg_hdr.ndescv = 0; kern_msg_hdr.ndesc_length = 0; } else { if (!NaClCopyInFromUser(nap, usr_desc, kern_nanimh.descv, kern_nanimh.desc_length * sizeof usr_desc[0])) { retval = -NACL_ABI_EFAULT; goto cleanup; } for (i = 0; i < kern_nanimh.desc_length; ++i) { if (kKnownInvalidDescNumber == usr_desc[i]) { kern_desc[i] = (struct NaClDesc *) NaClDescInvalidMake(); } else { /* NaCl modules are ILP32, so this works on ILP32 and LP64 systems */ kern_desc[i] = NaClAppGetDesc(nap, usr_desc[i]); } if (NULL == kern_desc[i]) { retval = -NACL_ABI_EBADF; goto cleanup; } } kern_msg_hdr.ndescv = kern_desc; kern_msg_hdr.ndesc_length = kern_nanimh.desc_length; } kern_msg_hdr.flags = kern_nanimh.flags; /* lock user memory ranges in kern_naiov */ for (i = 0; i < kern_nanimh.iov_length; ++i) { NaClVmIoWillStart(nap, kern_naiov[i].base, kern_naiov[i].base + kern_naiov[i].length - 1); } ssize_retval = NACL_VTBL(NaClDesc, ndp)->SendMsg(ndp, &kern_msg_hdr, flags); /* unlock user memory ranges in kern_naiov */ for (i = 0; i < kern_nanimh.iov_length; ++i) { NaClVmIoHasEnded(nap, kern_naiov[i].base, kern_naiov[i].base + kern_naiov[i].length - 1); } if (NaClSSizeIsNegErrno(&ssize_retval)) { /* * NaClWouldBlock uses TSD (for both the errno-based and * GetLastError()-based implementations), so this is threadsafe. */ if (0 != (flags & NACL_DONT_WAIT) && NaClWouldBlock()) { retval = -NACL_ABI_EAGAIN; } else if (-NACL_ABI_EMSGSIZE == ssize_retval) { /* * Allow the caller to handle the case when imc_sendmsg fails because * the message is too large for the system to send in one piece. */ retval = -NACL_ABI_EMSGSIZE; } else { /* * TODO(bsy): the else case is some mysterious internal error. * Should we destroy the ndp or otherwise mark it as bad? Was * the failure atomic? Did it send some partial data? Linux * implementation appears okay. */ retval = -NACL_ABI_EIO; } } else if (ssize_retval > INT32_MAX || ssize_retval < INT32_MIN) { retval = -NACL_ABI_EOVERFLOW; } else { /* cast is safe due to range checks above */ retval = (int32_t)ssize_retval; } cleanup: for (i = 0; i < kern_nanimh.desc_length; ++i) { if (NULL != kern_desc[i]) { NaClDescUnref(kern_desc[i]); kern_desc[i] = NULL; } } NaClDescUnref(ndp); cleanup_leave: NaClLog(3, "NaClSysImcSendmsg: returning %d\n", retval); return retval; }