/* * 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, ®s); RegsAssertEqual(®s, 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, ®s); /* * 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, ®s); RegsAssertEqual(®s, expected_regs); /* * Clean up: Skip over the instruction that faulted and let the * thread run to completion. */ regs.prog_ctr += kBreakInstructionSize; NaClAppThreadSetSuspendedRegisters(natp, ®s); 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, ®s); regs.flags |= NACL_X86_TRAP_FLAG; expected_regs = regs; NaClAppThreadSetSuspendedRegisters(natp, ®s); 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, ®s); 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, ®s); } RegsAssertEqual(®s, &expected_regs); } /* Unset the trap flag so that the thread can continue. */ regs.flags &= ~NACL_X86_TRAP_FLAG; NaClAppThreadSetSuspendedRegisters(natp, ®s); }
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, ®s); RegsAssertEqual(®s, expected_regs); /* Skip over the instruction that faulted. */ regs.prog_ctr += kBreakInstructionSize; NaClAppThreadSetSuspendedRegisters(natp, ®s); TestSingleStepping(natp); NaClUntrustedThreadsResumeAll(nap); CHECK(NaClWaitForMainThreadToExit(nap) == 0); }
void CheckSavedRegisters(struct NaClSignalContext *regs) { RegsAssertEqual(regs, &thread_test_shm->expected_regs); _exit(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); } }