Luna/kernel/src/sys/exec.cpp

251 lines
7.1 KiB
C++

#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 <bits/modes.h>
#include <bits/open-flags.h>
#include <luna/CString.h>
#include <luna/ScopeGuard.h>
#include <luna/Vector.h>
static Result<Vector<String>> copy_string_vector_from_userspace(u64 address)
{
Vector<String> 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<String>& 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<u64> 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<String> 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<u64> sys_fork(Registers* regs, SyscallArgs)
{
auto current = Process::current();
auto current_thread = Scheduler::current();
TRY(check_pledge(current, Promise::p_proc));
Vector<gid_t> 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(&current_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<Process>());
Option<FileDescriptor> 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;
}