/*
 * This tests that when a new thread is created, untrusted code is
 * entered with well-defined register state.  None of the registers
 * should come from uninitialised values.
 */
void TestInitialRegsAtThreadEntry(void) {
  char *stack_top = g_stack + sizeof(g_stack);
  uintptr_t aligned_stack_top =
      ((uintptr_t) stack_top & ~NACL_STACK_ALIGN_MASK)
      - NACL_STACK_PAD_BELOW_ALIGN;
  /*
   * We do not care about TLS for this test, but sel_ldr rejects a
   * zero tls argument, so use an arbitrary non-zero value.
   */
  char *tls = (char *) 0x1000;
  g_stack_in_use = 1;
  int rc = NACL_SYSCALL(thread_create)((void *) (uintptr_t) ThreadFuncWrapper,
                                       stack_top, tls, 0);
  assert(rc == 0);
  /* Spin until the thread exits. */
  while (g_stack_in_use) {
    sched_yield();
  }
  ANNOTATE_CONDVAR_WAIT(&g_stack_in_use);
  struct NaClSignalContext actual_regs = g_initial_thread_regs;

  struct NaClSignalContext expected_regs;
  /* By default, we expect registers to be initialised to zero. */
  memset(&expected_regs, 0, sizeof(expected_regs));
  expected_regs.prog_ctr = (uintptr_t) ThreadFuncWrapper;
  expected_regs.stack_ptr = aligned_stack_top;
  RegsApplySandboxConstraints(&expected_regs);
  SetNaClSwitchExpectations(&expected_regs);
#if defined(__x86_64__)
  /* NaCl happens to initialise %rbp to be the same as %rsp. */
  expected_regs.rbp = expected_regs.stack_ptr;
#endif

  RegsAssertEqual(&actual_regs, &expected_regs);
}
/*
 * 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 CheckRegistersAfterSyscall(struct NaClSignalContext *regs) {
  /* Ignore the register containing the return value. */
#if defined(__i386__)
  g_expected_regs.eax = regs->eax;
#elif defined(__x86_64__)
  g_expected_regs.rax = regs->rax;
#elif defined(__arm__)
  g_expected_regs.r0 = regs->r0;
#else
# error Unsupported architecture
#endif

  RegsAssertEqual(regs, &g_expected_regs);
  longjmp(g_return_jmp_buf, 1);
}
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);
}
void CheckSavedRegisters(struct NaClSignalContext *regs) {
  RegsAssertEqual(regs, &thread_test_shm->expected_regs);
  _exit(0);
}
Ejemplo n.º 7
0
static void TrapSignalHandler(int signal,
                              const struct NaClSignalContext *context_ptr,
                              int is_untrusted) {
  uint32_t prog_ctr;
  char buf[100];
  int len;
  struct NaClSignalContext *expected_regs = &g_test_shm->expected_regs;
  struct NaClSignalContext context = *context_ptr;

  if (signal != SIGTRAP) {
    SignalSafeLogStringLiteral("Error: Received unexpected signal\n");
    _exit(1);
  }

  /* Get the prog_ctr value relative to untrusted address space. */
  prog_ctr = (uint32_t) context.prog_ctr;
  /*
   * The trampoline code is not untrusted code because it is fixed by
   * the TCB.  We don't want thread suspension to report prog_ctr as
   * being inside the trampoline code because we are not providing any
   * DWARF unwind info for the trampoline code.  We want the CALL
   * instruction that jumps to a syscall trampoline to appear to be
   * atomic from the point of view of thread suspension: prog_ctr
   * should be reported as either at the CALL or after the CALL.
   *
   * TODO(mseaborn): Move this range check into the non-test part of
   * the thread suspension code.
   */
  if (prog_ctr >= NACL_TRAMPOLINE_START && prog_ctr < NACL_TRAMPOLINE_END) {
    is_untrusted = 0;
  }
  if (g_in_untrusted_code != is_untrusted) {
    g_context_switch_count++;
    g_in_untrusted_code = is_untrusted;
  }

  if (!*(uint32_t *) NaClUserToSys(g_natp->nap,
                                   (uintptr_t) g_test_shm->regs_should_match))
    return;

  len = snprintf(buf, sizeof(buf), "prog_ctr=0x%"NACL_PRIxNACL_REG": ",
                 context.prog_ctr);
  SignalSafeWrite(buf, len);

  if (is_untrusted) {
    SignalSafeLogStringLiteral("Untrusted context\n");
    RegsUnsetNonCalleeSavedRegisters(&context);
    /*
     * Don't compare prog_ctr if we are executing untrusted code.
     * Untrusted code executes a small loop for calling the syscall,
     * so there are multiple values that prog_ctr can have here.
     */
    context.prog_ctr = expected_regs->prog_ctr;
    RegsAssertEqual(&context, expected_regs);
  } else if ((g_natp->suspend_state & NACL_APP_THREAD_TRUSTED) != 0) {
    SignalSafeLogStringLiteral("Trusted (syscall) context\n");
    NaClThreadContextToSignalContext(&g_natp->user, &context);
    RegsAssertEqual(&context, expected_regs);
  } else {
    enum NaClUnwindCase unwind_case = 0;
    const char *str;

    SignalSafeLogStringLiteral("Inside a context switch: ");

    NaClGetRegistersForContextSwitch(g_natp, &context, &unwind_case);

    str = NaClUnwindCaseToString(unwind_case);
    CHECK(str != NULL);
    SignalSafeWrite(str, strlen(str));
    SignalSafeLogStringLiteral("\n");

    RegsAssertEqual(&context, expected_regs);
  }
}