Ejemplo n.º 1
0
static void MakeDynamicCodePagesVisible(struct NaClApp *nap,
                                        uint32_t page_index_min,
                                        uint32_t page_index_max,
                                        uint8_t *writable_addr) {
  void *user_addr;
  uint32_t index;
  size_t size = (page_index_max - page_index_min) * NACL_MAP_PAGESIZE;

  for (index = page_index_min; index < page_index_max; index++) {
    CHECK(!BitmapIsBitSet(nap->dynamic_page_bitmap, index));
    BitmapSetBit(nap->dynamic_page_bitmap, index);
  }
  user_addr = (void *) NaClUserToSys(nap, nap->dynamic_text_start
                                     + page_index_min * NACL_MAP_PAGESIZE);

#if NACL_WINDOWS
  NaClUntrustedThreadsSuspendAll(nap, /* save_registers= */ 0);

  /*
   * The VirtualAlloc() call here has two effects:
   *
   *  1) It commits the page in the shared memory (SHM) object,
   *     allocating swap space and making the page accessible.  This
   *     affects our writable mapping of the shared memory object too.
   *     Before the VirtualAlloc() call, dereferencing writable_addr
   *     would fault.
   *  2) It changes the page permissions of the mapping to
   *     read+execute.  Since this exposes the page in its unsafe,
   *     non-HLT-filled state, this must be done with untrusted
   *     threads suspended.
   */
  {
    uintptr_t offset;
    for (offset = 0; offset < size; offset += NACL_MAP_PAGESIZE) {
      void *user_page_addr = (char *) user_addr + offset;
      if (VirtualAlloc(user_page_addr, NACL_MAP_PAGESIZE,
                       MEM_COMMIT, PAGE_EXECUTE_READ) != user_page_addr) {
        NaClLog(LOG_FATAL, "MakeDynamicCodePagesVisible: "
                "VirtualAlloc() failed -- probably out of swap space\n");
      }
    }
  }
#endif

  /* Sanity check:  Ensure the page is not already in use. */
  CHECK(*writable_addr == 0);

  NaClFillMemoryRegionWithHalt(writable_addr, size);

#if NACL_WINDOWS
  NaClUntrustedThreadsResumeAll(nap);
#else
  if (NaClMprotect(user_addr, size, PROT_READ | PROT_EXEC) != 0) {
    NaClLog(LOG_FATAL, "MakeDynamicCodePageVisible: NaClMprotect() failed\n");
  }
#endif
}
/*
 * This is a test for a bug that occurred only on Mac OS X on x86-32.
 * If we modify the registers of a suspended thread on x86-32 Mac, we
 * force the thread to return to untrusted code via the special
 * trusted routine NaClSwitchRemainingRegsViaECX().  If we later
 * suspend the thread while it is still executing this routine, we
 * need to return the untrusted register state that the routine is
 * attempting to restore -- we test that here.
 *
 * Suspending a thread while it is still blocked by a fault is the
 * easiest way to test this, since this allows us to test suspension
 * while the thread is at the start of
 * NaClSwitchRemainingRegsViaECX().  This is also how the debug stub
 * runs into this problem.
 */
void TestGettingRegistersInMacSwitchRemainingRegs(struct NaClApp *nap) {
  struct NaClSignalContext *expected_regs = StartGuestWithSharedMemory(nap);
  struct NaClSignalContext regs;
  struct NaClAppThread *natp;
  int signal;

  WaitForThreadToFault(nap);
  NaClUntrustedThreadsSuspendAll(nap, /* save_registers= */ 1);
  natp = GetOnlyThread(nap);
  NaClAppThreadGetSuspendedRegisters(natp, &regs);
  RegsAssertEqual(&regs, expected_regs);
  /*
   * On Mac OS X on x86-32, this changes the underlying Mac thread's
   * state so that the thread will execute
   * NaClSwitchRemainingRegsViaECX().
   */
  NaClAppThreadSetSuspendedRegisters(natp, &regs);

  /*
   * Resume the thread without unblocking it and then re-suspend it.
   * This causes osx/thread_suspension.c to re-fetch the register
   * state from the Mac kernel.
   */
  NaClUntrustedThreadsResumeAll(nap);
  NaClUntrustedThreadsSuspendAll(nap, /* save_registers= */ 1);
  ASSERT_EQ(GetOnlyThread(nap), natp);

  /* We should get the same register state as before. */
  NaClAppThreadGetSuspendedRegisters(natp, &regs);
  RegsAssertEqual(&regs, expected_regs);

  /*
   * Clean up:  Skip over the instruction that faulted and let the
   * thread run to completion.
   */
  regs.prog_ctr += kBreakInstructionSize;
  NaClAppThreadSetSuspendedRegisters(natp, &regs);
  ASSERT_EQ(NaClAppThreadUnblockIfFaulted(natp, &signal), 1);
  NaClUntrustedThreadsResumeAll(nap);
  CHECK(NaClWaitForMainThreadToExit(nap) == 0);
}
/*
 * This function is entered with threads suspended, and it exits with
 * threads suspended too.
 */
void TestSingleStepping(struct NaClAppThread *natp) {
  struct NaClApp *nap = natp->nap;
  struct NaClSignalContext regs;
  struct NaClSignalContext expected_regs;
  int instr_size;

  /* Set the trap flag to enable single-stepping. */
  NaClAppThreadGetSuspendedRegisters(natp, &regs);
  regs.flags |= NACL_X86_TRAP_FLAG;
  expected_regs = regs;
  NaClAppThreadSetSuspendedRegisters(natp, &regs);

  for (instr_size = 1; instr_size <= 5; instr_size++) {
    int signal;

    fprintf(stderr, "Now expecting to single-step through "
            "an instruction %i bytes in size at address 0x%x\n",
            instr_size, (int) expected_regs.prog_ctr);
    expected_regs.prog_ctr += instr_size;

    NaClUntrustedThreadsResumeAll(nap);
    WaitForThreadToFault(nap);
    NaClUntrustedThreadsSuspendAll(nap, /* save_registers= */ 1);
    ASSERT_EQ(GetOnlyThread(nap), natp);
    ASSERT_EQ(NaClAppThreadUnblockIfFaulted(natp, &signal), 1);
    /*
     * TODO(mseaborn): Move ExceptionToSignal() out of debug_stub and
     * enable this check on Windows too.
     */
    if (!NACL_WINDOWS) {
      ASSERT_EQ(signal, NACL_ABI_SIGTRAP);
    }

    NaClAppThreadGetSuspendedRegisters(natp, &regs);
    if (NACL_WINDOWS) {
      /*
       * On Windows, but not elsewhere, the trap flag gets unset after
       * each instruction, so we must set it again.
       */
      regs.flags |= NACL_X86_TRAP_FLAG;
      NaClAppThreadSetSuspendedRegisters(natp, &regs);
    }
    RegsAssertEqual(&regs, &expected_regs);
  }

  /* Unset the trap flag so that the thread can continue. */
  regs.flags &= ~NACL_X86_TRAP_FLAG;
  NaClAppThreadSetSuspendedRegisters(natp, &regs);
}
void TestReceivingFault(struct NaClApp *nap) {
  struct NaClSignalContext *expected_regs = StartGuestWithSharedMemory(nap);
  struct NaClSignalContext regs;
  struct NaClAppThread *natp;
  int signal = 0;
  int dummy_signal;

  WaitForThreadToFault(nap);
  NaClUntrustedThreadsSuspendAll(nap, /* save_registers= */ 1);
  natp = GetOnlyThread(nap);
  ASSERT_EQ(NaClAppThreadUnblockIfFaulted(natp, &signal), 1);
  /*
   * TODO(mseaborn): Move ExceptionToSignal() out of debug_stub and
   * enable this check on Windows too.
   */
  if (!NACL_WINDOWS) {
    ASSERT_EQ(signal, kBreakInstructionSignal);
  }

  /* Check that faulted_thread_count is updated correctly. */
  ASSERT_EQ(nap->faulted_thread_count, 0);
  /*
   * Check that NaClAppThreadUnblockIfFaulted() returns false when
   * called on a thread that is not blocked.
   */
  ASSERT_EQ(NaClAppThreadUnblockIfFaulted(natp, &dummy_signal), 0);

  NaClAppThreadGetSuspendedRegisters(natp, &regs);
  RegsAssertEqual(&regs, expected_regs);

  /* Skip over the instruction that faulted. */
  regs.prog_ctr += kBreakInstructionSize;
  NaClAppThreadSetSuspendedRegisters(natp, &regs);

  TestSingleStepping(natp);

  NaClUntrustedThreadsResumeAll(nap);
  CHECK(NaClWaitForMainThreadToExit(nap) == 0);
}