/* 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); }
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); } } } }