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, ®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 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); }