Kernel: Rework kernel-side ELF loading

ELFs are now loaded as MemoryRegions so they don't need special handling
anywhere. This also allows file backed COW optimizations to work. This
was not the case before.

This patch removes now obsolete LoadableELF and unused ELF files from
LibElf.
This commit is contained in:
Bananymous 2024-09-15 23:20:32 +03:00
parent 54732edff4
commit edc69ab2cd
9 changed files with 300 additions and 1043 deletions

View File

@ -22,6 +22,7 @@ set(KERNEL_SOURCES
kernel/Device/NullDevice.cpp
kernel/Device/RandomDevice.cpp
kernel/Device/ZeroDevice.cpp
kernel/ELF.cpp
kernel/Errors.cpp
kernel/FS/DevFS/FileSystem.cpp
kernel/FS/Ext2/FileSystem.cpp
@ -151,10 +152,6 @@ set(KLIBC_SOURCES
klibc/string.cpp
)
set(LIBELF_SOURCES
../userspace/libraries/LibELF/LibELF/LoadableELF.cpp
)
set(LIBFONT_SOURCES
../userspace/libraries/LibFont/Font.cpp
../userspace/libraries/LibFont/PSF.cpp
@ -169,7 +166,6 @@ set(KERNEL_SOURCES
${KERNEL_SOURCES}
${BAN_SOURCES}
${KLIBC_SOURCES}
${LIBELF_SOURCES}
${LIBFONT_SOURCES}
${LIBINPUT_SOURCE}
)

View File

@ -0,0 +1,18 @@
#pragma once
#include <kernel/FS/Inode.h>
#include <kernel/Memory/MemoryRegion.h>
namespace Kernel::ELF
{
struct LoadResult
{
bool has_interpreter;
vaddr_t entry_point;
BAN::Vector<BAN::UniqPtr<MemoryRegion>> regions;
};
BAN::ErrorOr<LoadResult> load_from_inode(BAN::RefPtr<Inode>, const Credentials&, PageTable&);
}

View File

@ -22,8 +22,6 @@
#include <sys/time.h>
#include <termios.h>
namespace LibELF { class LoadableELF; }
namespace Kernel
{
@ -269,7 +267,6 @@ namespace Kernel
OpenFileDescriptorSet m_open_file_descriptors;
BAN::UniqPtr<LibELF::LoadableELF> m_loadable_elf;
BAN::Vector<BAN::UniqPtr<MemoryRegion>> m_mapped_regions;
pid_t m_sid;

253
kernel/kernel/ELF.cpp Normal file
View File

@ -0,0 +1,253 @@
#include <kernel/ELF.h>
#include <kernel/FS/VirtualFileSystem.h>
#include <kernel/Memory/FileBackedRegion.h>
#include <kernel/Memory/MemoryBackedRegion.h>
#include <LibELF/Types.h>
#include <LibELF/Values.h>
#include <ctype.h>
#include <fcntl.h>
namespace Kernel::ELF
{
using namespace LibELF;
static BAN::ErrorOr<ElfNativeFileHeader> read_and_validate_file_header(BAN::RefPtr<Inode> inode)
{
if ((size_t)inode->size() < sizeof(ElfNativeFileHeader))
{
dprintln("File is too small to be ELF");
return BAN::Error::from_errno(ENOEXEC);
}
ElfNativeFileHeader file_header;
size_t nread = TRY(inode->read(0, BAN::ByteSpan::from(file_header)));
ASSERT(nread == sizeof(file_header));
if (file_header.e_ident[EI_MAG0] != ELFMAG0 ||
file_header.e_ident[EI_MAG1] != ELFMAG1 ||
file_header.e_ident[EI_MAG2] != ELFMAG2 ||
file_header.e_ident[EI_MAG3] != ELFMAG3)
{
dprintln("Not an ELF file");
return BAN::Error::from_errno(ENOEXEC);
}
if (file_header.e_ident[EI_DATA] != ELFDATA2LSB)
{
dprintln("Not in little-endian");
return BAN::Error::from_errno(ENOEXEC);
}
if (file_header.e_ident[EI_VERSION] != EV_CURRENT)
{
dprintln("Unsupported version {}", file_header.e_ident[EI_VERSION]);
return BAN::Error::from_errno(ENOEXEC);
}
#if ARCH(i686)
if (file_header.e_ident[EI_CLASS] != ELFCLASS32)
#elif ARCH(x86_64)
if (file_header.e_ident[EI_CLASS] != ELFCLASS64)
#endif
{
dprintln("Not in native format");
return BAN::Error::from_errno(EINVAL);
}
if (file_header.e_type != ET_EXEC && file_header.e_type != ET_DYN)
{
dprintln("Unsupported file header type {}", file_header.e_type);
return BAN::Error::from_errno(ENOTSUP);
}
if (file_header.e_version != EV_CURRENT)
{
dprintln("Unsupported version {}", file_header.e_version);
return BAN::Error::from_errno(EINVAL);
}
if (file_header.e_phentsize < sizeof(ElfNativeProgramHeader))
{
dprintln("Too small program header size ({} bytes)", file_header.e_phentsize);
return BAN::Error::from_errno(EINVAL);
}
return file_header;
}
static BAN::ErrorOr<BAN::Vector<ElfNativeProgramHeader>> read_program_headers(BAN::RefPtr<Inode> inode, const ElfNativeFileHeader& file_header)
{
BAN::Vector<uint8_t> program_header_buffer;
TRY(program_header_buffer.resize(file_header.e_phnum * file_header.e_phentsize));
TRY(inode->read(file_header.e_phoff, BAN::ByteSpan(program_header_buffer.span())));
BAN::Vector<ElfNativeProgramHeader> program_headers;
TRY(program_headers.reserve(file_header.e_phnum));
for (size_t i = 0; i < file_header.e_phnum; i++)
{
const auto& pheader = *reinterpret_cast<ElfNativeProgramHeader*>(program_header_buffer.data() + i * file_header.e_phentsize);
if (pheader.p_memsz < pheader.p_filesz)
{
dprintln("Invalid program header, memsz less than filesz");
return BAN::Error::from_errno(EINVAL);
}
MUST(program_headers.emplace_back(pheader));
}
return BAN::move(program_headers);
}
BAN::ErrorOr<LoadResult> load_from_inode(BAN::RefPtr<Inode> inode, const Credentials& credentials, PageTable& page_table)
{
auto file_header = TRY(read_and_validate_file_header(inode));
auto program_headers = TRY(read_program_headers(inode, file_header));
vaddr_t executable_end = 0;
BAN::String interpreter;
for (const auto& program_header : program_headers)
{
if (program_header.p_type == PT_LOAD)
executable_end = BAN::Math::max<vaddr_t>(executable_end, program_header.p_vaddr + program_header.p_memsz);
else if (program_header.p_type == PT_INTERP)
{
BAN::Vector<uint8_t> interp_buffer;
TRY(interp_buffer.resize(program_header.p_memsz, 0));
TRY(inode->read(program_header.p_offset, BAN::ByteSpan(interp_buffer.data(), program_header.p_filesz)));
if (interp_buffer.empty() || interp_buffer.front() != '/' || interp_buffer.back() != '\0')
{
dprintln("ELF interpreter is not an valid absolute path");
return BAN::Error::from_errno(EINVAL);
}
auto interpreter_sv = BAN::StringView(reinterpret_cast<const char*>(interp_buffer.data()), interp_buffer.size() - 1);
for (char ch : interpreter_sv)
{
if (isprint(ch))
continue;
dprintln("ELF interpreter name contains non-printable characters");
return BAN::Error::from_errno(EINVAL);
}
interpreter.clear();
TRY(interpreter.append(interpreter_sv));
}
}
if (file_header.e_type == ET_DYN)
executable_end = 0x400000;
if (!interpreter.empty())
{
auto interpreter_inode = TRY(VirtualFileSystem::get().file_from_absolute_path(credentials, interpreter, O_EXEC)).inode;
auto interpreter_file_header = TRY(read_and_validate_file_header(interpreter_inode));
auto interpreter_program_headers = TRY(read_program_headers(interpreter_inode, interpreter_file_header));
for (const auto& program_header : interpreter_program_headers)
{
if (program_header.p_type == PT_INTERP)
{
dprintln("ELF interpreter has an interpreter specified");
return BAN::Error::from_errno(EINVAL);
}
}
inode = interpreter_inode;
file_header = interpreter_file_header;
program_headers = BAN::move(interpreter_program_headers);
}
const vaddr_t load_base_vaddr =
[&file_header, executable_end]() -> vaddr_t
{
if (file_header.e_type == ET_EXEC)
return 0;
if (file_header.e_type == ET_DYN)
return (executable_end + PAGE_SIZE - 1) & PAGE_ADDR_MASK;
ASSERT_NOT_REACHED();
}();
BAN::Vector<BAN::UniqPtr<MemoryRegion>> memory_regions;
for (const auto& program_header : program_headers)
{
if (program_header.p_type != PT_LOAD)
continue;
const PageTable::flags_t flags =
[&program_header]() -> int
{
PageTable::flags_t result = PageTable::Flags::UserSupervisor;
if (program_header.p_flags & PF_R)
result |= PageTable::Flags::Present;
if (program_header.p_flags & PF_W)
result |= PageTable::Flags::ReadWrite;
if (program_header.p_flags & PF_X)
result |= PageTable::Flags::Execute;
return result;
}();
const size_t file_backed_size =
[&program_header]() -> size_t
{
if ((program_header.p_vaddr & 0xFFF) || (program_header.p_offset & 0xFFF))
return 0;
if (program_header.p_filesz == program_header.p_memsz)
return program_header.p_filesz;
return program_header.p_filesz & ~(uintptr_t)0xFFF;
}();
const vaddr_t pheader_base = load_base_vaddr + program_header.p_vaddr;
if (file_backed_size)
{
auto region = TRY(FileBackedRegion::create(
inode,
page_table,
program_header.p_offset,
file_backed_size,
{ .start = pheader_base, .end = pheader_base + file_backed_size },
MemoryRegion::Type::PRIVATE,
flags
));
TRY(memory_regions.emplace_back(BAN::move(region)));
}
if (file_backed_size < program_header.p_memsz)
{
const vaddr_t aligned_vaddr = pheader_base & PAGE_ADDR_MASK;
auto region = TRY(MemoryBackedRegion::create(
page_table,
(pheader_base + program_header.p_memsz) - (aligned_vaddr + file_backed_size),
{ .start = aligned_vaddr + file_backed_size, .end = pheader_base + program_header.p_memsz },
MemoryRegion::Type::PRIVATE,
flags
));
if (file_backed_size < program_header.p_filesz)
{
BAN::Vector<uint8_t> file_data_buffer;
TRY(file_data_buffer.resize(program_header.p_filesz - file_backed_size));
if (TRY(inode->read(program_header.p_offset + file_backed_size, file_data_buffer.span())) != file_data_buffer.size())
return BAN::Error::from_errno(EFAULT);
TRY(region->copy_data_to_region(pheader_base - aligned_vaddr, file_data_buffer.data(), file_data_buffer.size()));
}
TRY(memory_regions.emplace_back(BAN::move(region)));
}
}
LoadResult result;
result.has_interpreter = !interpreter.empty();
result.entry_point = load_base_vaddr + file_header.e_entry;
result.regions = BAN::move(memory_regions);
return BAN::move(result);
}
}

View File

@ -1,6 +1,7 @@
#include <BAN/ScopeGuard.h>
#include <BAN/StringView.h>
#include <kernel/ACPI/ACPI.h>
#include <kernel/ELF.h>
#include <kernel/FS/DevFS/FileSystem.h>
#include <kernel/FS/ProcFS/FileSystem.h>
#include <kernel/FS/VirtualFileSystem.h>
@ -121,13 +122,8 @@ namespace Kernel
auto absolute_path = TRY(process->absolute_path_of(path));
auto executable_inode = TRY(VirtualFileSystem::get().file_from_absolute_path(process->m_credentials, absolute_path, O_EXEC)).inode;
process->m_loadable_elf = TRY(LibELF::LoadableELF::load_from_inode(process->page_table(), process->m_credentials, executable_inode));
if (!process->m_loadable_elf->is_address_space_free())
{
dprintln("Could not load ELF address space");
return BAN::Error::from_errno(ENOEXEC);
}
process->m_loadable_elf->reserve_address_space();
auto executable = TRY(ELF::load_from_inode(executable_inode, process->m_credentials, process->page_table()));
process->m_mapped_regions = BAN::move(executable.regions);
char** argv = nullptr;
{
@ -154,8 +150,21 @@ namespace Kernel
MUST(process->m_mapped_regions.push_back(BAN::move(argv_region)));
}
if (executable_inode->mode().mode & +Inode::Mode::ISUID)
process->m_credentials.set_euid(executable_inode->uid());
if (executable_inode->mode().mode & +Inode::Mode::ISGID)
process->m_credentials.set_egid(executable_inode->gid());
if (executable.has_interpreter)
{
VirtualFileSystem::File file;
TRY(file.canonical_path.append("<self>"));
file.inode = executable_inode;
process->m_userspace_info.file_fd = TRY(process->m_open_file_descriptors.open(BAN::move(file), O_RDONLY));
}
process->m_is_userspace = true;
process->m_userspace_info.entry = process->m_loadable_elf->entry_point();
process->m_userspace_info.entry = executable.entry_point;
process->m_userspace_info.argc = 1;
process->m_userspace_info.argv = argv;
process->m_userspace_info.envp = nullptr;
@ -185,7 +194,6 @@ namespace Kernel
{
ASSERT(m_threads.empty());
ASSERT(m_mapped_regions.empty());
ASSERT(!m_loadable_elf);
ASSERT(&PageTable::current() != m_page_table.ptr());
}
@ -216,7 +224,6 @@ namespace Kernel
// NOTE: We must unmap ranges while the page table is still alive
m_mapped_regions.clear();
m_loadable_elf.clear();
}
bool Process::on_thread_exit(Thread& thread)
@ -302,11 +309,6 @@ namespace Kernel
meminfo.virt_pages += region->virtual_page_count();
meminfo.phys_pages += region->physical_page_count();
}
if (m_loadable_elf)
{
meminfo.virt_pages += m_loadable_elf->virtual_page_count();
meminfo.phys_pages += m_loadable_elf->physical_page_count();
}
}
size_t bytes = BAN::Math::min<size_t>(sizeof(proc_meminfo_t) - offset, buffer.size());
@ -424,15 +426,12 @@ namespace Kernel
for (auto& mapped_region : m_mapped_regions)
MUST(mapped_regions.push_back(TRY(mapped_region->clone(*page_table))));
auto loadable_elf = TRY(m_loadable_elf->clone(*page_table));
Process* forked = create_process(m_credentials, m_pid, m_sid, m_pgrp);
forked->m_controlling_terminal = m_controlling_terminal;
forked->m_working_directory = BAN::move(working_directory);
forked->m_page_table = BAN::move(page_table);
forked->m_open_file_descriptors = BAN::move(open_file_descriptors);
forked->m_mapped_regions = BAN::move(mapped_regions);
forked->m_loadable_elf = BAN::move(loadable_elf);
forked->m_is_userspace = m_is_userspace;
forked->m_userspace_info = m_userspace_info;
forked->m_has_called_exec = false;
@ -461,7 +460,6 @@ namespace Kernel
auto absolute_path = TRY(absolute_path_of(path));
auto executable_inode = TRY(VirtualFileSystem::get().file_from_absolute_path(m_credentials, absolute_path, O_EXEC)).inode;
auto loadable_elf = TRY(LibELF::LoadableELF::load_from_inode(page_table(), m_credentials, executable_inode));
BAN::Vector<BAN::String> str_argv;
for (int i = 0; argv && argv[i]; i++)
@ -479,29 +477,24 @@ namespace Kernel
TRY(str_envp.emplace_back(envp[i]));
}
BAN::String executable_path;
TRY(executable_path.append(path));
m_open_file_descriptors.close_cloexec();
m_mapped_regions.clear();
m_loadable_elf = BAN::move(loadable_elf);
if (!m_loadable_elf->is_address_space_free())
{
dprintln("ELF has unloadable address space");
MUST(sys_kill(pid(), SIGKILL));
// NOTE: signal will only execute after return from syscall
return BAN::Error::from_errno(EINTR);
}
m_loadable_elf->reserve_address_space();
m_loadable_elf->update_suid_sgid(m_credentials);
m_userspace_info.entry = m_loadable_elf->entry_point();
if (m_loadable_elf->has_interpreter())
auto executable = TRY(ELF::load_from_inode(executable_inode, m_credentials, page_table()));
m_mapped_regions = BAN::move(executable.regions);
if (executable_inode->mode().mode & +Inode::Mode::ISUID)
m_credentials.set_euid(executable_inode->uid());
if (executable_inode->mode().mode & +Inode::Mode::ISGID)
m_credentials.set_egid(executable_inode->gid());
m_userspace_info.entry = executable.entry_point;
if (executable.has_interpreter)
{
VirtualFileSystem::File file;
TRY(file.canonical_path.append("<self>"));
file.inode = m_loadable_elf->executable();
file.inode = executable_inode;
m_userspace_info.file_fd = TRY(m_open_file_descriptors.open(BAN::move(file), O_RDONLY));
}
@ -845,12 +838,6 @@ namespace Kernel
return true;
}
if (m_loadable_elf && m_loadable_elf->contains(address))
{
TRY(m_loadable_elf->load_page_to_memory(address));
return true;
}
return false;
}
@ -2387,10 +2374,6 @@ namespace Kernel
return {};
}
// FIXME: elf should use MemoryRegions instead of mapping executables itself
if (m_loadable_elf->contains(vaddr))
return {};
unauthorized_access:
dwarnln("process {}, thread {} attempted to make an invalid pointer access to 0x{H}->0x{H}", pid(), Thread::current().tid(), vaddr, vaddr + size);
Debug::dump_stack_trace();

View File

@ -1,405 +0,0 @@
#include <BAN/ScopeGuard.h>
#include <LibELF/ELF.h>
#include <LibELF/Values.h>
#ifdef __is_kernel
#include <kernel/FS/VirtualFileSystem.h>
#include <kernel/Process.h>
#endif
#include <fcntl.h>
#define ELF_PRINT_HEADERS 0
#ifdef __is_kernel
extern uint8_t g_kernel_end[];
using namespace Kernel;
#endif
namespace LibELF
{
#ifdef __is_kernel
BAN::ErrorOr<BAN::UniqPtr<ELF>> ELF::load_from_file(BAN::RefPtr<Inode> inode)
{
BAN::Vector<uint8_t> buffer;
TRY(buffer.resize(inode->size()));
TRY(inode->read(0, buffer.data(), inode->size()));
ELF* elf_ptr = new ELF(BAN::move(buffer));
if (elf_ptr == nullptr)
return BAN::Error::from_errno(ENOMEM);
auto elf = BAN::UniqPtr<ELF>::adopt(elf_ptr);
TRY(elf->load());
return BAN::move(elf);
}
#else
BAN::ErrorOr<ELF*> ELF::load_from_file(BAN::StringView file_path)
{
ELF* elf = nullptr;
{
BAN::Vector<uint8_t> data;
int fd = TRY(Kernel::Process::current().open(file_path, O_RDONLY));
BAN::ScopeGuard _([fd] { MUST(Kernel::Process::current().close(fd)); });
struct stat st;
TRY(Kernel::Process::current().fstat(fd, &st));
TRY(data.resize(st.st_size));
TRY(Kernel::Process::current().read(fd, data.data(), data.size()));
elf = new ELF(BAN::move(data));
ASSERT(elf);
}
if (auto res = elf->load(); res.is_error())
{
delete elf;
return res.error();
}
return elf;
}
#endif
BAN::ErrorOr<void> ELF::load()
{
if (m_data.size() < EI_NIDENT)
{
dprintln("Too small ELF file");
return BAN::Error::from_errno(EINVAL);
}
if (m_data[EI_MAG0] != ELFMAG0 ||
m_data[EI_MAG1] != ELFMAG1 ||
m_data[EI_MAG2] != ELFMAG2 ||
m_data[EI_MAG3] != ELFMAG3)
{
dprintln("Invalid ELF header");
return BAN::Error::from_errno(EINVAL);
}
if (m_data[EI_DATA] != ELFDATA2LSB)
{
dprintln("Only little-endian is supported");
return BAN::Error::from_errno(EINVAL);
}
if (m_data[EI_VERSION] != EV_CURRENT)
{
dprintln("Invalid ELF version");
return BAN::Error::from_errno(EINVAL);
}
if (m_data[EI_CLASS] == ELFCLASS64)
{
if (m_data.size() <= sizeof(Elf64FileHeader))
{
dprintln("Too small ELF file");
return BAN::Error::from_errno(EINVAL);
}
auto& header = file_header64();
if (!parse_elf64_file_header(header))
return BAN::Error::from_errno(EINVAL);
for (size_t i = 0; i < header.e_phnum; i++)
{
auto& program_header = program_header64(i);
if (!parse_elf64_program_header(program_header))
return BAN::Error::from_errno(EINVAL);
}
for (size_t i = 1; i < header.e_shnum; i++)
{
auto& section_header = section_header64(i);
if (!parse_elf64_section_header(section_header))
return BAN::Error::from_errno(EINVAL);
}
}
else if (m_data[EI_CLASS] == ELFCLASS32)
{
if (m_data.size() <= sizeof(Elf32FileHeader))
{
dprintln("Too small ELF file");
return BAN::Error::from_errno(EINVAL);
}
auto& header = file_header32();
if (!parse_elf32_file_header(header))
return BAN::Error::from_errno(EINVAL);
for (size_t i = 0; i < header.e_phnum; i++)
{
auto& program_header = program_header32(i);
if (!parse_elf32_program_header(program_header))
return BAN::Error::from_errno(EINVAL);
}
for (size_t i = 1; i < header.e_shnum; i++)
{
auto& section_header = section_header32(i);
if (!parse_elf32_section_header(section_header))
return BAN::Error::from_errno(EINVAL);
}
}
return {};
}
bool ELF::is_x86_32() const { return m_data[EI_CLASS] == ELFCLASS32; }
bool ELF::is_x86_64() const { return m_data[EI_CLASS] == ELFCLASS64; }
/*
64 bit ELF
*/
const char* ELF::lookup_section_name64(uint32_t offset) const
{
return lookup_string64(file_header64().e_shstrndx, offset);
}
const char* ELF::lookup_string64(size_t table_index, uint32_t offset) const
{
if (table_index == SHN_UNDEF)
return nullptr;
auto& section_header = section_header64(table_index);
return (const char*)m_data.data() + section_header.sh_offset + offset;
}
bool ELF::parse_elf64_file_header(const Elf64FileHeader& header)
{
if (header.e_type != ET_EXEC)
{
dprintln("Only executable files are supported");
return false;
}
if (header.e_version != EV_CURRENT)
{
dprintln("Invalid ELF version");
return false;
}
return true;
}
bool ELF::parse_elf64_program_header(const Elf64ProgramHeader& header)
{
#if ELF_PRINT_HEADERS
dprintln("program header");
dprintln(" type {H}", header.p_type);
dprintln(" flags {H}", header.p_flags);
dprintln(" offset {H}", header.p_offset);
dprintln(" vaddr {H}", header.p_vaddr);
dprintln(" paddr {H}", header.p_paddr);
dprintln(" filesz {}", header.p_filesz);
dprintln(" memsz {}", header.p_memsz);
dprintln(" align {}", header.p_align);
#endif
(void)header;
return true;
}
bool ELF::parse_elf64_section_header(const Elf64SectionHeader& header)
{
#if ELF_PRINT_HEADERS
if (auto* name = lookup_section_name64(header.sh_name))
dprintln("{}", name);
switch (header.sh_type)
{
case SHT_NULL:
dprintln(" SHT_NULL");
break;
case SHT_PROGBITS:
dprintln(" SHT_PROGBITS");
break;
case SHT_SYMTAB:
for (size_t i = 1; i < header.sh_size / header.sh_entsize; i++)
{
auto& symbol = ((const Elf64Symbol*)(m_data.data() + header.sh_offset))[i];
if (auto* name = lookup_string64(header.sh_link, symbol.st_name))
dprintln(" {}", name);
}
break;
case SHT_STRTAB:
dprintln(" SHT_STRTAB");
break;
case SHT_RELA:
dprintln(" SHT_RELA");
break;
case SHT_NOBITS:
dprintln(" SHT_NOBITS");
break;
case SHT_REL:
dprintln(" SHT_REL");
break;
case SHT_SHLIB:
dprintln(" SHT_SHLIB");
break;
case SHT_DYNSYM:
dprintln(" SHT_DYNSYM");
break;
default:
ASSERT(false);
}
#endif
(void)header;
return true;
}
const Elf64FileHeader& ELF::file_header64() const
{
ASSERT(is_x86_64());
return *(const Elf64FileHeader*)m_data.data();
}
const Elf64ProgramHeader& ELF::program_header64(size_t index) const
{
ASSERT(is_x86_64());
const auto& file_header = file_header64();
ASSERT(index < file_header.e_phnum);
return *(const Elf64ProgramHeader*)(m_data.data() + file_header.e_phoff + file_header.e_phentsize * index);
}
const Elf64SectionHeader& ELF::section_header64(size_t index) const
{
ASSERT(is_x86_64());
const auto& file_header = file_header64();
ASSERT(index < file_header.e_shnum);
return *(const Elf64SectionHeader*)(m_data.data() + file_header.e_shoff + file_header.e_shentsize * index);
}
/*
32 bit ELF
*/
const char* ELF::lookup_section_name32(uint32_t offset) const
{
return lookup_string32(file_header32().e_shstrndx, offset);
}
const char* ELF::lookup_string32(size_t table_index, uint32_t offset) const
{
if (table_index == SHN_UNDEF)
return nullptr;
auto& section_header = section_header32(table_index);
return (const char*)m_data.data() + section_header.sh_offset + offset;
}
bool ELF::parse_elf32_file_header(const Elf32FileHeader& header)
{
if (header.e_type != ET_EXEC)
{
dprintln("Only executable files are supported");
return false;
}
if (header.e_version != EV_CURRENT)
{
dprintln("Invalid ELF version");
return false;
}
return true;
}
bool ELF::parse_elf32_program_header(const Elf32ProgramHeader& header)
{
#if ELF_PRINT_HEADERS
dprintln("program header");
dprintln(" type {H}", header.p_type);
dprintln(" flags {H}", header.p_flags);
dprintln(" offset {H}", header.p_offset);
dprintln(" vaddr {H}", header.p_vaddr);
dprintln(" paddr {H}", header.p_paddr);
dprintln(" filesz {}", header.p_filesz);
dprintln(" memsz {}", header.p_memsz);
dprintln(" align {}", header.p_align);
#endif
(void)header;
return true;
}
bool ELF::parse_elf32_section_header(const Elf32SectionHeader& header)
{
#if ELF_PRINT_HEADERS
if (auto* name = lookup_section_name32(header.sh_name))
dprintln("{}", name);
switch (header.sh_type)
{
case SHT_NULL:
dprintln(" SHT_NULL");
break;
case SHT_PROGBITS:
dprintln(" SHT_PROGBITS");
break;
case SHT_SYMTAB:
for (size_t i = 1; i < header.sh_size / header.sh_entsize; i++)
{
auto& symbol = ((const Elf32Symbol*)(m_data.data() + header.sh_offset))[i];
if (auto* name = lookup_string32(header.sh_link, symbol.st_name))
dprintln(" {}", name);
}
break;
case SHT_STRTAB:
dprintln(" SHT_STRTAB");
break;
case SHT_RELA:
dprintln(" SHT_RELA");
break;
case SHT_NOBITS:
dprintln(" SHT_NOBITS");
break;
case SHT_REL:
dprintln(" SHT_REL");
break;
case SHT_SHLIB:
dprintln(" SHT_SHLIB");
break;
case SHT_DYNSYM:
dprintln(" SHT_DYNSYM");
break;
default:
ASSERT(false);
}
#endif
(void)header;
return true;
}
const Elf32FileHeader& ELF::file_header32() const
{
ASSERT(is_x86_32());
return *(const Elf32FileHeader*)m_data.data();
}
const Elf32ProgramHeader& ELF::program_header32(size_t index) const
{
ASSERT(is_x86_32());
const auto& file_header = file_header32();
ASSERT(index < file_header.e_phnum);
return *(const Elf32ProgramHeader*)(m_data.data() + file_header.e_phoff + file_header.e_phentsize * index);
}
const Elf32SectionHeader& ELF::section_header32(size_t index) const
{
ASSERT(is_x86_32());
const auto& file_header = file_header32();
ASSERT(index < file_header.e_shnum);
return *(const Elf32SectionHeader*)(m_data.data() + file_header.e_shoff + file_header.e_shentsize * index);
}
}

View File

@ -1,422 +0,0 @@
#include <BAN/ScopeGuard.h>
#include <kernel/FS/VirtualFileSystem.h>
#include <kernel/Lock/LockGuard.h>
#include <kernel/Memory/Heap.h>
#include <kernel/Random.h>
#include <LibELF/LoadableELF.h>
#include <LibELF/Values.h>
#include <fcntl.h>
namespace LibELF
{
using namespace Kernel;
BAN::ErrorOr<BAN::UniqPtr<LoadableELF>> LoadableELF::load_from_inode(PageTable& page_table, const Credentials& credentials, BAN::RefPtr<Inode> inode)
{
auto elf = TRY(BAN::UniqPtr<LoadableELF>::create(page_table));
TRY(elf->initialize(credentials, inode));
return elf;
}
LoadableELF::LoadableELF(PageTable& page_table)
: m_page_table(page_table)
{
}
LoadableELF::~LoadableELF()
{
if (!m_is_loaded)
return;
for (const auto& header : m_program_headers)
{
ASSERT(header.p_type == PT_LOAD);
const vaddr_t vaddr = header.p_vaddr & PAGE_ADDR_MASK;
const size_t pages = range_page_count(header.p_vaddr, header.p_memsz);
for (size_t i = 0; i < pages; i++)
if (paddr_t paddr = m_page_table.physical_address_of(vaddr + i * PAGE_SIZE))
Heap::get().release_page(paddr);
m_page_table.unmap_range(vaddr, pages * PAGE_SIZE);
}
}
static BAN::ErrorOr<ElfNativeFileHeader> read_and_validate_file_header(BAN::RefPtr<Inode> inode)
{
if ((size_t)inode->size() < sizeof(ElfNativeFileHeader))
{
dprintln("File is too small to be ELF");
return BAN::Error::from_errno(ENOEXEC);
}
ElfNativeFileHeader file_header;
size_t nread = TRY(inode->read(0, BAN::ByteSpan::from(file_header)));
ASSERT(nread == sizeof(file_header));
if (file_header.e_ident[EI_MAG0] != ELFMAG0 ||
file_header.e_ident[EI_MAG1] != ELFMAG1 ||
file_header.e_ident[EI_MAG2] != ELFMAG2 ||
file_header.e_ident[EI_MAG3] != ELFMAG3)
{
dprintln("Not an ELF file");
return BAN::Error::from_errno(ENOEXEC);
}
if (file_header.e_ident[EI_DATA] != ELFDATA2LSB)
{
dprintln("Not in little-endian");
return BAN::Error::from_errno(ENOEXEC);
}
if (file_header.e_ident[EI_VERSION] != EV_CURRENT)
{
dprintln("Unsupported version {}", file_header.e_ident[EI_VERSION]);
return BAN::Error::from_errno(ENOEXEC);
}
#if ARCH(i686)
if (file_header.e_ident[EI_CLASS] != ELFCLASS32)
#elif ARCH(x86_64)
if (file_header.e_ident[EI_CLASS] != ELFCLASS64)
#endif
{
dprintln("Not in native format");
return BAN::Error::from_errno(EINVAL);
}
if (file_header.e_type != ET_EXEC && file_header.e_type != ET_DYN)
{
dprintln("Unsupported file header type {}", file_header.e_type);
return BAN::Error::from_errno(ENOTSUP);
}
if (file_header.e_version != EV_CURRENT)
{
dprintln("Unsupported version {}", file_header.e_version);
return BAN::Error::from_errno(EINVAL);
}
if (file_header.e_phentsize < sizeof(ElfNativeProgramHeader))
{
dprintln("Too small program header size ({} bytes)", file_header.e_phentsize);
return BAN::Error::from_errno(EINVAL);
}
return file_header;
}
BAN::ErrorOr<LoadableELF::LoadResult> LoadableELF::load_elf_file(const Credentials& credentials, BAN::RefPtr<Inode> inode) const
{
auto file_header = TRY(read_and_validate_file_header(inode));
BAN::Vector<uint8_t> pheader_buffer;
TRY(pheader_buffer.resize(file_header.e_phnum * file_header.e_phentsize));
TRY(inode->read(file_header.e_phoff, BAN::ByteSpan(pheader_buffer.span())));
BAN::Vector<ElfNativeProgramHeader> program_headers;
BAN::RefPtr<Inode> interp;
for (size_t i = 0; i < file_header.e_phnum; i++)
{
const auto& pheader = *reinterpret_cast<ElfNativeProgramHeader*>(pheader_buffer.data() + i * file_header.e_phentsize);
if (pheader.p_memsz < pheader.p_filesz)
{
dprintln("Invalid program header, memsz less than filesz");
return BAN::Error::from_errno(EINVAL);
}
switch (pheader.p_type)
{
case PT_LOAD:
for (const auto& program_header : program_headers)
{
const vaddr_t a1 = program_header.p_vaddr & PAGE_ADDR_MASK;
const vaddr_t b1 = pheader.p_vaddr & PAGE_ADDR_MASK;
const vaddr_t a2 = (program_header.p_vaddr + program_header.p_memsz + PAGE_SIZE - 1) & PAGE_ADDR_MASK;
const vaddr_t b2 = (pheader.p_vaddr + pheader.p_memsz + PAGE_SIZE - 1) & PAGE_ADDR_MASK;
if (a1 < b2 && b1 < a2)
{
dwarnln("Overlapping LOAD segments");
return BAN::Error::from_errno(EINVAL);
}
}
TRY(program_headers.push_back(pheader));
break;
case PT_INTERP:
{
BAN::Vector<uint8_t> buffer;
TRY(buffer.resize(pheader.p_memsz, 0));
TRY(inode->read(pheader.p_offset, BAN::ByteSpan(buffer.data(), pheader.p_filesz)));
BAN::StringView path(reinterpret_cast<const char*>(buffer.data()));
interp = TRY(VirtualFileSystem::get().file_from_absolute_path(credentials, path, O_EXEC)).inode;
break;
}
default:
break;
}
}
return LoadResult {
.inode = inode,
.interp = interp,
.file_header = file_header,
.program_headers = BAN::move(program_headers)
};
}
static bool do_program_headers_overlap(BAN::Span<const ElfNativeProgramHeader> pheaders1, BAN::Span<const ElfNativeProgramHeader> pheaders2, vaddr_t base2)
{
for (const auto& pheader1 : pheaders1)
{
for (const auto& pheader2 : pheaders2)
{
const vaddr_t s1 = pheader1.p_vaddr & PAGE_ADDR_MASK;
const vaddr_t e1 = (pheader1.p_vaddr + pheader1.p_memsz + PAGE_SIZE - 1) & PAGE_ADDR_MASK;
const vaddr_t s2 = pheader2.p_vaddr & PAGE_ADDR_MASK;
const vaddr_t e2 = (pheader2.p_vaddr + pheader2.p_memsz + PAGE_SIZE - 1) & PAGE_ADDR_MASK;
if (s1 < e2 + base2 && s2 + base2 < e1)
return true;
}
}
return false;
}
BAN::ErrorOr<void> LoadableELF::initialize(const Credentials& credentials, BAN::RefPtr<Inode> inode)
{
const auto generate_random_dynamic_base =
[]() -> vaddr_t
{
// 1 MiB -> 2 GiB + 1 MiB
return (Random::get_u32() & 0x7FFFF000) + 0x100000;
};
auto executable_load_result = TRY(load_elf_file(credentials, inode));
m_executable = executable_load_result.inode;
m_interpreter = executable_load_result.interp;
vaddr_t dynamic_base = 0;
if (m_interpreter)
{
auto interp_load_result = TRY(load_elf_file(credentials, m_interpreter));
if (interp_load_result.interp)
{
dwarnln("ELF interpreter has an interpreter");
return BAN::Error::from_errno(EINVAL);
}
if (executable_load_result.file_header.e_type == ET_EXEC)
{
if (interp_load_result.file_header.e_type == ET_EXEC)
{
const bool has_overlap = do_program_headers_overlap(
executable_load_result.program_headers.span(),
interp_load_result.program_headers.span(),
0
);
if (has_overlap)
{
dwarnln("Executable and interpreter LOAD segments overlap");
return BAN::Error::from_errno(EINVAL);
}
}
else
{
for (int attempt = 0; attempt < 100; attempt++)
{
const vaddr_t test_dynamic_base = generate_random_dynamic_base();
const bool has_overlap = do_program_headers_overlap(
executable_load_result.program_headers.span(),
interp_load_result.program_headers.span(),
test_dynamic_base
);
if (has_overlap)
continue;
dynamic_base = test_dynamic_base;
break;
}
if (dynamic_base == 0)
{
dwarnln("Could not find space to load interpreter");
return BAN::Error::from_errno(EINVAL);
}
}
}
m_file_header = interp_load_result.file_header;
m_program_headers = BAN::move(interp_load_result.program_headers);
}
else
{
m_file_header = executable_load_result.file_header;
m_program_headers = BAN::move(executable_load_result.program_headers);
}
if (m_file_header.e_type == ET_DYN && dynamic_base == 0)
dynamic_base = generate_random_dynamic_base();
if (dynamic_base)
{
m_file_header.e_entry += dynamic_base;
for (auto& program_header : m_program_headers)
program_header.p_vaddr += dynamic_base;
}
return {};
}
bool LoadableELF::contains(vaddr_t address) const
{
for (const auto& program_header : m_program_headers)
if (program_header.p_vaddr <= address && address < program_header.p_vaddr + program_header.p_memsz)
return true;
return false;
}
bool LoadableELF::is_address_space_free() const
{
for (const auto& program_header : m_program_headers)
{
ASSERT(program_header.p_type == PT_LOAD);
const vaddr_t page_vaddr = program_header.p_vaddr & PAGE_ADDR_MASK;
const size_t pages = range_page_count(program_header.p_vaddr, program_header.p_memsz);
if (!m_page_table.is_range_free(page_vaddr, pages * PAGE_SIZE))
return false;
}
return true;
}
void LoadableELF::reserve_address_space()
{
for (const auto& program_header : m_program_headers)
{
ASSERT(program_header.p_type == PT_LOAD);
const vaddr_t page_vaddr = program_header.p_vaddr & PAGE_ADDR_MASK;
const size_t pages = range_page_count(program_header.p_vaddr, program_header.p_memsz);
if (!m_page_table.reserve_range(page_vaddr, pages * PAGE_SIZE))
ASSERT_NOT_REACHED();
m_virtual_page_count += pages;
}
m_is_loaded = true;
}
void LoadableELF::update_suid_sgid(Kernel::Credentials& credentials)
{
if (m_executable->mode().mode & +Inode::Mode::ISUID)
credentials.set_euid(m_executable->uid());
if (m_executable->mode().mode & +Inode::Mode::ISGID)
credentials.set_egid(m_executable->gid());
}
BAN::ErrorOr<void> LoadableELF::load_page_to_memory(vaddr_t address)
{
auto inode = has_interpreter() ? m_interpreter : m_executable;
// FIXME: use MemoryBackedRegion/FileBackedRegion instead of manually mapping and allocating pages
for (const auto& program_header : m_program_headers)
{
ASSERT(program_header.p_type == PT_LOAD);
if (!(program_header.p_vaddr <= address && address < program_header.p_vaddr + program_header.p_memsz))
continue;
PageTable::flags_t flags = PageTable::Flags::UserSupervisor | PageTable::Flags::Present;
if (program_header.p_flags & LibELF::PF_W)
flags |= PageTable::Flags::ReadWrite;
if (program_header.p_flags & LibELF::PF_X)
flags |= PageTable::Flags::Execute;
const vaddr_t vaddr = address & PAGE_ADDR_MASK;
const paddr_t paddr = Heap::get().take_free_page();
if (paddr == 0)
return BAN::Error::from_errno(ENOMEM);
// Temporarily map page as RW so kernel can write to it
m_page_table.map_page_at(paddr, vaddr, PageTable::Flags::ReadWrite | PageTable::Flags::Present);
m_physical_page_count++;
memset((void*)vaddr, 0x00, PAGE_SIZE);
if (vaddr / PAGE_SIZE < BAN::Math::div_round_up<size_t>(program_header.p_vaddr + program_header.p_filesz, PAGE_SIZE))
{
size_t vaddr_offset = 0;
if (vaddr < program_header.p_vaddr)
vaddr_offset = program_header.p_vaddr - vaddr;
size_t file_offset = 0;
if (vaddr > program_header.p_vaddr)
file_offset = vaddr - program_header.p_vaddr;
size_t bytes = BAN::Math::min<size_t>(PAGE_SIZE - vaddr_offset, program_header.p_filesz - file_offset);
TRY(inode->read(program_header.p_offset + file_offset, { (uint8_t*)vaddr + vaddr_offset, bytes }));
}
// Map page with the correct flags
m_page_table.map_page_at(paddr, vaddr, flags);
return {};
}
ASSERT_NOT_REACHED();
}
BAN::ErrorOr<BAN::UniqPtr<LoadableELF>> LoadableELF::clone(Kernel::PageTable& new_page_table)
{
auto elf = TRY(BAN::UniqPtr<LoadableELF>::create(new_page_table));
elf->m_executable = m_executable;
elf->m_interpreter = m_interpreter;
elf->m_file_header = m_file_header;
TRY(elf->m_program_headers.reserve(m_program_headers.size()));
for (const auto& program_header : m_program_headers)
MUST(elf->m_program_headers.emplace_back(program_header));
elf->reserve_address_space();
for (const auto& program_header : m_program_headers)
{
ASSERT(program_header.p_type == PT_LOAD);
if (!(program_header.p_flags & LibELF::PF_W))
continue;
PageTable::flags_t flags = PageTable::Flags::UserSupervisor | PageTable::Flags::Present;
if (program_header.p_flags & LibELF::PF_W)
flags |= PageTable::Flags::ReadWrite;
if (program_header.p_flags & LibELF::PF_X)
flags |= PageTable::Flags::Execute;
vaddr_t start = program_header.p_vaddr & PAGE_ADDR_MASK;
size_t pages = range_page_count(program_header.p_vaddr, program_header.p_memsz);
for (size_t i = 0; i < pages; i++)
{
if (m_page_table.physical_address_of(start + i * PAGE_SIZE) == 0)
continue;
paddr_t paddr = Heap::get().take_free_page();
if (paddr == 0)
return BAN::Error::from_errno(ENOMEM);
PageTable::with_fast_page(paddr, [&] {
memcpy(PageTable::fast_page_as_ptr(), (void*)(start + i * PAGE_SIZE), PAGE_SIZE);
});
new_page_table.map_page_at(paddr, start + i * PAGE_SIZE, flags);
elf->m_physical_page_count++;
}
}
return elf;
}
}

View File

@ -1,89 +0,0 @@
#pragma once
#ifdef __is_kernel
#include <kernel/FS/Inode.h>
#include <kernel/Memory/VirtualRange.h>
#endif
#include <BAN/StringView.h>
#include <BAN/UniqPtr.h>
#include <BAN/Vector.h>
#include <kernel/Arch.h>
#include "Types.h"
namespace LibELF
{
class ELF
{
public:
#ifdef __is_kernel
static BAN::ErrorOr<BAN::UniqPtr<ELF>> load_from_file(BAN::RefPtr<Kernel::Inode>);
#else
static BAN::ErrorOr<BAN::UniqPtr<ELF>> load_from_file(BAN::StringView);
#endif
const Elf64FileHeader& file_header64() const;
const Elf64ProgramHeader& program_header64(size_t) const;
const Elf64SectionHeader& section_header64(size_t) const;
const char* lookup_section_name64(uint32_t) const;
const char* lookup_string64(size_t, uint32_t) const;
#if ARCH(x86_64)
const Elf64FileHeader& file_header_native() const { return file_header64(); }
const Elf64ProgramHeader& program_header_native(size_t index) const { return program_header64(index); }
const Elf64SectionHeader& section_header_native(size_t index) const { return section_header64(index); }
const char* lookup_section_name_native(uint32_t offset) const { return lookup_section_name64(offset); }
const char* lookup_string_native(size_t table_index, uint32_t offset) const { return lookup_string64(table_index, offset); }
bool is_native() const { return is_x86_64(); }
#endif
const Elf32FileHeader& file_header32() const;
const Elf32ProgramHeader& program_header32(size_t) const;
const Elf32SectionHeader& section_header32(size_t) const;
const char* lookup_section_name32(uint32_t) const;
const char* lookup_string32(size_t, uint32_t) const;
#if ARCH(i686)
const Elf32FileHeader& file_header_native() const { return file_header32(); }
const Elf32ProgramHeader& program_header_native(size_t index) const { return program_header32(index); }
const Elf32SectionHeader& section_header_native(size_t index) const { return section_header32(index); }
const char* lookup_section_name_native(uint32_t offset) const { return lookup_section_name32(offset); }
const char* lookup_string_native(size_t table_index, uint32_t offset) const { return lookup_string32(table_index, offset); }
bool is_native() const { return is_x86_32(); }
#endif
const uint8_t* data() const { return m_data.data(); }
bool is_x86_32() const;
bool is_x86_64() const;
private:
//#ifdef __is_kernel
// ELF(BAN::UniqPtr<Kernel::VirtualRange>&& storage, size_t size)
// : m_storage(BAN::move(storage))
// , m_data((const uint8_t*)m_storage->vaddr(), size)
// {}
//#else
ELF(BAN::Vector<uint8_t>&& data)
: m_data(BAN::move(data))
{}
//#endif
BAN::ErrorOr<void> load();
bool parse_elf64_file_header(const Elf64FileHeader&);
bool parse_elf64_program_header(const Elf64ProgramHeader&);
bool parse_elf64_section_header(const Elf64SectionHeader&);
bool parse_elf32_file_header(const Elf32FileHeader&);
bool parse_elf32_program_header(const Elf32ProgramHeader&);
bool parse_elf32_section_header(const Elf32SectionHeader&);
private:
//#ifdef __is_kernel
// BAN::UniqPtr<Kernel::VirtualRange> m_storage;
// BAN::Span<const uint8_t> m_data;
//#else
const BAN::Vector<uint8_t> m_data;
//#endif
};
}

View File

@ -1,74 +0,0 @@
#pragma once
#ifndef __is_kernel
#error "This is kernel only header"
#endif
#include <BAN/UniqPtr.h>
#include <BAN/Vector.h>
#include <kernel/Credentials.h>
#include <kernel/FS/Inode.h>
#include <kernel/Memory/PageTable.h>
#include <LibELF/Types.h>
namespace LibELF
{
class LoadableELF
{
BAN_NON_COPYABLE(LoadableELF);
BAN_NON_MOVABLE(LoadableELF);
public:
static BAN::ErrorOr<BAN::UniqPtr<LoadableELF>> load_from_inode(Kernel::PageTable&, const Kernel::Credentials&, BAN::RefPtr<Kernel::Inode>);
~LoadableELF();
Kernel::vaddr_t entry_point() const { return m_file_header.e_entry; }
bool has_interpreter() const { return !!m_interpreter; }
BAN::RefPtr<Kernel::Inode> executable() { return m_executable; }
bool contains(Kernel::vaddr_t address) const;
bool is_address_space_free() const;
void reserve_address_space();
void update_suid_sgid(Kernel::Credentials&);
BAN::ErrorOr<void> load_page_to_memory(Kernel::vaddr_t address);
BAN::ErrorOr<BAN::UniqPtr<LoadableELF>> clone(Kernel::PageTable&);
size_t virtual_page_count() const { return m_virtual_page_count; }
size_t physical_page_count() const { return m_physical_page_count; }
private:
struct LoadResult
{
BAN::RefPtr<Kernel::Inode> inode;
BAN::RefPtr<Kernel::Inode> interp;
ElfNativeFileHeader file_header;
BAN::Vector<ElfNativeProgramHeader> program_headers;
};
private:
LoadableELF(Kernel::PageTable&);
BAN::ErrorOr<void> initialize(const Kernel::Credentials&, BAN::RefPtr<Kernel::Inode>);
BAN::ErrorOr<LoadResult> load_elf_file(const Kernel::Credentials&, BAN::RefPtr<Kernel::Inode>) const;
private:
BAN::RefPtr<Kernel::Inode> m_executable;
BAN::RefPtr<Kernel::Inode> m_interpreter;
ElfNativeFileHeader m_file_header;
BAN::Vector<ElfNativeProgramHeader> m_program_headers;
Kernel::PageTable& m_page_table;
size_t m_virtual_page_count { 0 };
size_t m_physical_page_count { 0 };
bool m_is_loaded { false };
friend class BAN::UniqPtr<LoadableELF>;
};
}