kernel/binfmt: Add documentation + support script interpreters being scripts themselves
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
apio 2023-07-31 20:41:18 +02:00
parent d01087362e
commit 4c87d72b44
Signed by: apio
GPG Key ID: B8A7D06E42258954
8 changed files with 117 additions and 26 deletions

View File

@ -23,17 +23,20 @@ Result<void> BinaryFormat::register_binary_format(binfmt_loader_creator_t creato
return g_binary_formats.try_append({ creator, arg });
}
Result<SharedPtr<BinaryFormatLoader>> BinaryFormat::create_loader(SharedPtr<VFS::Inode> inode)
Result<SharedPtr<BinaryFormatLoader>> BinaryFormat::create_loader(SharedPtr<VFS::Inode> inode, int recursion_level)
{
if (recursion_level >= 8) return err(ELOOP);
for (const auto& format : g_binary_formats)
{
auto loader = TRY(format.creator(inode, format.arg));
auto loader = TRY(format.creator(inode, format.arg, recursion_level));
if (TRY(loader->sniff())) return loader;
}
return err(ENOEXEC);
}
BinaryFormatLoader::BinaryFormatLoader(SharedPtr<VFS::Inode> inode) : m_inode(inode)
BinaryFormatLoader::BinaryFormatLoader(SharedPtr<VFS::Inode> inode, int recursion_level)
: m_inode(inode), m_recursion_level(recursion_level)
{
}

View File

@ -5,28 +5,110 @@
class BinaryFormatLoader : public Shareable
{
public:
/**
* @brief Determine if the given executable file matches this binary format.
*
* @return Result<bool> An error, or whether the file matches the binary format.
*/
virtual Result<bool> sniff() = 0;
/**
* @brief Load the given executable binary file into an address space.
*
* Depending on the binary format, this function may load an arbitrary interpreter instead, which will interpret the
* file on its own.
*
* @param space The address space to load the executable into.
* @return Result<u64> An error, or the entry point of the executable in memory.
*/
virtual Result<u64> load(AddressSpace* space) = 0;
/**
* @brief Return the short name associated with this format.
*
* @return StringView The format's name.
*/
virtual StringView format() const = 0;
virtual Result<Vector<String>> cmdline(Vector<String> args) = 0;
/**
* @brief Transform an interpreted program's command line arguments.
*
* Example: A script 'foo.sh' with a shebang line '#!/bin/sh' is loaded using a BinaryFormatLoader.
*
* This function will then be called with path="/path/to/foo.sh" and args={"foo.sh", "--enable-bar"}
*
* The function should return {"/bin/sh", "/path/to/foo.sh", "--enable-bar"} (prepending the interpreter command
* line and substituting args[0] for the full path of the script).
*
* For native executable formats that do not require an interpreter (e.g. ELF), this function should just ignore
* path and return args unmodified.
*
* @param path The path (absolute or relative to the current process's working directory) of the current program
* file. This should be used instead of args[0] as arbitrary values can be passed there, leaving the interpreter
* unable to find the target program.
* @param args The original command line arguments passed to execve().
* @return Result<Vector<String>> An error, or the transformed command line arguments.
*/
virtual Result<Vector<String>> cmdline(const String& path, Vector<String> args) = 0;
virtual ~BinaryFormatLoader() = default;
BinaryFormatLoader(SharedPtr<VFS::Inode>);
/**
* @brief Construct a new BinaryFormatLoader.
*
* This should not be directly used, instead each subclass of BinaryFormatLoader should implement a static create()
* method which implements the binfmt_loader_creator_t type and register it using
* BinaryFormat::register_binary_format().
*
* Then, anyone that needs an appropriate BinaryFormatLoader for an executable file should call
* BinaryFormat::create_loader(), which will find an appropriate loader out of all default/registered loaders.
*
* @param inode The executable program file to load into memory.
* @param recursion_level In normal cases, 0. If the BinaryFormatLoader is created inside another loader (for
* example, a script loader loading the script's interpreter), the caller shall pass its recursion_level + 1 to
* BinaryFormat::create_loader(), which will forward it to this constructor. This avoids infinite recursion.
*/
BinaryFormatLoader(SharedPtr<VFS::Inode> inode, int recursion_level);
protected:
SharedPtr<VFS::Inode> m_inode;
int m_recursion_level;
};
typedef Result<SharedPtr<BinaryFormatLoader>> (*binfmt_loader_creator_t)(SharedPtr<VFS::Inode>, void*);
/**
* @brief The factory function signature for binary format loaders.
*/
typedef Result<SharedPtr<BinaryFormatLoader>> (*binfmt_loader_creator_t)(SharedPtr<VFS::Inode>, void*, int);
namespace BinaryFormat
{
/**
* @brief Register the default binary format loaders.
*
* @return Result<void> Whether the operation succeeded.
*/
Result<void> init();
/**
* @brief Register a new binary format loader type.
*
* @param creator A factory function to create said binary format loader. The function shall be passed the inode to
* load the program from, the arbitrary argument passed to this function, and a recursion index to avoid infinite
* recursion.
* @param arg An arbitrary argument that will be passed to the above factory function.
* @return Result<void> Whether the operation succeeded.
*/
Result<void> register_binary_format(binfmt_loader_creator_t creator, void* arg);
Result<SharedPtr<BinaryFormatLoader>> create_loader(SharedPtr<VFS::Inode> inode);
/**
* @brief Create an appropriate loader object for an executable file.
*
* @param inode The executable file to create the loader for. If no appropriate loader could be found for this file
* type, this function shall return ENOEXEC.
* @param recursion_level In most cases, 0. If called inside another loader, the loader shall pass its
* own recursion_level variable + 1, to avoid infinite recursion. If recursion_level >= 8, this function shall
* immediately return ELOOP.
* @return Result<SharedPtr<BinaryFormatLoader>> An error, or the new loader object created for this file.
*/
Result<SharedPtr<BinaryFormatLoader>> create_loader(SharedPtr<VFS::Inode> inode, int recursion_level = 0);
}

View File

@ -132,16 +132,16 @@ Result<u64> ELFLoader::load(AddressSpace* space)
return elf_header.e_entry;
}
Result<Vector<String>> ELFLoader::cmdline(Vector<String> args)
Result<Vector<String>> ELFLoader::cmdline(const String&, Vector<String> args)
{
return args;
}
ELFLoader::ELFLoader(SharedPtr<VFS::Inode> inode) : BinaryFormatLoader(inode)
ELFLoader::ELFLoader(SharedPtr<VFS::Inode> inode, int recursion_level) : BinaryFormatLoader(inode, recursion_level)
{
}
Result<SharedPtr<BinaryFormatLoader>> ELFLoader::create(SharedPtr<VFS::Inode> inode, void*)
Result<SharedPtr<BinaryFormatLoader>> ELFLoader::create(SharedPtr<VFS::Inode> inode, void*, int recursion_level)
{
return (SharedPtr<BinaryFormatLoader>)TRY(make_shared<ELFLoader>(inode));
return (SharedPtr<BinaryFormatLoader>)TRY(make_shared<ELFLoader>(inode, recursion_level));
}

View File

@ -55,14 +55,14 @@ class ELFLoader : public BinaryFormatLoader
Result<bool> sniff() override;
Result<u64> load(AddressSpace* space) override;
Result<Vector<String>> cmdline(Vector<String> args) override;
Result<Vector<String>> cmdline(const String& path, Vector<String> args) override;
StringView format() const override
{
return "elf";
}
ELFLoader(SharedPtr<VFS::Inode> inode);
ELFLoader(SharedPtr<VFS::Inode> inode, int recursion_level);
static Result<SharedPtr<BinaryFormatLoader>> create(SharedPtr<VFS::Inode> inode, void*);
static Result<SharedPtr<BinaryFormatLoader>> create(SharedPtr<VFS::Inode> inode, void*, int);
};

View File

@ -39,23 +39,29 @@ Result<u64> ScriptLoader::load(AddressSpace* space)
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);
auto loader = TRY(BinaryFormat::create_loader(interpreter, m_recursion_level + 1));
u64 entry = TRY(loader->load(space));
m_interpreter_cmdline = TRY(loader->cmdline(interpreter_path, move(m_interpreter_cmdline)));
return entry;
}
Result<Vector<String>> ScriptLoader::cmdline(Vector<String> args)
Result<Vector<String>> ScriptLoader::cmdline(const String& path, 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))); }
auto arg = TRY(path.clone());
TRY(new_args.try_append(move(arg)));
for (usize i = 1; i < args.size(); i++) { TRY(new_args.try_append(move(args[i]))); }
return new_args;
}
ScriptLoader::ScriptLoader(SharedPtr<VFS::Inode> inode) : BinaryFormatLoader(inode)
ScriptLoader::ScriptLoader(SharedPtr<VFS::Inode> inode, int recursion_level)
: BinaryFormatLoader(inode, recursion_level)
{
}
Result<SharedPtr<BinaryFormatLoader>> ScriptLoader::create(SharedPtr<VFS::Inode> inode, void*)
Result<SharedPtr<BinaryFormatLoader>> ScriptLoader::create(SharedPtr<VFS::Inode> inode, void*, int recursion_level)
{
return (SharedPtr<BinaryFormatLoader>)TRY(make_shared<ScriptLoader>(inode));
return (SharedPtr<BinaryFormatLoader>)TRY(make_shared<ScriptLoader>(inode, recursion_level));
}

View File

@ -9,16 +9,16 @@ class ScriptLoader : public BinaryFormatLoader
Result<bool> sniff() override;
Result<u64> load(AddressSpace* space) override;
Result<Vector<String>> cmdline(Vector<String> args) override;
Result<Vector<String>> cmdline(const String& path, Vector<String> args) override;
StringView format() const override
{
return "script";
}
ScriptLoader(SharedPtr<VFS::Inode> inode);
ScriptLoader(SharedPtr<VFS::Inode> inode, int recursion_level);
static Result<SharedPtr<BinaryFormatLoader>> create(SharedPtr<VFS::Inode> inode, void*);
static Result<SharedPtr<BinaryFormatLoader>> create(SharedPtr<VFS::Inode> inode, void*, int);
private:
Vector<String> m_interpreter_cmdline;

View File

@ -80,7 +80,7 @@ Result<u64> sys_execve(Registers* regs, SyscallArgs args)
auto image = TRY(ThreadImage::try_load_from_binary(loader));
argv = TRY(loader->cmdline(move(argv)));
argv = TRY(loader->cmdline(path, move(argv)));
u64 user_argv = TRY(image->push_string_vector_on_stack(argv));
usize user_argc = argv.size();

View File

@ -148,7 +148,7 @@ namespace Scheduler
auto guard = make_scope_guard([&] { delete thread; });
// Contrary to other programs, which use BinaryFormat::create_loader(), init must be a native executable.
auto loader = TRY(ELFLoader::create(inode, nullptr));
auto loader = TRY(ELFLoader::create(inode, nullptr, 0));
auto image = TRY(ThreadImage::try_load_from_binary(loader));
u64 argv = TRY(image->push_string_vector_on_stack(args));