From c750ee26a8df392dfed70a2752d4eaded29610d7 Mon Sep 17 00:00:00 2001 From: Jeremy Soller <jackpot51@gmail.com> Date: Sat, 20 Aug 2022 21:21:32 -0600 Subject: [PATCH] Implement setting FS/GS offset on x86 --- src/arch/x86/gdt.rs | 21 +++++++++-------- src/arch/x86/start.rs | 4 ++++ src/context/arch/x86.rs | 8 ++++--- src/scheme/proc.rs | 51 +++++++++++++++++++++++++++++++++++++++-- 4 files changed, 70 insertions(+), 14 deletions(-) diff --git a/src/arch/x86/gdt.rs b/src/arch/x86/gdt.rs index f8b1e8ea..6e1eb58f 100644 --- a/src/arch/x86/gdt.rs +++ b/src/arch/x86/gdt.rs @@ -16,11 +16,12 @@ pub const GDT_NULL: usize = 0; pub const GDT_KERNEL_CODE: usize = 1; pub const GDT_KERNEL_DATA: usize = 2; pub const GDT_KERNEL_KPCR: usize = 3; -pub const GDT_USER_CODE32_UNUSED: usize = 4; +pub const GDT_USER_CODE: usize = 4; pub const GDT_USER_DATA: usize = 5; -pub const GDT_USER_CODE: usize = 6; -pub const GDT_TSS: usize = 7; -pub const GDT_CPU_ID_CONTAINER: usize = 8; +pub const GDT_USER_FS: usize = 6; +pub const GDT_USER_GS: usize = 7; +pub const GDT_TSS: usize = 8; +pub const GDT_CPU_ID_CONTAINER: usize = 9; pub const GDT_A_PRESENT: u8 = 1 << 7; pub const GDT_A_RING_0: u8 = 0 << 5; @@ -52,7 +53,7 @@ static mut INIT_GDT: [GdtEntry; 4] = [ ]; #[thread_local] -pub static mut GDT: [GdtEntry; 9] = [ +pub static mut GDT: [GdtEntry; 10] = [ // Null GdtEntry::new(0, 0, 0, 0), // Kernel code @@ -61,12 +62,14 @@ pub static mut GDT: [GdtEntry; 9] = [ GdtEntry::new(0, 0, GDT_A_PRESENT | GDT_A_RING_0 | GDT_A_SYSTEM | GDT_A_PRIVILEGE, GDT_F_PROTECTED_MODE), // Kernel TLS GdtEntry::new(0, 0, GDT_A_PRESENT | GDT_A_RING_0 | GDT_A_SYSTEM | GDT_A_PRIVILEGE, GDT_F_PROTECTED_MODE), - // Dummy 32-bit user code - apparently necessary for SYSEXIT. We restrict it to ring 0 anyway. - GdtEntry::new(0, 0, GDT_A_PRESENT | GDT_A_RING_0 | GDT_A_SYSTEM | GDT_A_EXECUTABLE | GDT_A_PRIVILEGE, GDT_F_PROTECTED_MODE), + // User (32-bit) code + GdtEntry::new(0, 0, GDT_A_PRESENT | GDT_A_RING_3 | GDT_A_SYSTEM | GDT_A_EXECUTABLE | GDT_A_PRIVILEGE, GDT_F_PROTECTED_MODE), // User data GdtEntry::new(0, 0, GDT_A_PRESENT | GDT_A_RING_3 | GDT_A_SYSTEM | GDT_A_PRIVILEGE, GDT_F_PROTECTED_MODE), - // User (64-bit) code - GdtEntry::new(0, 0, GDT_A_PRESENT | GDT_A_RING_3 | GDT_A_SYSTEM | GDT_A_EXECUTABLE | GDT_A_PRIVILEGE, GDT_F_PROTECTED_MODE), + // User FS (for TLS) + GdtEntry::new(0, 0, GDT_A_PRESENT | GDT_A_RING_3 | GDT_A_SYSTEM | GDT_A_PRIVILEGE, GDT_F_PROTECTED_MODE), + // User GS (for TLS) + GdtEntry::new(0, 0, GDT_A_PRESENT | GDT_A_RING_3 | GDT_A_SYSTEM | GDT_A_PRIVILEGE, GDT_F_PROTECTED_MODE), // TSS GdtEntry::new(0, 0, GDT_A_PRESENT | GDT_A_RING_3 | GDT_A_TSS_AVAIL, 0), // Unused entry which stores the CPU ID. This is necessary for paranoid interrupts as they have diff --git a/src/arch/x86/start.rs b/src/arch/x86/start.rs index 84b063a8..92d26cd7 100644 --- a/src/arch/x86/start.rs +++ b/src/arch/x86/start.rs @@ -322,7 +322,9 @@ pub unsafe extern "C" fn usermode(_ip: usize, _sp: usize, _arg: usize, _is_singl mov eax, {user_data_seg_selector} mov ds, eax mov es, eax + mov eax, {user_fs_seg_selector} mov fs, eax + mov eax, {user_gs_seg_selector} mov gs, eax // Set up iret stack @@ -353,6 +355,8 @@ pub unsafe extern "C" fn usermode(_ip: usize, _sp: usize, _arg: usize, _is_singl shift_singlestep = const(SHIFT_SINGLESTEP), user_data_seg_selector = const(gdt::GDT_USER_DATA << 3 | 3), user_code_seg_selector = const(gdt::GDT_USER_CODE << 3 | 3), + user_fs_seg_selector = const(gdt::GDT_USER_FS << 3 | 3), + user_gs_seg_selector = const(gdt::GDT_USER_GS << 3 | 3), options(noreturn), ); diff --git a/src/context/arch/x86.rs b/src/context/arch/x86.rs index 8a5ce7bc..ff541dfa 100644 --- a/src/context/arch/x86.rs +++ b/src/context/arch/x86.rs @@ -3,7 +3,7 @@ use core::sync::atomic::AtomicBool; use alloc::sync::Arc; -use crate::gdt::{GDT, GDT_TSS}; +use crate::gdt::{GDT, GDT_USER_FS, GDT_USER_GS}; use crate::paging::{RmmA, RmmArch, TableKind}; use crate::syscall::FloatRegisters; @@ -135,8 +135,10 @@ pub unsafe fn switch_to(prev: &mut super::Context, next: &mut super::Context) { ); { - prev.arch.gsbase = GDT[GDT_TSS].offset() as usize; - GDT[GDT_TSS].set_offset(next.arch.gsbase as u32); + prev.arch.fsbase = GDT[GDT_USER_FS].offset() as usize; + GDT[GDT_USER_FS].set_offset(next.arch.fsbase as u32); + prev.arch.gsbase = GDT[GDT_USER_GS].offset() as usize; + GDT[GDT_USER_GS].set_offset(next.arch.gsbase as u32); } match next.addr_space { diff --git a/src/scheme/proc.rs b/src/scheme/proc.rs index 63d4167f..a7c6efc8 100644 --- a/src/scheme/proc.rs +++ b/src/scheme/proc.rs @@ -404,7 +404,24 @@ impl ProcScheme { Err(Error::new(EINVAL)) } - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + #[cfg(target_arch = "x86")] + fn read_env_regs(&self, info: &Info) -> Result<EnvRegisters> { + let (fsbase, gsbase) = if info.pid == context::context_id() { + unsafe { + ( + crate::gdt::GDT[crate::gdt::GDT_USER_FS].offset() as u64, + crate::gdt::GDT[crate::gdt::GDT_USER_GS].offset() as u64 + ) + } + } else { + try_stop_context(info.pid, |context| { + Ok((context.arch.fsbase as u64, context.arch.gsbase as u64)) + })? + }; + Ok(EnvRegisters { fsbase: fsbase as _, gsbase: gsbase as _ }) + } + + #[cfg(target_arch = "x86_64")] fn read_env_regs(&self, info: &Info) -> Result<EnvRegisters> { let (fsbase, gsbase) = if info.pid == context::context_id() { #[cfg(not(feature = "x86_fsgsbase"))] @@ -442,7 +459,37 @@ impl ProcScheme { Err(Error::new(EINVAL)) } - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + #[cfg(target_arch = "x86")] + fn write_env_regs(&self, info: &Info, regs: EnvRegisters) -> Result<()> { + println!("{:?}", regs); + + if !(RmmA::virt_is_valid(VirtualAddress::new(regs.fsbase as usize)) && RmmA::virt_is_valid(VirtualAddress::new(regs.gsbase as usize))) { + return Err(Error::new(EINVAL)); + } + + if info.pid == context::context_id() { + unsafe { + crate::gdt::GDT[crate::gdt::GDT_USER_FS].set_offset(regs.fsbase); + crate::gdt::GDT[crate::gdt::GDT_USER_GS].set_offset(regs.gsbase); + + match context::contexts().current().ok_or(Error::new(ESRCH))?.write().arch { + ref mut arch => { + arch.fsbase = regs.fsbase as usize; + arch.gsbase = regs.gsbase as usize; + } + } + } + } else { + try_stop_context(info.pid, |context| { + context.arch.fsbase = regs.fsbase as usize; + context.arch.gsbase = regs.gsbase as usize; + Ok(()) + })?; + } + Ok(()) + } + + #[cfg(target_arch = "x86_64")] fn write_env_regs(&self, info: &Info, regs: EnvRegisters) -> Result<()> { if !(RmmA::virt_is_valid(VirtualAddress::new(regs.fsbase as usize)) && RmmA::virt_is_valid(VirtualAddress::new(regs.gsbase as usize))) { return Err(Error::new(EINVAL)); -- GitLab