diff --git a/kernel/src/process/rlimit.rs b/kernel/src/process/rlimit.rs index 1f9ab7512..1605bca87 100644 --- a/kernel/src/process/rlimit.rs +++ b/kernel/src/process/rlimit.rs @@ -1,15 +1,16 @@ // SPDX-License-Identifier: MPL-2.0 // FIXME: The resource limits should be respected by the corresponding subsystems of the kernel. -#![expect(non_camel_case_types)] - use core::{ array, sync::atomic::{AtomicU64, Ordering}, }; use super::process_vm::{INIT_STACK_SIZE, USER_HEAP_SIZE_LIMIT}; -use crate::prelude::*; +use crate::{ + prelude::*, + process::{UserNamespace, credentials::capabilities::CapSet}, +}; // Constants for the boot-time rlimit defaults // See https://github.com/torvalds/linux/blob/fac04efc5c793dccbd07e2d59af9f90b7fc0dca4/include/asm-generic/resource.h#L11 @@ -34,7 +35,7 @@ pub struct ResourceLimits { } impl ResourceLimits { - // Get a reference to a specific resource limit + /// Returns a reference to a specific resource limit. pub fn get_rlimit(&self, resource: ResourceType) -> &RLimit64 { &self.rlimits[resource as usize] } @@ -44,38 +45,32 @@ impl Default for ResourceLimits { fn default() -> Self { let mut rlimits: [RLimit64; RLIMIT_COUNT] = array::from_fn(|_| RLimit64::default()); - // Setting the resource limits with predefined values - rlimits[ResourceType::RLIMIT_CPU as usize] = - RLimit64::new(RLIM_INFINITY, RLIM_INFINITY).unwrap(); - rlimits[ResourceType::RLIMIT_FSIZE as usize] = - RLimit64::new(RLIM_INFINITY, RLIM_INFINITY).unwrap(); + // Sets the resource limits with predefined values + rlimits[ResourceType::RLIMIT_CPU as usize] = RLimit64::new(RLIM_INFINITY, RLIM_INFINITY); + rlimits[ResourceType::RLIMIT_FSIZE as usize] = RLimit64::new(RLIM_INFINITY, RLIM_INFINITY); rlimits[ResourceType::RLIMIT_DATA as usize] = - RLimit64::new(USER_HEAP_SIZE_LIMIT as u64, RLIM_INFINITY).unwrap(); + RLimit64::new(USER_HEAP_SIZE_LIMIT as u64, RLIM_INFINITY); rlimits[ResourceType::RLIMIT_STACK as usize] = - RLimit64::new(INIT_STACK_SIZE as u64, RLIM_INFINITY).unwrap(); - rlimits[ResourceType::RLIMIT_CORE as usize] = RLimit64::new(0, RLIM_INFINITY).unwrap(); - rlimits[ResourceType::RLIMIT_RSS as usize] = - RLimit64::new(RLIM_INFINITY, RLIM_INFINITY).unwrap(); + RLimit64::new(INIT_STACK_SIZE as u64, RLIM_INFINITY); + rlimits[ResourceType::RLIMIT_CORE as usize] = RLimit64::new(0, RLIM_INFINITY); + rlimits[ResourceType::RLIMIT_RSS as usize] = RLimit64::new(RLIM_INFINITY, RLIM_INFINITY); rlimits[ResourceType::RLIMIT_NPROC as usize] = - RLimit64::new(INIT_RLIMIT_NPROC, INIT_RLIMIT_NPROC).unwrap(); + RLimit64::new(INIT_RLIMIT_NPROC, INIT_RLIMIT_NPROC); rlimits[ResourceType::RLIMIT_NOFILE as usize] = - RLimit64::new(INIT_RLIMIT_NOFILE_CUR, INIT_RLIMIT_NOFILE_MAX).unwrap(); + RLimit64::new(INIT_RLIMIT_NOFILE_CUR, INIT_RLIMIT_NOFILE_MAX); rlimits[ResourceType::RLIMIT_MEMLOCK as usize] = - RLimit64::new(INIT_RLIMIT_MEMLOCK, INIT_RLIMIT_MEMLOCK).unwrap(); - rlimits[ResourceType::RLIMIT_AS as usize] = - RLimit64::new(RLIM_INFINITY, RLIM_INFINITY).unwrap(); - rlimits[ResourceType::RLIMIT_LOCKS as usize] = - RLimit64::new(RLIM_INFINITY, RLIM_INFINITY).unwrap(); + RLimit64::new(INIT_RLIMIT_MEMLOCK, INIT_RLIMIT_MEMLOCK); + rlimits[ResourceType::RLIMIT_AS as usize] = RLimit64::new(RLIM_INFINITY, RLIM_INFINITY); + rlimits[ResourceType::RLIMIT_LOCKS as usize] = RLimit64::new(RLIM_INFINITY, RLIM_INFINITY); rlimits[ResourceType::RLIMIT_SIGPENDING as usize] = - RLimit64::new(INIT_RLIMIT_SIGPENDING, INIT_RLIMIT_SIGPENDING).unwrap(); + RLimit64::new(INIT_RLIMIT_SIGPENDING, INIT_RLIMIT_SIGPENDING); rlimits[ResourceType::RLIMIT_MSGQUEUE as usize] = - RLimit64::new(INIT_RLIMIT_MSGQUEUE, INIT_RLIMIT_MSGQUEUE).unwrap(); + RLimit64::new(INIT_RLIMIT_MSGQUEUE, INIT_RLIMIT_MSGQUEUE); rlimits[ResourceType::RLIMIT_NICE as usize] = - RLimit64::new(INIT_RLIMIT_NICE, INIT_RLIMIT_NICE).unwrap(); + RLimit64::new(INIT_RLIMIT_NICE, INIT_RLIMIT_NICE); rlimits[ResourceType::RLIMIT_RTPRIO as usize] = - RLimit64::new(INIT_RLIMIT_RTPRIO, INIT_RLIMIT_RTPRIO).unwrap(); - rlimits[ResourceType::RLIMIT_RTTIME as usize] = - RLimit64::new(RLIM_INFINITY, RLIM_INFINITY).unwrap(); + RLimit64::new(INIT_RLIMIT_RTPRIO, INIT_RLIMIT_RTPRIO); + rlimits[ResourceType::RLIMIT_RTTIME as usize] = RLimit64::new(RLIM_INFINITY, RLIM_INFINITY); ResourceLimits { rlimits } } @@ -83,6 +78,7 @@ impl Default for ResourceLimits { #[repr(u32)] #[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromInt)] +#[expect(non_camel_case_types)] pub enum ResourceType { RLIMIT_CPU = 0, RLIMIT_FSIZE = 1, @@ -102,7 +98,7 @@ pub enum ResourceType { RLIMIT_RTTIME = 15, } -pub const RLIMIT_COUNT: usize = 16; +const RLIMIT_COUNT: usize = 16; #[derive(Debug, Clone, Copy, Pod)] #[repr(C)] @@ -112,7 +108,6 @@ pub struct RawRLimit64 { } #[derive(Debug)] -#[repr(C)] pub struct RLimit64 { cur: AtomicU64, max: AtomicU64, @@ -120,47 +115,68 @@ pub struct RLimit64 { } impl RLimit64 { - pub fn new(cur_: u64, max_: u64) -> Result { - if cur_ > max_ { - return_errno_with_message!(Errno::EINVAL, "invalid rlimit"); - } - Ok(Self { - cur: AtomicU64::new(cur_), - max: AtomicU64::new(max_), + #[track_caller] + pub(self) const fn new(cur: u64, max: u64) -> Self { + assert!(cur <= max, "the current rlimit exceeds the max rlimit"); + Self { + cur: AtomicU64::new(cur), + max: AtomicU64::new(max), lock: SpinLock::new(()), - }) + } } - /// Gets the current rlimit without synchronization. + /// Returns the current rlimit without synchronization. pub fn get_cur(&self) -> u64 { self.cur.load(Ordering::Relaxed) } - /// Gets the max rlimit without synchronization. - pub fn get_max(&self) -> u64 { + /// Returns the max rlimit without synchronization. + fn get_max(&self) -> u64 { self.max.load(Ordering::Relaxed) } /// Gets the rlimit with synchronization. /// /// Only called when handling the `getrlimit` or `prlimit` syscall. - pub fn get_cur_and_max(&self) -> (u64, u64) { + pub fn get_raw_rlimit(&self) -> RawRLimit64 { let _guard = self.lock.lock(); - (self.get_cur(), self.get_max()) + RawRLimit64 { + cur: self.cur.load(Ordering::Relaxed), + max: self.max.load(Ordering::Relaxed), + } } - /// Sets the rlimit with synchronization. + /// Sets the rlimit with synchronization and returns the old value. /// - /// Only called when handling the `setrlimit` or `prlimit` syscall - /// or during init process creation. - pub fn set_cur_and_max(&self, new_cur: u64, new_max: u64) -> Result<()> { - if new_cur > new_max { - return_errno_with_message!(Errno::EINVAL, "invalid rlimit"); + /// Only called when handling the `setrlimit` or `prlimit` syscall. + pub fn set_raw_rlimit(&self, new: RawRLimit64, ctx: &Context) -> Result { + if new.cur > new.max { + return_errno_with_message!(Errno::EINVAL, "the current rlimit exceeds the max rlimit"); } let _guard = self.lock.lock(); - self.cur.store(new_cur, Ordering::Relaxed); - self.max.store(new_max, Ordering::Relaxed); - Ok(()) + if new.max > self.get_max() { + let init_user_ns = UserNamespace::get_init_singleton(); + init_user_ns.check_cap(CapSet::SYS_RESOURCE, ctx.posix_thread)?; + } + let old = RawRLimit64 { + cur: self.cur.load(Ordering::Relaxed), + max: self.max.load(Ordering::Relaxed), + }; + self.set_raw_rlimit_unchecked(new); + Ok(old) + } + + /// Sets the rlimit _without_ synchronization and permission check. + /// + /// Only called during init process creation. + #[track_caller] + pub(self) fn set_raw_rlimit_unchecked(&self, new: RawRLimit64) { + assert!( + new.cur <= new.max, + "the current rlimit exceeds the max rlimit" + ); + self.cur.store(new.cur, Ordering::Relaxed); + self.max.store(new.max, Ordering::Relaxed); } } @@ -176,10 +192,10 @@ impl Default for RLimit64 { impl Clone for RLimit64 { fn clone(&self) -> Self { - let (cur, max) = self.get_cur_and_max(); + let raw_limit = self.get_raw_rlimit(); Self { - cur: AtomicU64::new(cur), - max: AtomicU64::new(max), + cur: AtomicU64::new(raw_limit.cur), + max: AtomicU64::new(raw_limit.max), lock: SpinLock::new(()), } } @@ -196,13 +212,15 @@ pub(super) fn new_resource_limits_for_init() -> ResourceLimits { // and other factors. // Reference: let max_threads: u64 = 100000; + let raw_rlimit = RawRLimit64 { + cur: max_threads / 2, + max: max_threads / 2, + }; resource_limits .get_rlimit(ResourceType::RLIMIT_NPROC) - .set_cur_and_max(max_threads / 2, max_threads / 2) - .unwrap(); + .set_raw_rlimit_unchecked(raw_rlimit); resource_limits .get_rlimit(ResourceType::RLIMIT_SIGPENDING) - .set_cur_and_max(max_threads / 2, max_threads / 2) - .unwrap(); + .set_raw_rlimit_unchecked(raw_rlimit); resource_limits } diff --git a/kernel/src/syscall/prlimit64.rs b/kernel/src/syscall/prlimit64.rs index ddc103e10..66f610c6c 100644 --- a/kernel/src/syscall/prlimit64.rs +++ b/kernel/src/syscall/prlimit64.rs @@ -8,39 +8,21 @@ use crate::{ process::{ Pid, Process, ResourceType, credentials::capabilities::CapSet, - posix_thread::{AsPosixThread, PosixThread}, + posix_thread::AsPosixThread, process_table, rlimit::{RawRLimit64, SYSCTL_NR_OPEN}, }, }; pub fn sys_getrlimit(resource: u32, rlim_addr: Vaddr, ctx: &Context) -> Result { - let resource = ResourceType::try_from(resource)?; - debug!("resource = {:?}, rlim_addr = 0x{:x}", resource, rlim_addr); - let resource_limits = ctx.process.resource_limits(); - let rlimit = resource_limits.get_rlimit(resource); - let (cur, max) = rlimit.get_cur_and_max(); - let rlimit_raw = RawRLimit64 { cur, max }; - ctx.user_space().write_val(rlim_addr, &rlimit_raw)?; + let old_raw = do_prlimit64(&ctx.process, resource, None, ctx)?; + ctx.user_space().write_val(rlim_addr, &old_raw)?; Ok(SyscallReturn::Return(0)) } pub fn sys_setrlimit(resource: u32, new_rlim_addr: Vaddr, ctx: &Context) -> Result { - let resource = ResourceType::try_from(resource)?; - debug!( - "resource = {:?}, new_rlim_addr = 0x{:x}", - resource, new_rlim_addr - ); - let new_raw: RawRLimit64 = ctx.user_space().read_val(new_rlim_addr)?; - let resource_limits = ctx.process.resource_limits(); - - if resource == ResourceType::RLIMIT_NOFILE && new_raw.max > SYSCTL_NR_OPEN { - return_errno_with_message!(Errno::EPERM, "the new limit exceeds the system limit"); - } - - resource_limits - .get_rlimit(resource) - .set_cur_and_max(new_raw.cur, new_raw.max)?; + let new_raw = ctx.user_space().read_val(new_rlim_addr)?; + do_prlimit64(&ctx.process, resource, Some(new_raw), ctx)?; Ok(SyscallReturn::Return(0)) } @@ -51,59 +33,67 @@ pub fn sys_prlimit64( old_rlim_addr: Vaddr, ctx: &Context, ) -> Result { - let resource = ResourceType::try_from(resource)?; - debug!( - "pid = {}, resource = {:?}, new_rlim_addr = 0x{:x}, old_rlim_addr = 0x{:x}", - pid, resource, new_rlim_addr, old_rlim_addr - ); + let userspace = ctx.user_space(); - let get_and_set_rlimit = |process: &Process| -> Result<()> { - let resource_limits = process.resource_limits(); - - if old_rlim_addr != 0 { - let rlimit = resource_limits.get_rlimit(resource); - let (cur, max) = rlimit.get_cur_and_max(); - let rlimit_raw = RawRLimit64 { cur, max }; - ctx.user_space().write_val(old_rlim_addr, &rlimit_raw)?; - } - if new_rlim_addr != 0 { - let new_raw: RawRLimit64 = ctx.user_space().read_val(new_rlim_addr)?; - debug!("new_rlimit = {:?}", new_raw); - if resource == ResourceType::RLIMIT_NOFILE && new_raw.max > SYSCTL_NR_OPEN { - return_errno_with_message!(Errno::EPERM, "the new limit exceeds the system limit"); - } - - resource_limits - .get_rlimit(resource) - .set_cur_and_max(new_raw.cur, new_raw.max)?; - } - - Ok(()) + let new_raw = if new_rlim_addr == 0 { + None + } else { + Some(userspace.read_val(new_rlim_addr)?) }; - if pid == 0 || pid == ctx.process.pid() { - get_and_set_rlimit(ctx.process.as_ref())?; + let old_raw = if pid == 0 || pid == ctx.process.pid() { + do_prlimit64(&ctx.process, resource, new_raw, ctx)? } else { let target_process = process_table::get_process(pid).ok_or_else(|| { Error::with_message(Errno::ESRCH, "the target process does not exist") })?; - // Check permissions - check_rlimit_perm(target_process.main_thread().as_posix_thread().unwrap(), ctx)?; - get_and_set_rlimit(target_process.as_ref())?; + check_rlimit_perm(&target_process, ctx)?; + do_prlimit64(&target_process, resource, new_raw, ctx)? + }; + + if old_rlim_addr != 0 { + userspace.write_val(old_rlim_addr, &old_raw)?; } + Ok(SyscallReturn::Return(0)) } +fn do_prlimit64( + target_process: &Process, + resource: u32, + new_raw: Option, + ctx: &Context, +) -> Result { + let resource = ResourceType::try_from(resource)?; + debug!( + "pid = {}, resource = {:?}, new_raw = {:?}", + target_process.pid(), + resource, + new_raw, + ); + + let rlimit = { + let resource_limits = target_process.resource_limits(); + resource_limits.get_rlimit(resource) + }; + + let old_raw = if let Some(new_raw) = new_raw { + if resource == ResourceType::RLIMIT_NOFILE && new_raw.max > SYSCTL_NR_OPEN { + return_errno_with_message!(Errno::EPERM, "the new limit exceeds the system limit"); + } + rlimit.set_raw_rlimit(new_raw, ctx)? + } else { + rlimit.get_raw_rlimit() + }; + + Ok(old_raw) +} + /// Checks whether the current process has permission to access /// the resource limits of the target process. // Reference: -fn check_rlimit_perm(target: &PosixThread, ctx: &Context) -> Result<()> { - let target_process = target.process(); - - let current_cred = ctx.posix_thread.credentials(); - let target_cred = target.credentials(); - +fn check_rlimit_perm(target_process: &Process, ctx: &Context) -> Result<()> { if target_process .user_ns() .lock() @@ -113,8 +103,16 @@ fn check_rlimit_perm(target: &PosixThread, ctx: &Context) -> Result<()> { return Ok(()); } - let current_ruid = current_cred.ruid(); - let current_rgid = current_cred.rgid(); + let (current_ruid, current_rgid) = { + let current_cred = ctx.posix_thread.credentials(); + (current_cred.ruid(), current_cred.rgid()) + }; + + let target_cred = target_process + .main_thread() + .as_posix_thread() + .unwrap() + .credentials(); if current_ruid == target_cred.ruid() && current_ruid == target_cred.euid() diff --git a/test/src/syscall/ltp/testcases/all.txt b/test/src/syscall/ltp/testcases/all.txt index 8907aefd6..0465c49ed 100644 --- a/test/src/syscall/ltp/testcases/all.txt +++ b/test/src/syscall/ltp/testcases/all.txt @@ -1446,8 +1446,8 @@ setreuid06 # setreuid07_16 # setrlimit01 -# setrlimit02 -# setrlimit03 +setrlimit02 +setrlimit03 setrlimit04 setrlimit05 # setrlimit06