Exemplo n.º 1
0
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;
}
Exemplo n.º 2
0
/*
 * 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;
}