diff --git a/kernel/src/process_manager/process.rs b/kernel/src/process_manager/process.rs index 2d02242d0..42400820d 100644 --- a/kernel/src/process_manager/process.rs +++ b/kernel/src/process_manager/process.rs @@ -7,6 +7,7 @@ use crate::address::PhysAddr; use crate::cpu::percpu::this_cpu_shared; use crate::cpu::percpu::this_cpu_unsafe; use crate::mm::PAGE_SIZE; +use crate::mm::pagetable::PageTableRef; use crate::mm::SVSM_PERCPU_VMSA_BASE; use crate::process_manager::process_memory::allocate_page; use crate::process_manager::allocation::AllocationRange; @@ -193,7 +194,7 @@ impl TrustedProcess { if data != 0 { let (function_code, function_code_range) = ProcessPageTableRef::copy_data_from_guest(data, size, pgt); let size = (4096 - (size & 0xFFF)) + size; - trustlet.base.page_table_ref.add_function(function_code, size); + trustlet.context.page_table_ref.add_function(function_code, size); function_code_range.delete(); } trustlet @@ -298,7 +299,6 @@ pub fn attest_trusted_process(_params: &mut RequestParams) -> Result<(), SvsmReq } pub fn check_page_table(pgd_addr: u64, test_location: u64) { - log::info!("Using Address: {:#x}", pgd_addr); let mut page_table_ref = ProcessPageTableRef::default(); page_table_ref.set_external_table(pgd_addr); @@ -373,6 +373,7 @@ pub struct ProcessContext { pub channel: MemoryChannel, pub sev_features: u64, pub measurements: ProcessMeasurements, + pub page_table_ref: ProcessPageTableRef, } impl Default for ProcessContext { @@ -383,6 +384,7 @@ impl Default for ProcessContext { channel: MemoryChannel::default(), sev_features: 0, measurements: ProcessMeasurements::default(), + page_table_ref: ProcessPageTableRef::default(), } } } @@ -390,8 +392,18 @@ impl Default for ProcessContext { impl ProcessContext { + /// This function is called to create a Trustlet from a Zygote pub fn init(&mut self, base: ProcessBaseContext, measurements: ProcessMeasurements) { + // Setup a new page table for the Process + // FIXME: this performs full deep copy of memory and page table from the base + // TODO: implement proper CoW + let mut new_page_table_ref = ProcessPageTableRef::default(); + new_page_table_ref.init_vmpl1(); + new_page_table_ref.copy_from(&base.page_table_ref); + let page_table_ref = new_page_table_ref; + //let page_table_ref = base.page_table_ref; + //Creating new VMSA for the Process let new_vmsa_page = allocate_page(); let new_vmsa_mapping = PerCPUPageMappingGuard::create_4k(new_vmsa_page).unwrap(); @@ -412,7 +424,7 @@ impl ProcessContext { //New VMSA Setup vmsa.vmpl = 1; // Trustlets always run in VMPL1 vmsa.cpl = 3; // Ring 3 - vmsa.cr3 = u64::from(base.page_table_ref.process_page_table); + vmsa.cr3 = u64::from(page_table_ref.process_page_table); vmsa.efer = vmsa.efer | 1u64 << 12; vmsa.rip = base.entry_point.into(); vmsa.sev_features = old_vmsa_ptr.sev_features | 4; // 4 is for #VC Reflect @@ -443,6 +455,7 @@ impl ProcessContext { self.sev_features = vmsa.sev_features; self.base = base; self.measurements = measurements; + self.page_table_ref = page_table_ref; } pub fn add_function(&mut self, function: VirtAddr, size: u64) { diff --git a/kernel/src/process_manager/process_paging.rs b/kernel/src/process_manager/process_paging.rs index 8cb325de1..6c9b78cde 100644 --- a/kernel/src/process_manager/process_paging.rs +++ b/kernel/src/process_manager/process_paging.rs @@ -16,9 +16,22 @@ use core::ffi::CStr; use super::memory_helper::{ZERO_PAGE}; // TP: Trusted Process -const TP_STACK_START_VADDR: u64 = 0x80_0000_0000; -const TP_MANIFEST_START_VADDR: u64 = 0x100_0000_0000; -const TP_LIBOS_START_VADDR: u64 = 0x180_0000_0000; +pub const TP_STACK_START_VADDR: u64 = 0x80_0000_0000; +pub const TP_MANIFEST_START_VADDR: u64 = 0x100_0000_0000; +pub const TP_LIBOS_START_VADDR: u64 = 0x180_0000_0000; + +// Gramine PAL protection flags (pal_prot_flags_t) +bitflags! { + #[repr(transparent)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub struct GraminePalProtFlags: u64 { + const READ = 0x1; + const WRITE = 0x2; + const EXEC = 0x4; + const WRITECOPY = 0x8; + const MASK = 0xF; + } +} // Flags for the Page Table // In general all Trusted Processes need to @@ -37,8 +50,7 @@ bitflags! { const HUGE_PAGE = 1 << 7; const GLOBAL = 1 << 8; - //const NO_EXECUTE = 1 << 63; - const NO_EXECUTE = 0; + const NO_EXECUTE = 1 << 63; } } @@ -159,7 +171,7 @@ impl ProcessPageTableRef { } } - fn init_vmpl1(&mut self){ + pub fn init_vmpl1(&mut self){ self.process_page_table = allocate_page(); let (mapping, table) = paddr_as_u64_slice!(self.process_page_table); for i in 0..512 { @@ -406,7 +418,6 @@ impl ProcessPageTableRef { pub fn virt_to_phys(&self, vaddr: VirtAddr) -> PhysAddr { let (_pgd_mapping, pgd_table) = paddr_as_table!(self.process_page_table); let mut current_mapping = self.page_walk(&pgd_table, self.process_page_table, vaddr); - //log::info!("Current Mapping {:?}", current_mapping); match current_mapping { ProcessTableLevelMapping::PTE(addr, index) => { let (_mapping, table) = paddr_as_u64_slice!(addr); @@ -417,6 +428,61 @@ impl ProcessPageTableRef { } + pub fn change_attr(&self, vaddr: VirtAddr, readable: bool, writable: bool, + executable: bool, writecopy: bool) { + // Change page table attributes + // NOTE: this function assumes that we operate on the trusted process's page table + // specifically, this function is called to handle Gramine's PAL mprotect request + + let (_pgd_mapping, pgd_table) = paddr_as_table!(self.process_page_table); + let current_mapping = self.page_walk(&pgd_table, self.process_page_table, vaddr); + + match current_mapping { + ProcessTableLevelMapping::PTE(addr, index) => { + let (_mapping, table) = paddr_as_u64_slice!(addr); + if readable { + // XXX: for now all pages are readable, do nothing + } + if writable || writecopy { + // XXX: see below the reason for setting writable if writecopy here + table[index] |= ProcessPageFlags::WRITABLE.bits(); + } else { + table[index] &= !ProcessPageFlags::WRITABLE.bits(); + } + if executable { + table[index] &= !ProcessPageFlags::NO_EXECUTE.bits(); + } else { + table[index] |= ProcessPageFlags::NO_EXECUTE.bits(); + } + if writecopy { + // FIXME: currently we skip this as we only support single process for now & don't have #PF handler + // TODO: implement proper CoW + + // the following copies the page immidiately at this handler + // confiremd to work, but for single process program it's not necessary (I think) + // so skip this to prefer performance & smaller memory footprint + /* + let phys_mask = 0xFFFF_FFFF_F000; + let entry_attr = table[index] & !phys_mask; + let entry_phys = PhysAddr::from(table[index] & phys_mask); + let new_page = allocate_page(); + let (_src_mapping, src_data) = paddr_as_slice!(entry_phys, u64); + let (new_page_mapping, new_page_mapped) = paddr_as_slice!(new_page); + rmp_adjust(new_page_mapping.virt_addr(), RMPFlags::VMPL1 | RMPFlags::RWX , PageSize::Regular).unwrap(); + for i in 0..512 { + new_page_mapped[i] = src_data[i]; + } + let new_entry = new_page.bits() as u64 | entry_attr | ProcessPageFlags::WRITABLE.bits(); + table[index] = new_entry; + */ + } + } + _ => { + // page non-present, skip (XXX: should we handle this?) + } + } + } + /// Takes the page table of the guest OS and copies the /// specified starteding from addr and edning at addr + size * pagesize @@ -493,4 +559,72 @@ impl ProcessPageTableRef { } } } + + fn _copy_page_table(&self, src: PhysAddr, dst: PhysAddr, level: u64) { + // Copy the page table and its memory recursively + + assert!(level <= 4 && level >= 1); + + let (_src_table_mapping, src_table) = paddr_as_table!(src); + let (_dst_table_mapping, dst_table) = paddr_as_table!(dst); + + for i in 0..512 { + let src_entry = src_table[i].0.bits(); + // FIXME: use proper mask for the physical address + let phys_mask = 0xFFFF_FFFF_F000; + let src_entry_attr = src_entry & !phys_mask; + let src_entry_phys = PhysAddr::from(src_entry & phys_mask); + let is_present = (src_entry_attr & ProcessPageFlags::PRESENT.bits() as usize) != 0; + let is_huge_page = (src_entry_attr & ProcessPageFlags::HUGE_PAGE.bits() as usize) != 0; + + if !is_present { + // XXX: we don't copy un-present pages, is this OK? + continue; + } + + let new_page_phys = if is_huge_page { + unimplemented!(); + } else { + allocate_page() + }; + assert!(new_page_phys != PhysAddr::null()); + + // copy the entry + dst_table[i] = ProcessPageTableEntry(PhysAddr::from(new_page_phys.bits() | src_entry_attr)); + + if level > 1 && !is_huge_page { + let (_new_mapping, _) = paddr_as_table!(new_page_phys); + rmp_adjust(_new_mapping.virt_addr(), RMPFlags::VMPL1 | RMPFlags::RWX, PageSize::Regular).unwrap(); + + // copy the next level + self._copy_page_table(src_entry_phys, new_page_phys, level - 1); + } else { + // this is the last level, copy the data into the new page + let (_src_mapping, src_data) = paddr_as_slice!(src_entry_phys, u64); + let (_dst_mapping, dst_data) = paddr_as_slice!(new_page_phys, u64); + rmp_adjust(_dst_mapping.virt_addr(), RMPFlags::VMPL1 | RMPFlags::RWX, PageSize::Regular).unwrap(); + + let size = if level == 1 { + 4096 + } else if level == 2 { + 512 * 4096 // 2MB + } else if level == 3 { + 512 * 512 * 4096 // 1GB + } else { + unreachable!(); + } / core::mem::size_of::(); + + for j in 0..size { + dst_data[j] = src_data[j]; + } + } + } + } + + pub fn copy_from(&mut self, other: &ProcessPageTableRef) { + // Copy the page table and its memory from the other ProcessPageTableRef + assert!(self.process_page_table != PhysAddr::null()); + assert!(other.process_page_table != PhysAddr::null()); + self._copy_page_table(other.process_page_table, self.process_page_table, 4); + } } diff --git a/kernel/src/process_runtime/runtime.rs b/kernel/src/process_runtime/runtime.rs index 147b9f201..878cf5569 100644 --- a/kernel/src/process_runtime/runtime.rs +++ b/kernel/src/process_runtime/runtime.rs @@ -2,7 +2,8 @@ use cpuarch::vmsa::VMSA; use igvm_defs::PAGE_SIZE_4K; use core::ffi::CStr; use core::str; -use crate::{address::VirtAddr, cpu::{cpuid::{cpuid_table_raw, CpuidResult}, percpu::{this_cpu, this_cpu_unsafe}}, map_paddr, mm::{PerCPUPageMappingGuard, PAGE_SIZE}, paddr_as_slice, process_manager::{process::{ProcessID, TrustedProcess, PROCESS_STORE}, process_memory::allocate_page, process_paging::{ProcessPageFlags, ProcessPageTableRef}}, protocols::{errors::SvsmReqError, RequestParams}}; +use crate::{address::VirtAddr, cpu::{cpuid::{cpuid_table_raw, CpuidResult}, percpu::{this_cpu, this_cpu_unsafe}}, map_paddr, mm::{PerCPUPageMappingGuard, PAGE_SIZE}, paddr_as_slice, process_manager::{process::{ProcessID, TrustedProcess, PROCESS_STORE}, process_memory::allocate_page, process_paging::{GraminePalProtFlags, ProcessPageFlags, ProcessPageTableRef}}, protocols::{errors::SvsmReqError, RequestParams}}; +use crate::process_manager::process_paging::TP_LIBOS_START_VADDR; use crate::vaddr_as_slice; use crate::types::PageSize; @@ -19,6 +20,7 @@ pub trait ProcessRuntime { fn pal_svsm_fail(&mut self) -> bool; fn pal_svsm_exit(&mut self) -> bool; fn pal_svsm_map(&mut self) -> bool; + fn pal_svsm_mprotect(&mut self) -> bool; fn pal_svsm_print_info(&mut self) -> bool; fn pal_svsm_set_tcb(&mut self) -> bool; fn pal_svsm_cpuid(&mut self) -> bool; @@ -65,38 +67,39 @@ pub fn invoke_trustlet(params: &mut RequestParams) -> Result<(), SvsmReqError> { string_pos: string_pos, }; - + // Execution loop of the trustlet + // Currently the trustlet runs to completion loop { unsafe {(*(*this_cpu_unsafe()).ghcb).ap_create(vmsa_paddr, u64::from(apic_id), TRUSTLET_VMPL, sev_features).unwrap()} - /*if !handle_process_request(vmsa, &mut string_buf, &mut string_pos){ - break; - }*/ if !rc.handle_process_request() { break; } } - - Ok(()) - - } impl ProcessRuntime for PALContext { + /// Handle request from the trustlet + /// + /// CPUID instructions in a trustlet (VMPL1) results in control being passed to the SVSM + /// We use this mechanism to implement monitor-call from the trustlet + /// We use some part of (unused) cpuid leaf range for monitor calls + /// Otherwise treat it as normal cpuid request and return the result + /// + /// Monitor call arguments are passed in the trustlet's registers + /// * rax: Monitor call code / cpuid leaf + /// * others: arguments to the monitor call (depends on the call) fn handle_process_request(&mut self) -> bool { let vmsa = &mut self.vmsa; let rax = vmsa.rax; - //vmsa.rax = 0; - let rip = vmsa.rip; - // The Trustlet exits with cpuid (2 Bytes) - vmsa.rip += 2; - let mut return_value = 0u64; + // Advance the trustlet's rip for the next execution (cpuid instruction is 2 bytes) + vmsa.rip += 2; match rax { 0..=23 | 0x80000000..=0x80000021 => { @@ -120,6 +123,9 @@ impl ProcessRuntime for PALContext { 0x4FFFFFFA => { return self.pal_svsm_set_tcb(); } + 0x4FFFFFF9 => { + return self.pal_svsm_mprotect(); + } 99 => { let c = vmsa.rbx; log::info!("{}", c); @@ -140,11 +146,24 @@ impl ProcessRuntime for PALContext { } + /// Handle CPUID instruction from the trustlet + /// + /// Register arguments: + /// * rax: cpuid leaf + /// * rcx: subleaf (if applicable) + /// + /// Return: + /// * rax: eax value of the cpuid result + /// * rbx: ebx value of the cpuid result + /// * rcx: ecx value of the cpuid result + /// * rdx: edx value of the cpuid result fn pal_svsm_cpuid(&mut self) -> bool { let eax = self.vmsa.rax as u32; log::info!("eax value: {:#x}",eax); let eax_tmp = self.vmsa.rax; let ecx_tmp = self.vmsa.rcx; + // Some cpuid leafs have subleaf (ecx) and some don't + // for the ones that don't we set ecx to 0 (otherwise CPUID table lookup fails) let ecx = match eax { 4 | 7 | 0xb | 0xd | 0xf| 0x10 | 0x12 | 0x14 | 0x17 | @@ -155,14 +174,7 @@ impl ProcessRuntime for PALContext { _ => 0 }; - //let ecx = if eax == 0x0 || eax == 0x1 || eax == 0xd { - // set zero for cpuid leaf that does not have subleaf (ecx) - // TODO: check if this is correct & update checks if so - // 0 - //} else { - // self.vmsa.rcx as u32 - //}; - + // NOTE: we must consult the cpuid table or make explict VMGEXIT, otherwise we'll get another #VC let res = match cpuid_table_raw(eax, ecx, 0, 0){ Some(r) => r, None => CpuidResult{eax: 0,ebx: 0, ecx: 0, edx: 0} @@ -181,6 +193,16 @@ impl ProcessRuntime for PALContext { return true; } + /// Allocate virtual memory in the trustlet's page table + /// + /// Register arguments: + /// * rax: monitor call code (0x4FFFFFFC) + /// * rbx: trustlet's virtual address to allocate + /// * rcx: size of memory to allocate + /// * rdx: flags (GraminePalProtFlags) + /// + /// Retrun: + /// * rcx: 0 on success, -1 on failure fn pal_svsm_virt_alloc(&mut self) -> bool { // Getting the Page Table of the current Trustlet being executed @@ -216,7 +238,18 @@ impl ProcessRuntime for PALContext { true } + /// Handle a PAL error + /// + /// Register arguments: + /// * rax: monitor call code (0x4FFFFFFF) + /// * rbx: error string address + /// * rcx: error number + /// + /// Return: + /// * no return to the trustlet (exit the trustlet) fn pal_svsm_fail(&mut self) -> bool{ + // PAL reports error, exit the trustlet + let page_table = self.vmsa.cr3; let string = self.vmsa.rbx; let errno = self.vmsa.rcx; @@ -233,12 +266,32 @@ impl ProcessRuntime for PALContext { false } + /// Exit the trustlet + /// + /// Register arguments: + /// * rax: monitor call code (0x4FFFFFFE) + /// * rbx: exit code + /// + /// Return: + /// * no return to the trustlet (exit the trustlet) fn pal_svsm_exit(&mut self) -> bool{ + // PAL exits, exit the trustlet let exit_code = self.vmsa.rbx; log::info!(" [Trustlet] Exit with Status Code: {}", exit_code); false } + /// Print debug message from the trustlet + /// + /// This function expects that the trustlet calls this function with each character, + /// and the final character is 0 + /// + /// Register arguments: + /// * rax: monitor call code (0x4FFFFFFD) + /// * rbx: character to print + /// + /// Return: + /// * no return value fn pal_svsm_debug_print(&mut self) -> bool { let c = self.vmsa.rbx; if self.string_pos < 255{ @@ -258,11 +311,26 @@ impl ProcessRuntime for PALContext { true } + /// Map a file into the trustlet's memory space + /// + /// FIXME: For now, this functions only supports mapping the libos file into the specified address. + /// (this works because that is the only callee of this function at the moment) + /// As the monitor loads the libos file into the predefined address (TP_LIBOS_START_VADDR) at the start, + /// this functions copy data from that region and create a new page table entry. + /// + /// Register arguments: + /// * rax: monitor call code (0x4FFFFFFB) + /// * rbx: virtual address to map + /// * rcx: size of memory to map + /// * rdx: flags (GraminePalProtFlags) + /// * r8: file descriptor (unused) + /// * r9: offset + /// + /// Return: + /// * rcx: 0 on success, -1 on failure fn pal_svsm_map(&mut self) -> bool { let addr = self.vmsa.rbx; let size = self.vmsa.rcx; - //let prot = self.vmsa.rdx >> 32; - //let flags = self.vmsa.rdx & 0xFFFFFFFF; let flags = self.vmsa.rdx; let fd = self.vmsa.r8; let offset = self.vmsa.r9; @@ -274,22 +342,30 @@ impl ProcessRuntime for PALContext { page_table_ref.set_external_table(page_table); if size % 4096 != 0 { + self.vmsa.rcx = u64::from_ne_bytes((-1i64).to_ne_bytes()); return false; } - let size = size / 4096; - - let copy = if flags & 0x8 != 0 { true } else { false }; + let num_pages = size / 4096; let vaddr = VirtAddr::from(addr); - let s_vaddr = VirtAddr::from(0x18000000000u64); - - let flags = ProcessPageFlags::PRESENT | ProcessPageFlags::WRITABLE | - ProcessPageFlags::USER_ACCESSIBLE | ProcessPageFlags::ACCESSED; + let s_vaddr = VirtAddr::from(TP_LIBOS_START_VADDR); + + let writable = (flags & GraminePalProtFlags::WRITE.bits()) != 0; + let executable = (flags & GraminePalProtFlags::EXEC.bits()) != 0; + let writecopy = (flags & GraminePalProtFlags::WRITECOPY.bits()) != 0; + let mut flags = ProcessPageFlags::PRESENT | ProcessPageFlags::USER_ACCESSIBLE | ProcessPageFlags::ACCESSED; + if writable || writecopy { + flags |= ProcessPageFlags::WRITABLE; + } + if !executable { + flags |= ProcessPageFlags::NO_EXECUTE; + } - for i in 0..size { + for i in 0..num_pages { let t = page_table_ref.virt_to_phys(s_vaddr + ((i * PAGE_SIZE_4K) as usize) + (offset as usize)); //log::info!("{:#x}, {:#x}, {:#} {:#?}",s_vaddr,offset, s_vaddr + ((i * PAGE_SIZE_4K) as usize) + (offset as usize), t); - if copy { + if writecopy { + // FIXME: for now we do not support CoW, so copy the page at this point let (_old_mapping, old_page_mapped) = paddr_as_slice!(t); let new_page = allocate_page(); let (mapping, new_page_mapped) = paddr_as_slice!(new_page); @@ -306,7 +382,6 @@ impl ProcessRuntime for PALContext { // new_page, //); - } else { page_table_ref.map_4k_page(vaddr + (i * PAGE_SIZE_4K).try_into().unwrap(), t, flags); @@ -325,9 +400,58 @@ impl ProcessRuntime for PALContext { } + self.vmsa.rcx = u64::from_ne_bytes((0i64).to_ne_bytes()); return true; } + /// Update the trusted process' page entry permissions + /// + /// Register arguments: + /// * rax: monitor call code (0x4FFFFFF9) + /// * rbx: virtual address + /// * rcx: size of memory to update + /// * rdx: flags (GraminePalProtFlags) + /// + /// Return: + /// * rcx: 0 on success, -1 on failure + fn pal_svsm_mprotect(&mut self) -> bool { + let addr = self.vmsa.rbx; + let size = self.vmsa.rcx; + let flags = self.vmsa.rdx; + + // log::info!("svsm_mprotect: addr={:#}, size={}, flags={}", addr, size, flags); + + let process_page_table = self.vmsa.cr3; + let mut process_page_table_ref = ProcessPageTableRef::default(); + process_page_table_ref.set_external_table(process_page_table); + + let offset = addr & 0xFFF; + let page_num = (offset + size + 4095) / PAGE_SIZE_4K; + let aligned_addr = addr & !0xFFF; + let vaddr = VirtAddr::from(aligned_addr); + + let readbable = flags & GraminePalProtFlags::READ.bits() != 0; + let writable = flags & GraminePalProtFlags::WRITE.bits() != 0; + let executable = flags & GraminePalProtFlags::EXEC.bits() != 0; + let writecopy = flags & GraminePalProtFlags::WRITECOPY.bits() != 0; + + // FIXME: this walks the page table every time. we can optimize this by updating entries while walking + for i in 0..page_num { + let target = vaddr + (i* PAGE_SIZE_4K).try_into().unwrap(); + process_page_table_ref.change_attr(target, readbable, writable, executable, writecopy); + } + + return true; + } + + /// Set the TCB (Thread Control Block) for the trustlet + /// + /// Register arguments: + /// * rax: monitor call code (0x4FFFFFFA) + /// * rbx: TCB address + /// + /// Return: + /// * no return value fn pal_svsm_set_tcb(&mut self) -> bool { let tcb = self.vmsa.rbx; self.vmsa.gs.base = tcb; // Set the base of the GS segment @@ -377,7 +501,4 @@ impl ProcessRuntime for PALContext { return true; } - - - }