Compare commits

...

3 Commits

Author SHA1 Message Date
3598dacbed
init: Remove support for 'Script' parameters
All checks were successful
continuous-integration/drone/push Build is passing
This can now be changed to good old 'Command', putting a shebang into the script,
thanks to the new shebang support in the kernel.
2023-07-30 18:47:38 +02:00
3638d3da46
kernel: Add binary format for shebang scripts 2023-07-30 18:46:19 +02:00
1c76675e40
kernel: Add a framework to add more executable formats, possibly from userspace
This lets us implement shebangs and possibly an interface similar to Linux's binfmt_misc.
2023-07-30 18:25:44 +02:00
16 changed files with 351 additions and 168 deletions

View File

@ -175,28 +175,10 @@ static Result<void> load_service(const os::Path& path)
if (parts[0].view() == "Command")
{
if (!service.command.is_empty())
{
do_log("[init] 'Command' cannot be specified after 'Script' has already been set! (%s)\n",
line.chars());
return {};
}
service.command = move(parts[1]);
continue;
}
if (parts[0].view() == "Script")
{
if (!service.command.is_empty())
{
do_log("[init] 'Script' cannot be specified after 'Command' has already been set! (%s)\n",
line.chars());
return {};
}
service.command = TRY(String::format("/bin/sh -- %s"_sv, parts[1].chars()));
continue;
}
if (parts[0].view() == "Restart")
{
if (parts[1].view() == "true" || parts[1].view().to_uint().value_or(0) == 1)

View File

@ -1,4 +1,4 @@
Name=mount-home
Description=Mount the user's home directory on a writable filesystem.
Script=/etc/startup/mount-home.sh
Command=/etc/startup/mount-home.sh
Wait=true

View File

@ -1,2 +1,3 @@
#!/bin/sh
mount -t tmpfs tmpfs /home/selene
chown selene:selene /home/selene

View File

@ -60,7 +60,9 @@ set(SOURCES
src/fs/devices/FramebufferDevice.cpp
src/fs/devices/UARTDevice.cpp
src/fs/InitRD.cpp
src/thread/ELF.cpp
src/binfmt/ELF.cpp
src/binfmt/BinaryFormat.cpp
src/binfmt/Script.cpp
)
if("${LUNA_ARCH}" MATCHES "x86_64")

View File

@ -0,0 +1,39 @@
#include "binfmt/BinaryFormat.h"
#include "binfmt/ELF.h"
#include "binfmt/Script.h"
struct BinaryFormatDescriptor
{
binfmt_loader_creator_t creator;
void* arg;
};
Vector<BinaryFormatDescriptor> g_binary_formats;
Result<void> BinaryFormat::init()
{
TRY(register_binary_format(ELFLoader::create, nullptr));
TRY(register_binary_format(ScriptLoader::create, nullptr));
return {};
}
Result<void> BinaryFormat::register_binary_format(binfmt_loader_creator_t creator, void* arg)
{
return g_binary_formats.try_append({ creator, arg });
}
Result<SharedPtr<BinaryFormatLoader>> BinaryFormat::create_loader(SharedPtr<VFS::Inode> inode)
{
for (const auto& format : g_binary_formats)
{
auto loader = TRY(format.creator(inode, format.arg));
if (TRY(loader->sniff())) return loader;
}
return err(ENOEXEC);
}
BinaryFormatLoader::BinaryFormatLoader(SharedPtr<VFS::Inode> inode) : m_inode(inode)
{
}

View File

@ -0,0 +1,32 @@
#pragma once
#include "fs/VFS.h"
#include "memory/AddressSpace.h"
class BinaryFormatLoader : public Shareable
{
public:
virtual Result<bool> sniff() = 0;
virtual Result<u64> load(AddressSpace* space) = 0;
virtual StringView format() const = 0;
virtual Result<Vector<String>> cmdline(Vector<String> args) = 0;
virtual ~BinaryFormatLoader() = default;
BinaryFormatLoader(SharedPtr<VFS::Inode>);
protected:
SharedPtr<VFS::Inode> m_inode;
};
typedef Result<SharedPtr<BinaryFormatLoader>> (*binfmt_loader_creator_t)(SharedPtr<VFS::Inode>, void*);
namespace BinaryFormat
{
Result<void> init();
Result<void> register_binary_format(binfmt_loader_creator_t creator, void* arg);
Result<SharedPtr<BinaryFormatLoader>> create_loader(SharedPtr<VFS::Inode> inode);
}

147
kernel/src/binfmt/ELF.cpp Normal file
View File

@ -0,0 +1,147 @@
#include "binfmt/ELF.h"
#include "Log.h"
#include "arch/CPU.h"
#include "arch/MMU.h"
#include "memory/MemoryManager.h"
#include <luna/Alignment.h>
#include <luna/Alloc.h>
#include <luna/CString.h>
#include <luna/ScopeGuard.h>
static bool can_execute_segment(u32 flags)
{
return flags & 1;
}
static bool can_write_segment(u32 flags)
{
return flags & 2;
}
/*static bool can_write_and_execute_segment(u32 flags)
{
return can_write_segment(flags) && can_execute_segment(flags);
}*/
Result<bool> ELFLoader::sniff()
{
u8 buf[SELFMAG];
usize nread = TRY(m_inode->read(buf, 0, sizeof buf));
if (nread < SELFMAG) return false;
return !memcmp(buf, ELFMAG, SELFMAG);
}
Result<u64> ELFLoader::load(AddressSpace* space)
{
Elf64_Ehdr elf_header;
usize nread = TRY(m_inode->read((u8*)&elf_header, 0, sizeof elf_header));
if (nread < sizeof elf_header)
{
kerrorln("Error while loading ELF: ELF header does not fit in file");
return err(ENOEXEC);
}
if (memcmp(elf_header.e_ident, ELFMAG, SELFMAG) != 0)
{
kerrorln("Error while loading ELF: ELF header has no valid magic");
return err(ENOEXEC);
}
if (elf_header.e_ident[EI_CLASS] != ELFCLASS64)
{
kerrorln("Error while loading ELF: ELF object is not 64-bit");
return err(ENOEXEC);
}
if (elf_header.e_ident[EI_DATA] != ELFDATA2LSB)
{
kerrorln("Error while loading ELF: ELF object is not 2's complement little-endian");
return err(ENOEXEC);
}
if (elf_header.e_type != ET_EXEC)
{
kerrorln("Error while loading ELF: ELF object is not an executable");
return err(ENOEXEC);
}
if (elf_header.e_machine != EM_MACH)
{
kerrorln("Error while loading ELF: ELF object's target architecture does not match the current one (%s)",
CPU::platform_string().chars());
return err(ENOEXEC);
}
if (elf_header.e_phnum == 0)
{
kerrorln("Error while loading ELF: ELF object has no program headers");
return err(ENOEXEC);
}
#ifdef ELF_DEBUG
kdbgln("ELF: Loading ELF with entry=%#.16lx", elf_header.e_entry);
#endif
usize i;
Elf64_Phdr program_header;
for (TRY(m_inode->read((u8*)&program_header, elf_header.e_phoff, sizeof program_header)), i = 0;
i < elf_header.e_phnum;
i++, TRY(m_inode->read((u8*)&program_header, elf_header.e_phoff + (i * elf_header.e_phentsize),
sizeof program_header)))
{
if (program_header.p_type == PT_LOAD)
{
#ifdef ELF_DEBUG
kdbgln("ELF: Loading segment (offset=%zu, base=%#.16lx, filesize=%zu, memsize=%zu)",
program_header.p_offset, program_header.p_vaddr, program_header.p_filesz, program_header.p_memsz);
#endif
u64 base_vaddr = align_down<ARCH_PAGE_SIZE>(program_header.p_vaddr);
u64 vaddr_diff = program_header.p_vaddr - base_vaddr;
/*expect(!can_write_and_execute_segment(program_header.p_flags),
"Segment is both writable and executable");*/
int flags = MMU::User | MMU::NoExecute;
if (can_write_segment(program_header.p_flags)) flags |= MMU::ReadWrite;
if (can_execute_segment(program_header.p_flags)) flags &= ~MMU::NoExecute;
if (!TRY(space->test_and_alloc_region(
base_vaddr, get_blocks_from_size(program_header.p_memsz + vaddr_diff, ARCH_PAGE_SIZE), true)))
return err(ENOMEM);
// Allocate physical memory for the segment
TRY(MemoryManager::alloc_at(
base_vaddr, get_blocks_from_size(program_header.p_memsz + vaddr_diff, ARCH_PAGE_SIZE), flags));
// Zero out unused memory (before the start of the segment)
memset((void*)base_vaddr, 0, vaddr_diff);
// Load the file section of the segment
m_inode->read((u8*)program_header.p_vaddr, program_header.p_offset, program_header.p_filesz);
// Fill out the rest of the segment with 0s
memset((void*)(program_header.p_vaddr + program_header.p_filesz), 0,
program_header.p_memsz - program_header.p_filesz);
}
else { kwarnln("ELF: Encountered non-loadable program header, skipping"); }
}
return elf_header.e_entry;
}
Result<Vector<String>> ELFLoader::cmdline(Vector<String> args)
{
return args;
}
ELFLoader::ELFLoader(SharedPtr<VFS::Inode> inode) : BinaryFormatLoader(inode)
{
}
Result<SharedPtr<BinaryFormatLoader>> ELFLoader::create(SharedPtr<VFS::Inode> inode, void*)
{
return (SharedPtr<BinaryFormatLoader>)TRY(make_shared<ELFLoader>(inode));
}

View File

@ -1,6 +1,8 @@
#pragma once
#include "binfmt/BinaryFormat.h"
#include "fs/VFS.h"
#include "memory/AddressSpace.h"
#include <luna/SharedPtr.h>
#include <luna/Types.h>
#define ELFMAG "\177ELF"
@ -47,12 +49,20 @@ typedef struct
u64 p_align; /* Segment alignment */
} Elf64_Phdr;
struct ELFData
class ELFLoader : public BinaryFormatLoader
{
u64 entry;
};
public:
Result<bool> sniff() override;
Result<u64> load(AddressSpace* space) override;
namespace ELFLoader
{
Result<ELFData> load(SharedPtr<VFS::Inode> inode, AddressSpace* space);
Result<Vector<String>> cmdline(Vector<String> args) override;
StringView format() const override
{
return "elf";
}
ELFLoader(SharedPtr<VFS::Inode> inode);
static Result<SharedPtr<BinaryFormatLoader>> create(SharedPtr<VFS::Inode> inode, void*);
};

View File

@ -0,0 +1,61 @@
#include "binfmt/Script.h"
#include "binfmt/ELF.h"
#include "thread/Scheduler.h"
#define SHEBANG "#!"
Result<bool> ScriptLoader::sniff()
{
u8 buf[2];
usize nread = TRY(m_inode->read(buf, 0, sizeof buf));
if (nread < 2) return false;
return !memcmp(buf, SHEBANG, 2);
}
Result<u64> ScriptLoader::load(AddressSpace* space)
{
u8 buf[256];
usize nread = TRY(m_inode->read(buf, 2, 255));
if (!nread) return err(ENOEXEC);
for (usize i = 0; i < nread; i++)
{
if (buf[i] == '\n') buf[i] = '\0';
else if (buf[i] == '\r' && (i + 1) < nread && buf[i + 1] == '\n')
buf[i] = buf[i + 1] = '\0';
else
continue;
break;
}
auto view = StringView { (const char*)buf };
m_interpreter_cmdline = TRY(view.split(" "));
if (!m_interpreter_cmdline.size()) return err(ENOEXEC);
auto& interpreter_path = m_interpreter_cmdline[0];
auto* current = Scheduler::current();
auto interpreter =
TRY(VFS::resolve_path(interpreter_path.chars(), current->auth, current->current_directory, true));
if (!VFS::can_execute(interpreter, current->auth)) return err(EACCES);
auto loader = TRY(ELFLoader::create(interpreter, nullptr));
return loader->load(space);
}
Result<Vector<String>> ScriptLoader::cmdline(Vector<String> args)
{
Vector<String> new_args;
for (auto& arg : m_interpreter_cmdline) { TRY(new_args.try_append(move(arg))); }
for (auto& arg : args) { TRY(new_args.try_append(move(arg))); }
return new_args;
}
ScriptLoader::ScriptLoader(SharedPtr<VFS::Inode> inode) : BinaryFormatLoader(inode)
{
}
Result<SharedPtr<BinaryFormatLoader>> ScriptLoader::create(SharedPtr<VFS::Inode> inode, void*)
{
return (SharedPtr<BinaryFormatLoader>)TRY(make_shared<ScriptLoader>(inode));
}

View File

@ -0,0 +1,25 @@
#pragma once
#include "binfmt/BinaryFormat.h"
#include "fs/VFS.h"
#include "memory/AddressSpace.h"
class ScriptLoader : public BinaryFormatLoader
{
public:
Result<bool> sniff() override;
Result<u64> load(AddressSpace* space) override;
Result<Vector<String>> cmdline(Vector<String> args) override;
StringView format() const override
{
return "script";
}
ScriptLoader(SharedPtr<VFS::Inode> inode);
static Result<SharedPtr<BinaryFormatLoader>> create(SharedPtr<VFS::Inode> inode, void*);
private:
Vector<String> m_interpreter_cmdline;
};

View File

@ -1,6 +1,7 @@
#include "Log.h"
#include "arch/CPU.h"
#include "arch/Timer.h"
#include "binfmt/BinaryFormat.h"
#include "boot/Init.h"
#include "config.h"
#include "fs/InitRD.h"
@ -45,6 +46,8 @@ void reap_thread()
mark_critical(InitRD::populate_vfs(), "Failed to load files from the initial ramdisk");
mark_critical(DeviceRegistry::init(), "Failed to register initial devices");
mark_critical(BinaryFormat::init(), "Failed to register initial binary formats");
auto init =
mark_critical(VFS::resolve_path("/bin/preinit", Credentials {}), "Can't find init in the initial ramfs!");
auto init_thread =

View File

@ -1,8 +1,8 @@
#include "Log.h"
#include "binfmt/BinaryFormat.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>
@ -70,9 +70,17 @@ Result<u64> sys_execve(Registers* regs, SyscallArgs args)
kdbgln("exec: attempting to replace current image with %s", path.chars());
#endif
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([current] { MMU::switch_page_directory(current->self_directory()); });
auto image = TRY(ThreadImage::try_load_from_elf(inode));
auto image = TRY(ThreadImage::try_load_from_binary(loader));
argv = TRY(loader->cmdline(move(argv)));
u64 user_argv = TRY(image->push_string_vector_on_stack(argv));
usize user_argc = argv.size();

View File

@ -1,128 +0,0 @@
#include "ELF.h"
#include "Log.h"
#include "arch/CPU.h"
#include "arch/MMU.h"
#include "memory/MemoryManager.h"
#include <luna/Alignment.h>
#include <luna/Alloc.h>
#include <luna/CString.h>
#include <luna/ScopeGuard.h>
static bool can_execute_segment(u32 flags)
{
return flags & 1;
}
static bool can_write_segment(u32 flags)
{
return flags & 2;
}
/*static bool can_write_and_execute_segment(u32 flags)
{
return can_write_segment(flags) && can_execute_segment(flags);
}*/
namespace ELFLoader
{
Result<ELFData> load(SharedPtr<VFS::Inode> inode, AddressSpace* space)
{
Elf64_Ehdr elf_header;
usize nread = TRY(inode->read((u8*)&elf_header, 0, sizeof elf_header));
if (nread < sizeof elf_header)
{
kerrorln("Error while loading ELF: ELF header does not fit in file");
return err(ENOEXEC);
}
if (memcmp(elf_header.e_ident, ELFMAG, SELFMAG) != 0)
{
kerrorln("Error while loading ELF: ELF header has no valid magic");
return err(ENOEXEC);
}
if (elf_header.e_ident[EI_CLASS] != ELFCLASS64)
{
kerrorln("Error while loading ELF: ELF object is not 64-bit");
return err(ENOEXEC);
}
if (elf_header.e_ident[EI_DATA] != ELFDATA2LSB)
{
kerrorln("Error while loading ELF: ELF object is not 2's complement little-endian");
return err(ENOEXEC);
}
if (elf_header.e_type != ET_EXEC)
{
kerrorln("Error while loading ELF: ELF object is not an executable");
return err(ENOEXEC);
}
if (elf_header.e_machine != EM_MACH)
{
kerrorln("Error while loading ELF: ELF object's target architecture does not match the current one (%s)",
CPU::platform_string().chars());
return err(ENOEXEC);
}
if (elf_header.e_phnum == 0)
{
kerrorln("Error while loading ELF: ELF object has no program headers");
return err(ENOEXEC);
}
#ifdef ELF_DEBUG
kdbgln("ELF: Loading ELF with entry=%#.16lx", elf_header.e_entry);
#endif
usize i;
Elf64_Phdr program_header;
for (TRY(inode->read((u8*)&program_header, elf_header.e_phoff, sizeof program_header)), i = 0;
i < elf_header.e_phnum;
i++, TRY(inode->read((u8*)&program_header, elf_header.e_phoff + (i * elf_header.e_phentsize),
sizeof program_header)))
{
if (program_header.p_type == PT_LOAD)
{
#ifdef ELF_DEBUG
kdbgln("ELF: Loading segment (offset=%zu, base=%#.16lx, filesize=%zu, memsize=%zu)",
program_header.p_offset, program_header.p_vaddr, program_header.p_filesz,
program_header.p_memsz);
#endif
u64 base_vaddr = align_down<ARCH_PAGE_SIZE>(program_header.p_vaddr);
u64 vaddr_diff = program_header.p_vaddr - base_vaddr;
/*expect(!can_write_and_execute_segment(program_header.p_flags),
"Segment is both writable and executable");*/
int flags = MMU::User | MMU::NoExecute;
if (can_write_segment(program_header.p_flags)) flags |= MMU::ReadWrite;
if (can_execute_segment(program_header.p_flags)) flags &= ~MMU::NoExecute;
if (!TRY(space->test_and_alloc_region(
base_vaddr, get_blocks_from_size(program_header.p_memsz + vaddr_diff, ARCH_PAGE_SIZE), true)))
return err(ENOMEM);
// Allocate physical memory for the segment
TRY(MemoryManager::alloc_at(
base_vaddr, get_blocks_from_size(program_header.p_memsz + vaddr_diff, ARCH_PAGE_SIZE), flags));
// Zero out unused memory (before the start of the segment)
memset((void*)base_vaddr, 0, vaddr_diff);
// Load the file section of the segment
inode->read((u8*)program_header.p_vaddr, program_header.p_offset, program_header.p_filesz);
// Fill out the rest of the segment with 0s
memset((void*)(program_header.p_vaddr + program_header.p_filesz), 0,
program_header.p_memsz - program_header.p_filesz);
}
else { kwarnln("ELF: Encountered non-loadable program header, skipping"); }
}
return ELFData { elf_header.e_entry };
}
}

View File

@ -1,8 +1,8 @@
#include "thread/Scheduler.h"
#include "ELF.h"
#include "Log.h"
#include "arch/CPU.h"
#include "arch/MMU.h"
#include "binfmt/ELF.h"
#include "memory/MemoryManager.h"
#include "thread/ThreadImage.h"
#include <luna/Alignment.h>
@ -147,7 +147,10 @@ namespace Scheduler
auto guard = make_scope_guard([&] { delete thread; });
auto image = TRY(ThreadImage::try_load_from_elf(inode));
// Contrary to other programs, which use BinaryFormat::create_loader(), init must be a native executable.
auto loader = TRY(ELFLoader::create(inode, nullptr));
auto image = TRY(ThreadImage::try_load_from_binary(loader));
u64 argv = TRY(image->push_string_vector_on_stack(args));
u64 envp = TRY(image->push_string_vector_on_stack(env));

View File

@ -24,7 +24,7 @@ static Result<void> create_user_stack(Stack& user_stack, AddressSpace* space)
return {};
}
Result<OwnedPtr<ThreadImage>> ThreadImage::try_load_from_elf(SharedPtr<VFS::Inode> inode)
Result<OwnedPtr<ThreadImage>> ThreadImage::try_load_from_binary(SharedPtr<BinaryFormatLoader> loader)
{
auto image = TRY(make_owned<ThreadImage>());
@ -36,7 +36,7 @@ Result<OwnedPtr<ThreadImage>> ThreadImage::try_load_from_elf(SharedPtr<VFS::Inod
auto guard = make_scope_guard([=] { MMU::switch_page_directory(old_directory); });
const ELFData data = TRY(ELFLoader::load(inode, address_space.ptr()));
const u64 entry = TRY(loader->load(address_space.ptr()));
Stack user_stack;
TRY(create_user_stack(user_stack, address_space.ptr()));
@ -44,7 +44,7 @@ Result<OwnedPtr<ThreadImage>> ThreadImage::try_load_from_elf(SharedPtr<VFS::Inod
guard.deactivate();
image->m_user_stack = user_stack;
image->m_loaded_image_data = data;
image->m_program_entry = entry;
image->m_address_space = move(address_space);
image->m_sp = user_stack.top();
@ -57,14 +57,12 @@ Result<OwnedPtr<ThreadImage>> ThreadImage::clone_from_thread(Thread* parent)
auto address_space = TRY(parent->address_space->clone());
const ELFData data = { .entry = parent->ip() };
const u64 kernel_stack_base = TRY(MemoryManager::alloc_for_kernel(4, MMU::ReadWrite | MMU::NoExecute));
Stack kernel_stack { kernel_stack_base, 4 * ARCH_PAGE_SIZE };
image->m_kernel_stack = kernel_stack;
image->m_user_stack = parent->stack;
image->m_loaded_image_data = data;
image->m_program_entry = parent->ip();
image->m_address_space = move(address_space);
image->m_sp = parent->sp();
@ -103,7 +101,7 @@ void ThreadImage::apply(Thread* thread)
{
thread->init_regs_user();
thread->set_ip(m_loaded_image_data.entry);
thread->set_ip(m_program_entry);
if (m_kernel_stack.bottom()) thread->kernel_stack = m_kernel_stack;
thread->stack = m_user_stack;

View File

@ -1,8 +1,8 @@
#pragma once
#include "ELF.h"
#include "arch/CPU.h"
#include "arch/MMU.h"
#include "binfmt/BinaryFormat.h"
#include "fs/VFS.h"
#include "memory/AddressSpace.h"
#include "thread/Thread.h"
@ -18,7 +18,7 @@ class Thread;
class ThreadImage
{
public:
static Result<OwnedPtr<ThreadImage>> try_load_from_elf(SharedPtr<VFS::Inode> inode);
static Result<OwnedPtr<ThreadImage>> try_load_from_binary(SharedPtr<BinaryFormatLoader> inode);
static Result<OwnedPtr<ThreadImage>> clone_from_thread(Thread* parent);
@ -31,6 +31,6 @@ class ThreadImage
OwnedPtr<AddressSpace> m_address_space;
Stack m_user_stack;
Stack m_kernel_stack;
ELFData m_loaded_image_data;
u64 m_program_entry;
u64 m_sp;
};