/* Set registers to known values and enter a NaCl syscall. */
static void SyscallRegisterSetterThread(struct SuspendTestShm *test_shm) {
  struct NaClSignalContext call_regs;
  char stack[0x10000];

  RegsFillTestValues(&call_regs, /* seed= */ 0);
  call_regs.stack_ptr = (uintptr_t) stack + sizeof(stack);
  call_regs.prog_ctr = (uintptr_t) ContinueAfterSyscall;
  RegsApplySandboxConstraints(&call_regs);

  /*
   * call_regs are the registers we set on entry to the syscall.
   * expected_regs are the registers that should be reported by
   * NaClAppThreadGetSuspendedRegisters().  Since not all registers
   * are saved when entering a syscall, expected_regs will be the same
   * as call_regs but with various registers zeroed out.
   */
  test_shm->expected_regs = call_regs;
  RegsUnsetNonCalleeSavedRegisters(&test_shm->expected_regs);

  uintptr_t syscall_addr = (uintptr_t) NACL_SYSCALL(test_syscall_1);
  if (!setjmp(return_jmp_buf)) {
#if defined(__i386__)
    test_shm->expected_regs.stack_ptr -= 4;  /* Account for argument */
    call_regs.eax = syscall_addr;
    call_regs.ecx = (uintptr_t) test_shm;  /* Scratch register */
    ASM_WITH_REGS(
        &call_regs,
        "push %%ecx\n"  /* Push syscall argument */
        "push $ContinueAfterSyscall\n"  /* Push return address */
        "nacljmp %%eax\n");
#elif defined(__x86_64__)
    call_regs.rax = syscall_addr;
    call_regs.rdi = (uintptr_t) test_shm;  /* Set syscall argument */
    ASM_WITH_REGS(
        &call_regs,
        "push $ContinueAfterSyscall\n"  /* Push return address */
        "nacljmp %%eax, %%r15\n");
#elif defined(__arm__)
    call_regs.r0 = (uintptr_t) test_shm;  /* Set syscall argument */
    call_regs.r1 = syscall_addr;  /* Scratch register */
    call_regs.lr = (uintptr_t) ContinueAfterSyscall;
    ASM_WITH_REGS(
        &call_regs,
        "bic r1, r1, #0xf000000f\n"
        "bx r1\n");
#elif defined(__mips__)
    call_regs.a0 = (uintptr_t) test_shm;  /* Set syscall argument */
    call_regs.t9 = syscall_addr;  /* Scratch register */
    call_regs.return_addr = (uintptr_t) ContinueAfterSyscall;
    ASM_WITH_REGS(
        &call_regs,
        "and $t9, $t9, $t6\n"
        "jr $t9\n"
        "nop\n");
#else
# error Unsupported architecture
#endif
    assert(!"Should not reach here");
  }
}
/* This tests a NaCl syscall that takes no arguments. */
void TestSyscall(uintptr_t syscall_addr) {
  struct NaClSignalContext call_regs;
  char stack[0x10000];

  RegsFillTestValues(&call_regs, /* seed= */ 0);
  call_regs.stack_ptr = (uintptr_t) stack + sizeof(stack);
  call_regs.prog_ctr = (uintptr_t) ContinueAfterSyscall;
  RegsApplySandboxConstraints(&call_regs);

  g_expected_regs = call_regs;
  RegsUnsetNonCalleeSavedRegisters(&g_expected_regs);
  SetNaClSwitchExpectations(&g_expected_regs);

  if (!setjmp(g_return_jmp_buf)) {
#if defined(__i386__)
    call_regs.eax = syscall_addr;
    ASM_WITH_REGS(
        &call_regs,
        "push $ContinueAfterSyscall\n"  /* Push return address */
        "nacljmp %%eax\n");
#elif defined(__x86_64__)
    /*
     * This fast path syscall happens to preserve various registers,
     * but that is obviously not guaranteed by the ABI.
     */
    if (syscall_addr == (uintptr_t) NACL_SYSCALL(tls_get) ||
        syscall_addr == (uintptr_t) NACL_SYSCALL(second_tls_get)) {
      /* Undo some effects of RegsUnsetNonCalleeSavedRegisters(). */
      g_expected_regs.rsi = call_regs.rsi;
      g_expected_regs.rdi = call_regs.rdi;
      g_expected_regs.r8 = call_regs.r8;
      g_expected_regs.r9 = call_regs.r9;
      g_expected_regs.r10 = call_regs.r10;
      /*
       * The current implementation clobbers %rcx with the
       * non-%r15-extended return address.
       */
      g_expected_regs.rcx = (uint32_t) g_expected_regs.prog_ctr;
    }

    call_regs.rax = syscall_addr;
    ASM_WITH_REGS(
        &call_regs,
        "push $ContinueAfterSyscall\n"  /* Push return address */
        "nacljmp %%eax, %%r15\n");
#elif defined(__arm__)
    call_regs.r1 = syscall_addr;  /* Scratch register */
    call_regs.lr = (uintptr_t) ContinueAfterSyscall;  /* Return address */
    ASM_WITH_REGS(
        &call_regs,
        "bic r1, r1, #0xf000000f\n"
        "bx r1\n");
#else
# error Unsupported architecture
#endif
    assert(!"Should not reach here");
  }
}
int main(int argc, char **argv) {
  if (argc != 2) {
    fprintf(stderr, "Expected 1 argument: <memory-address>\n");
    return 1;
  }

  char *end;
  struct NaClSignalContext *expected_regs =
      (struct NaClSignalContext *) strtoul(argv[1], &end, 0);
  assert(*end == '\0');

  struct NaClSignalContext call_regs;
  char stack[0x10000];

  RegsFillTestValues(&call_regs);
  call_regs.stack_ptr = (uintptr_t) stack + sizeof(stack);
  call_regs.prog_ctr = (uintptr_t) SyscallReturnAddress;
  RegsApplySandboxConstraints(&call_regs);
  RegsUnsetNonCalleeSavedRegisters(&call_regs);

  uintptr_t syscall_addr = NACL_SYSCALL_ADDR(NACL_sys_test_syscall_1);
#if defined(__i386__)
  call_regs.esi = syscall_addr;
  *expected_regs = call_regs;
  ASM_WITH_REGS(
      &call_regs,
      "SyscallLoop:\n"
      "naclcall %%esi\n"
      "SyscallReturnAddress:\n"
      "jmp SyscallLoop\n");
#elif defined(__x86_64__)
  call_regs.r12 = syscall_addr;
  *expected_regs = call_regs;
  ASM_WITH_REGS(
      &call_regs,
      "SyscallLoop:\n"
      /* Call via a temporary register so as not to modify %r12. */
      "mov %%r12d, %%eax\n"
      "naclcall %%eax, %%r15\n"
      "SyscallReturnAddress:\n"
      "jmp SyscallLoop\n");
#else
# error Unsupported architecture
#endif
}
/*
 * Set registers to known values and call a NaCl syscall in an
 * infinite loop.  This is used for testing that the same register
 * state is reported while the thread is in untrusted code or inside
 * the syscall.
 */
static void SyscallRegisterSetterLoopThread(struct SuspendTestShm *test_shm) {
  struct NaClSignalContext *regs = &test_shm->expected_regs;
  char stack[0x10000];

  RegsFillTestValues(regs, /* seed= */ 0);
  regs->stack_ptr = (uintptr_t) stack + sizeof(stack);
  regs->prog_ctr = (uintptr_t) SyscallReturnAddress;
  RegsApplySandboxConstraints(regs);
  RegsUnsetNonCalleeSavedRegisters(regs);

  uintptr_t syscall_addr = NACL_SYSCALL_ADDR(NACL_sys_test_syscall_2);
#if defined(__i386__)
  regs->esi = syscall_addr;
#elif defined(__x86_64__)
  regs->r12 = syscall_addr;
#elif defined(__arm__)
  regs->r4 = syscall_addr;
#elif defined(__mips__)
  regs->s0 = syscall_addr;
#else
# error Unsupported architecture
#endif
  JUMP_WITH_REGS(regs, SyscallReturnAddress);
}
Beispiel #5
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);
  }
}
int main(int argc, char **argv) {
  if (argc != 2) {
    fprintf(stderr, "Expected 1 argument: <memory-address>\n");
    return 1;
  }

  char *end;
  struct RegsTestShm *test_shm =
      (struct RegsTestShm *) strtoul(argv[1], &end, 0);
  assert(*end == '\0');
  test_shm->regs_should_match = &g_regs_should_match;

  struct NaClSignalContext call_regs;
  char stack[0x10000];

  int call_count = 0;
  for (call_count = 0; ; call_count++) {
    uintptr_t syscall_addr;
    /*
     * Test fast-path TLS syscalls.  We shoe-horn these in after the
     * first call to test_syscall_1 has enabled single-stepping.
     */
    if (call_count == 1) {
      syscall_addr = NACL_SYSCALL_ADDR(NACL_sys_tls_get);
    } else if (call_count == 2) {
      syscall_addr = NACL_SYSCALL_ADDR(NACL_sys_second_tls_get);
    } else {
      syscall_addr = NACL_SYSCALL_ADDR(NACL_sys_test_syscall_1);
    }

    /*
     * Use different expected register values for each call.
     * Otherwise, the test could accidentally pass because the
     * stack_ptr reported during the entry to a syscall can happen to
     * match the stack_ptr saved by the previous syscall.
     */
    RegsFillTestValues(&call_regs, /* seed= */ call_count);
#if defined(__i386__)
    call_regs.esi = syscall_addr;
#elif defined(__x86_64__)
    call_regs.r12 = syscall_addr;
#elif defined(__arm__)
    call_regs.r4 = syscall_addr;
    call_regs.r5 = (uintptr_t) &g_regs_should_match;
    call_regs.r6 = 1;
    call_regs.r7 = 0;
#elif defined(__mips__)
    call_regs.s0 = syscall_addr;
    call_regs.s1 = (uintptr_t) &g_regs_should_match;
    call_regs.s2 = 1;
#else
# error Unsupported architecture
#endif
    call_regs.prog_ctr = (uintptr_t) (call_count % 2 == 0
                                      ? SyscallReturnAddress1
                                      : SyscallReturnAddress2);
    call_regs.stack_ptr =
        (uintptr_t) stack + sizeof(stack) - (call_count % 2) * 0x100;
    RegsApplySandboxConstraints(&call_regs);
    RegsUnsetNonCalleeSavedRegisters(&call_regs);
    test_shm->expected_regs = call_regs;
    if (!setjmp(g_jmp_buf)) {
      if (call_count % 2 == 0) {
        JUMP_WITH_REGS(&call_regs, SyscallCaller1);
      } else {
        JUMP_WITH_REGS(&call_regs, SyscallCaller2);
      }
    }
  }
}