diff --git a/kernel/src/binfmt/BinaryFormat.cpp b/kernel/src/binfmt/BinaryFormat.cpp index 4b8be629..6388d023 100644 --- a/kernel/src/binfmt/BinaryFormat.cpp +++ b/kernel/src/binfmt/BinaryFormat.cpp @@ -23,17 +23,20 @@ Result BinaryFormat::register_binary_format(binfmt_loader_creator_t creato return g_binary_formats.try_append({ creator, arg }); } -Result> BinaryFormat::create_loader(SharedPtr inode) +Result> BinaryFormat::create_loader(SharedPtr 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 inode) : m_inode(inode) +BinaryFormatLoader::BinaryFormatLoader(SharedPtr inode, int recursion_level) + : m_inode(inode), m_recursion_level(recursion_level) { } diff --git a/kernel/src/binfmt/BinaryFormat.h b/kernel/src/binfmt/BinaryFormat.h index e37d106f..003af6a8 100644 --- a/kernel/src/binfmt/BinaryFormat.h +++ b/kernel/src/binfmt/BinaryFormat.h @@ -5,28 +5,110 @@ class BinaryFormatLoader : public Shareable { public: + /** + * @brief Determine if the given executable file matches this binary format. + * + * @return Result An error, or whether the file matches the binary format. + */ virtual Result 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 An error, or the entry point of the executable in memory. + */ virtual Result 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> cmdline(Vector 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> An error, or the transformed command line arguments. + */ + virtual Result> cmdline(const String& path, Vector args) = 0; virtual ~BinaryFormatLoader() = default; - BinaryFormatLoader(SharedPtr); + /** + * @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 inode, int recursion_level); protected: SharedPtr m_inode; + int m_recursion_level; }; -typedef Result> (*binfmt_loader_creator_t)(SharedPtr, void*); +/** + * @brief The factory function signature for binary format loaders. + */ +typedef Result> (*binfmt_loader_creator_t)(SharedPtr, void*, int); namespace BinaryFormat { + /** + * @brief Register the default binary format loaders. + * + * @return Result Whether the operation succeeded. + */ Result 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 Whether the operation succeeded. + */ Result register_binary_format(binfmt_loader_creator_t creator, void* arg); - Result> create_loader(SharedPtr 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> An error, or the new loader object created for this file. + */ + Result> create_loader(SharedPtr inode, int recursion_level = 0); } diff --git a/kernel/src/binfmt/ELF.cpp b/kernel/src/binfmt/ELF.cpp index 4a5ed642..c4dc19cd 100644 --- a/kernel/src/binfmt/ELF.cpp +++ b/kernel/src/binfmt/ELF.cpp @@ -132,16 +132,16 @@ Result ELFLoader::load(AddressSpace* space) return elf_header.e_entry; } -Result> ELFLoader::cmdline(Vector args) +Result> ELFLoader::cmdline(const String&, Vector args) { return args; } -ELFLoader::ELFLoader(SharedPtr inode) : BinaryFormatLoader(inode) +ELFLoader::ELFLoader(SharedPtr inode, int recursion_level) : BinaryFormatLoader(inode, recursion_level) { } -Result> ELFLoader::create(SharedPtr inode, void*) +Result> ELFLoader::create(SharedPtr inode, void*, int recursion_level) { - return (SharedPtr)TRY(make_shared(inode)); + return (SharedPtr)TRY(make_shared(inode, recursion_level)); } diff --git a/kernel/src/binfmt/ELF.h b/kernel/src/binfmt/ELF.h index 7448cec5..b145e155 100644 --- a/kernel/src/binfmt/ELF.h +++ b/kernel/src/binfmt/ELF.h @@ -55,14 +55,14 @@ class ELFLoader : public BinaryFormatLoader Result sniff() override; Result load(AddressSpace* space) override; - Result> cmdline(Vector args) override; + Result> cmdline(const String& path, Vector args) override; StringView format() const override { return "elf"; } - ELFLoader(SharedPtr inode); + ELFLoader(SharedPtr inode, int recursion_level); - static Result> create(SharedPtr inode, void*); + static Result> create(SharedPtr inode, void*, int); }; diff --git a/kernel/src/binfmt/Script.cpp b/kernel/src/binfmt/Script.cpp index 4c7bc6e0..6af3f612 100644 --- a/kernel/src/binfmt/Script.cpp +++ b/kernel/src/binfmt/Script.cpp @@ -39,23 +39,29 @@ Result 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> ScriptLoader::cmdline(Vector args) +Result> ScriptLoader::cmdline(const String& path, Vector args) { Vector 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 inode) : BinaryFormatLoader(inode) +ScriptLoader::ScriptLoader(SharedPtr inode, int recursion_level) + : BinaryFormatLoader(inode, recursion_level) { } -Result> ScriptLoader::create(SharedPtr inode, void*) +Result> ScriptLoader::create(SharedPtr inode, void*, int recursion_level) { - return (SharedPtr)TRY(make_shared(inode)); + return (SharedPtr)TRY(make_shared(inode, recursion_level)); } diff --git a/kernel/src/binfmt/Script.h b/kernel/src/binfmt/Script.h index ffbe92bb..720dd493 100644 --- a/kernel/src/binfmt/Script.h +++ b/kernel/src/binfmt/Script.h @@ -9,16 +9,16 @@ class ScriptLoader : public BinaryFormatLoader Result sniff() override; Result load(AddressSpace* space) override; - Result> cmdline(Vector args) override; + Result> cmdline(const String& path, Vector args) override; StringView format() const override { return "script"; } - ScriptLoader(SharedPtr inode); + ScriptLoader(SharedPtr inode, int recursion_level); - static Result> create(SharedPtr inode, void*); + static Result> create(SharedPtr inode, void*, int); private: Vector m_interpreter_cmdline; diff --git a/kernel/src/sys/exec.cpp b/kernel/src/sys/exec.cpp index 58c979ed..d0c361a3 100644 --- a/kernel/src/sys/exec.cpp +++ b/kernel/src/sys/exec.cpp @@ -80,7 +80,7 @@ Result 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(); diff --git a/kernel/src/thread/Scheduler.cpp b/kernel/src/thread/Scheduler.cpp index 6e472228..d5d5df16 100644 --- a/kernel/src/thread/Scheduler.cpp +++ b/kernel/src/thread/Scheduler.cpp @@ -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));