diff --git a/src/elf/src/lib.rs b/src/elf/src/lib.rs index eda354d09..858674a03 100644 --- a/src/elf/src/lib.rs +++ b/src/elf/src/lib.rs @@ -135,11 +135,16 @@ impl Elf { return Err(OpenError::UnsupportedEndianness); } + if LE::read_u16(&hdr[0x36..]) != 0x38 { + // PS4 make assumption that the program entry is 0x38 bytes. + return Err(OpenError::InvalidProgramEntrySize); + } + // Load ELF header. let e_type = FileType::new(LE::read_u16(&hdr[0x10..])); let e_entry = LE::read_u64(&hdr[0x18..]); let e_phoff = offset + 0x40; // PS4 is hard-coded this value. - let e_phnum = LE::read_u16(&hdr[0x38..]) as usize; + let e_phnum: usize = LE::read_u16(&hdr[0x38..]).into(); // Seek to first program header. match image.seek(SeekFrom::Start(e_phoff)) { @@ -705,6 +710,9 @@ pub enum OpenError { #[error("unsupported endianness")] UnsupportedEndianness, + #[error("e_phentsize is not valid")] + InvalidProgramEntrySize, + #[error("e_phoff is not valid")] InvalidProgramOffset, diff --git a/src/kernel/src/ee/llvm/mod.rs b/src/kernel/src/ee/llvm/mod.rs index 144aff1c9..8e88f8406 100644 --- a/src/kernel/src/ee/llvm/mod.rs +++ b/src/kernel/src/ee/llvm/mod.rs @@ -11,13 +11,13 @@ use thiserror::Error; mod codegen; /// An implementation of [`ExecutionEngine`] using JIT powered by LLVM IR. -pub struct LlvmEngine<'a, 'b: 'a> { +pub struct LlvmEngine { llvm: &'static Llvm, - rtld: &'a RuntimeLinker<'b>, + rtld: &'static RuntimeLinker, } -impl<'a, 'b: 'a> LlvmEngine<'a, 'b> { - pub fn new(llvm: &'static Llvm, rtld: &'a RuntimeLinker<'b>) -> Self { +impl LlvmEngine { + pub fn new(llvm: &'static Llvm, rtld: &'static RuntimeLinker) -> Self { Self { llvm, rtld } } @@ -30,7 +30,10 @@ impl<'a, 'b: 'a> LlvmEngine<'a, 'b> { Ok(()) } - fn lift(&self, module: &Module) -> Result, LiftError> { + fn lift( + &self, + module: &Module, + ) -> Result, LiftError> { // Get a list of public functions. let path = module.path(); let targets = match module.entry() { @@ -71,7 +74,7 @@ impl<'a, 'b: 'a> LlvmEngine<'a, 'b> { } } -impl<'a, 'b: 'a> ExecutionEngine for LlvmEngine<'a, 'b> { +impl ExecutionEngine for LlvmEngine { type RunErr = RunError; unsafe fn run(&mut self, arg: EntryArg, stack: VPages) -> Result<(), Self::RunErr> { diff --git a/src/kernel/src/ee/mod.rs b/src/kernel/src/ee/mod.rs index be6b75683..eacd9e5b7 100644 --- a/src/kernel/src/ee/mod.rs +++ b/src/kernel/src/ee/mod.rs @@ -1,8 +1,13 @@ -use crate::fs::VPath; -use crate::memory::VPages; +use crate::arc4::Arc4; +use crate::memory::{Protections, VPages}; +use crate::process::{ResourceLimit, VProc}; +use crate::rtld::Module; use std::error::Error; use std::ffi::CString; -use std::ops::Deref; +use std::marker::PhantomPinned; +use std::mem::size_of_val; +use std::pin::Pin; +use std::sync::Arc; pub mod llvm; #[cfg(target_arch = "x86_64")] @@ -22,33 +27,99 @@ pub trait ExecutionEngine: Sync { /// Encapsulate an argument of the PS4 entry point. pub struct EntryArg { - app: CString, + vp: &'static VProc, + app: Arc, + name: CString, + path: CString, + canary: [u8; 64], + pagesizes: [usize; 3], + stack_prot: Protections, vec: Vec, + _pin: PhantomPinned, } impl EntryArg { - pub fn new(app: &VPath) -> Self { + pub fn new(arnd: &Arc4, vp: &'static VProc, app: Arc) -> Self { + let path = app.path(); + let name = CString::new(path.file_name().unwrap()).unwrap(); + let path = CString::new(path.as_str()).unwrap(); + let mut canary = [0; 64]; + + arnd.rand_bytes(&mut canary); + Self { - app: CString::new(app.deref()).unwrap(), + vp, + app, + name, + path, + canary, + pagesizes: [0x4000, 0, 0], + stack_prot: Protections::CPU_READ | Protections::CPU_WRITE, vec: Vec::new(), + _pin: PhantomPinned, } } - pub fn as_vec(&mut self) -> &Vec { + pub fn stack_prot(&self) -> Protections { + self.stack_prot + } + + pub fn as_vec(self: Pin<&mut Self>) -> &Vec { + let pin = unsafe { self.get_unchecked_mut() }; + let mem = pin.app.memory(); let mut argc = 0; // Build argv. - self.vec.clear(); - self.vec.push(0); + pin.vec.clear(); + pin.vec.push(0); - self.vec.push(self.app.as_ptr() as _); + pin.vec.push(pin.name.as_ptr() as _); argc += 1; - self.vec[0] = argc; - self.vec.push(0); // End of arguments. - self.vec.push(0); // End of environment. + pin.vec[0] = argc; + pin.vec.push(0); // End of arguments. + pin.vec.push(0); // End of environment. + + // Push auxiliary data. + pin.vec.push(3); // AT_PHDR + pin.vec.push(0); + pin.vec.push(4); // AT_PHENT + pin.vec.push(0x38); + pin.vec.push(5); // AT_PHNUM + pin.vec.push(pin.app.programs().len()); + pin.vec.push(6); // AT_PAGESZ + pin.vec.push(0x4000); + pin.vec.push(8); // AT_FLAGS + pin.vec.push(0); + pin.vec.push(9); // AT_ENTRY + pin.vec.push(mem.addr() + pin.app.entry().unwrap()); + pin.vec.push(7); // AT_BASE + pin.vec.push( + mem.addr() + + mem.data_segment().start() + + pin.vp.limit(ResourceLimit::DATA).unwrap().max() + + 0x3fff + & 0xffffffffffffc000, + ); + pin.vec.push(15); // AT_EXECPATH + pin.vec.push(pin.path.as_ptr() as _); + pin.vec.push(18); // AT_OSRELDATE + pin.vec.push(0x000DBBA0); + pin.vec.push(16); // AT_CANARY + pin.vec.push(pin.canary.as_ptr() as _); + pin.vec.push(17); // AT_CANARYLEN + pin.vec.push(pin.canary.len()); + pin.vec.push(19); // AT_NCPUS + pin.vec.push(8); + pin.vec.push(20); // AT_PAGESIZES + pin.vec.push(pin.pagesizes.as_ptr() as _); + pin.vec.push(21); // AT_PAGESIZESLEN + pin.vec.push(size_of_val(&pin.pagesizes)); + pin.vec.push(23); // AT_STACKPROT + pin.vec.push(pin.stack_prot.bits().try_into().unwrap()); + pin.vec.push(0); // AT_NULL + pin.vec.push(0); - // TODO: Seems like there are something beyond the environment. - &self.vec + &pin.vec } } diff --git a/src/kernel/src/ee/native/mod.rs b/src/kernel/src/ee/native/mod.rs index ea6c92d12..40befc60f 100644 --- a/src/kernel/src/ee/native/mod.rs +++ b/src/kernel/src/ee/native/mod.rs @@ -12,20 +12,19 @@ use iced_x86::code_asm::{ use llt::Thread; use std::mem::{size_of, transmute}; use std::ops::Deref; -use std::ptr::null_mut; use thiserror::Error; /// An implementation of [`ExecutionEngine`] for running the PS4 binary natively. -pub struct NativeEngine<'a, 'b: 'a> { - rtld: &'a RuntimeLinker<'b>, - syscalls: &'a Syscalls<'a, 'b>, +pub struct NativeEngine { + rtld: &'static RuntimeLinker, + syscalls: &'static Syscalls, vp: &'static VProc, } -impl<'a, 'b: 'a> NativeEngine<'a, 'b> { +impl NativeEngine { pub fn new( - rtld: &'a RuntimeLinker<'b>, - syscalls: &'a Syscalls<'a, 'b>, + rtld: &'static RuntimeLinker, + syscalls: &'static Syscalls, vp: &'static VProc, ) -> Self { Self { rtld, syscalls, vp } @@ -46,7 +45,7 @@ impl<'a, 'b: 'a> NativeEngine<'a, 'b> { Ok(counts) } - fn syscalls(&self) -> *const Syscalls<'a, 'b> { + fn syscalls(&self) -> *const Syscalls { self.syscalls } @@ -446,7 +445,7 @@ impl<'a, 'b: 'a> NativeEngine<'a, 'b> { #[cfg(unix)] fn join_thread(thr: Thread) -> Result<(), std::io::Error> { - let err = unsafe { libc::pthread_join(thr, null_mut()) }; + let err = unsafe { libc::pthread_join(thr, std::ptr::null_mut()) }; if err != 0 { Err(std::io::Error::from_raw_os_error(err)) @@ -470,10 +469,10 @@ impl<'a, 'b: 'a> NativeEngine<'a, 'b> { } } -impl<'a, 'b> ExecutionEngine for NativeEngine<'a, 'b> { +impl ExecutionEngine for NativeEngine { type RunErr = RunError; - unsafe fn run(&mut self, mut arg: EntryArg, mut stack: VPages) -> Result<(), Self::RunErr> { + unsafe fn run(&mut self, arg: EntryArg, mut stack: VPages) -> Result<(), Self::RunErr> { // Get eboot.bin. if self.rtld.app().file_info().is_none() { todo!("statically linked eboot.bin"); @@ -486,7 +485,8 @@ impl<'a, 'b> ExecutionEngine for NativeEngine<'a, 'b> { unsafe { transmute(mem + boot.entry().unwrap()) }; // Spawn main thread. - let entry = move || unsafe { entry(arg.as_vec().as_ptr()) }; + let mut arg = Box::pin(arg); + let entry = move || unsafe { entry(arg.as_mut().as_vec().as_ptr()) }; let runner = match self.vp.new_thread(stack.as_mut_ptr(), stack.len(), entry) { Ok(v) => v, Err(e) => return Err(RunError::CreateMainThreadFailed(e)), diff --git a/src/kernel/src/fs/path.rs b/src/kernel/src/fs/path.rs index 00b315800..82fe96125 100644 --- a/src/kernel/src/fs/path.rs +++ b/src/kernel/src/fs/path.rs @@ -60,6 +60,10 @@ impl VPath { Components(unsafe { self.0.get_unchecked(1..) }) } + pub fn as_str(&self) -> &str { + &self.0 + } + fn is_valid(data: &str) -> bool { // Do a simple check first. if data.is_empty() || !data.starts_with('/') || data.ends_with('/') { diff --git a/src/kernel/src/log/entry.rs b/src/kernel/src/log/entry.rs index 8f703608d..79f044454 100644 --- a/src/kernel/src/log/entry.rs +++ b/src/kernel/src/log/entry.rs @@ -26,13 +26,6 @@ impl LogEntry { e } - pub fn sink() -> Self { - Self { - stdout: None, - plain: Vec::new(), - } - } - pub fn into_raw(self) -> Option<(Buffer, Vec)> { self.stdout.map(|b| (b, self.plain)) } diff --git a/src/kernel/src/main.rs b/src/kernel/src/main.rs index 169ba26a5..a12154a78 100644 --- a/src/kernel/src/main.rs +++ b/src/kernel/src/main.rs @@ -3,7 +3,7 @@ use crate::ee::EntryArg; use crate::fs::{Fs, VPath}; use crate::llvm::Llvm; use crate::log::{print, LOGGER}; -use crate::memory::{MappingFlags, MemoryManager, Protections}; +use crate::memory::{MappingFlags, MemoryManager}; use crate::process::VProc; use crate::rtld::{ModuleFlags, RuntimeLinker}; use crate::syscalls::Syscalls; @@ -104,8 +104,8 @@ fn main() -> ExitCode { // Initialize filesystem. info!("Initializing file system."); - let fs = match Fs::new(args.system, args.game) { - Ok(v) => v, + let fs: &'static Fs = match Fs::new(args.system, args.game) { + Ok(v) => Box::leak(v.into()), Err(e) => { error!(e, "Initialize failed"); return ExitCode::FAILURE; @@ -131,13 +131,19 @@ fn main() -> ExitCode { // Initialize virtual process. info!("Initializing virtual process."); - let vp: &'static VProc = Box::leak(VProc::new().into()); + let vp: &'static VProc = match VProc::new() { + Ok(v) => Box::leak(v.into()), + Err(e) => { + error!(e, "Initialize failed"); + return ExitCode::FAILURE; + } + }; // Initialize runtime linker. info!("Initializing runtime linker."); - let mut ld = match RuntimeLinker::new(&fs) { - Ok(v) => v, + let ld: &'static mut RuntimeLinker = match RuntimeLinker::new(fs) { + Ok(v) => Box::leak(v.into()), Err(e) => { error!(e, "Initialize failed"); return ExitCode::FAILURE; @@ -192,11 +198,12 @@ fn main() -> ExitCode { info!("Initializing system call routines."); let sysctl: &'static Sysctl = Box::leak(Sysctl::new(arc4).into()); - let syscalls = Syscalls::new(&sysctl, &ld, vp); + let syscalls: &'static Syscalls = Box::leak(Syscalls::new(sysctl, ld, vp).into()); // Bootstrap execution engine. info!("Initializing execution engine."); + let arg = EntryArg::new(arc4, vp, ld.app().clone()); let ee = match args.execution_engine { Some(v) => v, #[cfg(target_arch = "x86_64")] @@ -208,7 +215,7 @@ fn main() -> ExitCode { match ee { #[cfg(target_arch = "x86_64")] ExecutionEngine::Native => { - let mut ee = ee::native::NativeEngine::new(&ld, &syscalls, vp); + let mut ee = ee::native::NativeEngine::new(ld, syscalls, vp); info!("Patching modules."); @@ -233,7 +240,7 @@ fn main() -> ExitCode { } } - exec(ee, &ld) + exec(ee, arg) } #[cfg(not(target_arch = "x86_64"))] ExecutionEngine::Native => { @@ -244,7 +251,7 @@ fn main() -> ExitCode { return ExitCode::FAILURE; } ExecutionEngine::Llvm => { - let mut ee = ee::llvm::LlvmEngine::new(llvm, &ld); + let mut ee = ee::llvm::LlvmEngine::new(llvm, ld); info!("Lifting modules."); @@ -253,15 +260,12 @@ fn main() -> ExitCode { return ExitCode::FAILURE; } - exec(ee, &ld) + exec(ee, arg) } } } -fn exec(mut ee: E, ld: &RuntimeLinker) -> ExitCode { - // Setup entry argument. - let arg = EntryArg::new(ld.app().path()); - +fn exec(mut ee: E, arg: EntryArg) -> ExitCode { // TODO: Check how the PS4 allocate the stack. // TODO: We should allocate a guard page to catch stack overflow. info!("Allocating application stack."); @@ -269,7 +273,7 @@ fn exec(mut ee: E, ld: &RuntimeLinker) -> ExitCode { let stack = match MemoryManager::current().mmap( 0, 1024 * 1024 * 10, // 10MB should be large enough. - Protections::CPU_READ | Protections::CPU_WRITE, + arg.stack_prot(), MappingFlags::MAP_ANON | MappingFlags::MAP_PRIVATE, -1, 0, diff --git a/src/kernel/src/process/mod.rs b/src/kernel/src/process/mod.rs index 7a52c6553..b6a8c6f31 100644 --- a/src/kernel/src/process/mod.rs +++ b/src/kernel/src/process/mod.rs @@ -1,3 +1,4 @@ +pub use self::rlimit::*; pub use self::thread::*; use gmtx::{GroupMutex, MutexGroup}; @@ -5,7 +6,9 @@ use llt::{SpawnError, Thread}; use std::num::NonZeroI32; use std::sync::atomic::{AtomicI32, Ordering}; use std::sync::Arc; +use thiserror::Error; +mod rlimit; mod thread; /// An implementation of `proc` structure represent the main application process. @@ -15,26 +18,33 @@ mod thread; /// compatibility from the user-mode application. #[derive(Debug)] pub struct VProc { - id: NonZeroI32, // p_pid - threads: GroupMutex>>, // p_threads + id: NonZeroI32, // p_pid + threads: GroupMutex>>, // p_threads + limits: [ResourceLimit; ResourceLimit::NLIMITS], // p_limit mtxg: Arc, } impl VProc { - pub fn new() -> Self { + pub fn new() -> Result { let mtxg = MutexGroup::new(); + let limits = Self::load_limits()?; - Self { + Ok(Self { id: Self::new_id(), threads: mtxg.new_member(Vec::new()), + limits, mtxg, - } + }) } pub fn id(&self) -> NonZeroI32 { self.id } + pub fn limit(&self, ty: usize) -> Option<&ResourceLimit> { + self.limits.get(ty) + } + /// Spawn a new [`VThread`]. /// /// The caller is responsible for `stack` deallocation. @@ -73,6 +83,17 @@ impl VProc { Ok(host) } + fn load_limits() -> Result<[ResourceLimit; ResourceLimit::NLIMITS], VProcError> { + type R = ResourceLimit; + type E = VProcError; + + Ok([ + R::new(R::CPU).map_err(|e| E::GetCpuLimitFailed(e))?, + R::new(R::FSIZE).map_err(|e| E::GetFileSizeLimitFailed(e))?, + R::new(R::DATA).map_err(|e| E::GetDataLimitFailed(e))?, + ]) + } + fn new_id() -> NonZeroI32 { let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); @@ -99,4 +120,17 @@ impl Drop for ActiveThread { } } +/// Represents an error when [`VProc`] construction is failed. +#[derive(Debug, Error)] +pub enum VProcError { + #[error("cannot get CPU time limit")] + GetCpuLimitFailed(#[source] std::io::Error), + + #[error("cannot get file size limit")] + GetFileSizeLimitFailed(#[source] std::io::Error), + + #[error("cannot get data size limit")] + GetDataLimitFailed(#[source] std::io::Error), +} + static NEXT_ID: AtomicI32 = AtomicI32::new(1); diff --git a/src/kernel/src/process/rlimit.rs b/src/kernel/src/process/rlimit.rs new file mode 100644 index 000000000..801f37df5 --- /dev/null +++ b/src/kernel/src/process/rlimit.rs @@ -0,0 +1,76 @@ +use std::io::Error; + +/// An implementation of `rlimit`. +#[derive(Debug)] +pub struct ResourceLimit { + cur: usize, + max: usize, +} + +impl ResourceLimit { + pub const CPU: usize = 0; + pub const FSIZE: usize = 1; + pub const DATA: usize = 2; + pub const NLIMITS: usize = 3; + + pub(super) fn new(ty: usize) -> Result { + // TODO: Make sure the value is not exceed the value on the PS4. + let mut l = Self::host(ty)?; + + match ty { + Self::DATA => { + let mb = 1024 * 1024; + let gb = mb * 1024; + let max = gb * 5; + + if l.max > max { + l.max = max; + l.cur = max; + } + } + _ => {} + } + + Ok(l) + } + + pub fn max(&self) -> usize { + self.max + } + + #[cfg(unix)] + fn host(ty: usize) -> Result { + use std::mem::MaybeUninit; + + let mut l = MaybeUninit::uninit(); + let r = match ty { + Self::CPU => libc::RLIMIT_CPU, + Self::FSIZE => libc::RLIMIT_FSIZE, + Self::DATA => libc::RLIMIT_DATA, + v => todo!("ResourceLimit::new({v})"), + }; + + if unsafe { libc::getrlimit(r, l.as_mut_ptr()) } < 0 { + return Err(Error::last_os_error()); + } + + let l = unsafe { l.assume_init() }; + + Ok(Self { + cur: l.rlim_cur.try_into().unwrap(), + max: l.rlim_max.try_into().unwrap(), + }) + } + + #[cfg(windows)] + fn host(ty: usize) -> Result { + let (cur, max) = match ty { + Self::CPU => (usize::MAX, usize::MAX), // TODO: Get the values from Windows. + Self::FSIZE => (usize::MAX, usize::MAX), // TODO: Get the values from Windows. + Self::DATA => (usize::MAX, usize::MAX), // TODO: Get the values from Windows. + v => todo!("ResourceLimit::new({v})"), + }; + + Ok(Self { cur, max }) + } +} diff --git a/src/kernel/src/rtld/mod.rs b/src/kernel/src/rtld/mod.rs index 085f50cde..466d61e06 100644 --- a/src/kernel/src/rtld/mod.rs +++ b/src/kernel/src/rtld/mod.rs @@ -22,20 +22,20 @@ mod resolver; /// An implementation of /// https://github.com/freebsd/freebsd-src/blob/release/9.1.0/libexec/rtld-elf/rtld.c. #[derive(Debug)] -pub struct RuntimeLinker<'a> { - fs: &'a Fs, +pub struct RuntimeLinker { + fs: &'static Fs, list: GroupMutex>>, // obj_list + obj_tail app: Arc, // obj_main kernel: GroupMutex>>, // obj_kernel mains: GroupMutex>>, // list_main - next_id: GroupMutex, // idtable on proc + next_id: GroupMutex, // proc::idtable tls: GroupMutex, flags: LinkerFlags, mtxg: Arc, } -impl<'a> RuntimeLinker<'a> { - pub fn new(fs: &'a Fs) -> Result { +impl RuntimeLinker { + pub fn new(fs: &'static Fs) -> Result { // Get path to eboot.bin. let mut path = fs.app().join("app0").unwrap(); diff --git a/src/kernel/src/rtld/module.rs b/src/kernel/src/rtld/module.rs index 667be6c98..163aa88a7 100644 --- a/src/kernel/src/rtld/module.rs +++ b/src/kernel/src/rtld/module.rs @@ -255,6 +255,10 @@ impl Module { self.symbols.get(i) } + pub fn programs(&self) -> &[Program] { + self.programs.as_ref() + } + pub fn symbols(&self) -> &[Symbol] { self.symbols.as_ref() } diff --git a/src/kernel/src/syscalls/mod.rs b/src/kernel/src/syscalls/mod.rs index 9d1a01085..461788a83 100644 --- a/src/kernel/src/syscalls/mod.rs +++ b/src/kernel/src/syscalls/mod.rs @@ -18,14 +18,14 @@ mod input; mod output; /// Provides PS4 kernel routines for PS4 process. -pub struct Syscalls<'a, 'b: 'a> { - sysctl: &'a Sysctl, - ld: &'a RuntimeLinker<'b>, +pub struct Syscalls { + sysctl: &'static Sysctl, + ld: &'static RuntimeLinker, vp: &'static VProc, } -impl<'a, 'b: 'a> Syscalls<'a, 'b> { - pub fn new(sysctl: &'a Sysctl, ld: &'a RuntimeLinker<'b>, vp: &'static VProc) -> Self { +impl Syscalls { + pub fn new(sysctl: &'static Sysctl, ld: &'static RuntimeLinker, vp: &'static VProc) -> Self { Self { sysctl, ld, vp } }