diff options
-rw-r--r-- | Kernel/Process.h | 20 | ||||
-rw-r--r-- | Kernel/Syscalls/execve.cpp | 318 | ||||
-rw-r--r-- | Kernel/Thread.cpp | 4 | ||||
-rw-r--r-- | Libraries/LibELF/AuxiliaryVector.h | 8 | ||||
-rw-r--r-- | Libraries/LibELF/Loader.cpp | 7 | ||||
-rw-r--r-- | Libraries/LibELF/Loader.h | 5 |
6 files changed, 215 insertions, 147 deletions
diff --git a/Kernel/Process.h b/Kernel/Process.h index e8e567f557..f21a79be01 100644 --- a/Kernel/Process.h +++ b/Kernel/Process.h @@ -421,6 +421,25 @@ public: int exec(String path, Vector<String> arguments, Vector<String> environment, int recusion_depth = 0); + struct LoadResult { + FlatPtr load_offset { 0 }; + FlatPtr entry_eip { 0 }; + size_t size { 0 }; + FlatPtr program_headers { 0 }; + size_t num_program_headers { 0 }; + AK::WeakPtr<Region> tls_region; + size_t tls_size { 0 }; + size_t tls_alignment { 0 }; + }; + + enum class ShouldAllocateTls { + No = 0, + Yes, + }; + + int load(NonnullRefPtr<FileDescription> main_program_description, RefPtr<FileDescription> interpreter_description); + KResultOr<LoadResult> load_elf_object(FileDescription& object_description, FlatPtr load_offset, ShouldAllocateTls); + bool is_superuser() const { return m_euid == 0; @@ -563,6 +582,7 @@ private: ThreadID m_exec_tid { 0 }; FlatPtr m_load_offset { 0U }; FlatPtr m_entry_eip { 0U }; + int m_main_program_fd { -1 }; OwnPtr<ThreadTracer> m_tracer; diff --git a/Kernel/Syscalls/execve.cpp b/Kernel/Syscalls/execve.cpp index 8d2b5af041..14ab994b64 100644 --- a/Kernel/Syscalls/execve.cpp +++ b/Kernel/Syscalls/execve.cpp @@ -24,6 +24,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include <AK/LexicalPath.h> #include <AK/ScopeGuard.h> #include <AK/TemporaryChange.h> #include <Kernel/FileSystem/Custody.h> @@ -45,15 +46,8 @@ namespace Kernel { -int Process::do_exec(NonnullRefPtr<FileDescription> main_program_description, Vector<String> arguments, Vector<String> environment, RefPtr<FileDescription> interpreter_description, Thread*& new_main_thread, u32& prev_flags) +static bool validate_stack_size(const Vector<String>& arguments, const Vector<String>& environment) { - ASSERT(is_user_process()); - ASSERT(!Processor::current().in_critical()); - auto path = main_program_description->absolute_path(); -#ifdef EXEC_DEBUG - dbg() << "do_exec(" << path << ")"; -#endif - size_t total_blob_size = 0; for (auto& a : arguments) total_blob_size += a.length() + 1; @@ -62,31 +56,100 @@ int Process::do_exec(NonnullRefPtr<FileDescription> main_program_description, Ve size_t total_meta_size = sizeof(char*) * (arguments.size() + 1) + sizeof(char*) * (environment.size() + 1); - // FIXME: How much stack space does process startup need? - if ((total_blob_size + total_meta_size) >= Thread::default_userspace_stack_size) - return -E2BIG; - - auto parts = path.split('/'); - if (parts.is_empty()) - return -ENOENT; + // FIXME: This doesn't account for the size of the auxiliary vector + return (total_blob_size + total_meta_size) < Thread::default_userspace_stack_size; +} - auto& inode = interpreter_description ? *interpreter_description->inode() : *main_program_description->inode(); +KResultOr<Process::LoadResult> Process::load_elf_object(FileDescription& object_description, FlatPtr load_offset, ShouldAllocateTls should_allocate_tls) +{ + auto& inode = *(object_description.inode()); auto vmobject = SharedInodeVMObject::create_with_inode(inode); - if (static_cast<const SharedInodeVMObject&>(*vmobject).writable_mappings()) { dbg() << "Refusing to execute a write-mapped program"; - return -ETXTBSY; + return KResult(-ETXTBSY); } + InodeMetadata loader_metadata = object_description.metadata(); - // Disable profiling temporarily in case it's running on this process. - bool was_profiling = is_profiling(); - TemporaryChange profiling_disabler(m_profiling, false); + auto region = MM.allocate_kernel_region_with_vmobject(*vmobject, PAGE_ROUND_UP(loader_metadata.size), "ELF loading", Region::Access::Read); + if (!region) { + dbg() << "Could not allocate memory for ELF loading"; + return KResult(-ENOMEM); + } - // Mark this thread as the current thread that does exec - // No other thread from this process will be scheduled to run - auto current_thread = Thread::current(); - m_exec_tid = current_thread->tid(); + Region* master_tls_region { nullptr }; + size_t master_tls_size = 0; + size_t master_tls_alignment = 0; + m_entry_eip = 0; + + MM.enter_process_paging_scope(*this); + String object_name = LexicalPath(object_description.absolute_path()).basename(); + RefPtr<ELF::Loader> loader = ELF::Loader::create(region->vaddr().as_ptr(), loader_metadata.size, move(object_name)); + loader->map_section_hook = [&](VirtualAddress vaddr, size_t size, size_t alignment, size_t offset_in_image, bool is_readable, bool is_writable, bool is_executable, const String& name) -> u8* { + ASSERT(size); + ASSERT(alignment == PAGE_SIZE); + int prot = 0; + if (is_readable) + prot |= PROT_READ; + if (is_writable) + prot |= PROT_WRITE; + if (is_executable) + prot |= PROT_EXEC; + if (auto* region = allocate_region_with_vmobject(vaddr.offset(load_offset), size, *vmobject, offset_in_image, String(name), prot)) { + region->set_shared(true); + return region->vaddr().as_ptr(); + } + return nullptr; + }; + loader->alloc_section_hook = [&](VirtualAddress vaddr, size_t size, size_t alignment, bool is_readable, bool is_writable, const String& name) -> u8* { + ASSERT(size); + ASSERT(alignment == PAGE_SIZE); + int prot = 0; + if (is_readable) + prot |= PROT_READ; + if (is_writable) + prot |= PROT_WRITE; + if (auto* region = allocate_region(vaddr.offset(load_offset), size, String(name), prot)) + return region->vaddr().as_ptr(); + return nullptr; + }; + if (should_allocate_tls == ShouldAllocateTls::Yes) { + loader->tls_section_hook = [&](size_t size, size_t alignment) { + ASSERT(size); + master_tls_region = allocate_region({}, size, String(), PROT_READ | PROT_WRITE); + master_tls_size = size; + master_tls_alignment = alignment; + return master_tls_region->vaddr().as_ptr(); + }; + } + + ASSERT(!Processor::current().in_critical()); + bool success = loader->load(); + if (!success) { + klog() << "do_exec: Failure loading program"; + return KResult(-ENOEXEC); + } + + if (!loader->entry().offset(load_offset).get()) { + klog() << "do_exec: Failure loading program, entry pointer is invalid! (" << loader->entry().offset(load_offset) << ")"; + return KResult(-ENOEXEC); + } + + // NOTE: At this point, we've committed to the new executable. + + return LoadResult { + load_offset, + loader->entry().offset(load_offset).get(), + (size_t)loader_metadata.size, + VirtualAddress(loader->image().program_header_table_offset()).offset(load_offset).get(), + loader->image().program_header_count(), + master_tls_region ? master_tls_region->make_weak_ptr() : nullptr, + master_tls_size, + master_tls_alignment + }; +} +int Process::load(NonnullRefPtr<FileDescription> main_program_description, RefPtr<FileDescription> interpreter_description) +{ RefPtr<PageDirectory> old_page_directory; NonnullOwnPtrVector<Region> old_regions; @@ -98,123 +161,92 @@ int Process::do_exec(NonnullRefPtr<FileDescription> main_program_description, Ve m_page_directory = PageDirectory::create_for_userspace(*this); } -#ifdef MM_DEBUG - dbg() << "Process " << pid().value() << " exec: PD=" << m_page_directory.ptr() << " created"; -#endif + ArmedScopeGuard rollback_regions_guard([&]() { + ASSERT(Process::current() == this); + // Need to make sure we don't swap contexts in the middle + ScopedCritical critical; + m_page_directory = move(old_page_directory); + m_regions = move(old_regions); + MM.enter_process_paging_scope(*this); + }); + + if (!interpreter_description) { + auto result = load_elf_object(main_program_description, FlatPtr { 0 }, ShouldAllocateTls::Yes); + if (result.is_error()) + return result.error(); + + m_load_offset = result.value().load_offset; + m_entry_eip = result.value().entry_eip; + m_master_tls_region = result.value().tls_region; + m_master_tls_size = result.value().tls_size; + m_master_tls_alignment = result.value().tls_alignment; - InodeMetadata loader_metadata; + rollback_regions_guard.disarm(); + return 0; + } - // FIXME: Hoooo boy this is a hack if I ever saw one. - // This is the 'random' offset we're giving to our ET_DYN executables to start as. - // It also happens to be the static Virtual Address offset every static executable gets :) - // Without this, some assumptions by the ELF loading hooks below are severely broken. - // 0x08000000 is a verified random number chosen by random dice roll https://xkcd.com/221/ - m_load_offset = interpreter_description ? 0x08000000 : 0; + // TODO: This should be randomized for ASLR + constexpr FlatPtr interpreter_load_offset = 0x08000000; - // FIXME: We should be able to load both the PT_INTERP interpreter and the main program... once the RTLD is smart enough - if (interpreter_description) { - loader_metadata = interpreter_description->metadata(); - // we don't need the interpreter file description after we've loaded (or not) it into memory - interpreter_description = nullptr; - } else { - loader_metadata = main_program_description->metadata(); - } + auto interpreter_load_result = load_elf_object(*interpreter_description, interpreter_load_offset, ShouldAllocateTls::No); + if (interpreter_load_result.is_error()) + return interpreter_load_result.error(); - auto region = MM.allocate_kernel_region_with_vmobject(*vmobject, PAGE_ROUND_UP(loader_metadata.size), "ELF loading", Region::Access::Read); - if (!region) - return -ENOMEM; + m_load_offset = interpreter_load_result.value().load_offset; + m_entry_eip = interpreter_load_result.value().entry_eip; - Region* master_tls_region { nullptr }; - size_t master_tls_size = 0; - size_t master_tls_alignment = 0; - m_entry_eip = 0; + // TLS allocation will be done in userspace by the loader + m_master_tls_region = nullptr; + m_master_tls_size = 0; + m_master_tls_alignment = 0; - MM.enter_process_paging_scope(*this); - RefPtr<ELF::Loader> loader; - { - ArmedScopeGuard rollback_regions_guard([&]() { - ASSERT(Process::current() == this); - // Need to make sure we don't swap contexts in the middle - ScopedCritical critical; - m_page_directory = move(old_page_directory); - m_regions = move(old_regions); - MM.enter_process_paging_scope(*this); - }); - loader = ELF::Loader::create(region->vaddr().as_ptr(), loader_metadata.size); - // Load the correct executable -- either interp or main program. - // FIXME: Once we actually load both interp and main, we'll need to be more clever about this. - // In that case, both will be ET_DYN objects, so they'll both be completely relocatable. - // That means, we can put them literally anywhere in User VM space (ASLR anyone?). - // ALSO FIXME: Reminder to really really fix that 'totally random offset' business. - loader->map_section_hook = [&](VirtualAddress vaddr, size_t size, size_t alignment, size_t offset_in_image, bool is_readable, bool is_writable, bool is_executable, const String& name) -> u8* { - ASSERT(size); - ASSERT(alignment == PAGE_SIZE); - int prot = 0; - if (is_readable) - prot |= PROT_READ; - if (is_writable) - prot |= PROT_WRITE; - if (is_executable) - prot |= PROT_EXEC; - if (auto* region = allocate_region_with_vmobject(vaddr.offset(m_load_offset), size, *vmobject, offset_in_image, String(name), prot)) { - region->set_shared(true); - return region->vaddr().as_ptr(); - } - return nullptr; - }; - loader->alloc_section_hook = [&](VirtualAddress vaddr, size_t size, size_t alignment, bool is_readable, bool is_writable, const String& name) -> u8* { - ASSERT(size); - ASSERT(alignment == PAGE_SIZE); - int prot = 0; - if (is_readable) - prot |= PROT_READ; - if (is_writable) - prot |= PROT_WRITE; - if (auto* region = allocate_region(vaddr.offset(m_load_offset), size, String(name), prot)) - return region->vaddr().as_ptr(); - return nullptr; - }; + rollback_regions_guard.disarm(); + return 0; +} - // FIXME: Move TLS region allocation to userspace: LibC and the dynamic loader. - // LibC if we end up with a statically linked executable, and the - // dynamic loader so that it can create new TLS blocks for each shared library - // that gets loaded as part of DT_NEEDED processing, and via dlopen() - // If that doesn't happen quickly, at least pass the location of the TLS region - // some ELF Auxiliary Vector so the loader can use it/create new ones as necessary. - loader->tls_section_hook = [&](size_t size, size_t alignment) { - ASSERT(size); - master_tls_region = allocate_region({}, size, String(), PROT_READ | PROT_WRITE); - master_tls_size = size; - master_tls_alignment = alignment; - return master_tls_region->vaddr().as_ptr(); - }; - ASSERT(!Processor::current().in_critical()); - bool success = loader->load(); - if (!success) { - klog() << "do_exec: Failure loading " << path.characters(); - return -ENOEXEC; - } - // FIXME: Validate that this virtual address is within executable region, - // instead of just non-null. You could totally have a DSO with entry point of - // the beginning of the text segment. - if (!loader->entry().offset(m_load_offset).get()) { - klog() << "do_exec: Failure loading " << path.characters() << ", entry pointer is invalid! (" << loader->entry().offset(m_load_offset) << ")"; - return -ENOEXEC; - } +int Process::do_exec(NonnullRefPtr<FileDescription> main_program_description, Vector<String> arguments, Vector<String> environment, RefPtr<FileDescription> interpreter_description, Thread*& new_main_thread, u32& prev_flags) +{ + ASSERT(is_user_process()); + ASSERT(!Processor::current().in_critical()); + auto path = main_program_description->absolute_path(); +#ifdef EXEC_DEBUG + dbg() << "do_exec(" << path << ")"; +#endif - rollback_regions_guard.disarm(); + // FIXME: How much stack space does process startup need? + if (!validate_stack_size(arguments, environment)) + return -E2BIG; + + auto parts = path.split('/'); + if (parts.is_empty()) + return -ENOENT; - // NOTE: At this point, we've committed to the new executable. - m_entry_eip = loader->entry().offset(m_load_offset).get(); + // Disable profiling temporarily in case it's running on this process. + bool was_profiling = is_profiling(); + TemporaryChange profiling_disabler(m_profiling, false); - kill_threads_except_self(); + // Mark this thread as the current thread that does exec + // No other thread from this process will be scheduled to run + auto current_thread = Thread::current(); + m_exec_tid = current_thread->tid(); -#ifdef EXEC_DEBUG - klog() << "Memory layout after ELF load:"; - dump_regions(); +#ifdef MM_DEBUG + dbg() << "Process " << pid().value() << " exec: PD=" << m_page_directory.ptr() << " created"; #endif + + int load_rc = load(main_program_description, interpreter_description); + if (load_rc) { + klog() << "do_exec: Failed to load main program or interpreter"; + return load_rc; } + kill_threads_except_self(); + +#ifdef EXEC_DEBUG + klog() << "Memory layout after ELF load:"; + dump_regions(); +#endif + m_executable = main_program_description->custody(); m_promises = m_execpromises; @@ -222,10 +254,6 @@ int Process::do_exec(NonnullRefPtr<FileDescription> main_program_description, Ve m_veil_state = VeilState::None; m_unveiled_paths.clear(); - // Copy of the master TLS region that we will clone for new threads - // FIXME: Handle this in userspace - m_master_tls_region = master_tls_region ? master_tls_region->make_weak_ptr() : nullptr; - auto main_program_metadata = main_program_description->metadata(); if (!(main_program_description->custody()->mount_flags() & MS_NOSUID)) { @@ -250,6 +278,14 @@ int Process::do_exec(NonnullRefPtr<FileDescription> main_program_description, Ve description_and_flags = {}; } + if (interpreter_description) { + m_main_program_fd = alloc_fd(); + ASSERT(m_main_program_fd >= 0); + main_program_description->seek(0, SEEK_SET); + main_program_description->set_readable(true); + m_fds[m_main_program_fd].set(move(main_program_description), FD_CLOEXEC); + } + new_main_thread = nullptr; if (¤t_thread->process() == this) { new_main_thread = current_thread; @@ -283,9 +319,6 @@ int Process::do_exec(NonnullRefPtr<FileDescription> main_program_description, Ve m_name = parts.take_last(); new_main_thread->set_name(m_name); - m_master_tls_size = master_tls_size; - m_master_tls_alignment = master_tls_alignment; - // FIXME: PID/TID ISSUE m_pid = new_main_thread->tid().value(); auto tsr_result = new_main_thread->make_thread_specific_region({}); @@ -325,7 +358,7 @@ Vector<AuxiliaryValue> Process::generate_auxiliary_vector() const // PH* auxv.append({ AuxiliaryValue::PageSize, PAGE_SIZE }); auxv.append({ AuxiliaryValue::BaseAddress, (void*)m_load_offset }); - // FLAGS + auxv.append({ AuxiliaryValue::Entry, (void*)m_entry_eip }); // NOTELF auxv.append({ AuxiliaryValue::Uid, (long)m_uid }); @@ -350,6 +383,8 @@ Vector<AuxiliaryValue> Process::generate_auxiliary_vector() const auxv.append({ AuxiliaryValue::ExecFilename, m_executable->absolute_path() }); + auxv.append({ AuxiliaryValue::ExecFileDescriptor, m_main_program_fd }); + auxv.append({ AuxiliaryValue::Null, 0L }); return auxv; } @@ -451,10 +486,12 @@ KResultOr<NonnullRefPtr<FileDescription>> Process::find_elf_interpreter_for_exec return KResult(-ENOEXEC); } - if (!interpreter_interpreter_path.is_empty()) { - dbg() << "exec(" << path << "): Interpreter (" << interpreter_description->absolute_path() << ") has its own interpreter (" << interpreter_interpreter_path << ")! No thank you!"; - return KResult(-ELOOP); - } + // FIXME: Uncomment this + // How do we get gcc to not insert an interpreter section to /usr/lib/Loader.so itself? + // if (!interpreter_interpreter_path.is_empty()) { + // dbg() << "exec(" << path << "): Interpreter (" << interpreter_description->absolute_path() << ") has its own interpreter (" << interpreter_interpreter_path << ")! No thank you!"; + // return KResult(-ELOOP); + // } return interpreter_description; } @@ -608,4 +645,5 @@ int Process::sys$execve(Userspace<const Syscall::SC_execve_params*> user_params) ASSERT(rc < 0); // We should never continue after a successful exec! return rc; } + } diff --git a/Kernel/Thread.cpp b/Kernel/Thread.cpp index 969fee8c49..b52d545d58 100644 --- a/Kernel/Thread.cpp +++ b/Kernel/Thread.cpp @@ -1105,6 +1105,10 @@ Vector<FlatPtr> Thread::raw_backtrace(FlatPtr ebp, FlatPtr eip) const KResult Thread::make_thread_specific_region(Badge<Process>) { + // The process may not require a TLS region + if (!process().m_master_tls_region) + return KSuccess; + size_t thread_specific_region_alignment = max(process().m_master_tls_alignment, alignof(ThreadSpecificData)); m_thread_specific_region_size = align_up_to(process().m_master_tls_size, thread_specific_region_alignment) + sizeof(ThreadSpecificData); auto* region = process().allocate_region({}, m_thread_specific_region_size, "Thread-specific", PROT_READ | PROT_WRITE, true); diff --git a/Libraries/LibELF/AuxiliaryVector.h b/Libraries/LibELF/AuxiliaryVector.h index 2acfb993bb..b47731c58d 100644 --- a/Libraries/LibELF/AuxiliaryVector.h +++ b/Libraries/LibELF/AuxiliaryVector.h @@ -46,7 +46,7 @@ typedef struct #define AT_PAGESZ 6 /* a_val gives system page size in bytes */ #define AT_BASE 7 /* a_ptr holds base address that Loader was loaded into memory */ #define AT_FLAGS 8 /* a_val holds 1 bit flags. Undefined flags are 0 */ -#define AT_ENTRY 9 /* a_ptr holds entry point of application for loader */ +#define AT_ENTRY 9 /* a_ptr holds entry point of the main program */ #define AT_NOTELF 10 /* a_val non-zero if the program is not ELF */ #define AT_UID 11 /* a_val holds real user id of process */ #define AT_EUID 12 /* a_val holds effective user id of process */ @@ -60,6 +60,8 @@ typedef struct #define AT_RANDOM 25 /* a_ptr points to 16 securely generated random bytes */ #define AT_HWCAP2 26 /* a_val holds extended hw feature mask. Currently 0 */ #define AT_EXECFN 31 /* a_ptr points to file name of executed program */ +#define AT_EXE_BASE 32 /* a_ptr holds base address where main program was loaded into memory */ +#define AT_EXE_SIZE 33 /* a_val holds the size of the main program in memory */ #ifdef __cplusplus # include <AK/String.h> @@ -89,7 +91,9 @@ struct AuxiliaryValue { BasePlatform = AT_BASE_PLATFORM, Random = AT_RANDOM, HwCap2 = AT_HWCAP2, - ExecFilename = AT_EXECFN + ExecFilename = AT_EXECFN, + ExeBaseAddress = AT_EXE_BASE, + ExeSize = AT_EXE_SIZE }; AuxiliaryValue(Type type, long val) diff --git a/Libraries/LibELF/Loader.cpp b/Libraries/LibELF/Loader.cpp index 8f7ccc722f..35b787d662 100644 --- a/Libraries/LibELF/Loader.cpp +++ b/Libraries/LibELF/Loader.cpp @@ -40,8 +40,9 @@ namespace ELF { -Loader::Loader(const u8* buffer, size_t size, bool verbose_logging) +Loader::Loader(const u8* buffer, size_t size, String&& name, bool verbose_logging) : m_image(buffer, size, verbose_logging) + , m_name(move(name)) { if (m_image.is_valid()) m_symbol_count = m_image.symbol_count(); @@ -101,7 +102,7 @@ bool Loader::layout() program_header.alignment(), program_header.is_readable(), program_header.is_writable(), - String::format("elf-alloc-%s%s", program_header.is_readable() ? "r" : "", program_header.is_writable() ? "w" : "")); + String::format("%s-alloc-%s%s", m_name.is_empty() ? "elf" : m_name.characters(), program_header.is_readable() ? "r" : "", program_header.is_writable() ? "w" : "")); if (!allocated_section) { failed = true; return; @@ -133,7 +134,7 @@ bool Loader::layout() program_header.is_readable(), program_header.is_writable(), program_header.is_executable(), - String::format("elf-map-%s%s%s", program_header.is_readable() ? "r" : "", program_header.is_writable() ? "w" : "", program_header.is_executable() ? "x" : "")); + String::format("%s-map-%s%s%s", m_name.is_empty() ? "elf" : m_name.characters(), program_header.is_readable() ? "r" : "", program_header.is_writable() ? "w" : "", program_header.is_executable() ? "x" : "")); if (!mapped_section) { failed = true; } diff --git a/Libraries/LibELF/Loader.h b/Libraries/LibELF/Loader.h index 7a0b9c82d8..adb3b0b17c 100644 --- a/Libraries/LibELF/Loader.h +++ b/Libraries/LibELF/Loader.h @@ -45,7 +45,7 @@ namespace ELF { class Loader : public RefCounted<Loader> { public: - static NonnullRefPtr<Loader> create(const u8* data, size_t size, bool verbose_logging = true) { return adopt(*new Loader(data, size, verbose_logging)); } + static NonnullRefPtr<Loader> create(const u8* data, size_t size, String&& name = String::empty(), bool verbose_logging = true) { return adopt(*new Loader(data, size, move(name), verbose_logging)); } ~Loader(); bool load(); @@ -67,11 +67,12 @@ public: Optional<Image::Symbol> find_symbol(u32 address, u32* offset = nullptr) const; private: - explicit Loader(const u8*, size_t, bool verbose_logging); + explicit Loader(const u8*, size_t, String&& name, bool verbose_logging); bool layout(); Image m_image; + String m_name; size_t m_symbol_count { 0 }; |