From 74dd519a10c2aeee49233c27d7cf57db5ace5bfb Mon Sep 17 00:00:00 2001 From: Chen Chengjun Date: Fri, 21 Nov 2025 09:27:13 +0000 Subject: [PATCH] Support PR_SET/GET_SECUREBITS --- .../src/process/credentials/credentials_.rs | 52 ++++-- kernel/src/process/credentials/mod.rs | 2 + kernel/src/process/credentials/secure_bits.rs | 154 ++++++++++++++++++ kernel/src/process/credentials/static_cap.rs | 29 +++- kernel/src/process/execve.rs | 2 +- kernel/src/syscall/prctl.rs | 23 ++- 6 files changed, 246 insertions(+), 16 deletions(-) create mode 100644 kernel/src/process/credentials/secure_bits.rs diff --git a/kernel/src/process/credentials/credentials_.rs b/kernel/src/process/credentials/credentials_.rs index 528e504d7..e9d5469a6 100644 --- a/kernel/src/process/credentials/credentials_.rs +++ b/kernel/src/process/credentials/credentials_.rs @@ -1,10 +1,12 @@ // SPDX-License-Identifier: MPL-2.0 -use core::sync::atomic::{AtomicBool, Ordering}; +use core::sync::atomic::Ordering; use ostd::sync::{PreemptDisabled, RwLockReadGuard, RwLockWriteGuard}; -use super::{group::AtomicGid, user::AtomicUid, Gid, Uid}; +use super::{ + group::AtomicGid, secure_bits::AtomicSecureBits, user::AtomicUid, Gid, SecureBits, Uid, +}; use crate::{ prelude::*, process::credentials::capabilities::{AtomicCapSet, CapSet}, @@ -47,8 +49,8 @@ pub(super) struct Credentials_ { /// Capability that we can actually use effective_capset: AtomicCapSet, - /// Keep capabilities flag - keep_capabilities: AtomicBool, + /// Secure bits flags + securebits: AtomicSecureBits, } impl Credentials_ { @@ -70,7 +72,7 @@ impl Credentials_ { inheritable_capset: AtomicCapSet::new(capset), permitted_capset: AtomicCapSet::new(capset), effective_capset: AtomicCapSet::new(capset), - keep_capabilities: AtomicBool::new(false), + securebits: AtomicSecureBits::new(SecureBits::new_empty()), } } @@ -97,7 +99,7 @@ impl Credentials_ { } pub(super) fn keep_capabilities(&self) -> bool { - self.keep_capabilities.load(Ordering::Relaxed) + self.securebits.load(Ordering::Relaxed).keep_capabilities() } pub(super) fn set_uid(&self, uid: Uid) { @@ -259,6 +261,13 @@ impl Credentials_ { old_suid }; + // If the `SECBIT_NO_SETUID_FIXUP` bit is set, do not adjust capabilities. + // Reference: The "SECBIT_NO_SETUID_FIXUP" section in + // https://man7.org/linux/man-pages/man7/capabilities.7.html + if self.securebits().no_setuid_fixup() { + return; + } + // Begin to adjust capabilities. // Reference: The "Effect of user ID changes on capabilities" section in // https://man7.org/linux/man-pages/man7/capabilities.7.html @@ -397,9 +406,15 @@ impl Credentials_ { self.sgid.store(sgid, Ordering::Relaxed); } - pub(super) fn set_keep_capabilities(&self, keep_capabilities: bool) { - self.keep_capabilities - .store(keep_capabilities, Ordering::Relaxed); + pub(super) fn set_keep_capabilities(&self, keep_capabilities: bool) -> Result<()> { + let current_bits = self.securebits(); + let stored_bits = if !keep_capabilities { + current_bits - SecureBits::KEEP_CAPS + } else { + current_bits | SecureBits::KEEP_CAPS + }; + + self.securebits.try_store(stored_bits, Ordering::Relaxed) } // For `setregid`, rgid can *NOT* be set to old sgid, @@ -503,6 +518,23 @@ impl Credentials_ { self.effective_capset .store(effective_capset, Ordering::Relaxed); } + + // ******* SecureBits methods ******* + + pub(super) fn securebits(&self) -> SecureBits { + self.securebits.load(Ordering::Relaxed) + } + + pub(super) fn set_securebits(&self, securebits: SecureBits) -> Result<()> { + if !self.effective_capset().contains(CapSet::SETPCAP) { + return_errno_with_message!( + Errno::EPERM, + "only threads with CAP_SETPCAP can change securebits" + ); + } + + self.securebits.try_store(securebits, Ordering::Relaxed) + } } impl Clone for Credentials_ { @@ -520,7 +552,7 @@ impl Clone for Credentials_ { inheritable_capset: self.inheritable_capset.clone(), permitted_capset: self.permitted_capset.clone(), effective_capset: self.effective_capset.clone(), - keep_capabilities: AtomicBool::new(self.keep_capabilities.load(Ordering::Relaxed)), + securebits: self.securebits.clone(), } } } diff --git a/kernel/src/process/credentials/mod.rs b/kernel/src/process/credentials/mod.rs index 2f36bbede..a7e8368f2 100644 --- a/kernel/src/process/credentials/mod.rs +++ b/kernel/src/process/credentials/mod.rs @@ -4,12 +4,14 @@ pub mod c_types; pub mod capabilities; mod credentials_; mod group; +mod secure_bits; mod static_cap; mod user; use aster_rights::FullOp; use credentials_::Credentials_; pub use group::Gid; +pub use secure_bits::SecureBits; pub use user::Uid; use crate::prelude::*; diff --git a/kernel/src/process/credentials/secure_bits.rs b/kernel/src/process/credentials/secure_bits.rs new file mode 100644 index 000000000..4d14abbc4 --- /dev/null +++ b/kernel/src/process/credentials/secure_bits.rs @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: MPL-2.0 + +use core::sync::atomic::{AtomicU16, Ordering}; + +use atomic_integer_wrapper::define_atomic_version_of_integer_like_type; +use bitflags::bitflags; + +use crate::prelude::*; + +bitflags! { + /// Represents the secure bits flags for a POSIX thread. + /// + /// These flags control the behavior of capabilities when changing UIDs. + /// Reference: + pub struct SecureBits: u16 { + /// If set, the kernel does not grant capabilities when a set-user-ID-root program + /// is executed, or when a process with an effective or real UID of 0 calls `execve`. + const NOROOT = 1 << 0; + + /// Make `NOROOT` bit immutable (irreversible). + const NOROOT_LOCKED = 1 << 1; + + /// If set, the kernel does not adjust the process's permitted, effective, and + /// ambient capability sets when the UIDs are switched between zero and nonzero values. + const NO_SETUID_FIXUP = 1 << 2; + + /// Make `NO_SETUID_FIXUP` bit immutable (irreversible). + const NO_SETUID_FIXUP_LOCKED = 1 << 3; + + /// If set, the kernel preserves permitted capabilities across UID changes, + /// specifically when all UIDs transition from root (0) to non-root values. + const KEEP_CAPS = 1 << 4; + + /// Make `KEEP_CAPS` bit immutable (irreversible). + const KEEP_CAPS_LOCKED = 1 << 5; + + /// If set, the kernel will not permit raising ambient capabilities via the + /// prctl PR_CAP_AMBIENT_RAISE operation. + const NO_CAP_AMBIENT_RAISE = 1 << 6; + + /// Make `NO_CAP_AMBIENT_RAISE` bit immutable (irreversible). + const NO_CAP_AMBIENT_RAISE_LOCKED = 1 << 7; + } +} + +impl SecureBits { + /// Mask of all lock bits. + const LOCK_MASK: u16 = 0b10101010; + /// Mask of all valid bits. + const ALL_VALID_BITS: u16 = Self::LOCK_MASK | (Self::LOCK_MASK >> 1); + + /// Creates a new `SecureBits` with default (empty) settings. + pub(super) const fn new_empty() -> Self { + SecureBits::empty() + } + + pub(super) const fn locked_bits(&self) -> SecureBits { + Self::from_bits_truncate((self.bits & Self::LOCK_MASK) >> 1) + } + + pub(super) fn keep_capabilities(&self) -> bool { + self.contains(SecureBits::KEEP_CAPS) + } + + pub(super) fn no_setuid_fixup(&self) -> bool { + self.contains(SecureBits::NO_SETUID_FIXUP) + } +} + +impl TryFrom for SecureBits { + type Error = Error; + + fn try_from(value: u16) -> Result { + if value & !SecureBits::ALL_VALID_BITS != 0 { + return_errno_with_message!(Errno::EINVAL, "invalid SecureBits value"); + } + + #[cfg(debug_assertions)] + { + // Warn about unsupported bits in debug builds. + const DUMMY_IMPL_BITS: u16 = + SecureBits::NOROOT.bits() | SecureBits::NO_CAP_AMBIENT_RAISE.bits(); + let dummy_bits = value & DUMMY_IMPL_BITS; + if dummy_bits != 0 { + warn!( + "Some SecureBits flags are unsupported currently: {:?}.", + SecureBits::from_bits_truncate(dummy_bits) + ); + } + } + + Ok(SecureBits { bits: value }) + } +} + +impl From for u16 { + fn from(value: SecureBits) -> Self { + value.bits() + } +} + +define_atomic_version_of_integer_like_type!(SecureBits, try_from = true, { + #[derive(Debug)] + struct AtomicSecureBitsInner(AtomicU16); +}); + +impl Clone for AtomicSecureBitsInner { + fn clone(&self) -> Self { + Self::new(self.load(Ordering::Relaxed)) + } +} + +/// An atomic wrapper around `SecureBits`. +#[derive(Debug, Clone)] +pub(super) struct AtomicSecureBits { + inner: AtomicSecureBitsInner, +} + +impl AtomicSecureBits { + /// Creates a new `AtomicSecureBits`. + pub(super) fn new(bits: SecureBits) -> Self { + Self { + inner: AtomicSecureBitsInner::new(bits), + } + } + + /// Loads the current `SecureBits` atomically. + pub(super) fn load(&self, ordering: Ordering) -> SecureBits { + self.inner.load(ordering) + } + + /// Attempts to store `SecureBits` atomically. + /// + /// Returning an error if one of the bits is locked. + pub(super) fn try_store(&self, bits: SecureBits, ordering: Ordering) -> Result<()> { + // A thread can only modify its own secure bits, so there are no + // race conditions and synchronization concerns. + + let current = self.inner.load(Ordering::Relaxed); + let locked_bits = current.locked_bits(); + + if locked_bits & current != locked_bits & bits { + return_errno_with_message!(Errno::EPERM, "one or more SecureBits are locked"); + } + + if SecureBits::LOCK_MASK & current.bits() & !bits.bits() != 0 { + return_errno_with_message!(Errno::EPERM, "cannot unlock the lock bits"); + } + + self.inner.store(bits, ordering); + + Ok(()) + } +} diff --git a/kernel/src/process/credentials/static_cap.rs b/kernel/src/process/credentials/static_cap.rs index b79ed965f..d8661296e 100644 --- a/kernel/src/process/credentials/static_cap.rs +++ b/kernel/src/process/credentials/static_cap.rs @@ -4,7 +4,7 @@ use aster_rights::{Dup, Read, TRights, Write}; use aster_rights_proc::require; use ostd::sync::{PreemptDisabled, RwLockReadGuard, RwLockWriteGuard}; -use super::{capabilities::CapSet, credentials_::Credentials_, Credentials, Gid, Uid}; +use super::{capabilities::CapSet, credentials_::Credentials_, Credentials, Gid, SecureBits, Uid}; use crate::prelude::*; impl Credentials { @@ -147,10 +147,12 @@ impl Credentials { /// Sets keep capabilities flag. /// + /// If the [`SecureBits::KEEP_CAPS_LOCKED`] is set, this method will return an error. + /// /// This method requires the `Write` right. #[require(R > Write)] - pub fn set_keep_capabilities(&self, keep_capabilities: bool) { - self.0.set_keep_capabilities(keep_capabilities); + pub fn set_keep_capabilities(&self, keep_capabilities: bool) -> Result<()> { + self.0.set_keep_capabilities(keep_capabilities) } // *********** Gid methods ********** @@ -247,6 +249,27 @@ impl Credentials { self.0.set_sgid(egid); } + // *********** SecureBits methods ********** + + /// Gets the secure bits. + /// + /// This method requires the `Read` right. + #[require(R > Read)] + pub fn securebits(&self) -> SecureBits { + self.0.securebits() + } + + /// Sets the secure bits. + /// + /// If the caller does not have the `CAP_SETPCAP` capability, or if it tries to set + /// locked bits, this method will return an error. + /// + /// This method requires the `Write` right. + #[require(R > Write)] + pub fn set_securebits(&self, securebits: SecureBits) -> Result<()> { + self.0.set_securebits(securebits) + } + // *********** Supplementary group methods ********** /// Acquires the read lock of supplementary group ids. diff --git a/kernel/src/process/execve.rs b/kernel/src/process/execve.rs index 828a19bb1..1cbb94eea 100644 --- a/kernel/src/process/execve.rs +++ b/kernel/src/process/execve.rs @@ -243,7 +243,7 @@ fn apply_caps_from_exec( let credentials = posix_thread.credentials_mut(); set_uid_from_elf(process, &credentials, elf_file)?; set_gid_from_elf(process, &credentials, elf_file)?; - credentials.set_keep_capabilities(false); + credentials.set_keep_capabilities(false)?; Ok(()) } diff --git a/kernel/src/syscall/prctl.rs b/kernel/src/syscall/prctl.rs index 9362af932..2ef37f748 100644 --- a/kernel/src/syscall/prctl.rs +++ b/kernel/src/syscall/prctl.rs @@ -3,7 +3,9 @@ use super::SyscallReturn; use crate::{ prelude::*, - process::{posix_thread::MAX_THREAD_NAME_LEN, signal::sig_num::SigNum}, + process::{ + credentials::SecureBits, posix_thread::MAX_THREAD_NAME_LEN, signal::sig_num::SigNum, + }, }; pub fn sys_prctl( @@ -58,7 +60,7 @@ pub fn sys_prctl( return_errno!(Errno::EINVAL) } let credentials = ctx.posix_thread.credentials_mut(); - credentials.set_keep_capabilities(keep_cap != 0); + credentials.set_keep_capabilities(keep_cap != 0)?; } PrctlCmd::PR_GET_NAME(write_to_addr) => { let thread_name = ctx.posix_thread.thread_name().lock(); @@ -87,6 +89,15 @@ pub fn sys_prctl( ctx.user_space() .write_val(write_addr, &(process.is_child_subreaper() as u32))?; } + PrctlCmd::PR_GET_SECUREBITS => { + let credentials = ctx.posix_thread.credentials(); + let securebits = credentials.securebits(); + return Ok(SyscallReturn::Return(securebits.bits() as _)); + } + PrctlCmd::PR_SET_SECUREBITS(securebits) => { + let credentials = ctx.posix_thread.credentials_mut(); + credentials.set_securebits(securebits)?; + } _ => todo!(), } Ok(SyscallReturn::Return(0)) @@ -100,6 +111,8 @@ const PR_GET_KEEPCAPS: i32 = 7; const PR_SET_KEEPCAPS: i32 = 8; const PR_SET_NAME: i32 = 15; const PR_GET_NAME: i32 = 16; +const PR_GET_SECUREBITS: i32 = 27; +const PR_SET_SECUREBITS: i32 = 28; const PR_SET_TIMERSLACK: i32 = 29; const PR_GET_TIMERSLACK: i32 = 30; const PR_SET_CHILD_SUBREAPER: i32 = 36; @@ -122,6 +135,8 @@ pub enum PrctlCmd { PR_GET_DUMPABLE, PR_SET_CHILD_SUBREAPER(bool), PR_GET_CHILD_SUBREAPER(Vaddr), + PR_GET_SECUREBITS, + PR_SET_SECUREBITS(SecureBits), } #[repr(u64)] @@ -150,6 +165,10 @@ impl PrctlCmd { PR_SET_KEEPCAPS => Ok(PrctlCmd::PR_SET_KEEPCAPS(arg2 as _)), PR_SET_CHILD_SUBREAPER => Ok(PrctlCmd::PR_SET_CHILD_SUBREAPER(arg2 > 0)), PR_GET_CHILD_SUBREAPER => Ok(PrctlCmd::PR_GET_CHILD_SUBREAPER(arg2 as _)), + PR_GET_SECUREBITS => Ok(PrctlCmd::PR_GET_SECUREBITS), + PR_SET_SECUREBITS => Ok(PrctlCmd::PR_SET_SECUREBITS(SecureBits::try_from( + arg2 as u16, + )?)), _ => { debug!("prctl cmd number: {}", option); return_errno_with_message!(Errno::EINVAL, "unsupported prctl command");