Luna/kernel/src/sys/exec.cpp
apio e9e7b22323
All checks were successful
continuous-integration/drone/pr Build is passing
kernel: Separate a thread's page directory into two
The self directory, and the active directory. The active directory is the one the thread is currently using,
and the self directory is the one the thread owns.

This lets us keep track of both, which fixes ext2 executables crashing the system.
2023-06-25 20:35:40 +02:00

166 lines
4.6 KiB
C++

#include "Log.h"
#include "fs/VFS.h"
#include "memory/MemoryManager.h"
#include "sys/Syscall.h"
#include "thread/ELF.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]));
auto envp = TRY(copy_string_vector_from_userspace(args[2]));
if ((calculate_userspace_stack_size(argv) + calculate_userspace_stack_size(envp)) > MAX_ARGV_STACK_SIZE)
return err(E2BIG);
auto current = Scheduler::current();
auto inode = TRY(VFS::resolve_path(path.chars(), current->auth, current->current_directory));
if (!VFS::can_execute(inode, current->auth)) return err(EACCES);
#ifdef EXEC_DEBUG
kdbgln("exec: attempting to replace current image with %s", path.chars());
#endif
auto guard = make_scope_guard([current] { MMU::switch_page_directory(current->self_directory); });
auto image = TRY(ThreadImage::try_load_from_elf(inode));
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();
for (int i = 0; i < FD_MAX; i++)
{
auto& descriptor = current->fd_table[i];
if (!descriptor.has_value()) continue;
if (descriptor->flags & O_CLOEXEC)
{
descriptor->inode->remove_handle();
descriptor = {};
}
}
MMU::delete_userspace_page_directory(current->self_directory);
if (VFS::is_setuid(inode)) current->auth.euid = current->auth.suid = inode->uid();
if (VFS::is_setgid(inode)) current->auth.egid = current->auth.sgid = inode->gid();
current->name = path.chars();
image->apply(current);
MMU::switch_page_directory(current->self_directory);
current->set_arguments(user_argc, user_argv, user_envc, user_envp);
memcpy(regs, &current->regs, sizeof(*regs));
kinfoln("exec: thread %lu was replaced with %s", current->id, path.chars());
return 0;
}
Result<u64> sys_fork(Registers* regs, SyscallArgs)
{
auto current = Scheduler::current();
auto guard = make_scope_guard([current] { MMU::switch_page_directory(current->self_directory); });
memcpy(&current->regs, regs, sizeof(*regs));
auto current_directory_path = TRY(current->current_directory_path.clone());
auto image = TRY(ThreadImage::clone_from_thread(current));
auto thread = TRY(new_thread());
thread->state = ThreadState::Runnable;
thread->is_kernel = false;
thread->fp_data.save();
thread->name = current->name;
thread->auth = current->auth;
thread->current_directory = current->current_directory;
thread->current_directory_path = move(current_directory_path);
thread->umask = current->umask;
thread->parent = current;
for (int i = 0; i < FD_MAX; i++)
{
thread->fd_table[i] = current->fd_table[i];
if (current->fd_table[i].has_value()) current->fd_table[i]->inode->add_handle();
}
image->apply(thread);
memcpy(&thread->regs, regs, sizeof(*regs));
thread->set_return(0);
Scheduler::add_thread(thread);
#ifdef FORK_DEBUG
kdbgln("fork: thread %lu forked into child %lu", current->id, thread->id);
#endif
return thread->id;
}