#include "Log.h" #include "Pledge.h" #include "binfmt/BinaryFormat.h" #include "fs/VFS.h" #include "memory/MemoryManager.h" #include "sys/Syscall.h" #include "thread/Scheduler.h" #include "thread/ThreadImage.h" #include #include #include #include #include static Result> copy_string_vector_from_userspace(u64 address) { Vector result; const u64* user_vector = (const u64*)address; u64 string_addr; while (true) { if (!MemoryManager::copy_from_user_typed(user_vector, &string_addr)) return err(EFAULT); if (!string_addr) break; auto string = TRY(MemoryManager::strdup_from_user(string_addr)); TRY(result.try_append(move(string))); user_vector++; } return result; } static u64 calculate_userspace_stack_size(const Vector& v) { u64 total { 0 }; for (const auto& str : v) { // The string's byte count + a terminating NUL byte. total += str.length() + 1; // The pointer to said string in the userspace array. total += sizeof(char*); } // The NULL pointer at the end of the userspace array. total += sizeof(char*); return total; } static constexpr usize MAX_ARGV_STACK_SIZE = 2 * ARCH_PAGE_SIZE; Result sys_execve(Registers* regs, SyscallArgs args) { auto path = TRY(MemoryManager::strdup_from_user(args[0])); auto argv = TRY(copy_string_vector_from_userspace(args[1])); Vector envp; if (args[2]) envp = TRY(copy_string_vector_from_userspace(args[2])); String cmdline = TRY(String::join(argv, " ")); if ((calculate_userspace_stack_size(argv) + calculate_userspace_stack_size(envp)) > MAX_ARGV_STACK_SIZE) return err(E2BIG); auto current = Process::current(); auto thread = Scheduler::current(); TRY(check_pledge(current, Promise::p_exec)); auto inode = TRY(VFS::resolve_path(path.chars(), current, current->current_directory)); if (!VFS::can_execute(inode, current)) return err(EACCES); #ifdef EXEC_DEBUG kdbgln("exec: attempting to replace current image with %s", path.chars()); #endif bool is_setuid = VFS::is_setuid(inode); bool is_setgid = VFS::is_setgid(inode); bool is_secure_environment = is_setgid || is_setuid; if (is_secure_environment && current->execpromises >= 0) return err(EACCES); auto loader = TRY(BinaryFormat::create_loader(inode)); #ifdef EXEC_DEBUG kdbgln("exec: created loader for binary format %s", loader->format().chars()); #endif auto guard = make_scope_guard([thread] { MMU::switch_page_directory(thread->self_directory()); }); auto image = TRY(ThreadImage::try_load_from_binary(loader)); argv = TRY(loader->cmdline(path, move(argv))); u64 user_argv = TRY(image->push_string_vector_on_stack(argv)); usize user_argc = argv.size(); u64 user_envp = TRY(image->push_string_vector_on_stack(envp)); usize user_envc = envp.size(); // From now on, nothing should fail. #ifdef EXEC_DEBUG kdbgln("exec: image load ok, will now replace existing process image"); #endif guard.deactivate(); // Terminate all other threads. Scheduler::for_each_thread(current, [thread](Thread* t) { if (t != thread) t->quit(); return true; }); Scheduler::signal_reap_thread(); current->thread_count = 1; current->real_timer.disarm(); current->virtual_timer.disarm(); current->profiling_timer.disarm(); for (int i = 0; i < MAX_POSIX_TIMERS; i++) { if (current->posix_timers[i].has_value()) { current->posix_timers[i]->disarm(); current->posix_timers[i] = {}; } } { auto table = current->fd_table.lock(); for (int i = 0; i < FD_MAX; i++) { auto& descriptor = (*table)[i]; if (!descriptor.has_value()) continue; if (descriptor->flags & O_CLOEXEC) { descriptor = {}; } } } { auto auth = current->auth.lock(); if (is_setuid) (*auth).euid = (*auth).suid = inode->metadata().uid; if (is_setgid) (*auth).egid = (*auth).sgid = inode->metadata().gid; } current->cmdline = cmdline.chars(); thread->cmdline = cmdline.chars(); image->apply(thread); MMU::switch_page_directory(thread->self_directory()); thread->set_arguments(user_argc, user_argv, user_envc, user_envp); current->promises = current->execpromises; current->execpromises = -1; memcpy(regs, &thread->regs, sizeof(*regs)); for (int i = 0; i < NSIG; i++) { thread->signal_handlers[i] = { .sa_handler = SIG_DFL, .sa_mask = 0, .sa_flags = 0 }; } current->has_called_exec = true; kinfoln("exec: thread %d was replaced with %s", current->id, path.chars()); return 0; } Result sys_fork(Registers* regs, SyscallArgs) { auto current = Process::current(); auto current_thread = Scheduler::current(); TRY(check_pledge(current, Promise::p_proc)); Vector extra_groups = TRY(current->copy_groups()); Credentials auth = current->credentials(); auto guard = make_scope_guard([current_thread] { MMU::switch_page_directory(current_thread->self_directory()); }); memcpy(¤t_thread->regs, regs, sizeof(*regs)); auto current_directory_path = TRY(current->current_directory_path.clone()); auto image = TRY(ThreadImage::clone_from_thread(current_thread)); auto thread = TRY(new_thread()); auto process = TRY(make()); Option fds[FD_MAX]; { auto table = current->fd_table.lock(); for (int i = 0; i < FD_MAX; i++) { fds[i] = (*table)[i]; } } thread->state = ThreadState::Runnable; thread->fp_data.save(); thread->cmdline = current_thread->cmdline; thread->process = process; process->thread_count = 1; process->id = thread->tid; process->current_directory = current->current_directory; process->current_directory_path = move(current_directory_path); process->umask = current->umask; process->parent = current; process->promises = current->promises; process->execpromises = current->execpromises; process->controlling_terminal = current->controlling_terminal; process->pgid = current->pgid; process->sid = current->sid; process->extra_groups = move(extra_groups); process->cmdline = current->cmdline; process->virtual_clock.set_resolution(1'000'000); process->profiling_clock.set_resolution(1'000'000); { auto credentials = process->auth.lock(); *credentials = auth; } { auto table = process->fd_table.lock(); for (int i = 0; i < FD_MAX; i++) { (*table)[i] = fds[i]; } } image->apply(thread); memcpy(&thread->regs, regs, sizeof(*regs)); for (int i = 0; i < NSIG; i++) thread->signal_handlers[i] = current_thread->signal_handlers[i]; thread->signal_mask = current_thread->signal_mask; thread->set_return(0); Scheduler::add_thread(thread); Scheduler::add_process(process); #ifdef FORK_DEBUG kdbgln("fork: thread %d forked into child %d", current->id, process->id); #endif return process->id; }