void TestDEPCheckFailurePath(void) { size_t size = NACL_PAGESIZE; void *page; CHECK(NaCl_page_alloc(&page, size) == 0); CHECK(NaCl_mprotect(page, size, PROT_READ | PROT_WRITE | PROT_EXEC) == 0); CHECK(!NaClAttemptToExecuteDataAtAddr(page, size)); /* DEP is not guaranteed to work on x86-32. */ if (!(NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86 && NACL_BUILD_SUBARCH == 32)) { CHECK(NaCl_mprotect(page, size, PROT_READ | PROT_WRITE) == 0); CHECK(NaClAttemptToExecuteDataAtAddr(page, size)); } NaCl_page_free(page, size); }
/* bool */ int NaClFindAddressSpaceRandomized(uintptr_t *addr, size_t memory_size, int max_tries) { void *map_addr; int tries_remaining; /* * Mask for keeping the low order NUM_USER_ADDRESS_BITS of a randomly * generated address. */ uintptr_t addr_mask; uintptr_t suggested_addr; int mmap_type; size_t map_failed_count_left = NACL_VALGRIND_MAP_FAILED_COUNT; CHECK(max_tries >= 0); NaClLog(4, "NaClFindAddressSpaceRandomized: looking for %"NACL_PRIxS" bytes\n", memory_size); NaClLog(4, "NaClFindAddressSpaceRandomized: max %d tries\n", max_tries); if (NACL_ARCH(NACL_BUILD_ARCH) != NACL_mips) { /* 4kB pages */ addr_mask = ~(((uintptr_t) 1 << 12) - 1); mmap_type = MAP_PRIVATE; } else { /* * 8kB pages in userspace memory region. * Apart from system page size, MIPS shared memory mask in kernel can depend * on dcache attributes. Ideally, we could use kernel constant SHMLBA, but * it is too large. Common case is that 8kB is sufficient. * As shared memory mask alignment is more rigid on MIPS, we need to pass * MAP_SHARED type to mmap, so it can return value applicable both for * private and shared mapping. */ addr_mask = ~(((uintptr_t) 1 << 13) - 1); mmap_type = MAP_SHARED; } /* * We cannot just do * * if (NUM_USER_ADDRESS_BITS < sizeof(uintptr_t) * 8) { * addr_mask &= (((uintptr_t) 1 << NUM_USER_ADDRESS_BITS) - 1); * } * * since when NUM_USER_ADDRESS_BITS is defined to be 32, for * example, and sizeof(uintptr_t) is 4, then even though the * constant expression NUM_USER_ADDRESS_BITS < sizeof(uintptr_t) * 8 * is false, the compiler still parses the block of code controlled * by the conditional. And the warning for shifting by too many * bits would be produced because we'd venture into * undefined-behavior territory: * * 6.5.7.3: "The integer promotions are performed on each of the * operands. The type of the result is that of the promoted left * operand. If the value of the right operand is negative or is * greater than or equal to the width of the promoted left * operand, the behavior is undefined." * * Since we compile with -Wall and -Werror, this would lead to a * build failure. */ #if NACL_POINTER_SIZE > NUM_USER_ADDRESS_BITS addr_mask &= (((uintptr_t) 1 << NUM_USER_ADDRESS_BITS) - 1); #endif tries_remaining = max_tries; for (;;) { #if NACL_POINTER_SIZE > 32 suggested_addr = (((uintptr_t) NaClGlobalSecureRngUint32() << 32) | (uintptr_t) NaClGlobalSecureRngUint32()); #else suggested_addr = ((uintptr_t) NaClGlobalSecureRngUint32()); #endif suggested_addr &= addr_mask; NaClLog(4, ("NaClFindAddressSpaceRandomized: non-MAP_FAILED tries" " remaining %d, hint addr %"NACL_PRIxPTR"\n"), tries_remaining, suggested_addr); map_addr = mmap((void *) suggested_addr, memory_size, PROT_NONE, MAP_ANONYMOUS | MAP_NORESERVE | mmap_type, -1, 0); /* * On most POSIX systems, the mmap syscall, without MAP_FIXED, * will use the first parameter (actual argument suggested_addr) * as a hint for where to find a hole in the address space for the * new memory -- often as a starting point for a search. * * However, when Valgrind is used (3.7.0 to 3.8.1 at least), the * mmap syscall is replaced with wrappers which do not behave * correctly: the Valgrind-provided mmap replacement will return * MAP_FAILED instead of ignoring the hint, until the "Warning: * set address range perms: large range [0x...., 0x....] * (noaccess)" message to warn about large allocations shows up. * So in order for this code to not fail when run under Valgrind, * we have to ignore MAP_FAILED and not count these attempts * against randomization failures. */ if (MAP_FAILED == map_addr) { if (--map_failed_count_left != 0) { NaClLog(LOG_INFO, "NaClFindAddressSpaceRandomized: MAP_FAILED\n"); } else { NaClLog(LOG_ERROR, "NaClFindAddressSpaceRandomized: too many MAP_FAILED\n"); return 0; } } else { /* * mmap will use a system-dependent algorithm to find a starting * address if the hint location cannot work, e.g., if there is * already memory mapped there. We do not trust that algorithm * to provide any randomness in the high-order bits, so we only * accept allocations that match the requested high-order bits * exactly. If the algorithm is to scan the address space * starting near the hint address, then it would be acceptable; * if it is to use a default algorithm that is independent of * the supplied hint, then it would not be. */ if ((addr_mask & ((uintptr_t) map_addr)) == suggested_addr) { NaClLog(5, "NaClFindAddressSpaceRandomized: high order bits matched.\n"); /* success */ break; } if (0 == tries_remaining--) { /* give up on retrying, and just use what was returned */ NaClLog(5, "NaClFindAddressSpaceRandomized: last try, taking as is.\n"); break; } /* * Remove undesirable mapping location before trying again. */ if (-1 == munmap(map_addr, memory_size)) { NaClLog(LOG_FATAL, "NaClFindAddressSpaceRandomized: could not unmap non-random" " memory\n"); } } } NaClLog(4, "NaClFindAddressSpaceRandomized: got addr %"NACL_PRIxPTR"\n", (uintptr_t) map_addr); *addr = (uintptr_t) map_addr; return 1; }
/* Test comparison for a single instruction. */ static void TryOneInstruction(uint8_t *itext, size_t nbytes) { NaClEnumerator pinst; /* for prod validator */ NaClEnumerator dinst; /* for dfa validator */ Bool prod_okay, rdfa_okay; IncrTried(); do { if (gVerbose) { printf("================"); PrintBytes(stdout, itext, nbytes); printf("\n"); } /* Try to parse the sequence of test bytes. */ InitInst(&pinst, itext, nbytes); InitInst(&dinst, itext, nbytes); vProd->_parse_inst_fn(&pinst, kTextAddress); vDFA->_parse_inst_fn(&dinst, kTextAddress); prod_okay = vProd->_maybe_inst_validates_fn(&pinst); rdfa_okay = vDFA->_maybe_inst_validates_fn(&dinst); if (prod_okay && rdfa_okay) { if (vProd->_inst_length_fn(&pinst) == vDFA->_inst_length_fn(&dinst)) { /* Both validators see a legal instruction, */ /* and they agree on critical details. */ IncrValid(); } else { DecoderError("LENGTH MISMATCH", &pinst, &dinst, ""); IncrErrors(); } } else if (prod_okay && !rdfa_okay) { /* * 32bit production validator by design is unable to distingush a lot of * instructions (the ones which work only with memory or only with * registers). To avoid commiting multimegabyte golden file don't count * these differences as substantial. It's not a security problem if we * reject some valid x86 instructions and if we'll lose something * important hopefully developers will remind us. */ if (NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86 && NACL_TARGET_SUBARCH == 32) { IncrIgnored(); } else { /* Validators disagree on instruction legality */ DecoderError("VALIDATORS DISAGREE (prod accepts, RDFA rejects)", &pinst, &dinst, ""); IncrErrors(); } } else if (!prod_okay && rdfa_okay) { /* Validators disagree on instruction legality */ DecoderError("VALIDATORS DISAGREE (prod rejects, RDFA accepts)", &pinst, &dinst, ""); IncrErrors(); } else { /* Both validators see an illegal instruction */ IncrInvalid(); } if (gVerbose) { PrintDisassembledInstructionVariants(&pinst, &dinst); } } while (0); }
/* * On x86-64 Windows, we expect the backtrace to contain the * following, with no gaps: * * NaClSyscallCSegHook * NaClSwitchSavingStackPtr * NaClStartThreadInApp * * We could check for those names, but symbols are not always * available. Instead we check for bogus stack frames below, which * the stack unwinder lets through. */ static void Backtrace(CONTEXT *initial_context) { int machine_type; CONTEXT context_for_frame = *initial_context; STACKFRAME64 frame = { 0 }; int frame_number = 0; int failed = 0; fprintf(stderr, "Stack backtrace:\n"); #if NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86 && NACL_BUILD_SUBARCH == 64 machine_type = IMAGE_FILE_MACHINE_AMD64; frame.AddrPC.Offset = initial_context->Rip; frame.AddrFrame.Offset = initial_context->Rbp; frame.AddrStack.Offset = initial_context->Rsp; #elif NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86 && NACL_BUILD_SUBARCH == 32 machine_type = IMAGE_FILE_MACHINE_I386; frame.AddrPC.Offset = initial_context->Eip; frame.AddrFrame.Offset = initial_context->Ebp; frame.AddrStack.Offset = initial_context->Esp; #else # error Unknown architecture #endif frame.AddrPC.Mode = AddrModeFlat; frame.AddrFrame.Mode = AddrModeFlat; frame.AddrStack.Mode = AddrModeFlat; frame.Virtual = 0; while (1) { STACKFRAME64 previous_frame = frame; if (!StackWalk64(machine_type, GetCurrentProcess(), GetCurrentThread(), &frame, &context_for_frame, NULL, /* use ReadMemory() */ SymFunctionTableAccess64, SymGetModuleBase64, NULL)) { break; } fprintf(stderr, "#%i: ip=%p stack=%llx frame=%llx ", frame_number, frame.AddrPC.Offset, frame.AddrStack.Offset, frame.AddrFrame.Offset); PrintSymbolForAddress(frame.AddrPC.Offset); /* * Perform some sanity checks. Windows' x86-64 stack unwinder * applies a fallback rule when it sees return addresses without * unwind info, but the fallback rule is for leaf functions. This * causes the stack unwinder to report stack frames that cannot * possibly be valid in the Windows x86-64 ABI. We check for such * frames here. An error here would suggest that the unwind info * for NaClSwitchSavingStackPtr is wrong. * * The frame for a non-leaf function Foo() looks like this: * 32 bytes shadow space (scratch space for Foo()) * ---- Foo()'s caller's AddrStack points here * 8 bytes return address (points into Foo()'s caller) * 8 bytes scratch space for Foo() * ---- Foo()'s AddrFrame points here (unless the hardware exception * occurred inside Foo(), in which case this is less well-defined) * 16*n bytes scratch space for Foo() (for some n >= 0) * 32 bytes shadow space (scratch space for Foo()'s callees) * ---- Foo()'s rsp and AddrStack point here * * The frame for a leaf function Bar() that never adjusts rsp * looks like this: * 32 bytes shadow space (scratch space for Bar()) * 8 bytes return address (points into Bar()'s caller) * ---- Bar()'s rsp and AddrStack points here * 8 bytes not usable by Bar() at all! * ---- Bar()'s AddrFrame points here */ if (NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86 && NACL_BUILD_SUBARCH == 64) { /* frame_size must be signed for the check to be useful. */ long long frame_size = frame.AddrFrame.Offset - frame.AddrStack.Offset; if (frame_number > 0 && frame_size < 32) { fprintf(stderr, "Error: frame_size=%i, which is too small\n", frame_size); failed = 1; } if (frame_number > 1 && frame.AddrStack.Offset != previous_frame.AddrFrame.Offset + 16) { fprintf(stderr, "Error: stack does not fit with previous frame\n"); failed = 1; } } frame_number++; } if (failed) { _exit(1); } }