From cb612c36ea520df3f71fa2b3747c9ff19953b4d3 Mon Sep 17 00:00:00 2001 From: "Justin C. Miller" Date: Wed, 20 Jan 2021 01:25:47 -0800 Subject: [PATCH] [boot][kernel] Split programs into sections To enable setting sections as NX or read-only, the boot program loader now loads programs as lists of sections, and the kernel args are updated accordingly. The kernel's loader now just takes a program pointer to iterate the sections. Also enable NX in IA32_EFER in the bootloader. --- src/boot/hardware.cpp | 28 ++++++++++++- src/boot/hardware.h | 4 +- src/boot/loader.cpp | 13 ++++-- src/boot/main.cpp | 8 +++- src/boot/paging.cpp | 38 +++++++++++++----- src/boot/paging.h | 15 ++++--- src/include/kernel_args.h | 19 ++++++++- src/include/kernel_memory.h | 5 +++ src/kernel/loader.s | 6 +-- src/kernel/main.cpp | 4 +- src/kernel/scheduler.cpp | 40 +++++++++++-------- src/kernel/scheduler.h | 14 ++++--- .../kutil/include/kutil/enum_bitfields.h | 7 +++- 13 files changed, 140 insertions(+), 61 deletions(-) diff --git a/src/boot/hardware.cpp b/src/boot/hardware.cpp index 152b533..c1be166 100644 --- a/src/boot/hardware.cpp +++ b/src/boot/hardware.cpp @@ -37,8 +37,25 @@ find_acpi_table(uefi::system_table *st) return reinterpret_cast(acpi1_table); } +static uint64_t +rdmsr(uint32_t addr) +{ + uint32_t low, high; + __asm__ __volatile__ ("rdmsr" : "=a"(low), "=d"(high) : "c"(addr)); + return (static_cast(high) << 32) | low; +} + +static void +wrmsr(uint32_t addr, uint64_t value) +{ + uint32_t low = value & 0xffffffff; + uint32_t high = value >> 32; + __asm__ __volatile__ ("wrmsr" :: "c"(addr), "a"(low), "d"(high)); +} + + void -setup_cr4() +setup_control_regs() { uint64_t cr4 = 0; asm volatile ( "mov %%cr4, %0" : "=r" (cr4) ); @@ -49,6 +66,15 @@ setup_cr4() 0x020000 | // Enable PCIDs 0; asm volatile ( "mov %0, %%cr4" :: "r" (cr4) ); + + // Set up IA32_EFER + constexpr uint32_t IA32_EFER = 0xC0000080; + uint64_t efer = rdmsr(IA32_EFER); + efer |= + 0x0001 | // Enable SYSCALL + 0x0800 | // Enable NX bit + 0; + wrmsr(IA32_EFER, efer); } } // namespace hw diff --git a/src/boot/hardware.h b/src/boot/hardware.h index a37c903..8d90947 100644 --- a/src/boot/hardware.h +++ b/src/boot/hardware.h @@ -13,8 +13,8 @@ namespace hw { /// significant bit set to 1. void * find_acpi_table(uefi::system_table *st); -/// Enable CPU options in CR4 for the kernel starting state. -void setup_cr4(); +/// Enable CPU options in CR4 etc for the kernel starting state. +void setup_control_regs(); } // namespace hw } // namespace boot diff --git a/src/boot/loader.cpp b/src/boot/loader.cpp index 3b5d748..67c0659 100644 --- a/src/boot/loader.cpp +++ b/src/boot/loader.cpp @@ -87,6 +87,9 @@ load_program( bs->set_mem(pages, total_size, 0); + program.base = prog_base; + program.total_size = total_size; + program.num_sections = 0; for (int i = 0; i < header->ph_num; ++i) { ptrdiff_t offset = header->ph_offset + i * header->ph_entsize; const elf::program_header *pheader = @@ -95,14 +98,18 @@ load_program( if (pheader->type != elf::PT_LOAD) continue; + args::program_section §ion = program.sections[program.num_sections++]; + void *src_start = offset_ptr(data.data, pheader->offset); void *dest_start = offset_ptr(pages, pheader->vaddr - prog_base); + bs->copy_mem(dest_start, src_start, pheader->file_size); + section.phys_addr = reinterpret_cast(dest_start); + section.virt_addr = pheader->vaddr; + section.size = pheader->mem_size; + section.type = static_cast(pheader->flags); } - program.phys_addr = reinterpret_cast(pages); - program.size = total_size; - program.virt_addr = prog_base; program.entrypoint = header->entrypoint; } diff --git a/src/boot/main.cpp b/src/boot/main.cpp index 7021340..2fe8faf 100644 --- a/src/boot/main.cpp +++ b/src/boot/main.cpp @@ -192,16 +192,20 @@ efi_main(uefi::handle image, uefi::system_table *st) status_bar status {con.fb()}; // Switch to fb status display args::program &kernel = args->programs[0]; - paging::map_pages(args, kernel.phys_addr, kernel.virt_addr, kernel.size); + for (auto §ion : kernel.sections) + if (section.size) + paging::map_section(args, section); + kernel::entrypoint kentry = reinterpret_cast(kernel.entrypoint); status.next(); + + hw::setup_control_regs(); memory::virtualize(args->pml4, map, st->runtime_services); status.next(); change_pointer(args->pml4); - hw::setup_cr4(); status.next(); kentry(args); diff --git a/src/boot/paging.cpp b/src/boot/paging.cpp index 9675d08..11427be 100644 --- a/src/boot/paging.cpp +++ b/src/boot/paging.cpp @@ -15,7 +15,7 @@ using memory::page_size; using ::memory::pml4e_kernel; using ::memory::table_entries; -// Flags: 0 0 0 1 0 0 0 0 0 0 1 1 = 0x0103 +// Flags: 0 0 0 1 0 0 0 0 0 0 0 1 = 0x0101 // IGN | | | | | | | | +- Present // | | | | | | | +--- Writeable // | | | | | | +----- Usermode access (supervisor only) @@ -26,7 +26,7 @@ using ::memory::table_entries; // | +---------------- PAT (determining memory type for page) // +------------------- Global /// Page table entry flags for entries pointing at a page -constexpr uint16_t page_flags = 0x103; +constexpr uint64_t page_flags = 0x101; // Flags: 0 0 0 0 1 1 0 0 0 1 0 1 1 = 0x018b // | IGN | | | | | | | | +- Present @@ -40,7 +40,7 @@ constexpr uint16_t page_flags = 0x103; // | +------------------- Global // +---------------------------- PAT (determining memory type for page) /// Page table entry flags for entries pointing at a huge page -constexpr uint16_t huge_page_flags = 0x183; +constexpr uint64_t huge_page_flags = 0x18b; // Flags: 0 0 0 0 0 0 0 0 0 0 1 1 = 0x0003 // IGNORED | | | | | | | +- Present @@ -52,7 +52,7 @@ constexpr uint16_t huge_page_flags = 0x183; // | +-------------- Ignored // +---------------- Reserved 0 (Table pointer, not page) /// Page table entry flags for entries pointing at another table -constexpr uint16_t table_flags = 0x003; +constexpr uint64_t table_flags = 0x003; /// Iterator over page table entries. template @@ -191,7 +191,7 @@ allocate_tables(kernel::args::header *args, uefi::boot_services *bs) status_line status(L"Allocating initial page tables"); static constexpr size_t pd_tables = 256; // number of pages for kernelspace PDs - static constexpr size_t extra_tables = 49; // number of extra pages + static constexpr size_t extra_tables = 64; // number of extra pages // number of pages for kernelspace PDs + PML4 static constexpr size_t kernel_tables = pd_tables + 1; @@ -223,23 +223,39 @@ allocate_tables(kernel::args::header *args, uefi::boot_services *bs) console::print(L" Set up initial mappings, %d spare tables.\r\n", args->table_count); } +template +constexpr bool has_flag(E set, E flag) { + return + (static_cast(set) & static_cast(flag)) == + static_cast(flag); +} + void -map_pages( +map_section( kernel::args::header *args, - uintptr_t phys, uintptr_t virt, - size_t size) + const kernel::args::program_section §ion) { paging::page_table *pml4 = reinterpret_cast(args->pml4); - size_t pages = memory::bytes_to_pages(size); + size_t pages = memory::bytes_to_pages(section.size); page_entry_iterator<4> iterator{ - virt, pml4, + section.virt_addr, pml4, args->page_tables, args->table_count}; + using kernel::args::section_flags; + + uint64_t flags = page_flags; + if (!has_flag(section.type, section_flags::execute)) + flags |= (1ull << 63); // set NX bit + + if (has_flag(section.type, section_flags::write)) + flags |= 2; + + uintptr_t phys = section.phys_addr; while (true) { - *iterator = phys | page_flags; + *iterator = phys | flags; if (--pages == 0) break; diff --git a/src/boot/paging.h b/src/boot/paging.h index 437d41c..7290f48 100644 --- a/src/boot/paging.h +++ b/src/boot/paging.h @@ -38,15 +38,14 @@ void allocate_tables( /// tables in the current PML4. void add_current_mappings(page_table *new_pml4); -/// Map a physical address to a virtual address in the given page tables. -/// \arg args The kernel args header, used for the page table cache and pml4 -/// \arg phys The phyiscal address to map in -/// \arg virt The virtual address to map in -/// \arg size The size in bytes of the mapping -void map_pages( +/// Map a program section in physical memory to its virtual address in the +/// given page tables. +/// \arg args The kernel args header, used for the page table cache and pml4 +/// \arg section The program section to load +void map_section( kernel::args::header *args, - uintptr_t phys, uintptr_t virt, - size_t bytes); + const kernel::args::program_section §ion); + } // namespace paging } // namespace boot diff --git a/src/include/kernel_args.h b/src/include/kernel_args.h index 36c81c6..1ae2102 100644 --- a/src/include/kernel_args.h +++ b/src/include/kernel_args.h @@ -35,11 +35,26 @@ struct framebuffer { fb_type type; }; -struct program { +enum class section_flags : uint32_t { + none = 0, + execute = 1, + write = 2, + read = 4, +}; + +struct program_section { uintptr_t phys_addr; uintptr_t virt_addr; + uint32_t size; + section_flags type; +}; + +struct program { uintptr_t entrypoint; - size_t size; + uintptr_t base; + size_t total_size; + size_t num_sections; + program_section sections[3]; }; enum class mem_type : uint32_t { diff --git a/src/include/kernel_memory.h b/src/include/kernel_memory.h index f875521..42ecfa6 100644 --- a/src/include/kernel_memory.h +++ b/src/include/kernel_memory.h @@ -58,6 +58,11 @@ namespace memory { return reinterpret_cast(a|page_offset); } + /// Convert a physical address to a virtual one (in the offset-mapped area) + template T * to_virtual(T *p) { + return to_virtual(reinterpret_cast(p)); + } + /// Get the number of pages needed for a given number of bytes. /// \arg bytes The number of bytes desired /// \returns The number of pages needed to contain the desired bytes diff --git a/src/kernel/loader.s b/src/kernel/loader.s index 07c31e0..c96793c 100644 --- a/src/kernel/loader.s +++ b/src/kernel/loader.s @@ -8,11 +8,7 @@ preloaded_process_init: ; create_process already pushed the arguments for load_process_image and ; the following iretq onto the stack for us - pop rdi ; the physical address of the program image - pop rsi ; the virtual address of the program image - pop rdx ; the size in bytes of the program image - pop rcx ; the address of this thread's TCB - + pop rdi ; a pointer to the program descriptor call load_process_image ; user rsp is now in rax, put it in the right place for iret diff --git a/src/kernel/main.cpp b/src/kernel/main.cpp index e59b90a..6c6737a 100644 --- a/src/kernel/main.cpp +++ b/src/kernel/main.cpp @@ -192,8 +192,8 @@ kernel_main(args::header *header) // Skip program 0, which is the kernel itself for (size_t i = 1; i < header->num_programs; ++i) { - args::program &prog = header->programs[i]; - thread *th = sched->load_process(prog.phys_addr, prog.virt_addr, prog.size, prog.entrypoint); + args::program *prog = memory::to_virtual(&header->programs[i]); + thread *th = sched->load_process(*prog); if (i == 2) { //th->set_state(thread::state::constant); } diff --git a/src/kernel/scheduler.cpp b/src/kernel/scheduler.cpp index 9b6ca39..d3c0afd 100644 --- a/src/kernel/scheduler.cpp +++ b/src/kernel/scheduler.cpp @@ -34,7 +34,7 @@ const uint64_t rflags_int = 0x202; extern "C" { void preloaded_process_init(); - uintptr_t load_process_image(uintptr_t phys, uintptr_t virt, size_t bytes, TCB *tcb); + uintptr_t load_process_image(const kernel::args::program*); }; extern uint64_t idle_stack_end; @@ -76,20 +76,28 @@ inline T * push(uintptr_t &rsp, size_t size = sizeof(T)) { } uintptr_t -load_process_image(uintptr_t phys, uintptr_t virt, size_t bytes, TCB *tcb) +load_process_image(const kernel::args::program *program) { using memory::page_align_down; using memory::page_align_up; + using kernel::args::section_flags; // We're now in the process space for this process, allocate memory for the // process code and load it process &proc = process::current(); thread &th = thread::current(); + TCB *tcb = th.tcb(); vm_space &space = proc.space(); - vm_area *vma = new vm_area_open(bytes, space, vm_flags::zero|vm_flags::write); - space.add(virt, vma); - vma->commit(phys, 0, memory::page_count(bytes)); + for (const auto § : program->sections) { + vm_flags flags = + (bitfield_has(sect.type, section_flags::execute) ? vm_flags::exec : vm_flags::none) | + (bitfield_has(sect.type, section_flags::write) ? vm_flags::write : vm_flags::none); + + vm_area *vma = new vm_area_open(sect.size, space, flags); + space.add(sect.virt_addr, vma); + vma->commit(sect.phys_addr, 0, memory::page_count(sect.size)); + } // double zero stack sentinel *push(tcb->rsp3) = 0; @@ -141,7 +149,8 @@ load_process_image(uintptr_t phys, uintptr_t virt, size_t bytes, TCB *tcb) // Crazypants framebuffer part if (fb) { - vma = new vm_area_open(fb->size, space, vm_flags::write|vm_flags::mmio|vm_flags::write_combine); + vm_area *vma = new vm_area_open(fb->size, space, + vm_flags::write|vm_flags::mmio|vm_flags::write_combine); space.add(0x100000000, vma); vma->commit(fb->phys_addr, 0, memory::page_count(fb->size)); } @@ -167,7 +176,7 @@ scheduler::create_process(bool user) } thread * -scheduler::load_process(uintptr_t phys, uintptr_t virt, size_t size, uintptr_t entry) +scheduler::load_process(kernel::args::program &program) { uint16_t kcs = (1 << 3) | 0; // Kernel CS is GDT entry 1, ring 0 @@ -180,23 +189,20 @@ scheduler::load_process(uintptr_t phys, uintptr_t virt, size_t size, uintptr_t e auto *tcb = th->tcb(); // Create an initial kernel stack space - uintptr_t *stack = reinterpret_cast(tcb->rsp0) - 9; + uintptr_t *stack = reinterpret_cast(tcb->rsp0) - 6; // Pass args to preloaded_process_init on the stack - stack[0] = reinterpret_cast(phys); - stack[1] = reinterpret_cast(virt); - stack[2] = reinterpret_cast(size); - stack[3] = reinterpret_cast(tcb); + stack[0] = reinterpret_cast(&program); tcb->rsp = reinterpret_cast(stack); th->add_thunk_kernel(reinterpret_cast(preloaded_process_init)); // Arguments for iret - rip will be pushed on before these - stack[4] = reinterpret_cast(entry); - stack[5] = cs; - stack[6] = rflags_int | (3 << 12); - stack[7] = process::stacks_top; - stack[8] = ss; + stack[1] = reinterpret_cast(program.entrypoint); + stack[2] = cs; + stack[3] = rflags_int | (3 << 12); + stack[4] = process::stacks_top; + stack[5] = ss; tcb->rsp3 = process::stacks_top; diff --git a/src/kernel/scheduler.h b/src/kernel/scheduler.h index 90a7789..f762798 100644 --- a/src/kernel/scheduler.h +++ b/src/kernel/scheduler.h @@ -5,6 +5,11 @@ #include #include "objects/thread.h" +namespace kernel { +namespace args { + struct program; +}} + class lapic; class process; struct page_table; @@ -41,12 +46,9 @@ public: scheduler(lapic *apic); /// Create a new process from a program image in memory. - /// \arg phys Physical address of the loaded program image - /// \arg virt Virtual address of the loaded program image - /// \arg size Size of the program image, in bytes - /// \arg entry Virtual address of the program entrypoint - /// \returns The main thread of the loaded process - thread * load_process(uintptr_t phys, uintptr_t virt, size_t size, uintptr_t entry); + /// \arg program The descriptor of the pogram in memory + /// \returns The main thread of the loaded process + thread * load_process(kernel::args::program &program); /// Create a new kernel task /// \arg proc Function to run as a kernel task diff --git a/src/libraries/kutil/include/kutil/enum_bitfields.h b/src/libraries/kutil/include/kutil/enum_bitfields.h index bbf334a..9d0b351 100644 --- a/src/libraries/kutil/include/kutil/enum_bitfields.h +++ b/src/libraries/kutil/include/kutil/enum_bitfields.h @@ -131,10 +131,13 @@ operator ! (E rhs) } template -constexpr typename std::enable_if::value,bool>::type +constexpr bool bitfield_has(E set, E flag) { - return (set & flag) == flag; + return + (static_cast::type>(set) & + static_cast::type>(flag)) == + static_cast::type>(flag); } // Overload the logical-and operator to be 'bitwise-and, bool-cast'