diff --git a/libkernel/src/memory/proc_vm/mod.rs b/libkernel/src/memory/proc_vm/mod.rs index 8e0c08d7..f8d81b84 100644 --- a/libkernel/src/memory/proc_vm/mod.rs +++ b/libkernel/src/memory/proc_vm/mod.rs @@ -15,10 +15,35 @@ pub mod vmarea; const BRK_PERMISSIONS: VMAPermissions = VMAPermissions::rw(); +/// Address bounds for the argv and environment strings in a process image. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ArgsEnvBounds { + /// First byte of the argument strings. + pub arg_start: VA, + /// One byte past the argument strings. + pub arg_end: VA, + /// First byte of the environment strings. + pub env_start: VA, + /// One byte past the environment strings. + pub env_end: VA, +} + +impl Default for ArgsEnvBounds { + fn default() -> Self { + Self { + arg_start: VA::null(), + arg_end: VA::null(), + env_start: VA::null(), + env_end: VA::null(), + } + } +} + /// The virtual memory state of a user-space process. pub struct ProcessVM { mm: MemoryMap, brk: VirtMemoryRegion, + args_env_bounds: ArgsEnvBounds, } impl ProcessVM { @@ -35,7 +60,11 @@ impl ProcessVM { let brk = VirtMemoryRegion::new(vma.region.end_address().align_up(PAGE_SIZE), 0); - Self { mm, brk } + Self { + mm, + brk, + args_env_bounds: ArgsEnvBounds::default(), + } } /// Constructs a new Process VM structure from the given VMA. The heap is @@ -47,7 +76,11 @@ impl ProcessVM { let brk = VirtMemoryRegion::new(vma.region.end_address().align_up(PAGE_SIZE), 0); - Ok(Self { mm, brk }) + Ok(Self { + mm, + brk, + args_env_bounds: ArgsEnvBounds::default(), + }) } /// Constructs a `ProcessVM` from an existing memory map. @@ -66,6 +99,7 @@ impl ProcessVM { Self { mm: map, brk: VirtMemoryRegion::new(brk, 0), + args_env_bounds: ArgsEnvBounds::default(), } } @@ -74,6 +108,7 @@ impl ProcessVM { Ok(Self { mm: MemoryMap::new()?, brk: VirtMemoryRegion::empty(), + args_env_bounds: ArgsEnvBounds::default(), }) } @@ -103,6 +138,36 @@ impl ProcessVM { self.brk.start_address() } + /// Returns the argv/environment address bounds for this process image. + pub fn args_env_bounds(&self) -> ArgsEnvBounds { + self.args_env_bounds + } + + /// Sets the argv/environment address bounds for this process image. + pub fn set_args_env_bounds(&mut self, bounds: ArgsEnvBounds) { + self.args_env_bounds = bounds; + } + + /// Sets the first byte of the argument strings. + pub fn set_arg_start(&mut self, addr: VA) { + self.args_env_bounds.arg_start = addr; + } + + /// Sets one byte past the argument strings. + pub fn set_arg_end(&mut self, addr: VA) { + self.args_env_bounds.arg_end = addr; + } + + /// Sets the first byte of the environment strings. + pub fn set_env_start(&mut self, addr: VA) { + self.args_env_bounds.env_start = addr; + } + + /// Sets one byte past the environment strings. + pub fn set_env_end(&mut self, addr: VA) { + self.args_env_bounds.env_end = addr; + } + /// Returns the current end address of the program break (heap). pub fn current_brk(&self) -> VA { self.brk.end_address() @@ -178,6 +243,7 @@ impl ProcessVM { Ok(Self { mm: self.mm.clone_as_cow()?, brk: self.brk, + args_env_bounds: self.args_env_bounds, }) } } @@ -207,6 +273,7 @@ mod tests { let initial_brk_start = VA::from_value(0x1000 + PAGE_SIZE); assert_eq!(vm.brk.start_address(), initial_brk_start); assert_eq!(vm.brk.size(), 0); + assert_eq!(vm.args_env_bounds(), ArgsEnvBounds::default()); assert_eq!(vm.current_brk(), initial_brk_start); // And the break region itself should not be mapped diff --git a/src/arch/arm64/exceptions/syscall.rs b/src/arch/arm64/exceptions/syscall.rs index 6ecef8c9..eb05bc17 100644 --- a/src/arch/arm64/exceptions/syscall.rs +++ b/src/arch/arm64/exceptions/syscall.rs @@ -620,7 +620,7 @@ pub async fn handle_syscall(mut ctx: ProcessCtx) { 0xa1 => sys_sethostname(&ctx, TUA::from_value(arg1 as _), arg2 as _).await, 0xa3 => Err(KernelError::InvalidValue), 0xa6 => sys_umask(&ctx, arg1 as _).map_err(|e| match e {}), - 0xa7 => sys_prctl(&ctx, arg1 as _, arg2, arg3).await, + 0xa7 => sys_prctl(&ctx, arg1 as _, arg2, arg3, arg4, arg5).await, 0xa8 => sys_getcpu(TUA::from_value(arg1 as _), TUA::from_value(arg2 as _)).await, 0xa9 => sys_gettimeofday(TUA::from_value(arg1 as _), TUA::from_value(arg2 as _)).await, 0xaa => sys_settimeofday(TUA::from_value(arg1 as _), TUA::from_value(arg2 as _)).await, diff --git a/src/drivers/fs/proc/task/task_file.rs b/src/drivers/fs/proc/task/task_file.rs index 267b27fb..d908a348 100644 --- a/src/drivers/fs/proc/task/task_file.rs +++ b/src/drivers/fs/proc/task/task_file.rs @@ -143,6 +143,7 @@ Threads:\t{tasks}\n", } let start_brk = vm.start_brk().value(); + let args_env_bounds = vm.args_env_bounds(); let mut output = String::new(); output.push_str(&format!("{} ", task.process.tgid.value())); // pid @@ -205,10 +206,10 @@ Threads:\t{tasks}\n", output.push_str(&format!("{start_data} ")); // start_data output.push_str(&format!("{end_data} ")); // end_data output.push_str(&format!("{start_brk} ")); // start_brk - output.push_str(&format!("{} ", 0)); // arg_start - output.push_str(&format!("{} ", 0)); // arg_end - output.push_str(&format!("{} ", 0)); // env_start - output.push_str(&format!("{} ", 0)); // env_end + output.push_str(&format!("{} ", args_env_bounds.arg_start.value())); // arg_start + output.push_str(&format!("{} ", args_env_bounds.arg_end.value())); // arg_end + output.push_str(&format!("{} ", args_env_bounds.env_start.value())); // env_start + output.push_str(&format!("{} ", args_env_bounds.env_end.value())); // env_end output.push_str(&format!("{} ", 0)); // exit_code output.push('\n'); output diff --git a/src/process/exec.rs b/src/process/exec.rs index 2a348e7a..2782e048 100644 --- a/src/process/exec.rs +++ b/src/process/exec.rs @@ -25,7 +25,7 @@ use libkernel::{ address::{TUA, VA}, paging::permissions::PtePermissions, proc_vm::{ - ProcessVM, + ArgsEnvBounds, ProcessVM, memory_map::MemoryMap, vmarea::{VMAPermissions, VMArea, VMAreaKind}, }, @@ -181,13 +181,14 @@ async fn exec_elf( vmas.push(stack_vma); let mut mem_map = MemoryMap::from_vmas(vmas)?; - let stack_ptr = setup_user_stack(&mut mem_map, &argv, &envp, auxv)?; + let (stack_ptr, args_env_bounds) = setup_user_stack(&mut mem_map, &argv, &envp, auxv)?; // We are now committed to the exec. Inform ptrace. ptrace_stop(ctx, TracePoint::Exec).await; let user_ctx = ArchImpl::new_user_context(entry_addr, stack_ptr); let mut vm = ProcessVM::from_map(mem_map); + vm.set_args_env_bounds(args_env_bounds); // We don't have to worry about actually calling for a full context switch // here. Parts of the old process that are replaced will go out of scope and @@ -292,7 +293,7 @@ fn setup_user_stack( argv: &[String], envp: &[String], mut auxv: Vec, -) -> Result { +) -> Result<(VA, ArgsEnvBounds)> { // Calculate the space needed and the virtual addresses for all strings and // pointers. let mut string_addrs = Vec::new(); @@ -314,6 +315,33 @@ fn setup_user_stack( let (envp_addrs, argv_addrs) = string_addrs.split_at(envp.len()); + let arg_start = argv_addrs + .first() + .copied() + .map(VA::from_value) + .unwrap_or_else(VA::null); + let arg_end = argv_addrs + .last() + .zip(argv.last()) + .map(|(&addr, s)| VA::from_value(addr + s.len() + 1)) + .unwrap_or_else(VA::null); + let env_start = envp_addrs + .first() + .copied() + .map(VA::from_value) + .unwrap_or_else(VA::null); + let env_end = envp_addrs + .last() + .zip(envp.last()) + .map(|(&addr, s)| VA::from_value(addr + s.len() + 1)) + .unwrap_or_else(VA::null); + let args_env_bounds = ArgsEnvBounds { + arg_start, + arg_end, + env_start, + env_end, + }; + let mut info_block = Vec::::new(); info_block.push(argv.len() as u64); // argc info_block.extend(argv_addrs.iter().map(|&addr| addr as u64)); @@ -384,7 +412,7 @@ fn setup_user_stack( .map_page(page.leak(), page_va, PtePermissions::rw(true))?; } - Ok(VA::from_value(final_sp_val)) + Ok((VA::from_value(final_sp_val), args_env_bounds)) } // Dynamic linker path: map PT_INTERP interpreter and return start address of diff --git a/src/process/prctl.rs b/src/process/prctl.rs index ce074480..5842aebb 100644 --- a/src/process/prctl.rs +++ b/src/process/prctl.rs @@ -5,17 +5,68 @@ use crate::sched::syscall_ctx::ProcessCtx; use bitflags::Flags; use core::ffi::c_char; use libkernel::error::{KernelError, Result}; -use libkernel::memory::address::TUA; +use libkernel::memory::address::{TUA, VA}; use libkernel::proc::caps::CapabilitiesFlags; +const PR_SET_PDEATHSIG: i32 = 1; +const PR_GET_PDEATHSIG: i32 = 2; +const PR_GET_DUMPABLE: i32 = 3; +const PR_SET_DUMPABLE: i32 = 4; const PR_CAPBSET_READ: i32 = 23; const PR_CAPBSET_DROP: i32 = 24; const PR_SET_NAME: i32 = 15; const PR_GET_NAME: i32 = 16; const PR_GET_SECUREBITS: i32 = 27; +const PR_SET_MM: i32 = 35; const PR_GET_NO_NEW_PRIVS: i32 = 39; const PR_CAP_AMBIENT: i32 = 47; +const TASK_SIZE: usize = 0x0000_8000_0000_0000; + +#[derive(Debug)] +enum SetMMOp { + StartCode = 1, + EndCode = 2, + StartData = 3, + EndData = 4, + StartStack = 5, + StartBrk = 6, + Brk = 7, + ArgStart = 8, + ArgEnd = 9, + EnvStart = 10, + EnvEnd = 11, + Aux = 12, + ExeFile = 13, + Map = 14, + MapSize = 15, +} + +impl TryFrom for SetMMOp { + type Error = KernelError; + + fn try_from(value: u64) -> Result { + match value { + 1 => Ok(SetMMOp::StartCode), + 2 => Ok(SetMMOp::EndCode), + 3 => Ok(SetMMOp::StartData), + 4 => Ok(SetMMOp::EndData), + 5 => Ok(SetMMOp::StartStack), + 6 => Ok(SetMMOp::StartBrk), + 7 => Ok(SetMMOp::Brk), + 8 => Ok(SetMMOp::ArgStart), + 9 => Ok(SetMMOp::ArgEnd), + 10 => Ok(SetMMOp::EnvStart), + 11 => Ok(SetMMOp::EnvEnd), + 12 => Ok(SetMMOp::Aux), + 13 => Ok(SetMMOp::ExeFile), + 14 => Ok(SetMMOp::Map), + 15 => Ok(SetMMOp::MapSize), + _ => Err(KernelError::InvalidValue), + } + } +} + #[derive(Debug)] enum AmbientCapOp { IsSet = 1, @@ -38,6 +89,14 @@ impl TryFrom for AmbientCapOp { } } +fn pr_set_pdeath_sig(_ctx: &ProcessCtx, _sig: i64) -> Result { + Ok(0) +} + +fn pr_get_pdeath_sig(_ctx: &ProcessCtx) -> Result { + Ok(0) +} + fn pr_read_capbset(ctx: &ProcessCtx, what: usize) -> Result { let what = CapabilitiesFlags::from_bits(1u64 << what).ok_or(KernelError::InvalidValue)?; let task = ctx.shared(); @@ -68,6 +127,77 @@ async fn pr_set_name(ctx: &ProcessCtx, str: TUA) -> Result { Ok(0) } +fn validate_stack_addr(vm: &crate::process::ProcVM, addr: usize, allow_end: bool) -> Result { + if addr > TASK_SIZE { + return Err(KernelError::InvalidValue); + } + + let va = VA::from_value(addr); + let vma = vm.mm().find_vma(va).or_else(|| { + if allow_end && addr != 0 { + let prev_addr = VA::from_value(addr - 1); + let prev_vma = vm.mm().find_vma(prev_addr)?; + + if prev_vma.region().end_address().value() == addr { + Some(prev_vma) + } else { + None + } + } else { + None + } + }); + + let vma = vma.ok_or(KernelError::InvalidValue)?; + let permissions = vma.permissions(); + + if !permissions.read || !permissions.write { + return Err(KernelError::InvalidValue); + } + + Ok(va) +} + +async fn pr_set_mm(ctx: &ProcessCtx, op: u64, addr: u64, arg3: u64, arg4: u64) -> Result { + if arg3 != 0 || arg4 != 0 { + return Err(KernelError::InvalidValue); + } + + let op = SetMMOp::try_from(op)?; + let task = ctx.shared(); + task.creds + .lock_save_irq() + .caps() + .check_capable(CapabilitiesFlags::CAP_SYS_RESOURCE)?; + + let addr = addr.try_into().map_err(|_| KernelError::InvalidValue)?; + let mut vm = task.vm.lock_save_irq(); + + match op { + SetMMOp::ArgStart => { + let addr = validate_stack_addr(&vm, addr, false)?; + vm.set_arg_start(addr); + Ok(0) + } + SetMMOp::ArgEnd => { + let addr = validate_stack_addr(&vm, addr, true)?; + vm.set_arg_end(addr); + Ok(0) + } + SetMMOp::EnvStart => { + let addr = validate_stack_addr(&vm, addr, false)?; + vm.set_env_start(addr); + Ok(0) + } + SetMMOp::EnvEnd => { + let addr = validate_stack_addr(&vm, addr, true)?; + vm.set_env_end(addr); + Ok(0) + } + _ => Err(KernelError::InvalidValue), + } +} + async fn pr_cap_ambient(ctx: &ProcessCtx, op: u64, arg1: u64) -> Result { let op = AmbientCapOp::try_from(op)?; let task = ctx.shared(); @@ -107,13 +237,28 @@ async fn pr_cap_ambient(ctx: &ProcessCtx, op: u64, arg1: u64) -> Result { } } -pub async fn sys_prctl(ctx: &ProcessCtx, op: i32, arg1: u64, arg2: u64) -> Result { +pub async fn sys_prctl( + ctx: &ProcessCtx, + op: i32, + arg1: u64, + arg2: u64, + arg3: u64, + arg4: u64, +) -> Result { match op { + PR_SET_PDEATHSIG => pr_set_pdeath_sig(ctx, arg1 as i64), + PR_GET_PDEATHSIG => pr_get_pdeath_sig(ctx), + PR_GET_DUMPABLE => Ok(1), + PR_SET_DUMPABLE => match arg1 { + 0 | 1 => Ok(0), + _ => Err(KernelError::InvalidValue), + }, PR_SET_NAME => pr_set_name(ctx, TUA::from_value(arg1 as usize)).await, PR_GET_NAME => pr_get_name(ctx, TUA::from_value(arg1 as usize)).await, PR_CAPBSET_READ => pr_read_capbset(ctx, arg1 as usize), PR_CAPBSET_DROP => pr_drop_capbset(ctx, arg1 as usize).await, PR_GET_SECUREBITS => Ok(0), + PR_SET_MM => pr_set_mm(ctx, arg1, arg2, arg3, arg4).await, PR_GET_NO_NEW_PRIVS => Ok(0), PR_CAP_AMBIENT => pr_cap_ambient(ctx, arg1, arg2).await, _ => todo!("prctl op: {}", op),