int NaClSelLdrMain(int argc, char **argv) { int opt; char *rest; struct redir *entry; struct redir *redir_queue; struct redir **redir_qend; struct NaClApp state; char *nacl_file = NULL; char *blob_library_file = NULL; int rpc_supplies_nexe = 0; int export_addr_to = -1; struct NaClApp *nap = &state; struct GioFile gout; NaClErrorCode errcode = LOAD_INTERNAL; struct GioMemoryFileSnapshot blob_file; int ret_code; struct DynArray env_vars; char *log_file = NULL; int verbosity = 0; int fuzzing_quit_after_load = 0; int debug_mode_bypass_acl_checks = 0; int debug_mode_ignore_validator = 0; int skip_qualification = 0; int handle_signals = 0; int enable_debug_stub = 0; struct NaClPerfCounter time_all_main; const char **envp; struct NaClEnvCleanser env_cleanser; #if NACL_OSX /* Mac dynamic libraries cannot access the environ variable directly. */ envp = (const char **) *_NSGetEnviron(); #else /* Overzealous code style check is overzealous. */ /* @IGNORE_LINES_FOR_CODE_HYGIENE[1] */ extern char **environ; envp = (const char **) environ; #endif ret_code = 1; redir_queue = NULL; redir_qend = &redir_queue; memset(&state, 0, sizeof state); NaClAllModulesInit(); NaClBootstrapChannelErrorReporterInit(); NaClErrorLogHookInit(NaClBootstrapChannelErrorReporter, &state); verbosity = NaClLogGetVerbosity(); NaClPerfCounterCtor(&time_all_main, "SelMain"); fflush((FILE *) NULL); NaClDebugExceptionHandlerStandaloneHandleArgs(argc, argv); if (!GioFileRefCtor(&gout, stdout)) { fprintf(stderr, "Could not create general standard output channel\n"); exit(1); } if (!NaClAppCtor(&state)) { NaClLog(LOG_FATAL, "NaClAppCtor() failed\n"); } if (!DynArrayCtor(&env_vars, 0)) { NaClLog(LOG_FATAL, "Failed to allocate env var array\n"); } /* * On platforms with glibc getopt, require POSIXLY_CORRECT behavior, * viz, no reordering of the arglist -- stop argument processing as * soon as an unrecognized argument is encountered, so that, for * example, in the invocation * * sel_ldr foo.nexe -vvv * * the -vvv flags are made available to the nexe, rather than being * consumed by getopt. This makes the behavior of the Linux build * of sel_ldr consistent with the Windows and OSX builds. */ while ((opt = my_getopt(argc, argv, #if NACL_LINUX "+D:z:" #endif "aB:ceE:f:Fgh:i:l:Qr:RsSvw:X:Z")) != -1) { switch (opt) { case 'a': fprintf(stderr, "DEBUG MODE ENABLED (bypass acl)\n"); debug_mode_bypass_acl_checks = 1; break; case 'B': blob_library_file = optarg; break; case 'c': ++debug_mode_ignore_validator; break; #if NACL_LINUX case 'D': NaClHandleRDebug(optarg, argv[0]); break; #endif case 'e': nap->enable_exception_handling = 1; break; case 'E': /* * For simplicity, we treat the environment variables as a * list of strings rather than a key/value mapping. We do not * try to prevent duplicate keys or require the strings to be * of the form "KEY=VALUE". This is in line with how execve() * works in Unix. * * We expect that most callers passing "-E" will either pass * in a fixed list or will construct the list using a * high-level language, in which case de-duplicating keys * outside of sel_ldr is easier. However, we could do * de-duplication here if it proves to be worthwhile. */ if (!DynArraySet(&env_vars, env_vars.num_entries, optarg)) { NaClLog(LOG_FATAL, "Adding item to env_vars failed\n"); } break; case 'f': nacl_file = optarg; break; case 'F': fuzzing_quit_after_load = 1; break; case 'g': enable_debug_stub = 1; break; case 'h': case 'r': case 'w': /* import host descriptor */ entry = malloc(sizeof *entry); if (NULL == entry) { fprintf(stderr, "No memory for redirection queue\n"); exit(1); } entry->next = NULL; entry->nacl_desc = strtol(optarg, &rest, 0); entry->tag = HOST_DESC; entry->u.host.d = strtol(rest+1, (char **) 0, 0); entry->u.host.mode = ImportModeMap(opt); *redir_qend = entry; redir_qend = &entry->next; break; case 'i': /* import IMC handle */ entry = malloc(sizeof *entry); if (NULL == entry) { fprintf(stderr, "No memory for redirection queue\n"); exit(1); } entry->next = NULL; entry->nacl_desc = strtol(optarg, &rest, 0); entry->tag = IMC_DESC; entry->u.handle = (NaClHandle) strtol(rest+1, (char **) 0, 0); *redir_qend = entry; redir_qend = &entry->next; break; case 'l': log_file = optarg; break; case 'Q': fprintf(stderr, "PLATFORM QUALIFICATION DISABLED BY -Q - " "Native Client's sandbox will be unreliable!\n"); skip_qualification = 1; break; case 'R': rpc_supplies_nexe = 1; break; /* case 'r': with 'h' and 'w' above */ case 's': nap->validator_stub_out_mode = 1; break; case 'S': handle_signals = 1; break; case 'v': ++verbosity; NaClLogIncrVerbosity(); break; /* case 'w': with 'h' and 'r' above */ case 'X': export_addr_to = strtol(optarg, (char **) 0, 0); break; #if NACL_LINUX case 'z': NaClHandleReservedAtZero(optarg); break; #endif case 'Z': NaClLog(LOG_WARNING, "Enabling Fixed-Feature CPU Mode\n"); nap->fixed_feature_cpu_mode = 1; if (!nap->validator->FixCPUFeatures(nap->cpu_features)) { NaClLog(LOG_ERROR, "This CPU lacks features required by " "fixed-function CPU mode.\n"); exit(1); } break; default: fprintf(stderr, "ERROR: unknown option: [%c]\n\n", opt); PrintUsage(); exit(-1); } } if (debug_mode_ignore_validator == 1) fprintf(stderr, "DEBUG MODE ENABLED (ignore validator)\n"); else if (debug_mode_ignore_validator > 1) fprintf(stderr, "DEBUG MODE ENABLED (skip validator)\n"); if (verbosity) { int ix; char const *separator = ""; fprintf(stderr, "sel_ldr argument list:\n"); for (ix = 0; ix < argc; ++ix) { fprintf(stderr, "%s%s", separator, argv[ix]); separator = " "; } putc('\n', stderr); } if (debug_mode_bypass_acl_checks) { NaClInsecurelyBypassAllAclChecks(); } /* * change stdout/stderr to log file now, so that subsequent error * messages will go there. unfortunately, error messages that * result from getopt processing -- usually out-of-memory, which * shouldn't happen -- won't show up. */ if (NULL != log_file) { NaClLogSetFile(log_file); } if (rpc_supplies_nexe) { if (NULL != nacl_file) { fprintf(stderr, "sel_ldr: mutually exclusive flags -f and -R both used\n"); exit(1); } /* post: NULL == nacl_file */ if (export_addr_to < 0) { fprintf(stderr, "sel_ldr: -R requires -X to set up secure command channel\n"); exit(1); } } else { if (NULL == nacl_file && optind < argc) { nacl_file = argv[optind]; ++optind; } if (NULL == nacl_file) { fprintf(stderr, "No nacl file specified\n"); exit(1); } /* post: NULL != nacl_file */ } /* * post condition established by the above code (in Hoare logic * terminology): * * NULL == nacl_file iff rpc_supplies_nexe * * so hence forth, testing !rpc_supplies_nexe suffices for * establishing NULL != nacl_file. */ CHECK((NULL == nacl_file) == rpc_supplies_nexe); /* to be passed to NaClMain, eventually... */ argv[--optind] = (char *) "NaClMain"; state.ignore_validator_result = (debug_mode_ignore_validator > 0); state.skip_validator = (debug_mode_ignore_validator > 1); if (getenv("NACL_UNTRUSTED_EXCEPTION_HANDLING") != NULL) { state.enable_exception_handling = 1; } if (state.enable_exception_handling || enable_debug_stub) { #if NACL_WINDOWS state.attach_debug_exception_handler_func = NaClDebugExceptionHandlerStandaloneAttach; #elif NACL_LINUX /* NaCl's signal handler is always enabled on Linux. */ #elif NACL_OSX if (!NaClInterceptMachExceptions()) { fprintf(stderr, "ERROR setting up Mach exception interception.\n"); return -1; } #else # error Unknown host OS #endif } if (NACL_LINUX) { handle_signals = 1; } errcode = LOAD_OK; /* * in order to report load error to the browser plugin through the * secure command channel, we do not immediate jump to cleanup code * on error. rather, we continue processing (assuming earlier * errors do not make it inappropriate) until the secure command * channel is set up, and then bail out. */ /* * Ensure the platform qualification checks pass. * * NACL_DANGEROUS_SKIP_QUALIFICATION_TEST is used by tsan / memcheck * (see src/third_party/valgrind/). */ if (!skip_qualification && getenv("NACL_DANGEROUS_SKIP_QUALIFICATION_TEST") != NULL) { fprintf(stderr, "PLATFORM QUALIFICATION DISABLED BY ENVIRONMENT - " "Native Client's sandbox will be unreliable!\n"); skip_qualification = 1; } if (!skip_qualification) { NaClErrorCode pq_error = NACL_FI_VAL("pq", NaClErrorCode, NaClRunSelQualificationTests()); if (LOAD_OK != pq_error) { errcode = pq_error; nap->module_load_status = pq_error; fprintf(stderr, "Error while loading \"%s\": %s\n", NULL != nacl_file ? nacl_file : "(no file, to-be-supplied-via-RPC)", NaClErrorString(errcode)); } } if (handle_signals) { NaClSignalHandlerInit(); } else { /* * Patch the Windows exception dispatcher to be safe in the case * of faults inside x86-64 sandboxed code. The sandbox is not * secure on 64-bit Windows without this. */ #if (NACL_WINDOWS && NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86 && \ NACL_BUILD_SUBARCH == 64) NaClPatchWindowsExceptionDispatcher(); #endif } NaClSignalTestCrashOnStartup(); /* * Open both files first because (on Mac OS X at least) * NaClAppLoadFile() enables an outer sandbox. */ if (NULL != blob_library_file) { NaClFileNameForValgrind(blob_library_file); if (0 == GioMemoryFileSnapshotCtor(&blob_file, blob_library_file)) { perror("sel_main"); fprintf(stderr, "Cannot open \"%s\".\n", blob_library_file); exit(1); } NaClPerfCounterMark(&time_all_main, "SnapshotBlob"); NaClPerfCounterIntervalLast(&time_all_main); } NaClAppInitialDescriptorHookup(nap); if (!rpc_supplies_nexe) { struct GioMemoryFileSnapshot main_file; NaClFileNameForValgrind(nacl_file); if (0 == GioMemoryFileSnapshotCtor(&main_file, nacl_file)) { perror("sel_main"); fprintf(stderr, "Cannot open \"%s\".\n", nacl_file); exit(1); } NaClPerfCounterMark(&time_all_main, "SnapshotNaclFile"); NaClPerfCounterIntervalLast(&time_all_main); if (LOAD_OK == errcode) { NaClLog(2, "Loading nacl file %s (non-RPC)\n", nacl_file); errcode = NaClAppLoadFile((struct Gio *) &main_file, nap); if (LOAD_OK != errcode) { fprintf(stderr, "Error while loading \"%s\": %s\n", nacl_file, NaClErrorString(errcode)); fprintf(stderr, ("Using the wrong type of nexe (nacl-x86-32" " on an x86-64 or vice versa)\n" "or a corrupt nexe file may be" " responsible for this error.\n")); } NaClPerfCounterMark(&time_all_main, "AppLoadEnd"); NaClPerfCounterIntervalLast(&time_all_main); NaClXMutexLock(&nap->mu); nap->module_load_status = errcode; NaClXCondVarBroadcast(&nap->cv); NaClXMutexUnlock(&nap->mu); } if (-1 == (*((struct Gio *) &main_file)->vtbl->Close)((struct Gio *) &main_file)) { fprintf(stderr, "Error while closing \"%s\".\n", nacl_file); } (*((struct Gio *) &main_file)->vtbl->Dtor)((struct Gio *) &main_file); if (fuzzing_quit_after_load) { exit(0); } } /* * Execute additional I/O redirections. NB: since the NaClApp * takes ownership of host / IMC socket descriptors, all but * the first run will not get access if the NaClApp closes * them. Currently a normal NaClApp process exit does not * close descriptors, since the underlying host OS will do so * as part of service runtime exit. */ NaClLog(4, "Processing I/O redirection/inheritance from command line\n"); for (entry = redir_queue; NULL != entry; entry = entry->next) { switch (entry->tag) { case HOST_DESC: NaClAddHostDescriptor(nap, entry->u.host.d, entry->u.host.mode, entry->nacl_desc); break; case IMC_DESC: NaClAddImcHandle(nap, entry->u.handle, entry->nacl_desc); break; } } /* * If export_addr_to is set to a non-negative integer, we create a * bound socket and socket address pair and bind the former to * descriptor NACL_SERVICE_PORT_DESCRIPTOR (3 [see sel_ldr.h]) and * the latter to descriptor NACL_SERVICE_ADDRESS_DESCRIPTOR (4). * The socket address is sent to the export_addr_to descriptor. * * The service runtime also accepts a connection on the bound socket * and spawns a secure command channel thread to service it. */ if (0 <= export_addr_to) { NaClCreateServiceSocket(nap); /* * LOG_FATAL errors that occur before NaClSetUpBootstrapChannel will * not be reported via the crash log mechanism (for Chromium * embedding of NaCl, shown in the JavaScript console). * * Some errors, such as due to NaClRunSelQualificationTests, do not * trigger a LOG_FATAL but instead set module_load_status to be sent * in the start_module RPC reply. Log messages associated with such * errors would be seen, since NaClSetUpBootstrapChannel will get * called. */ NaClSetUpBootstrapChannel(nap, (NaClHandle) export_addr_to); /* * NB: spawns a thread that uses the command channel. we do * this after NaClAppLoadFile so that NaClApp object is more * fully populated. Hereafter any changes to nap should be done * while holding locks. */ NaClSecureCommandChannel(nap); } /* * May have created a thread, so need to synchronize uses of nap * contents henceforth. */ if (rpc_supplies_nexe) { errcode = NaClWaitForLoadModuleStatus(nap); NaClPerfCounterMark(&time_all_main, "WaitForLoad"); NaClPerfCounterIntervalLast(&time_all_main); } else { /************************************************************************** * TODO(bsy): This else block should be made unconditional and * invoked after the LoadModule RPC completes, eliminating the * essentially dulicated code in latter part of NaClLoadModuleRpc. * This cannot be done until we have full saucer separation * technology, since Chrome currently uses sel_main_chrome.c and * relies on the functionality of the duplicated code. *************************************************************************/ if (LOAD_OK == errcode) { if (verbosity) { gprintf((struct Gio *) &gout, "printing NaClApp details\n"); NaClAppPrintDetails(nap, (struct Gio *) &gout); } /* * Finish setting up the NaCl App. On x86-32, this means * allocating segment selectors. On x86-64 and ARM, this is * (currently) a no-op. */ errcode = NaClAppPrepareToLaunch(nap); if (LOAD_OK != errcode) { nap->module_load_status = errcode; fprintf(stderr, "NaClAppPrepareToLaunch returned %d", errcode); } NaClPerfCounterMark(&time_all_main, "AppPrepLaunch"); NaClPerfCounterIntervalLast(&time_all_main); } /* Give debuggers a well known point at which xlate_base is known. */ NaClGdbHook(&state); } /* * Tell the debug stub to bind a TCP port before enabling the outer * sandbox. This is only needed on Mac OS X since that is the only * platform where we have an outer sandbox in standalone sel_ldr. * In principle this call should work on all platforms, but Windows * XP seems to have some problems when we do bind()/listen() on a * separate thread from accept(). */ if (enable_debug_stub && NACL_OSX) { if (!NaClDebugBindSocket()) { exit(1); } } /* * Enable the outer sandbox, if one is defined. Do this as soon as * possible. * * This must come after NaClWaitForLoadModuleStatus(), which waits * for another thread to have called NaClAppLoadFile(). * NaClAppLoadFile() does not work inside the Mac outer sandbox in * standalone sel_ldr when using a dynamic code area because it uses * NaClCreateMemoryObject() which opens a file in /tmp. * * We cannot enable the sandbox if file access is enabled. */ if (!NaClAclBypassChecks && g_enable_outer_sandbox_func != NULL) { g_enable_outer_sandbox_func(); } if (NULL != blob_library_file) { if (LOAD_OK == errcode) { NaClLog(2, "Loading blob file %s\n", blob_library_file); errcode = NaClAppLoadFileDynamically(nap, (struct Gio *) &blob_file); if (LOAD_OK != errcode) { fprintf(stderr, "Error while loading \"%s\": %s\n", blob_library_file, NaClErrorString(errcode)); } NaClPerfCounterMark(&time_all_main, "BlobLoaded"); NaClPerfCounterIntervalLast(&time_all_main); } if (-1 == (*((struct Gio *) &blob_file)->vtbl->Close)((struct Gio *) &blob_file)) { fprintf(stderr, "Error while closing \"%s\".\n", blob_library_file); } (*((struct Gio *) &blob_file)->vtbl->Dtor)((struct Gio *) &blob_file); if (verbosity) { gprintf((struct Gio *) &gout, "printing post-IRT NaClApp details\n"); NaClAppPrintDetails(nap, (struct Gio *) &gout); } } /* * Print out a marker for scripts to use to mark the start of app * output. */ NaClLog(1, "NACL: Application output follows\n"); /* * Make sure all the file buffers are flushed before entering * the application code. */ fflush((FILE *) NULL); if (NULL != nap->secure_service) { NaClErrorCode start_result; /* * wait for start_module RPC call on secure channel thread. */ start_result = NaClWaitForStartModuleCommand(nap); NaClPerfCounterMark(&time_all_main, "WaitedForStartModuleCommand"); NaClPerfCounterIntervalLast(&time_all_main); if (LOAD_OK == errcode) { errcode = start_result; } } /* * error reporting done; can quit now if there was an error earlier. */ if (LOAD_OK != errcode) { NaClLog(4, "Not running app code since errcode is %s (%d)\n", NaClErrorString(errcode), errcode); goto done; } if (!DynArraySet(&env_vars, env_vars.num_entries, NULL)) { NaClLog(LOG_FATAL, "Adding env_vars NULL terminator failed\n"); } NaClEnvCleanserCtor(&env_cleanser, 0); if (!NaClEnvCleanserInit(&env_cleanser, envp, (char const *const *)env_vars.ptr_array)) { NaClLog(LOG_FATAL, "Failed to initialise env cleanser\n"); } if (!NaClAppLaunchServiceThreads(nap)) { fprintf(stderr, "Launch service threads failed\n"); goto done; } if (enable_debug_stub) { if (!NaClDebugInit(nap)) { goto done; } } NACL_TEST_INJECTION(BeforeMainThreadLaunches, ()); if (!NaClCreateMainThread(nap, argc - optind, argv + optind, NaClEnvCleanserEnvironment(&env_cleanser))) { fprintf(stderr, "creating main thread failed\n"); goto done; } NaClEnvCleanserDtor(&env_cleanser); NaClPerfCounterMark(&time_all_main, "CreateMainThread"); NaClPerfCounterIntervalLast(&time_all_main); DynArrayDtor(&env_vars); ret_code = NaClWaitForMainThreadToExit(nap); NaClPerfCounterMark(&time_all_main, "WaitForMainThread"); NaClPerfCounterIntervalLast(&time_all_main); NaClPerfCounterMark(&time_all_main, "SelMainEnd"); NaClPerfCounterIntervalTotal(&time_all_main); /* * exit_group or equiv kills any still running threads while module * addr space is still valid. otherwise we'd have to kill threads * before we clean up the address space. */ NaClExit(ret_code); done: fflush(stdout); if (verbosity) { gprintf((struct Gio *) &gout, "exiting -- printing NaClApp details\n"); NaClAppPrintDetails(nap, (struct Gio *) &gout); printf("Dumping vmmap.\n"); fflush(stdout); PrintVmmap(nap); fflush(stdout); } /* * If there is a secure command channel, we sent an RPC reply with * the reason that the nexe was rejected. If we exit now, that * reply may still be in-flight and the various channel closure (esp * reverse channel) may be detected first. This would result in a * crash being reported, rather than the error in the RPC reply. * Instead, we wait for the hard-shutdown on the command channel. */ if (LOAD_OK != errcode) { NaClBlockIfCommandChannelExists(nap); } if (verbosity > 0) { printf("Done.\n"); } fflush(stdout); if (handle_signals) NaClSignalHandlerFini(); NaClAllModulesFini(); NaClExit(ret_code); /* Unreachable, but having the return prevents a compiler error. */ return ret_code; }
/* * Attempt to map into the NaClApp object nap from the NaCl descriptor * ndp an ELF segment of type p_flags that start at file_offset for * segment_size bytes, to memory starting at paddr (system address). * If it is a code segment, make a scratch mapping and check * validation in readonly_text mode -- if it succeeds, we map into the * target address; if it fails, we return failure so that pread-based * loading can proceed. For rodata and data segments, less checking * is needed. In the text and data case, the end of the segment may * not land on a NACL_MAP_PAGESIZE boundary; when this occurs, we will * map in all whole NACL_MAP_PAGESIZE chunks, and pread in the tail * partial chunk. * * Returns: LOAD_OK, LOAD_STATUS_UNKNOWN, other error codes. * * LOAD_OK -- if the segment has been fully handled * LOAD_STATUS_UNKNOWN -- if pread-based fallback is required * other error codes -- if a fatal error occurs, and the caller * should propagate up * * See NaClSysMmapIntern in nacl_syscall_common.c for corresponding * mmap syscall where PROT_EXEC allows shared libraries to be mapped * into dynamic code space. */ static NaClErrorCode NaClElfFileMapSegment(struct NaClApp *nap, struct NaClDesc *ndp, Elf_Word p_flags, Elf_Off file_offset, Elf_Off segment_size, uintptr_t vaddr, uintptr_t paddr) { size_t rounded_filesz; /* 64k rounded */ int mmap_prot = 0; uintptr_t image_sys_addr; NaClValidationStatus validator_status = NaClValidationFailed; struct NaClValidationMetadata metadata; int read_last_page_if_partial_allocation_page = 1; ssize_t read_ret; struct NaClPerfCounter time_mmap_segment; NaClPerfCounterCtor(&time_mmap_segment, "NaClElfFileMapSegment"); rounded_filesz = NaClRoundAllocPage(segment_size); NaClLog(4, "NaClElfFileMapSegment: checking segment flags 0x%x" " to determine map checks\n", p_flags); /* * Is this the text segment? If so, map into scratch memory and * run validation (possibly cached result) with !stubout_mode, * readonly_text. If validator says it's okay, map directly into * target location with NACL_ABI_PROT_READ|_EXEC. If anything * failed, fall back to PRead. NB: the assumption is that there * is only one PT_LOAD with PF_R|PF_X segment; this assumption is * enforced by phdr seen_seg checks above in * NaClElfImageValidateProgramHeaders. * * After this function returns, we will be setting memory protection * in NaClMemoryProtection, so the actual memory protection used is * immaterial. * * For rodata and data/bss, we mmap with NACL_ABI_PROT_READ or * NACL_ABI_PROT_READ | NACL_ABI_PROT_WRITE as appropriate, * without doing validation. There is no fallback to PRead, since * we don't validate the contents. */ switch (p_flags) { case PF_R | PF_X: NaClLog(4, "NaClElfFileMapSegment: text segment and" " file is safe for mmap\n"); if (NACL_VTBL(NaClDesc, ndp)->typeTag != NACL_DESC_HOST_IO) { NaClLog(4, "NaClElfFileMapSegment: not supported type, got %d\n", NACL_VTBL(NaClDesc, ndp)->typeTag); return LOAD_STATUS_UNKNOWN; } /* * Unlike the mmap case, we do not re-run validation to * allow patching here; instead, we handle validation * failure by going to the pread_fallback case. In the * future, we should consider doing an in-place mapping and * allowing HLT patch validation, which should be cheaper * since those pages that do not require patching (hopefully * majority) will remain file-backed and not require swap * space, even if we had to fault in every page. */ NaClLog(1, "NaClElfFileMapSegment: mapping for validation\n"); NaClPerfCounterMark(&time_mmap_segment, "PreMap"); NaClPerfCounterIntervalLast(&time_mmap_segment); image_sys_addr = (*NACL_VTBL(NaClDesc, ndp)-> Map)(ndp, NaClDescEffectorTrustedMem(), (void *) NULL, rounded_filesz, NACL_ABI_PROT_READ, NACL_ABI_MAP_PRIVATE, file_offset); NaClPerfCounterMark(&time_mmap_segment, "MapForValidate"); NaClPerfCounterIntervalLast(&time_mmap_segment); if (NaClPtrIsNegErrno(&image_sys_addr)) { NaClLog(LOG_INFO, "NaClElfFileMapSegment: Could not make scratch mapping," " falling back to reading\n"); return LOAD_STATUS_UNKNOWN; } /* ask validator / validation cache */ NaClMetadataFromNaClDescCtor(&metadata, ndp); CHECK(segment_size == nap->static_text_end - NACL_TRAMPOLINE_END); validator_status = NACL_FI_VAL( "ELF_LOAD_FORCE_VALIDATION_STATUS", enum NaClValidationStatus, (*nap->validator-> Validate)(vaddr, (uint8_t *) image_sys_addr, segment_size, /* actual size */ 0, /* stubout_mode: no */ 1, /* readonly_text: yes */ nap->cpu_features, &metadata, nap->validation_cache)); NaClPerfCounterMark(&time_mmap_segment, "ValidateMapped"); NaClPerfCounterIntervalLast(&time_mmap_segment); NaClLog(3, "NaClElfFileMapSegment: validator_status %d\n", validator_status); NaClMetadataDtor(&metadata); /* * Remove scratch mapping, then map directly into untrusted * address space or pread. */ NaClDescUnmapUnsafe(ndp, (void *) image_sys_addr, rounded_filesz); NACL_MAKE_MEM_UNDEFINED((void *) paddr, rounded_filesz); if (NaClValidationSucceeded != validator_status) { NaClLog(3, ("NaClElfFileMapSegment: readonly_text validation for mmap" " failed. Will retry validation allowing HALT stubbing out" " of unsupported instruction extensions.\n")); return LOAD_STATUS_UNKNOWN; } NaClLog(1, "NaClElfFileMapSegment: mapping into code space\n"); /* * Windows appears to not allow RWX mappings. This interferes * with HALT_SLED and having to HALT pad the last page. We * allow partial code pages, so * read_last_page_if_partial_allocation_page will ensure that * the last page is writable, so we will be able to write HALT * instructions as needed. */ mmap_prot = NACL_ABI_PROT_READ | NACL_ABI_PROT_EXEC; /* * NB: the log string is used by tests/mmap_main_nexe/nacl.scons * and must be logged at a level that is less than or equal to * the requested verbosity level there. */ NaClLog(1, "NaClElfFileMapSegment: EXERCISING MMAP LOAD PATH\n"); nap->main_exe_prevalidated = 1; break; case PF_R | PF_W: /* read-write (initialized data) */ mmap_prot = NACL_ABI_PROT_READ | NACL_ABI_PROT_WRITE; /* * NB: the partial page processing will result in zeros * following the initialized data, so that the BSS will be zero. * On a typical system, this page is mapped in and the BSS * region is memset to zero, which means that this partial page * is faulted in. Rather than saving a syscall (pread) and * faulting it in, we just use the same code path as for code, * which is (slightly) simpler. */ break; case PF_R: /* read-only */ mmap_prot = NACL_ABI_PROT_READ; /* * For rodata, we allow mapping in "garbage" past a partial * page; this potentially eliminates a disk I/O operation * (if data section has no partial page), possibly delaying * disk spin-up if the code was in the validation cache. * And it saves another 64kB of swap. */ read_last_page_if_partial_allocation_page = 0; break; default: NaClLog(LOG_FATAL, "NaClElfFileMapSegment: unexpected p_flags %d\n", p_flags); } if (rounded_filesz != segment_size && read_last_page_if_partial_allocation_page) { uintptr_t tail_offset = rounded_filesz - NACL_MAP_PAGESIZE; size_t tail_size = segment_size - tail_offset; NaClLog(4, "NaClElfFileMapSegment: pread tail\n"); read_ret = (*NACL_VTBL(NaClDesc, ndp)-> PRead)(ndp, (void *) (paddr + tail_offset), tail_size, (nacl_off64_t) (file_offset + tail_offset)); NaClPerfCounterMark(&time_mmap_segment, "PRead tail"); NaClPerfCounterIntervalLast(&time_mmap_segment); if (NaClSSizeIsNegErrno(&read_ret) || (size_t) read_ret != tail_size) { NaClLog(LOG_ERROR, "NaClElfFileMapSegment: pread load of page tail failed\n"); return LOAD_SEGMENT_BAD_PARAM; } rounded_filesz -= NACL_MAP_PAGESIZE; } /* mmap in */ if (rounded_filesz == 0) { NaClLog(4, "NaClElfFileMapSegment: no pages to map, probably because" " the segment was a partial page, so it was processed by" " reading.\n"); } else { NaClLog(4, "NaClElfFileMapSegment: mapping %"NACL_PRIuS" (0x%" NACL_PRIxS") bytes to" " address 0x%"NACL_PRIxPTR", position %" NACL_PRIdElf_Off" (0x%"NACL_PRIxElf_Off")\n", rounded_filesz, rounded_filesz, paddr, file_offset, file_offset); image_sys_addr = (*NACL_VTBL(NaClDesc, ndp)-> Map)(ndp, nap->effp, (void *) paddr, rounded_filesz, mmap_prot, NACL_ABI_MAP_PRIVATE | NACL_ABI_MAP_FIXED, file_offset); NaClPerfCounterMark(&time_mmap_segment, "MapFinal"); NaClPerfCounterIntervalLast(&time_mmap_segment); if (image_sys_addr != paddr) { NaClLog(LOG_FATAL, ("NaClElfFileMapSegment: map to 0x%"NACL_PRIxPTR" (prot %x) " "failed: got 0x%"NACL_PRIxPTR"\n"), paddr, mmap_prot, image_sys_addr); } /* Tell Valgrind that we've mapped a segment of nacl_file. */ NaClFileMappingForValgrind(paddr, rounded_filesz, file_offset); } return LOAD_OK; }
NaClErrorCode NaClAppLoadFileAslr(struct NaClDesc *ndp, struct NaClApp *nap, enum NaClAslrMode aslr_mode) { NaClErrorCode ret = LOAD_INTERNAL; NaClErrorCode subret = LOAD_INTERNAL; uintptr_t rodata_end; uintptr_t data_end; uintptr_t max_vaddr; struct NaClElfImage *image = NULL; struct NaClPerfCounter time_load_file; struct NaClElfImageInfo info; NaClPerfCounterCtor(&time_load_file, "NaClAppLoadFile"); /* NACL_MAX_ADDR_BITS < 32 */ if (nap->addr_bits > NACL_MAX_ADDR_BITS) { ret = LOAD_ADDR_SPACE_TOO_BIG; goto done; } nap->stack_size = NaClRoundAllocPage(nap->stack_size); /* temporay object will be deleted at end of function */ image = NaClElfImageNew(ndp, &subret); if (NULL == image || LOAD_OK != subret) { ret = subret; goto done; } subret = NaClElfImageValidateProgramHeaders(image, nap->addr_bits, &info); if (LOAD_OK != subret) { ret = subret; goto done; } if (nap->initial_nexe_max_code_bytes != 0) { size_t code_segment_size = info.static_text_end - NACL_TRAMPOLINE_END; if (code_segment_size > nap->initial_nexe_max_code_bytes) { NaClLog(LOG_ERROR, "NaClAppLoadFileAslr: " "Code segment size (%"NACL_PRIuS" bytes) exceeds limit (%" NACL_PRId32" bytes)\n", code_segment_size, nap->initial_nexe_max_code_bytes); ret = LOAD_CODE_SEGMENT_TOO_LARGE; goto done; } } nap->static_text_end = info.static_text_end; nap->rodata_start = info.rodata_start; rodata_end = info.rodata_end; nap->data_start = info.data_start; data_end = info.data_end; max_vaddr = info.max_vaddr; if (0 == nap->data_start) { if (0 == nap->rodata_start) { if (NaClRoundAllocPage(max_vaddr) - max_vaddr < NACL_HALT_SLED_SIZE) { /* * if no rodata and no data, we make sure that there is space for * the halt sled. */ max_vaddr += NACL_MAP_PAGESIZE; } } else { /* * no data, but there is rodata. this means max_vaddr is just * where rodata ends. this might not be at an allocation * boundary, and in this the page would not be writable. round * max_vaddr up to the next allocation boundary so that bss will * be at the next writable region. */ ; } max_vaddr = NaClRoundAllocPage(max_vaddr); } /* * max_vaddr -- the break or the boundary between data (initialized * and bss) and the address space hole -- does not have to be at a * page boundary. * * Memory allocation will use NaClRoundPage(nap->break_addr), but * the system notion of break is always an exact address. Even * though we must allocate and make accessible multiples of pages, * the linux-style brk system call (which returns current break on * failure) permits a non-aligned address as argument. */ nap->break_addr = max_vaddr; nap->data_end = max_vaddr; NaClLog(4, "Values from NaClElfImageValidateProgramHeaders:\n"); NaClLog(4, "rodata_start = 0x%08"NACL_PRIxPTR"\n", nap->rodata_start); NaClLog(4, "rodata_end = 0x%08"NACL_PRIxPTR"\n", rodata_end); NaClLog(4, "data_start = 0x%08"NACL_PRIxPTR"\n", nap->data_start); NaClLog(4, "data_end = 0x%08"NACL_PRIxPTR"\n", data_end); NaClLog(4, "max_vaddr = 0x%08"NACL_PRIxPTR"\n", max_vaddr); /* We now support only one bundle size. */ nap->bundle_size = NACL_INSTR_BLOCK_SIZE; nap->initial_entry_pt = NaClElfImageGetEntryPoint(image); NaClLogAddressSpaceLayout(nap); if (!NaClAddrIsValidEntryPt(nap, nap->initial_entry_pt)) { ret = LOAD_BAD_ENTRY; goto done; } subret = NaClCheckAddressSpaceLayoutSanity(nap, rodata_end, data_end, max_vaddr); if (LOAD_OK != subret) { ret = subret; goto done; } NaClLog(2, "Allocating address space\n"); NaClPerfCounterMark(&time_load_file, "PreAllocAddrSpace"); NaClPerfCounterIntervalLast(&time_load_file); subret = NaClAllocAddrSpaceAslr(nap, aslr_mode); NaClPerfCounterMark(&time_load_file, NACL_PERF_IMPORTANT_PREFIX "AllocAddrSpace"); NaClPerfCounterIntervalLast(&time_load_file); if (LOAD_OK != subret) { ret = subret; goto done; } /* * Make sure the static image pages are marked writable before we try * to write them. */ NaClLog(2, "Loading into memory\n"); ret = NaClMprotect((void *) (nap->mem_start + NACL_TRAMPOLINE_START), NaClRoundAllocPage(nap->data_end) - NACL_TRAMPOLINE_START, PROT_READ | PROT_WRITE); if (0 != ret) { NaClLog(LOG_FATAL, "NaClAppLoadFile: Failed to make image pages writable. " "Error code 0x%x\n", ret); } subret = NaClElfImageLoad(image, ndp, nap); NaClPerfCounterMark(&time_load_file, NACL_PERF_IMPORTANT_PREFIX "NaClElfImageLoad"); NaClPerfCounterIntervalLast(&time_load_file); if (LOAD_OK != subret) { ret = subret; goto done; } /* * NB: mem_map object has been initialized, but is empty. * NaClMakeDynamicTextShared does not touch it. * * NaClMakeDynamicTextShared also fills the dynamic memory region * with the architecture-specific halt instruction. If/when we use * memory mapping to save paging space for the dynamic region and * lazily halt fill the memory as the pages become * readable/executable, we must make sure that the *last* * NACL_MAP_PAGESIZE chunk is nonetheless mapped and written with * halts. */ NaClLog(2, ("Replacing gap between static text and" " (ro)data with shareable memory\n")); subret = NaClMakeDynamicTextShared(nap); NaClPerfCounterMark(&time_load_file, NACL_PERF_IMPORTANT_PREFIX "MakeDynText"); NaClPerfCounterIntervalLast(&time_load_file); if (LOAD_OK != subret) { ret = subret; goto done; } /* * NaClFillEndOfTextRegion will fill with halt instructions the * padding space after the static text region. * * Shm-backed dynamic text space was filled with halt instructions * in NaClMakeDynamicTextShared. This extends to the rodata. For * non-shm-backed text space, this extend to the next page (and not * allocation page). static_text_end is updated to include the * padding. */ NaClFillEndOfTextRegion(nap); if (nap->main_exe_prevalidated) { NaClLog(2, "Main executable segment hit validation cache and mapped in," " skipping validation.\n"); subret = LOAD_OK; } else { NaClLog(2, "Validating image\n"); subret = NaClValidateImage(nap); } NaClPerfCounterMark(&time_load_file, NACL_PERF_IMPORTANT_PREFIX "ValidateImg"); NaClPerfCounterIntervalLast(&time_load_file); if (LOAD_OK != subret) { ret = subret; goto done; } NaClLog(2, "Initializing arch switcher\n"); NaClInitSwitchToApp(nap); NaClLog(2, "Installing trampoline\n"); NaClLoadTrampoline(nap, aslr_mode); NaClLog(2, "Installing springboard\n"); NaClLoadSpringboard(nap); /* * NaClMemoryProtection also initializes the mem_map w/ information * about the memory pages and their current protection value. * * The contents of the dynamic text region will get remapped as * non-writable. */ NaClLog(2, "Applying memory protection\n"); subret = NaClMemoryProtection(nap); if (LOAD_OK != subret) { ret = subret; goto done; } NaClLog(2, "NaClAppLoadFile done; "); NaClLogAddressSpaceLayout(nap); ret = LOAD_OK; done: NaClElfImageDelete(image); NaClPerfCounterMark(&time_load_file, "EndLoadFile"); NaClPerfCounterIntervalTotal(&time_load_file); return ret; }
NaClErrorCode NaClAppLoadFile(struct Gio *gp, struct NaClApp *nap) { NaClErrorCode ret = LOAD_INTERNAL; NaClErrorCode subret; uintptr_t rodata_end; uintptr_t data_end; uintptr_t max_vaddr; struct NaClElfImage *image = NULL; struct NaClPerfCounter time_load_file; NaClPerfCounterCtor(&time_load_file, "NaClAppLoadFile"); /* NACL_MAX_ADDR_BITS < 32 */ if (nap->addr_bits > NACL_MAX_ADDR_BITS) { ret = LOAD_ADDR_SPACE_TOO_BIG; goto done; } nap->stack_size = NaClRoundAllocPage(nap->stack_size); /* temporay object will be deleted at end of function */ image = NaClElfImageNew(gp, &subret); if (NULL == image) { ret = subret; goto done; } subret = NaClElfImageValidateElfHeader(image); if (LOAD_OK != subret) { ret = subret; goto done; } subret = NaClElfImageValidateProgramHeaders(image, nap->addr_bits, &nap->static_text_end, &nap->rodata_start, &rodata_end, &nap->data_start, &data_end, &max_vaddr); if (LOAD_OK != subret) { ret = subret; goto done; } if (0 == nap->data_start) { if (0 == nap->rodata_start) { if (NaClRoundAllocPage(max_vaddr) - max_vaddr < NACL_HALT_SLED_SIZE) { /* * if no rodata and no data, we make sure that there is space for * the halt sled. */ max_vaddr += NACL_MAP_PAGESIZE; } } else { /* * no data, but there is rodata. this means max_vaddr is just * where rodata ends. this might not be at an allocation * boundary, and in this the page would not be writable. round * max_vaddr up to the next allocation boundary so that bss will * be at the next writable region. */ ; } max_vaddr = NaClRoundAllocPage(max_vaddr); } /* * max_vaddr -- the break or the boundary between data (initialized * and bss) and the address space hole -- does not have to be at a * page boundary. */ nap->break_addr = max_vaddr; nap->data_end = max_vaddr; NaClLog(4, "Values from NaClElfImageValidateProgramHeaders:\n"); NaClLog(4, "rodata_start = 0x%08"NACL_PRIxPTR"\n", nap->rodata_start); NaClLog(4, "rodata_end = 0x%08"NACL_PRIxPTR"\n", rodata_end); NaClLog(4, "data_start = 0x%08"NACL_PRIxPTR"\n", nap->data_start); NaClLog(4, "data_end = 0x%08"NACL_PRIxPTR"\n", data_end); NaClLog(4, "max_vaddr = 0x%08"NACL_PRIxPTR"\n", max_vaddr); /* We now support only one bundle size. */ nap->bundle_size = NACL_INSTR_BLOCK_SIZE; nap->initial_entry_pt = NaClElfImageGetEntryPoint(image); NaClLogAddressSpaceLayout(nap); if (!NaClAddrIsValidEntryPt(nap, nap->initial_entry_pt)) { ret = LOAD_BAD_ENTRY; goto done; } subret = NaClCheckAddressSpaceLayoutSanity(nap, rodata_end, data_end, max_vaddr); if (LOAD_OK != subret) { ret = subret; goto done; } NaClLog(2, "Allocating address space\n"); NaClPerfCounterMark(&time_load_file, "PreAllocAddrSpace"); NaClPerfCounterIntervalLast(&time_load_file); subret = NaClAllocAddrSpace(nap); NaClPerfCounterMark(&time_load_file, NACL_PERF_IMPORTANT_PREFIX "AllocAddrSpace"); NaClPerfCounterIntervalLast(&time_load_file); if (LOAD_OK != subret) { ret = subret; goto done; } /* * Make sure the static image pages are marked writable before we try * to write them. */ NaClLog(2, "Loading into memory\n"); ret = NaCl_mprotect((void *) (nap->mem_start + NACL_TRAMPOLINE_START), NaClRoundAllocPage(nap->data_end) - NACL_TRAMPOLINE_START, NACL_ABI_PROT_READ | NACL_ABI_PROT_WRITE); if (0 != ret) { NaClLog(LOG_FATAL, "NaClAppLoadFile: Failed to make image pages writable. " "Error code 0x%x\n", ret); } subret = NaClElfImageLoad(image, gp, nap->addr_bits, nap->mem_start); if (LOAD_OK != subret) { ret = subret; goto done; } /* * NB: mem_map object has been initialized, but is empty. * NaClMakeDynamicTextShared does not touch it. * * NaClMakeDynamicTextShared also fills the dynamic memory region * with the architecture-specific halt instruction. If/when we use * memory mapping to save paging space for the dynamic region and * lazily halt fill the memory as the pages become * readable/executable, we must make sure that the *last* * NACL_MAP_PAGESIZE chunk is nonetheless mapped and written with * halts. */ NaClLog(2, ("Replacing gap between static text and" " (ro)data with shareable memory\n")); subret = NaClMakeDynamicTextShared(nap); NaClPerfCounterMark(&time_load_file, NACL_PERF_IMPORTANT_PREFIX "MakeDynText"); NaClPerfCounterIntervalLast(&time_load_file); if (LOAD_OK != subret) { ret = subret; goto done; } /* * NaClFillEndOfTextRegion will fill with halt instructions the * padding space after the static text region. * * Shm-backed dynamic text space was filled with halt instructions * in NaClMakeDynamicTextShared. This extends to the rodata. For * non-shm-backed text space, this extend to the next page (and not * allocation page). static_text_end is updated to include the * padding. */ NaClFillEndOfTextRegion(nap); #if 0 == NACL_DANGEROUS_DEBUG_MODE_DISABLE_INNER_SANDBOX NaClLog(2, "Validating image\n"); subret = NaClValidateImage(nap); NaClPerfCounterMark(&time_load_file, NACL_PERF_IMPORTANT_PREFIX "ValidateImg"); NaClPerfCounterIntervalLast(&time_load_file); if (LOAD_OK != subret) { ret = subret; goto done; } #endif NaClLog(2, "Initializing arch switcher\n"); NaClInitSwitchToApp(nap); NaClLog(2, "Installing trampoline\n"); NaClLoadTrampoline(nap); /* * NaClMemoryProtect also initializes the mem_map w/ information * about the memory pages and their current protection value. * * The contents of the dynamic text region will get remapped as * non-writable. */ NaClLog(2, "Applying memory protection\n"); subret = NaClMemoryProtection(nap); if (LOAD_OK != subret) { ret = subret; goto done; } NaClLog(2, "NaClAppLoadFile done; "); NaClLogAddressSpaceLayout(nap); ret = LOAD_OK; done: NaClElfImageDelete(image); NaClPerfCounterMark(&time_load_file, "EndLoadFile"); NaClPerfCounterIntervalTotal(&time_load_file); return ret; }
int32_t NaClTextDyncodeCreate(struct NaClApp *nap, uint32_t dest, void *code_copy, uint32_t size, const struct NaClValidationMetadata *metadata) { uintptr_t dest_addr; uint8_t *mapped_addr; int32_t retval = -NACL_ABI_EINVAL; int validator_result; struct NaClPerfCounter time_dyncode_create; NaClPerfCounterCtor(&time_dyncode_create, "NaClTextDyncodeCreate"); if (NULL == nap->text_shm) { NaClLog(1, "NaClTextDyncodeCreate: Dynamic loading not enabled\n"); return -NACL_ABI_EINVAL; } if (0 != (dest & (nap->bundle_size - 1)) || 0 != (size & (nap->bundle_size - 1))) { NaClLog(1, "NaClTextDyncodeCreate: Non-bundle-aligned address or size\n"); return -NACL_ABI_EINVAL; } dest_addr = NaClUserToSysAddrRange(nap, dest, size); if (kNaClBadAddress == dest_addr) { NaClLog(1, "NaClTextDyncodeCreate: Dest address out of range\n"); return -NACL_ABI_EFAULT; } if (dest < nap->dynamic_text_start) { NaClLog(1, "NaClTextDyncodeCreate: Below dynamic code area\n"); return -NACL_ABI_EFAULT; } /* * We ensure that the final HLTs of the dynamic code region cannot * be overwritten, just in case of CPU bugs. */ if (dest + size > nap->dynamic_text_end - NACL_HALT_SLED_SIZE) { NaClLog(1, "NaClTextDyncodeCreate: Above dynamic code area\n"); return -NACL_ABI_EFAULT; } if (0 == size) { /* Nothing to load. Succeed trivially. */ return 0; } NaClXMutexLock(&nap->dynamic_load_mutex); /* * Validate the code before trying to create the region. This avoids the need * to delete the region if validation fails. * See: http://code.google.com/p/nativeclient/issues/detail?id=2566 */ if (!nap->skip_validator) { validator_result = NaClValidateCode(nap, dest, code_copy, size, metadata); } else { NaClLog(LOG_ERROR, "VALIDATION SKIPPED.\n"); validator_result = LOAD_OK; } NaClPerfCounterMark(&time_dyncode_create, NACL_PERF_IMPORTANT_PREFIX "DynRegionValidate"); NaClPerfCounterIntervalLast(&time_dyncode_create); if (validator_result != LOAD_OK && nap->ignore_validator_result) { NaClLog(LOG_ERROR, "VALIDATION FAILED for dynamically-loaded code: " "continuing anyway...\n"); validator_result = LOAD_OK; } if (validator_result != LOAD_OK) { NaClLog(1, "NaClTextDyncodeCreate: " "Validation of dynamic code failed\n"); retval = -NACL_ABI_EINVAL; goto cleanup_unlock; } if (NaClDynamicRegionCreate(nap, dest_addr, size, 0) != 1) { /* target addr is in use */ NaClLog(1, "NaClTextDyncodeCreate: Code range already allocated\n"); retval = -NACL_ABI_EINVAL; goto cleanup_unlock; } if (!NaClTextMapWrapper(nap, dest, size, &mapped_addr)) { retval = -NACL_ABI_ENOMEM; goto cleanup_unlock; } CopyCodeSafelyInitial(mapped_addr, code_copy, size, nap->bundle_size); /* * Flush the processor's instruction cache. This is not necessary * for security, because any old cached instructions will just be * safe halt instructions. It is only necessary to ensure that * untrusted code runs correctly when it tries to execute the * dynamically-loaded code. */ NaClFlushCacheForDoublyMappedCode(mapped_addr, (uint8_t *) dest_addr, size); retval = 0; NaClTextMapClearCacheIfNeeded(nap, dest, size); cleanup_unlock: NaClXMutexUnlock(&nap->dynamic_load_mutex); return retval; }