NORETURN void CreateSession(struct NaClApp *nap) { uintptr_t stack_ptr; assert(nap != NULL); /* set up user stack */ stack_ptr = nap->mem_start + ((uintptr_t)1U << nap->addr_bits); stack_ptr -= STACK_USER_DATA_SIZE; memset((void*)stack_ptr, 0, STACK_USER_DATA_SIZE); ((uint32_t*)stack_ptr)[4] = 1; ((uint32_t*)stack_ptr)[5] = 0xfffffff0; /* construct "nacl_user" global */ ThreadContextCtor(nacl_user, nap, nap->initial_entry_pt, stack_ptr, 0); nacl_user->sysret = nap->break_addr; nacl_user->prog_ctr = NaClUserToSys(nap, nap->initial_entry_pt); nacl_user->new_prog_ctr = NaClUserToSys(nap, nap->initial_entry_pt); /* initialize "nacl_sys" global */ nacl_sys->rbp = GetStackPtr(); nacl_sys->rsp = GetStackPtr(); /* pass control to the user side */ ZLOGS(LOG_DEBUG, "SESSION %d STARTED", nap->manifest->node); SwitchToApp(nap, nacl_user->new_prog_ctr); ZLOGFAIL(1, EFAULT, "the unreachable has been reached"); }
/* * d'b: alternative mechanism to pass control to user side * note: initializes "nacl_user" global */ NORETURN void SwitchToApp(struct NaClApp *nap, uintptr_t stack_ptr) { /* initialize "nacl_user" global */ if(!nacl_user) nacl_user = malloc(sizeof(*nacl_user)); assert(nacl_user != NULL); /* construct "nacl_user" global */ NaClThreadContextCtor(nacl_user, nap, nap->initial_entry_pt, NaClSysToUserStackAddr(nap, stack_ptr), 0); assert(NaClSignalStackAllocate(&nap->signal_stack)); nacl_user->sysret = nap->break_addr; nacl_user->prog_ctr = NaClUserToSys(nap, nap->initial_entry_pt); nacl_user->new_prog_ctr = NaClUserToSys(nap, nap->initial_entry_pt); /* initialize "nacl_sys" global */ if(!nacl_sys) nacl_sys = malloc(sizeof(*nacl_sys)); assert(nacl_sys != NULL); nacl_sys->rbp = NaClGetStackPtr(); nacl_sys->rsp = NaClGetStackPtr(); /* set global nap to current nap object */ gnap = nap; /* * todo: put here switch to chose proper function: avx or sse */ NaClSwitchSSE(nacl_user); /* unreachable */ }
/* * validate and set syscallback (both local and global) * return 0 if syscallback installed, otherwise -1 * * update: please note, syscallback is same far jump. so it * must be tested the same way. we cannot use validator and to * prevent potential danger the code below must be *very* safe * and must check all possibilities. */ static int32_t UpdateSyscallback(struct NaClApp *nap, struct SetupList *hint) { int32_t addr = hint->syscallback; int32_t retcode = ERR_CODE; syscallback = 0; nap->manifest->user_setup->syscallback = 0; if(!addr) return OK_CODE; /* user wants to uninstall syscallback */ #define OP_ALIGNEMENT 0x20 /* check alignement */ if(addr & (OP_ALIGNEMENT - 1)) return ERR_CODE; /* seek syscallback in static text (aware of right border proximity) */ if(addr >= nap->dynamic_text_start && addr < nap->dynamic_text_end) retcode = OK_CODE; /* seek placement in dynamic text (aware of right border proximity) */ if(addr >= NACL_TRAMPOLINE_END && addr < nap->static_text_end) retcode = OK_CODE; /* set the new syscallback if found in the proper place */ if(retcode == OK_CODE) { nap->manifest->user_setup->syscallback = addr; syscallback = NaClUserToSys(nap, (intptr_t) addr); } return retcode; }
int main(int argc, char **argv) { struct NaClApp app; struct NaClApp *nap = &app; struct NaClSyscallTableEntry syscall_table[NACL_MAX_SYSCALLS] = {{0}}; int index; int use_separate_thread = 0; for (index = 0; index < NACL_MAX_SYSCALLS; index++) { syscall_table[index].handler = NotImplementedDecoder; } syscall_table[TEST_SYSCALL_INVOKE].handler = MySyscallInvoke; syscall_table[TEST_SYSCALL_EXIT].handler = MySyscallExit; NaClAllModulesInit(); CHECK(NaClAppWithSyscallTableCtor(&app, syscall_table)); CHECK(NaClAppLoadFileFromFilename(&app, argv[1]) == LOAD_OK); struct NaClAppThread *natp; uintptr_t stack_ptr = NaClGetInitialStackTop(nap); /* Ensure the stack pointer is suitably aligned. */ stack_ptr &= ~NACL_STACK_ALIGN_MASK; stack_ptr -= NACL_STACK_PAD_BELOW_ALIGN; /* TODO(mseaborn): Make this interface more straightforward to use. */ stack_ptr = NaClSysToUserStackAddr(nap, NaClUserToSys(nap, stack_ptr)); natp = NaClAppThreadMake(nap, nap->initial_entry_pt, stack_ptr, 0, 0); CHECK(natp != NULL); NaClAppThreadLauncher(natp); printf("game over\n"); return 1; }
/* * set syscallback * returns a new syscallback when successfully installed or * returns an old syscallback if installation failed * note: global syscallback uses system address space, * nap->system_manifest->syscallback - user space */ static int32_t ZVMSyscallback(struct NaClApp *nap, int32_t addr) { int32_t good_pos = 0; assert(nap != NULL); assert(nap->system_manifest != NULL); /* uninstall syscallback if 0 given */ if(addr == 0) { syscallback = 0; /* global variable */ nap->system_manifest->syscallback = 0; return 0; } /* check alignement */ if(addr & (OP_ALIGNEMENT - 1)) return nap->system_manifest->syscallback; /* check if syscallback points to text */ good_pos |= addr >= NACL_TRAMPOLINE_END && addr < nap->static_text_end; good_pos |= addr >= nap->dynamic_text_start && addr < nap->dynamic_text_end; if(good_pos) { nap->system_manifest->syscallback = addr; syscallback = NaClUserToSys(nap, (intptr_t) addr); } return nap->system_manifest->syscallback; }
/* set pointer to user manifest */ static void SetUserManifestPtr(struct NaClApp *nap, void *mft) { uintptr_t *p; p = (void*)NaClUserToSys(nap, FOURGIG - nap->stack_size - USER_PTR_SIZE); *p = NaClSysToUser(nap, (uintptr_t)mft); }
/* preallocate memory area of given size. abort if fail */ static void PreallocateUserMemory(struct NaClApp *nap) { uintptr_t i; int64_t heap; void *p; assert(nap != NULL); assert(nap->system_manifest != NULL); /* quit function if max_mem is not specified or invalid */ ZLOGFAIL(nap->heap_end == 0 || nap->heap_end > FOURGIG, ENOMEM, "invalid memory size"); /* calculate user heap size (must be allocated next to the data_end) */ p = (void*)NaClRoundAllocPage(nap->data_end); heap = nap->heap_end - nap->stack_size; heap = NaClRoundAllocPage(heap) - NaClRoundAllocPage(nap->data_end); ZLOGFAIL(heap <= LEAST_USER_HEAP_SIZE, ENOMEM, "user heap size is too small"); /* since 4gb of user space is already allocated just set protection to the heap */ p = (void*)NaClUserToSys(nap, (uintptr_t)p); i = NaCl_mprotect(p, heap, PROT_READ | PROT_WRITE); ZLOGFAIL(0 != i, -i, "cannot set protection on user heap"); nap->heap_end = NaClSysToUser(nap, (uintptr_t)p + heap); nap->mem_map[HeapIdx].size += heap; nap->mem_map[HeapIdx].end += heap; }
/* ### move to documentation or rewrite * supported three functions: * ZVMSetup(char *hint, char answer[]) * TrapRead(int32_t buffer, int32_t size, int64_t offset) * TrapWrite(int32_t buffer, int32_t size, int64_t offset) * * "args" is an array of syscall name and its arguments: * FunctionName(arg1,arg2,..) where arg1/2/3 are values/pointers * note: since nacl spoils 1st two arguments if they are pointers, arg[1] are not used */ int32_t TrapHandler(struct NaClApp *nap, uint32_t args) { uint64_t *sys_args; int retcode = 0; /* translate address from user space to system. note: cannot set "trap error" */ if(!nap->manifest) return -1; /* return error if not manifest found */ sys_args = (uint64_t*)NaClUserToSys(nap, (uintptr_t) args); NaClLog(4, "NaClSysNanosleep received in = 0x%lx\n", (intptr_t)sys_args); switch(*sys_args) { case TrapExit: retcode = TrapExitHandle(nap, (int32_t) sys_args[2]); break; case TrapUserSetup: retcode = TrapUserSetupHandle(nap, (struct SetupList*) sys_args[2]); break; case TrapRead: retcode = TrapReadHandle(nap, (enum ChannelType)sys_args[2], (char*)sys_args[3], (int32_t)sys_args[4], sys_args[5]); break; case TrapWrite: retcode = TrapWriteHandle(nap, (enum ChannelType)sys_args[2], (char*)sys_args[3], (int32_t)sys_args[4], sys_args[5]); break; default: retcode = ERR_CODE; NaClLog(LOG_ERROR, "function %ld is not supported\n", *sys_args); break; } return retcode; }
int NaClThreadContextCtor(struct NaClThreadContext *ntcp, struct NaClApp *nap, nacl_reg_t prog_ctr, nacl_reg_t stack_ptr, uint32_t tls_idx) { ntcp->rax = 0; ntcp->rbx = 0; ntcp->rcx = 0; ntcp->rdx = 0; ntcp->rbp = stack_ptr; /* must be a valid stack addr! */ ntcp->rsi = 0; ntcp->rdi = 0; ntcp->rsp = stack_ptr; ntcp->r8 = 0; ntcp->r9 = 0; ntcp->r10 = 0; ntcp->r11 = 0; ntcp->r12 = 0; ntcp->r13 = 0; ntcp->r14 = 0; ntcp->r15 = nap->mem_start; ntcp->prog_ctr = NaClUserToSys(nap, prog_ctr); ntcp->new_prog_ctr = 0; ntcp->sysret = -NACL_ABI_EINVAL; ntcp->tls_base = NULL; ntcp->tls_idx = tls_idx; return 1; }
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 }
/* * read specified amount of bytes from given desc/offset to buffer * return amount of read bytes or negative error code if call failed */ static int32_t ZVMReadHandle(struct NaClApp *nap, int ch, char *buffer, int32_t size, int64_t offset) { struct ChannelDesc *channel; int64_t tail; char *sys_buffer; assert(nap != NULL); assert(nap->manifest != NULL); assert(nap->manifest->channels != NULL); /* check the channel number */ if(ch < 0 || ch >= nap->manifest->channels->len) { ZLOGS(LOG_DEBUG, "channel_id=%d, buffer=%p, size=%d, offset=%ld", ch, buffer, size, offset); return -EINVAL; } channel = CH_CH(nap->manifest, ch); ZLOGS(LOG_INSANE, "channel %s, buffer=%p, size=%d, offset=%ld", channel->alias, buffer, size, offset); /* check buffer and convert address */ if(CheckRAMAccess(nap, (uintptr_t)buffer, size, PROT_WRITE) == -1) return -EINVAL; sys_buffer = (char*)NaClUserToSys(nap, (uintptr_t)buffer); /* ignore user offset for sequential access read */ if(CH_SEQ_READABLE(channel)) offset = channel->getpos; else /* prevent reading beyond the end of the random access channels */ size = MIN(channel->size - offset, size); /* check arguments sanity */ if(size == 0) return 0; /* success. user has read 0 bytes */ if(size < 0) return -EFAULT; if(offset < 0) return -EINVAL; /* check for eof */ if(channel->eof) return 0; /* check limits */ if(channel->counters[GetsLimit] >= channel->limits[GetsLimit]) return -EDQUOT; if(CH_RND_READABLE(channel)) if(offset >= channel->limits[PutSizeLimit] - channel->counters[PutSizeLimit] + channel->size) return -EINVAL; /* calculate i/o leftovers */ tail = channel->limits[GetSizeLimit] - channel->counters[GetSizeLimit]; if(size > tail) size = tail; if(size < 1) return -EDQUOT; /* read data */ return ChannelRead(channel, sys_buffer, (size_t)size, (off_t)offset); }
int32_t TrapHandler(struct NaClApp *nap, uint32_t args) { uint64_t *sargs; int retcode = 0; int i; assert(nap != NULL); assert(nap->manifest != NULL); /* * translate address from user space to system * note: cannot set "trap error" */ sargs = (uint64_t*)NaClUserToSys(nap, (uintptr_t)args); i = FunctionIndexById(*sargs); ZLOGS(LOG_DEBUG, "%s called", function[i]); ZTrace("untrusted code"); switch(*sargs) { case TrapFork: if(Daemon(nap) == 0) { SyscallZTrace(5, function[5]); ZVMExitHandle(nap, 0); } break; case TrapExit: ZVMExitHandle(nap, (int32_t)sargs[2]); break; case TrapRead: retcode = ZVMReadHandle(nap, (int)sargs[2], (char*)sargs[3], (int32_t)sargs[4], sargs[5]); break; case TrapWrite: retcode = ZVMWriteHandle(nap, (int)sargs[2], (char*)sargs[3], (int32_t)sargs[4], sargs[5]); break; case TrapJail: retcode = ZVMJailHandle(nap, (uint32_t)sargs[2], (int32_t)sargs[3]); break; case TrapUnjail: retcode = ZVMUnjailHandle(nap, (uint32_t)sargs[2], (int32_t)sargs[3]); break; default: retcode = -EPERM; ZLOG(LOG_ERROR, "function %ld is not supported", *sargs); break; } /* report, ztrace and return */ FastReport(); ZLOGS(LOG_DEBUG, "%s returned %d", function[i], retcode); SyscallZTrace(i, function[i], sargs[2], sargs[3], sargs[4], sargs[5], retcode); return retcode; }
/* * "One Ring" syscall main routine. the nacl syscalls replacement. * "args" is an array of syscall name and its arguments * note: since nacl patch two 1st arguments if they are pointers, arg[1] are not used * todo(d'b): check how nacl decide to patch arguments. */ int32_t TrapHandler(struct NaClApp *nap, uint32_t args) { uint64_t *sys_args; int retcode = 0; assert(nap != NULL); assert(nap->system_manifest != NULL); /* * translate address from user space to system * note: cannot set "trap error" */ sys_args = (uint64_t*)NaClUserToSys(nap, (uintptr_t) args); ZLOGS(LOG_DEBUG, "%s called", FunctionNameById(sys_args[0])); switch(*sys_args) { case TrapExit: retcode = ZVMExitHandle(nap, (int32_t) sys_args[2]); break; case TrapRead: retcode = ZVMReadHandle(nap, (int)sys_args[2], (char*)sys_args[3], (int32_t)sys_args[4], sys_args[5]); break; case TrapWrite: retcode = ZVMWriteHandle(nap, (int)sys_args[2], (char*)sys_args[3], (int32_t)sys_args[4], sys_args[5]); break; case TrapSyscallback: retcode = ZVMSyscallback(nap, (int32_t)sys_args[2]); break; case TrapChannels: retcode = ZVMChannels(nap, (struct ZVMChannel*)sys_args[2]); break; case TrapChannelName: retcode = ZVMChannelName(nap, (struct ZVMChannel*)sys_args[2], (int32_t)sys_args[3]); break; case TrapHeapEnd: retcode = ZVMHeapEnd(nap); break; case TrapHeapPtr: retcode = ZVMHeapPtr(nap); break; default: retcode = -EPERM; ZLOG(LOG_ERROR, "function %ld is not supported", *sys_args); break; } ZLOGS(LOG_DEBUG, "%s returned %d", FunctionNameById(sys_args[0]), retcode); return retcode; }
/* * initializer: channels * returns channels number if buffer == NULL, otherwise * returns channels number and initializes given buffer with channels data */ static int32_t ZVMChannels(struct NaClApp *nap, struct ZVMChannel *buf) { struct ZVMChannel *uchannels; struct ChannelDesc *channels; int ch; assert(nap != NULL); assert(nap->system_manifest != NULL); /* user asked for the channels count */ if(buf == NULL) return nap->system_manifest->channels_count; channels = nap->system_manifest->channels; uchannels = (struct ZVMChannel*)NaClUserToSys(nap, (uintptr_t)buf); /* populate given array with the channels information */ for(ch = 0; ch < nap->system_manifest->channels_count; ++ch) { int i; uchannels[ch].name = NULL; /* see ZVMChannelName() */ uchannels[ch].type = channels[ch].type; /* copy limits and counters */ for(i = 0; i < IOLimitsCount; ++i) uchannels[ch].limits[i] = channels[ch].limits[i]; /* channel size/position */ switch(uchannels[ch].type) { case SGetSPut: /* size/position is not defined */ break; case SGetRPut: uchannels[ch].size = channels[ch].size; break; case RGetSPut: uchannels[ch].size = channels[ch].size; break; case RGetRPut: /* in this case get or put updates both positions synchronously */ uchannels[ch].size = channels[ch].size; break; default: /* invalid access type */ break; } } return nap->system_manifest->channels_count; }
/* * return channel name length + 1 if given pointer is not NULL * and "name" field in the given channel descriptor is NULL * otherwise also copy channel alias to "name" field */ int32_t ZVMChannelName(struct NaClApp *nap, struct ZVMChannel *chnl, int ch) { struct ZVMChannel *uchannel; struct ChannelDesc *channel; char *alias; assert(nap != NULL); assert(nap->system_manifest != NULL); if(chnl == NULL) return -EINVAL; uchannel = (struct ZVMChannel*)NaClUserToSys(nap, (uintptr_t)chnl); channel = &nap->system_manifest->channels[ch]; /* user asked for the name. give her the channel alias */ if(uchannel->name != NULL) { alias = (char*)NaClUserToSys(nap, (uintptr_t)uchannel->name); strcpy(alias, channel->alias); } return strlen(channel->alias) + 1; }
/* * write specified amount of bytes from buffer to given desc/offset * return amount of read bytes or negative error code if call failed */ static int32_t ZVMWriteHandle(struct NaClApp *nap, int ch, const char *buffer, int32_t size, int64_t offset) { struct ChannelDesc *channel; int64_t tail; const char *sys_buffer; assert(nap != NULL); assert(nap->manifest != NULL); assert(nap->manifest->channels != NULL); /* check the channel number */ if(ch < 0 || ch >= nap->manifest->channels->len) { ZLOGS(LOG_DEBUG, "channel_id=%d, buffer=%p, size=%d, offset=%ld", ch, buffer, size, offset); return -EINVAL; } channel = CH_CH(nap->manifest, ch); ZLOGS(LOG_INSANE, "channel %s, buffer=%p, size=%d, offset=%ld", channel->alias, buffer, size, offset); /* check buffer and convert address */ if(CheckRAMAccess(nap, (uintptr_t)buffer, size, PROT_READ) == -1) return -EINVAL; sys_buffer = (char*)NaClUserToSys(nap, (uintptr_t) buffer); /* ignore user offset for sequential access write */ if(CH_SEQ_WRITEABLE(channel)) offset = channel->putpos; /* check arguments sanity */ if(size == 0) return 0; /* success. user has read 0 bytes */ if(size < 0) return -EFAULT; if(offset < 0) return -EINVAL; /* check limits */ if(channel->counters[PutsLimit] >= channel->limits[PutsLimit]) return -EDQUOT; tail = channel->limits[PutSizeLimit] - channel->counters[PutSizeLimit]; if(offset >= channel->limits[PutSizeLimit] && !((CH_RW_TYPE(channel) & 1) == 1)) return -EINVAL; if(offset >= channel->size + tail) return -EINVAL; if(size > tail) size = tail; if(size < 1) return -EDQUOT; /* write data */ return ChannelWrite(channel, sys_buffer, (size_t)size, (off_t)offset); }
int main(int argc, char **argv) { struct NaClApp app; uint32_t mmap_addr; char arg_string[32]; char *args[] = {"prog_name", arg_string}; NaClAllModulesInit(); if (argc != 2) { NaClLog(LOG_FATAL, "Expected 1 argument: executable filename\n"); } NaClAddSyscall(NACL_sys_test_syscall_1, TestSyscall); CHECK(NaClAppCtor(&app)); CHECK(NaClAppLoadFileFromFilename(&app, argv[1]) == LOAD_OK); NaClAppInitialDescriptorHookup(&app); CHECK(NaClAppPrepareToLaunch(&app) == LOAD_OK); NaClSignalHandlerInit(); NaClSignalHandlerSet(TrapSignalHandler); /* * Allocate some space in untrusted address space. We pass the * address to the guest program so that it can write a register * snapshot for us to compare against. */ mmap_addr = NaClSysMmapIntern( &app, NULL, sizeof(*g_test_shm), NACL_ABI_PROT_READ | NACL_ABI_PROT_WRITE, NACL_ABI_MAP_PRIVATE | NACL_ABI_MAP_ANONYMOUS, -1, 0); g_test_shm = (struct RegsTestShm *) NaClUserToSys(&app, mmap_addr); SNPRINTF(arg_string, sizeof(arg_string), "0x%x", (unsigned int) mmap_addr); CHECK(NaClCreateMainThread(&app, 2, args, NULL)); CHECK(NaClWaitForMainThreadToExit(&app) == 0); CHECK(!g_in_untrusted_code); ASSERT_EQ(g_context_switch_count, (kNumberOfCallsToTest + kFastPathSyscallsToTest - 1) * 2); /* * Avoid calling exit() because it runs process-global destructors * which might break code that is running in our unjoined threads. */ NaClExit(0); return 0; }
/* * read specified amount of bytes from given desc/offset to buffer * return amount of read bytes or negative error code if call failed */ int32_t TrapReadHandle(struct NaClApp *nap, enum ChannelType desc, char *buffer, int32_t size, int64_t offset) { struct PreOpenedFileDesc *fd; int64_t tail; char *sys_buffer; int32_t retcode; // ### make it function with editable list of available channels /* only allow this call for InputChannel, OutputChannel */ if(desc != InputChannel && desc != OutputChannel) return -INVALID_DESC; /* convert address and check buffer */ sys_buffer = (char*)NaClUserToSys(nap, (uintptr_t) buffer); NaClLog(4, "%s() invoked: desc=%d, buffer=0x%lx, size=%d, offset=%ld\n", __func__, desc, (intptr_t)buffer, size, offset); /* take fd from nap with given desc */ if(nap == NULL) return -INTERNAL_ERR; fd = &nap->manifest->user_setup->channels[desc]; if(fd == NULL) return -INVALID_DESC; if(fd->mounted != LOADED) return -INVALID_MODE; // ### make it function with editable list of available channels /* check arguments sanity */ if(size < 1) return -INSANE_SIZE; if(offset < 0) return -INSANE_OFFSET; /* check/update limits/counters */ if(offset >= fd->fsize) return -OUT_OF_BOUNDS; if(fd->cnt_gets >= fd->max_gets) return -OUT_OF_LIMITS; tail = fd->max_get_size - fd->cnt_get_size; if(size > tail) size = tail; if(size < 1) return -OUT_OF_LIMITS; /* update counters (even if syscall failed) */ ++fd->cnt_gets; fd->cnt_get_size += size; /* read data */ retcode = pread(fd->handle, sys_buffer, (size_t)size, (off_t)offset); return retcode; }
struct NaClSignalContext *StartGuestWithSharedMemory( struct NaClApp *nap) { char arg_string[32]; char *args[] = {"prog_name", arg_string}; uint32_t mmap_addr; struct NaClSignalContext *expected_regs; /* * Allocate some space in untrusted address space. We pass the * address to the guest program so that we can share data with it. */ mmap_addr = NaClSysMmapIntern( nap, NULL, sizeof(*expected_regs), NACL_ABI_PROT_READ | NACL_ABI_PROT_WRITE, NACL_ABI_MAP_PRIVATE | NACL_ABI_MAP_ANONYMOUS, -1, 0); SNPRINTF(arg_string, sizeof(arg_string), "0x%x", (unsigned int) mmap_addr); expected_regs = (struct NaClSignalContext *) NaClUserToSys(nap, mmap_addr); WaitForThreadToExitFully(nap); CHECK(NaClCreateMainThread(nap, NACL_ARRAY_SIZE(args), args, NULL)); return expected_regs; }
/* * read specified amount of bytes from given desc/offset to buffer * return amount of read bytes or negative error code if call failed */ static int32_t ZVMReadHandle(struct NaClApp *nap, int ch, char *buffer, int32_t size, int64_t offset) { struct ChannelDesc *channel; int64_t tail; char *sys_buffer; int32_t retcode = -1; assert(nap != NULL); assert(nap->system_manifest != NULL); assert(nap->system_manifest->channels != NULL); /* check the channel number */ if(ch < 0 || ch >= nap->system_manifest->channels_count) { ZLOGS(LOG_DEBUG, "channel_id=%d, buffer=0x%lx, size=%d, offset=%ld", ch, (intptr_t)buffer, size, offset); return -EINVAL; } channel = &nap->system_manifest->channels[ch]; ZLOGS(LOG_DEBUG, "channel %s, buffer=0x%lx, size=%d, offset=%ld", channel->alias, (intptr_t)buffer, size, offset); /* check buffer and convert address */ if(CheckRAMAccess(nap, (uintptr_t)buffer, size, PROT_READ) == -1) return -EINVAL; sys_buffer = (char*)NaClUserToSys(nap, (uintptr_t) buffer); /* ignore user offset for sequential access read */ if(CHANNEL_SEQ_READABLE(channel)) offset = channel->getpos; else /* prevent reading beyond the end of the random access channels */ size = MIN(channel->size - offset, size); /* check arguments sanity */ if(size == 0) return 0; /* success. user has read 0 bytes */ if(size < 0) return -EFAULT; if(offset < 0) return -EINVAL; /* check for eof */ if(channel->eof) return 0; /* check limits */ if(channel->counters[GetsLimit] >= channel->limits[GetsLimit]) return -EDQUOT; if(CHANNEL_RND_READABLE(channel)) if(offset >= channel->limits[PutSizeLimit] - channel->counters[PutSizeLimit] + channel->size) return -EINVAL; /* calculate i/o leftovers */ tail = channel->limits[GetSizeLimit] - channel->counters[GetSizeLimit]; if(size > tail) size = tail; if(size < 1) return -EDQUOT; /* read data and update position */ switch(channel->source) { case ChannelRegular: retcode = pread(channel->handle, sys_buffer, (size_t)size, (off_t)offset); if(retcode == -1) retcode = -errno; break; case ChannelCharacter: case ChannelFIFO: retcode = fread(sys_buffer, 1, (size_t)size, (FILE*)channel->socket); if(retcode == -1) retcode = -errno; break; case ChannelTCP: retcode = FetchMessage(channel, sys_buffer, size); if(retcode == -1) retcode = -EIO; break; default: /* design error */ ZLOGFAIL(1, EFAULT, "invalid channel source"); break; } /* update the channel counter, size, position and tag */ ++channel->counters[GetsLimit]; if(retcode > 0) { channel->counters[GetSizeLimit] += retcode; UpdateChannelTag(channel, (const char*)sys_buffer, retcode); /* * current get cursor. must be updated if channel have seq get * but there is nothing wrong to update it even it have random get */ channel->getpos = offset + retcode; /* if channel have random put update put cursor. not allowed for cdr */ if(CHANNEL_RND_WRITEABLE(channel)) channel->putpos = offset + retcode; } /* * set eof if 0 bytes has been read. it is safe because * 1. if user asked for a 0 bytes control will not reach this code * 2. if user asked more then 0 bytes and got 0 that means end of data * 3. if quota exceeded user will get an error before an actual read */ if(retcode == 0) channel->eof = 1; return retcode; }
/* serialize system data to user space */ static void SetSystemData(struct NaClApp *nap) { struct SystemManifest *manifest; struct ChannelSerialized *channels; struct UserManifestSerialized *user_manifest; void *ptr; /* pointer to the user manifest area */ int64_t size; int i; assert(nap != NULL); assert(nap->system_manifest != NULL); assert(nap->system_manifest->channels != NULL); manifest = nap->system_manifest; /* * 1. calculate channels array size (w/o aliases) * 2. calculate user manifest size (w/o aliases) * 3. calculate pointer to user manifest * 4. calculate pointer to channels array */ size = manifest->channels_count * CHANNEL_STRUCT_SIZE; size += USER_MANIFEST_STRUCT_SIZE + USER_PTR_SIZE; ptr = (void*)(FOURGIG - nap->stack_size - size); user_manifest = (void*)NaClUserToSys(nap, (uintptr_t)ptr); channels = (void*)((uintptr_t)&user_manifest->channels + USER_PTR_SIZE); /* make the 1st page of user manifest writeable */ CopyDown((void*)NaClUserToSys(nap, FOURGIG - nap->stack_size), ""); ptr = user_manifest; /* initialize pointer to user manifest */ SetUserManifestPtr(nap, user_manifest); /* serialize channels */ for(i = 0; i < manifest->channels_count; ++i) { /* limits */ int j; for(j = 0; j < IOLimitsCount; ++j) channels[i].limits[j] = manifest->channels[i].limits[j]; /* type and size */ channels[i].type = (int32_t)manifest->channels[i].type; channels[i].size = channels[i].type == SGetSPut ? 0 : manifest->channels[i].size; /* alias */ ptr = CopyDown(ptr, manifest->channels[i].alias); channels[i].name = NaClSysToUser(nap, (uintptr_t)ptr); } /* update heap_size in the user manifest */ size = ROUNDDOWN_64K(NaClSysToUser(nap, (uintptr_t)ptr)); size = MIN(nap->heap_end, size); user_manifest->heap_size = size - nap->break_addr; /* note that rw data merged with heap! */ /* update memory map */ nap->mem_map[HeapIdx].end = NaClUserToSys(nap, size); nap->mem_map[HeapIdx].size = user_manifest->heap_size; /* serialize the rest of the user manifest records */ user_manifest->heap_ptr = nap->break_addr; user_manifest->stack_size = nap->stack_size; user_manifest->channels_count = manifest->channels_count; user_manifest->channels = NaClSysToUser(nap, (uintptr_t)channels); /* make the user manifest read only */ ProtectUserManifest(nap, ptr); }
int32_t TrapHandler(struct NaClApp *nap, uint32_t args) { uint64_t *sargs; int retcode = 0; assert(nap != NULL); assert(nap->manifest != NULL); /* * translate address from user space to system * note: cannot set "trap error" */ sargs = (uint64_t*)NaClUserToSys(nap, (uintptr_t)args); ZLOGS(LOG_DEBUG, "%s called", FunctionName(*sargs)); ZTrace("untrusted code"); switch(*sargs) { case TrapFork: retcode = Daemon(nap); if(retcode) break; SyscallZTrace(*sargs, 0); SyscallZTrace(TrapExit, 0); ZVMExitHandle(nap, 0); break; case TrapExit: SyscallZTrace(*sargs, sargs[2]); ZVMExitHandle(nap, (int32_t)sargs[2]); break; case TrapRead: retcode = ZVMReadHandle(nap, (int)sargs[2], (char*)sargs[3], (int32_t)sargs[4], sargs[5]); break; case TrapWrite: retcode = ZVMWriteHandle(nap, (int)sargs[2], (char*)sargs[3], (int32_t)sargs[4], sargs[5]); break; case TrapJail: retcode = ZVMJailHandle(nap, (uint32_t)sargs[2], (int32_t)sargs[3]); break; case TrapUnjail: retcode = ZVMUnjailHandle(nap, (uint32_t)sargs[2], (int32_t)sargs[3]); break; #ifdef ZVM_SOCKETS case TrapSocket: retcode = ZVM_socket((int)sargs[2], (int)sargs[3], (int)sargs[4]); break; case TrapBind: { const struct sockaddr *addr = (void*)NaClUserToSys(nap, (uintptr_t)sargs[3]); retcode = ZVM_bind((int)sargs[2], addr, (socklen_t)sargs[4]); break; } case TrapConnect: { const struct sockaddr *addr = (void*)NaClUserToSys(nap, (uintptr_t)sargs[3]); retcode = ZVM_connect((int)sargs[2], addr, (socklen_t)sargs[4]); break; } case TrapAccept: { struct sockaddr *addr = (void*)NaClUserToSys(nap, (uintptr_t)sargs[3]); socklen_t *len = (void*)NaClUserToSys(nap, (uintptr_t)sargs[4]); retcode = ZVM_accept((int)sargs[2], addr, len); break; } case TrapListen: retcode = ZVM_listen((int)sargs[2], (int)sargs[3]); break; case TrapRecv: { void *buf = (void*)NaClUserToSys(nap, (uintptr_t)sargs[3]); retcode = ZVM_recv((int)sargs[2], buf, (size_t)sargs[4], (int)sargs[5]); break; } case TrapRecvfrom: { void *buf = (void*)NaClUserToSys(nap, (uintptr_t)sargs[3]); struct sockaddr *addr = (void*)NaClUserToSys(nap, (uintptr_t)sargs[6]); socklen_t *len = (void*)NaClUserToSys(nap, (uintptr_t)sargs[7]); retcode = ZVM_recvfrom((int)sargs[2], buf, (size_t)sargs[4], (int)sargs[5], addr, len); break; } case TrapRecvmsg: { struct msghdr *msg = (void*)NaClUserToSys(nap, (uintptr_t)sargs[3]); retcode = ZVM_recvmsg((int)sargs[2], msg, (int)sargs[4]); break; } case TrapSend: { const void *buf = (void*)NaClUserToSys(nap, (uintptr_t)sargs[3]); retcode = ZVM_send((int)sargs[2], buf, (size_t)sargs[4], (int)sargs[5]); break; } case TrapSendto: { const void *buf = (void*)NaClUserToSys(nap, (uintptr_t)sargs[3]); const struct sockaddr *addr = (void*)NaClUserToSys(nap, (uintptr_t)sargs[6]); retcode = ZVM_sendto((int)sargs[2], buf, (size_t)sargs[4], (int)sargs[5], addr, (socklen_t)sargs[7]); break; } case TrapSendmsg: { const struct msghdr *msg = (void*)NaClUserToSys(nap, (uintptr_t)sargs[3]); retcode = ZVM_sendmsg((int)sargs[2], msg, (int)sargs[4]); break; } case TrapGetsockopt: { void *optval = (void*)NaClUserToSys(nap, (uintptr_t)sargs[5]); socklen_t *len = (void*)NaClUserToSys(nap, (uintptr_t)sargs[6]); retcode = ZVM_getsockopt((int)sargs[2], (int)sargs[3], (int)sargs[4], optval, len); break; } case TrapSetsockopt: { const void *optval = (void*)NaClUserToSys(nap, (uintptr_t)sargs[5]); retcode = ZVM_setsockopt((int)sargs[2], (int)sargs[3], (int)sargs[4], optval, (socklen_t)sargs[6]); break; } case TrapSelect: { fd_set *readfds = (void*)NaClUserToSys(nap, (uintptr_t)sargs[3]); fd_set *writefds = (void*)NaClUserToSys(nap, (uintptr_t)sargs[4]); fd_set *exceptfds = (void*)NaClUserToSys(nap, (uintptr_t)sargs[5]); struct timeval *timeout = (void*)NaClUserToSys(nap, (uintptr_t)sargs[6]); retcode = ZVM_select((int)sargs[2], readfds, writefds, exceptfds, timeout); break; } case TrapPoll: { struct pollfd *fds = (void*)NaClUserToSys(nap, (uintptr_t)sargs[2]); retcode = ZVM_poll(fds, (nfds_t)sargs[3], (int)sargs[4]); break; } case TrapGethostbyname: { const char *name = (void*)NaClUserToSys(nap, (uintptr_t)sargs[2]); retcode = (int)(intptr_t)ZVM_gethostbyname(name); break; } case TrapGethostbyaddr: { const void *addr = (void*)NaClUserToSys(nap, (uintptr_t)sargs[2]); retcode = (int)(intptr_t)ZVM_gethostbyaddr(addr, (socklen_t)sargs[3], (int)sargs[4]); break; } case TrapClose: retcode = ZVM_close((int)sargs[2]); break; #endif default: retcode = -EPERM; ZLOG(LOG_ERROR, "function %ld is not supported", *sargs); break; } /* report, ztrace and return */ FastReport(); ZLOGS(LOG_DEBUG, "%s returned %d", FunctionName(*sargs), retcode); SyscallZTrace(*sargs, retcode, sargs[2], sargs[3], sargs[4], sargs[5], sargs[6], sargs[7]); return retcode; }
NaClErrorCode NaClElfImageLoadDynamically( struct NaClElfImage *image, struct NaClApp *nap, struct NaClDesc *ndp, struct NaClValidationMetadata *metadata) { ssize_t read_ret; int segnum; for (segnum = 0; segnum < image->ehdr.e_phnum; ++segnum) { const Elf_Phdr *php = &image->phdrs[segnum]; Elf_Addr vaddr = php->p_vaddr & ~(NACL_MAP_PAGESIZE - 1); Elf_Off offset = php->p_offset & ~(NACL_MAP_PAGESIZE - 1); Elf_Off filesz = php->p_offset + php->p_filesz - offset; Elf_Off memsz = php->p_offset + php->p_memsz - offset; int32_t result; /* * By checking if filesz is larger than memsz, we no longer run the risk of * a malicious ELF object overrunning into the trusted address space when * reading data of size "filez" into a buffer of size "memsz". */ if (filesz > memsz) { return LOAD_UNLOADABLE; } /* * We check for PT_LOAD directly rather than using the "loadable" * array because we are not using NaClElfImageValidateProgramHeaders() * to fill out the "loadable" array for this ELF object. This ELF * object does not have to fit such strict constraints (such as * having code at 0x20000), and safety checks are applied by * NaClTextDyncodeCreate() and NaClSysMmapIntern(). */ if (PT_LOAD != php->p_type) { continue; } if (0 != (php->p_flags & PF_X)) { /* Load code segment. */ /* * We make a copy of the code. This is not ideal given that this * code path is used only for loading the IRT, and we could assume * that the contents of the irt.nexe file will not change underneath * us. We should be able to mmap() the IRT's code segment instead of * copying it. * TODO(mseaborn): Reduce the amount of copying here. */ char *code_copy = malloc(filesz); if (NULL == code_copy) { NaClLog(LOG_ERROR, "NaClElfImageLoadDynamically: malloc failed\n"); return LOAD_NO_MEMORY; } read_ret = (*NACL_VTBL(NaClDesc, ndp)-> PRead)(ndp, code_copy, filesz, (nacl_off64_t) offset); if (NaClSSizeIsNegErrno(&read_ret) || (size_t) read_ret != filesz) { free(code_copy); NaClLog(LOG_ERROR, "NaClElfImageLoadDynamically: " "failed to read code segment\n"); return LOAD_READ_ERROR; } if (NULL != metadata) { metadata->code_offset = offset; } result = NaClTextDyncodeCreate(nap, (uint32_t) vaddr, code_copy, (uint32_t) filesz, metadata); free(code_copy); if (0 != result) { NaClLog(LOG_ERROR, "NaClElfImageLoadDynamically: " "failed to load code segment\n"); return LOAD_UNLOADABLE; } } else { /* Load data segment. */ void *paddr = (void *) NaClUserToSys(nap, vaddr); size_t mapping_size = NaClRoundAllocPage(memsz); /* * Note that we do not used NACL_ABI_MAP_FIXED because we do not * want to silently overwrite any existing mappings, such as the * user app's data segment or the stack. We detect overmapping * when mmap chooses not to use the preferred address we supply. * (Ideally mmap would provide a MAP_EXCL option for this * instead.) */ result = NaClSysMmapIntern( nap, (void *) (uintptr_t) vaddr, mapping_size, NACL_ABI_PROT_READ | NACL_ABI_PROT_WRITE, NACL_ABI_MAP_ANONYMOUS | NACL_ABI_MAP_PRIVATE, -1, 0); if ((int32_t) vaddr != result) { NaClLog(LOG_ERROR, "NaClElfImageLoadDynamically: " "failed to map data segment\n"); return LOAD_UNLOADABLE; } read_ret = (*NACL_VTBL(NaClDesc, ndp)-> PRead)(ndp, paddr, filesz, (nacl_off64_t) offset); if (NaClSSizeIsNegErrno(&read_ret) || (size_t) read_ret != filesz) { NaClLog(LOG_ERROR, "NaClElfImageLoadDynamically: " "failed to read data segment\n"); return LOAD_READ_ERROR; } /* * Note that we do not need to zero the BSS (the region from * p_filesz to p_memsz) because it should already be zero * filled. This would not be the case if we were mapping the * data segment from the file. */ if (0 == (php->p_flags & PF_W)) { /* Handle read-only data segment. */ int rc = NaClMprotect(paddr, mapping_size, NACL_ABI_PROT_READ); if (0 != rc) { NaClLog(LOG_ERROR, "NaClElfImageLoadDynamically: " "failed to mprotect read-only data segment\n"); return LOAD_MPROTECT_FAIL; } NaClVmmapAddWithOverwrite(&nap->mem_map, vaddr >> NACL_PAGESHIFT, mapping_size >> NACL_PAGESHIFT, NACL_ABI_PROT_READ, NACL_ABI_MAP_PRIVATE, NULL, 0, 0); } } }
NaClErrorCode NaClMakeDynamicTextShared(struct NaClApp *nap) { enum NaClErrorCode retval = LOAD_INTERNAL; uintptr_t dynamic_text_size; struct NaClDescImcShm *shm = NULL; uintptr_t shm_vaddr_base; int mmap_protections; uintptr_t mmap_ret; uintptr_t shm_upper_bound; uintptr_t text_sysaddr; shm_vaddr_base = NaClEndOfStaticText(nap); NaClLog(4, "NaClMakeDynamicTextShared: shm_vaddr_base = %08"NACL_PRIxPTR"\n", shm_vaddr_base); shm_vaddr_base = NaClRoundAllocPage(shm_vaddr_base); NaClLog(4, "NaClMakeDynamicTextShared: shm_vaddr_base = %08"NACL_PRIxPTR"\n", shm_vaddr_base); /* * Default is that there is no usable dynamic code area. */ nap->dynamic_text_start = shm_vaddr_base; nap->dynamic_text_end = shm_vaddr_base; if (!nap->use_shm_for_dynamic_text) { NaClLog(4, "NaClMakeDynamicTextShared:" " rodata / data segments not allocation aligned\n"); NaClLog(4, " not using shm for text\n"); return LOAD_OK; } /* * Allocate a shm region the size of which is nap->rodata_start - * end-of-text. This implies that the "core" text will not be * backed by shm. */ shm_upper_bound = nap->rodata_start; if (0 == shm_upper_bound) { shm_upper_bound = NaClTruncAllocPage(nap->data_start); } if (0 == shm_upper_bound) { shm_upper_bound = shm_vaddr_base; } NaClLog(4, "shm_upper_bound = %08"NACL_PRIxPTR"\n", shm_upper_bound); dynamic_text_size = shm_upper_bound - shm_vaddr_base; NaClLog(4, "NaClMakeDynamicTextShared: dynamic_text_size = %"NACL_PRIxPTR"\n", dynamic_text_size); if (0 == dynamic_text_size) { NaClLog(4, "Empty JITtable region\n"); return LOAD_OK; } shm = (struct NaClDescImcShm *) malloc(sizeof *shm); if (NULL == shm) { NaClLog(4, "NaClMakeDynamicTextShared: shm object allocation failed\n"); retval = LOAD_NO_MEMORY; goto cleanup; } if (!NaClDescImcShmAllocCtor(shm, dynamic_text_size, /* executable= */ 1)) { /* cleanup invariant is if ptr is non-NULL, it's fully ctor'd */ free(shm); shm = NULL; NaClLog(4, "NaClMakeDynamicTextShared: shm alloc ctor for text failed\n"); retval = LOAD_NO_MEMORY_FOR_DYNAMIC_TEXT; goto cleanup; } text_sysaddr = NaClUserToSys(nap, shm_vaddr_base); /* Existing memory is anonymous paging file backed. */ NaClPageFree((void *) text_sysaddr, dynamic_text_size); /* * Unix allows us to map pages with PROT_NONE initially and later * increase the mapping permissions with mprotect(). * * Windows does not allow this, however: the initial permissions are * an upper bound on what the permissions may later be changed to * with VirtualProtect() or VirtualAlloc(). Given this, using * PROT_NONE at this point does not even make sense. On Windows, * the pages start off as uncommitted, which makes them inaccessible * regardless of the page permissions they are mapped with. * * Write permissions are included here for nacl64-gdb to set * breakpoints. */ #if NACL_WINDOWS mmap_protections = NACL_ABI_PROT_READ | NACL_ABI_PROT_EXEC | NACL_ABI_PROT_WRITE; #else mmap_protections = NACL_ABI_PROT_NONE; #endif NaClLog(4, "NaClMakeDynamicTextShared: Map(,,0x%"NACL_PRIxPTR",size = 0x%x," " prot=0x%x, flags=0x%x, offset=0)\n", text_sysaddr, (int) dynamic_text_size, mmap_protections, NACL_ABI_MAP_SHARED | NACL_ABI_MAP_FIXED); mmap_ret = (*((struct NaClDescVtbl const *) shm->base.base.vtbl)-> Map)((struct NaClDesc *) shm, NaClDescEffectorTrustedMem(), (void *) text_sysaddr, dynamic_text_size, mmap_protections, NACL_ABI_MAP_SHARED | NACL_ABI_MAP_FIXED, 0); if (text_sysaddr != mmap_ret) { NaClLog(LOG_FATAL, "Could not map in shm for dynamic text region\n"); } nap->dynamic_page_bitmap = BitmapAllocate((uint32_t) (dynamic_text_size / NACL_MAP_PAGESIZE)); if (NULL == nap->dynamic_page_bitmap) { NaClLog(LOG_FATAL, "NaClMakeDynamicTextShared: BitmapAllocate() failed\n"); } nap->dynamic_text_start = shm_vaddr_base; nap->dynamic_text_end = shm_upper_bound; nap->text_shm = &shm->base; retval = LOAD_OK; cleanup: if (LOAD_OK != retval) { NaClDescSafeUnref((struct NaClDesc *) shm); free(shm); } return retval; }
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); } }
NaClErrorCode NaClElfImageLoadDynamically(struct NaClElfImage *image, struct NaClApp *nap, struct Gio *gfile) { int segnum; for (segnum = 0; segnum < image->ehdr.e_phnum; ++segnum) { const Elf_Phdr *php = &image->phdrs[segnum]; int32_t result; /* * We check for PT_LOAD directly rather than using the "loadable" * array because we are not using NaClElfImageValidateProgramHeaders() * to fill out the "loadable" array for this ELF object. This ELF * object does not have to fit such strict constraints (such as * having code at 0x20000), and safety checks are applied by * NaClTextDyncodeCreate() and NaClCommonSysMmapIntern(). */ if (PT_LOAD != php->p_type) { continue; } /* * Ideally, Gio would have a Pread() method which we would use * instead of Seek(). In practice, though, there is no * Seek()/Read() race condition here because both * GioMemoryFileSnapshot and NaClGioShm use a seek position that * is local and not shared between processes. */ if ((*gfile->vtbl->Seek)(gfile, (off_t) php->p_offset, SEEK_SET) == (off_t) -1) { NaClLog(1, "NaClElfImageLoadDynamically: seek failed\n"); return LOAD_READ_ERROR; } if (0 != (php->p_flags & PF_X)) { /* Load code segment. */ /* * We make a copy of the code. This is not ideal given that * GioMemoryFileSnapshot and NaClGioShm already have a copy of * the file in memory or mmapped. * TODO(mseaborn): Reduce the amount of copying here. */ char *code_copy = malloc(php->p_filesz); if (NULL == code_copy) { NaClLog(1, "NaClElfImageLoadDynamically: malloc failed\n"); return LOAD_NO_MEMORY; } if ((Elf_Word) (*gfile->vtbl->Read)(gfile, code_copy, php->p_filesz) != php->p_filesz) { free(code_copy); NaClLog(1, "NaClElfImageLoadDynamically: " "failed to read code segment\n"); return LOAD_READ_ERROR; } result = NaClTextDyncodeCreate(nap, (uint32_t) php->p_vaddr, code_copy, (uint32_t) php->p_filesz); free(code_copy); if (0 != result) { NaClLog(1, "NaClElfImageLoadDynamically: " "failed to load code segment\n"); return LOAD_UNLOADABLE; } } else { /* Load data segment. */ void *paddr = (void *) NaClUserToSys(nap, php->p_vaddr); size_t mapping_size = NaClRoundAllocPage(php->p_memsz); /* * Note that we do not used NACL_ABI_MAP_FIXED because we do not * want to silently overwrite any existing mappings, such as the * user app's data segment or the stack. We detect overmapping * when mmap chooses not to use the preferred address we supply. * (Ideally mmap would provide a MAP_EXCL option for this * instead.) */ result = NaClCommonSysMmapIntern( nap, (void *) php->p_vaddr, mapping_size, NACL_ABI_PROT_READ | NACL_ABI_PROT_WRITE, NACL_ABI_MAP_ANONYMOUS | NACL_ABI_MAP_PRIVATE, -1, 0); if ((int32_t) php->p_vaddr != result) { NaClLog(1, "NaClElfImageLoadDynamically: failed to map data segment\n"); return LOAD_UNLOADABLE; } if ((Elf_Word) (*gfile->vtbl->Read)(gfile, paddr, php->p_filesz) != php->p_filesz) { NaClLog(1, "NaClElfImageLoadDynamically: " "failed to read data segment\n"); return LOAD_READ_ERROR; } /* * Note that we do not need to zero the BSS (the region from * p_filesz to p_memsz) because it should already be zero * filled. This would not be the case if we were mapping the * data segment from the file. */ if (0 == (php->p_flags & PF_W)) { /* Handle read-only data segment. */ int rc = NaCl_mprotect(paddr, mapping_size, NACL_ABI_PROT_READ); if (0 != rc) { NaClLog(1, "NaClElfImageLoadDynamically: " "failed to mprotect read-only data segment\n"); return LOAD_MPROTECT_FAIL; } NaClVmmapUpdate(&nap->mem_map, php->p_vaddr >> NACL_PAGESHIFT, mapping_size >> NACL_PAGESHIFT, PROT_READ, NULL, 0 /* remove: false */); } } }
/* * Apply memory protection to memory regions. */ void NaClMemoryProtection(struct NaClApp *nap) { uintptr_t start_addr; size_t region_size; int err; /* * The first NACL_SYSCALL_START_ADDR bytes are mapped as PROT_NONE. * This enables NULL pointer checking, and provides additional protection * against addr16/data16 prefixed operations being used for attacks. * * NaClMprotectGuards also sets up guard pages outside of the * virtual address space of the NaClApp -- for the ARM and x86-64 * where data sandboxing only sandbox memory writes and not reads, * we need to ensure that certain addressing modes that might * otherwise allow the NaCl app to write outside its address space * (given how we using masking / base registers to implement data * write sandboxing) won't affect the trusted data structures. */ ZLOGS(LOG_DEBUG, "Protecting guard pages for 0x%08x", nap->mem_start); NaClMprotectGuards(nap); start_addr = nap->mem_start + NACL_SYSCALL_START_ADDR; /* * The next pages up to NACL_TRAMPOLINE_END are the trampolines. * Immediately following that is the loaded text section. * These are collectively marked as PROT_READ | PROT_EXEC. */ region_size = NaClRoundPage(nap->static_text_end - NACL_SYSCALL_START_ADDR); ZLOGS(LOG_DEBUG, "Trampoline/text region start 0x%08x, size 0x%08x, end 0x%08x", start_addr, region_size, start_addr + region_size); err = NaCl_mprotect((void *)start_addr, region_size, PROT_READ | PROT_EXEC); ZLOGFAIL(0 != err, err, FAILED_MSG); SET_MEM_MAP_IDX(nap->mem_map[TextIdx], "Text", start_addr, region_size, PROT_READ | PROT_EXEC); /* * Page protections for this region have already been set up by * nacl_text.c. * * todo(d'b): since text.c exists no more, protection should be set here * * We record the mapping for consistency with other fixed * mappings, but the record is not actually used. Overmapping is * prevented by a separate range check, which is done by * NaClSysCommonAddrRangeContainsExecutablePages_mu(). */ /* * zerovm does not support dynamic text. the code below will check its * existence, log information and fail if needed. * todo(d'b): after the dynamic text support will be added or completely * removed the block below should be rewritten or removed */ start_addr = NaClUserToSys(nap, nap->dynamic_text_start); region_size = nap->dynamic_text_end - nap->dynamic_text_start; ZLOGS(LOG_DEBUG, "shm txt region start 0x%08x, size 0x%08x, end 0x%08x", start_addr, region_size, start_addr + region_size); ZLOGFAIL(0 != region_size, EFAULT, "zerovm does not support nexe with dynamic text!"); if(0 != nap->rodata_start) { uintptr_t rodata_end; /* * TODO(mseaborn): Could reduce the number of cases by ensuring * that nap->data_start is always non-zero, even if * nap->rodata_start == nap->data_start == nap->break_addr. */ if(0 != nap->data_start) rodata_end = nap->data_start; else rodata_end = nap->break_addr; start_addr = NaClUserToSys(nap, nap->rodata_start); region_size = NaClRoundPage(NaClRoundAllocPage(rodata_end) - NaClSysToUser(nap, start_addr)); ZLOGS(LOG_DEBUG, "RO data region start 0x%08x, size 0x%08x, end 0x%08x", start_addr, region_size, start_addr + region_size); err = NaCl_mprotect((void *)start_addr, region_size, PROT_READ); ZLOGFAIL(0 != err, err, FAILED_MSG); SET_MEM_MAP_IDX(nap->mem_map[RODataIdx], "ROData", start_addr, region_size, PROT_READ); } /* * data_end is max virtual addr seen, so start_addr <= data_end * must hold. */ if(0 != nap->data_start) { start_addr = NaClUserToSys(nap, nap->data_start); region_size = NaClRoundPage(NaClRoundAllocPage(nap->data_end) - NaClSysToUser(nap, start_addr)); ZLOGS(LOG_DEBUG, "RW data region start 0x%08x, size 0x%08x, end 0x%08x", start_addr, region_size, start_addr + region_size); err = NaCl_mprotect((void *)start_addr, region_size, PROT_READ | PROT_WRITE); ZLOGFAIL(0 != err, err, FAILED_MSG); SET_MEM_MAP_IDX(nap->mem_map[HeapIdx], "Heap", start_addr, region_size, PROT_READ | PROT_WRITE); } /* stack is read/write but not execute */ region_size = nap->stack_size; start_addr = NaClUserToSys(nap, NaClTruncAllocPage(((uintptr_t) 1U << nap->addr_bits) - nap->stack_size)); ZLOGS(LOG_DEBUG, "RW stack region start 0x%08x, size 0x%08lx, end 0x%08x", start_addr, region_size, start_addr + region_size); err = NaCl_mprotect((void *)start_addr, NaClRoundAllocPage(nap->stack_size), PROT_READ | PROT_WRITE); ZLOGFAIL(0 != err, err, FAILED_MSG); SET_MEM_MAP_IDX(nap->mem_map[StackIdx], "Stack", start_addr, NaClRoundAllocPage(nap->stack_size), PROT_READ | PROT_WRITE); }
/* * user request to change limits for system resources. for now we only can decrease bounds * return: function update given SetupList object (hint) and if there were * unsuccessful attempt to change policy return -1, otherwirse return 0 */ static int32_t TrapUserSetupHandle(struct NaClApp *nap, struct SetupList *h) { struct PreOpenedFileDesc *policy_channel; struct PreOpenedFileDesc *hint_channel; struct SetupList *policy = nap->manifest->user_setup; struct SetupList *hint = (struct SetupList*)NaClUserToSys(nap, (intptr_t) h); int32_t retcode = OK_CODE; enum ChannelType ch; /* * check/count this call. decrease number of syscalls * since this call is not really "system" */ --nap->manifest->user_setup->cnt_syscalls; if(policy->max_setup_calls < ++policy->cnt_setup_calls) return -OUT_OF_LIMITS; /* check i/o limits */ for(ch = InputChannel; ch < CHANNELS_COUNT; ++ch) { /* pick current channel settings */ policy_channel = &policy->channels[ch]; hint_channel = &hint->channels[ch]; hint_channel->self_size = policy_channel->self_size; /* set self size */ #define STRNCPY_NULL(a, b, n) if ((a) && (b)) strncpy((a), (b), (n)); #define TRY_UPDATE(pretender, current)\ do {\ if(policy->cnt_setup_calls == 1) { pretender = current; break; }\ if((pretender) < (current)) current = pretender;\ else { pretender = current; retcode = ERR_CODE; }\ } while(0) /* check/update i/o limits. 1st time - set fields in hint */ TRY_UPDATE(hint_channel->max_size, policy_channel->max_size); TRY_UPDATE(hint_channel->max_get_size, policy_channel->max_get_size); TRY_UPDATE(hint_channel->max_gets, policy_channel->max_gets); TRY_UPDATE(hint_channel->max_put_size, policy_channel->max_put_size); TRY_UPDATE(hint_channel->max_puts, policy_channel->max_puts); /* set i/o fields n/a to change */ hint_channel->bsize = policy_channel->bsize; hint_channel->buffer = policy_channel->buffer; hint_channel->fsize = policy_channel->fsize; hint_channel->type = policy_channel->type; } /* check/update system limits. 1st time - set fields in hint */ TRY_UPDATE(hint->max_cpu, policy->max_cpu); TRY_UPDATE(hint->max_mem, policy->max_mem); /* ### call unmap here? */ TRY_UPDATE(hint->max_syscalls, policy->max_syscalls); TRY_UPDATE(hint->max_setup_calls, policy->max_setup_calls); /* set system fields n/a to change */ hint->self_size = policy->self_size; /* set self size */ hint->heap_ptr = policy->heap_ptr; /* set self size */ STRNCPY_NULL(hint->content_type, policy->content_type, CONTENT_TYPE_LEN); STRNCPY_NULL(hint->timestamp, policy->timestamp, TIMESTAMP_LEN); STRNCPY_NULL(hint->user_etag, policy->user_etag, USER_TAG_LEN); STRNCPY_NULL(hint->x_object_meta_tag, policy->x_object_meta_tag, X_OBJECT_META_TAG_LEN); /* update syscallback */ if(UpdateSyscallback(nap, hint) == ERR_CODE) retcode = ERR_CODE; #undef STRNCPY_NULL #undef TRY_UPDATE return retcode; }
/* * preconditions: * argc > 0, argc and argv table is consistent * envv may be NULL (this happens on MacOS/Cocoa * if envv is non-NULL it is 'consistent', null terminated etc. */ int NaClCreateMainThread(struct NaClApp *nap, int argc, char **argv, char const *const *envv) { /* * Compute size of string tables for argv and envv */ int retval; int envc; size_t size; size_t ptr_tbl_size; int i; char *p; char *strp; size_t *argv_len; size_t *envv_len; struct NaClAppThread *natp; uintptr_t stack_ptr; /* * Set an exception handler so Windows won't show a message box if * an exception occurs */ WINDOWS_EXCEPTION_TRY; retval = 0; /* fail */ CHECK(argc > 0); CHECK(NULL != argv); envc = 0; if (NULL != envv) { char const *const *pp; for (pp = envv; NULL != *pp; ++pp) { ++envc; } } envv_len = 0; argv_len = malloc(argc * sizeof argv_len[0]); envv_len = malloc(envc * sizeof envv_len[0]); if (NULL == argv_len) { goto cleanup; } if (NULL == envv_len && 0 != envc) { goto cleanup; } /* * Store information for remote query when debugging. */ NaClDebugSetAppInfo(nap); NaClDebugSetAppEnvironment(argc, (char const * const *) argv, envc, (char const * const *) envv); NaClDebugStart(); size = 0; /* * The following two loops cannot overflow. The reason for this is * that they are counting the number of bytes used to hold the * NUL-terminated strings that comprise the argv and envv tables. * If the entire address space consisted of just those strings, then * the size variable would overflow; however, since there's the code * space required to hold the code below (and we are not targetting * Harvard architecture machines), at least one page holds code, not * data. We are assuming that the caller is non-adversarial and the * code does not look like string data.... */ for (i = 0; i < argc; ++i) { argv_len[i] = strlen(argv[i]) + 1; size += argv_len[i]; } for (i = 0; i < envc; ++i) { envv_len[i] = strlen(envv[i]) + 1; size += envv_len[i]; } /* * NaCl modules are ILP32, so the argv, envv pointers, as well as * the terminating NULL pointers at the end of the argv/envv tables, * are 32-bit values. We also have the empty auxv to take into * account, so that's 6 additional 32-bit values on top of the * argv/envv contents. Note that on nacl64, argc is popped, and * is an 8-byte value! * * The argv and envv pointer tables came from trusted code and is * part of memory. Thus, by the same argument above, adding in * (argc+envc+4)*sizeof(void *) cannot possibly overflow the size * variable since it is a size_t object. However, the two more * pointers for auxv and the space for argv could cause an overflow. * The fact that we used stack to get here etc means that * ptr_tb_size could not have overflowed. * * NB: the underlying OS would have limited the amount of space used * for argv and envv -- on linux, it is ARG_MAX, or 128KB -- and * hence the overflow check is for obvious auditability rather than * for correctness. */ ptr_tbl_size = (argc + envc + 6) * sizeof(uint32_t) + sizeof(int); if (SIZE_T_MAX - size < ptr_tbl_size) { NaClLog(LOG_WARNING, "NaClCreateMainThread: ptr_tb_size cause size of" " argv / environment copy to overflow!?!\n"); retval = 0; goto cleanup; } size += ptr_tbl_size; size = (size + NACL_STACK_ALIGN_MASK) & ~NACL_STACK_ALIGN_MASK; if (size > nap->stack_size) { retval = 0; goto cleanup; } /* write strings and char * arrays to stack */ stack_ptr = (nap->mem_start + ((uintptr_t) 1U << nap->addr_bits) - size); NaClLog(2, "setting stack to : %016"NACL_PRIxPTR"\n", stack_ptr); VCHECK(0 == (stack_ptr & NACL_STACK_ALIGN_MASK), ("stack_ptr not aligned: %016"NACL_PRIxPTR"\n", stack_ptr)); p = (char *) stack_ptr; strp = p + ptr_tbl_size; #define BLAT(t, v) do { \ *(t *) p = (t) v; p += sizeof(t); \ } while (0); BLAT(nacl_reg_t, argc); for (i = 0; i < argc; ++i) { BLAT(uint32_t, NaClSysToUser(nap, (uintptr_t) strp)); NaClLog(2, "copying arg %d %p -> %p\n", i, argv[i], strp); strcpy(strp, argv[i]); strp += argv_len[i]; } BLAT(uint32_t, 0); for (i = 0; i < envc; ++i) { BLAT(uint32_t, NaClSysToUser(nap, (uintptr_t) strp)); NaClLog(2, "copying env %d %p -> %p\n", i, envv[i], strp); strcpy(strp, envv[i]); strp += envv_len[i]; } BLAT(uint32_t, 0); /* Push an empty auxv for glibc support */ BLAT(uint32_t, 0); BLAT(uint32_t, 0); #undef BLAT /* now actually spawn the thread */ natp = malloc(sizeof *natp); if (!natp) { goto cleanup; } /* We are ready to distinguish crashes in trusted and untrusted code. */ NaClSignalRegisterApp(nap); nap->running = 1; NaClLog(2, "system stack ptr : %016"NACL_PRIxPTR"\n", stack_ptr); NaClLog(2, " user stack ptr : %016"NACL_PRIxPTR"\n", NaClSysToUserStackAddr(nap, stack_ptr)); /* e_entry is user addr */ if (!NaClAppThreadAllocSegCtor(natp, nap, 1, nap->entry_pt, NaClSysToUserStackAddr(nap, stack_ptr), NaClUserToSys(nap, nap->break_addr), 1)) { retval = 0; goto cleanup; } /* * NB: Hereafter locking is required to access nap. */ retval = 1; cleanup: free(argv_len); free(envv_len); WINDOWS_EXCEPTION_CATCH; return retval; }
/* * write specified amount of bytes from buffer to given desc/offset * return amount of read bytes or negative error code if call failed */ static int32_t ZVMWriteHandle(struct NaClApp *nap, int ch, const char *buffer, int32_t size, int64_t offset) { struct ChannelDesc *channel; int64_t tail; const char *sys_buffer; int32_t retcode = -1; assert(nap != NULL); assert(nap->system_manifest != NULL); assert(nap->system_manifest->channels != NULL); /* check the channel number */ if(ch < 0 || ch >= nap->system_manifest->channels_count) { ZLOGS(LOG_DEBUG, "channel_id=%d, buffer=0x%lx, size=%d, offset=%ld", ch, (intptr_t)buffer, size, offset); return -EINVAL; } channel = &nap->system_manifest->channels[ch]; ZLOGS(LOG_DEBUG, "channel %s, buffer=0x%lx, size=%d, offset=%ld", channel->alias, (intptr_t)buffer, size, offset); /* check buffer and convert address */ if(CheckRAMAccess(nap, (uintptr_t)buffer, size, PROT_READ) == -1) return -EINVAL; sys_buffer = (char*)NaClUserToSys(nap, (uintptr_t) buffer); /* ignore user offset for sequential access write */ if(CHANNEL_SEQ_WRITEABLE(channel)) offset = channel->putpos; /* check arguments sanity */ if(size == 0) return 0; /* success. user has read 0 bytes */ if(size < 0) return -EFAULT; if(offset < 0) return -EINVAL; /* check limits */ if(channel->counters[PutsLimit] >= channel->limits[PutsLimit]) return -EDQUOT; tail = channel->limits[PutSizeLimit] - channel->counters[PutSizeLimit]; if(offset >= channel->limits[PutSizeLimit] && !CHANNEL_READABLE(channel)) return -EINVAL; if(offset >= channel->size + tail) return -EINVAL; if(size > tail) size = tail; if(size < 1) return -EDQUOT; /* write data and update position */ switch(channel->source) { case ChannelRegular: retcode = pwrite(channel->handle, sys_buffer, (size_t)size, (off_t)offset); if(retcode == -1) retcode = -errno; break; case ChannelCharacter: case ChannelFIFO: retcode = fwrite(sys_buffer, 1, (size_t)size, (FILE*)channel->socket); if(retcode == -1) retcode = -errno; break; case ChannelTCP: retcode = SendMessage(channel, sys_buffer, size); if(retcode == -1) retcode = -EIO; break; default: /* design error */ ZLOGFAIL(1, EFAULT, "invalid channel source"); break; } /* update the channel counter, size, position and tag */ ++channel->counters[PutsLimit]; if(retcode > 0) { channel->counters[PutSizeLimit] += retcode; UpdateChannelTag(channel, (const char*)sys_buffer, retcode); channel->putpos = offset + retcode; channel->size = (channel->type == SGetRPut) || (channel->type == RGetRPut) ? MAX(channel->size, channel->putpos) : channel->putpos; channel->getpos = channel->putpos; } return retcode; }