diff --git a/services/libs/jinux-std/src/process/credentials/credentials_.rs b/services/libs/jinux-std/src/process/credentials/credentials_.rs new file mode 100644 index 00000000..b26584da --- /dev/null +++ b/services/libs/jinux-std/src/process/credentials/credentials_.rs @@ -0,0 +1,347 @@ +use super::group::AtomicGid; +use super::user::AtomicUid; +use super::{Gid, Uid}; +use crate::prelude::*; +use jinux_frame::sync::{RwLockReadGuard, RwLockWriteGuard}; + +#[derive(Debug)] +pub(super) struct Credentials_ { + /// Real user id. The user to which the process belongs. + ruid: AtomicUid, + /// Effective user id. Used to determine the permissions granted to a process when it tries to perform various operations (i.e., system calls) + euid: AtomicUid, + /// Saved-set uid. Used by set_uid elf, the saved_set_uid will be set if the elf has setuid bit + suid: AtomicUid, + /// User id used for filesystem checks. + fsuid: AtomicUid, + + /// Real group id. The group to which the process belongs + rgid: AtomicGid, + /// Effective gid, + egid: AtomicGid, + /// Saved-set gid. Used by set_gid elf, the saved_set_gid will be set if the elf has setgid bit + sgid: AtomicGid, + /// Group id used for file system checks. + fsgid: AtomicGid, + + // A set of additional groups to which a process belongs. + supplementary_gids: RwLock>, +} + +impl Credentials_ { + /// Create a new credentials. ruid, euid, suid will be set as the same uid, and gid is the same. + pub fn new(uid: Uid, gid: Gid) -> Self { + let mut supplementary_gids = BTreeSet::new(); + supplementary_gids.insert(gid); + + Self { + ruid: AtomicUid::new(uid), + euid: AtomicUid::new(uid), + suid: AtomicUid::new(uid), + fsuid: AtomicUid::new(uid), + rgid: AtomicGid::new(gid), + egid: AtomicGid::new(gid), + sgid: AtomicGid::new(gid), + fsgid: AtomicGid::new(gid), + supplementary_gids: RwLock::new(supplementary_gids), + } + } + + fn is_privileged(&self) -> bool { + self.euid.is_root() + } + + // ******* Uid methods ******* + + pub(super) fn ruid(&self) -> Uid { + self.ruid.get() + } + + pub(super) fn euid(&self) -> Uid { + self.euid.get() + } + + pub(super) fn suid(&self) -> Uid { + self.suid.get() + } + + pub(super) fn fsuid(&self) -> Uid { + self.fsuid.get() + } + + pub(super) fn set_uid(&self, uid: Uid) { + if self.is_privileged() { + self.ruid.set(uid); + self.euid.set(uid); + self.suid.set(uid); + } else { + self.euid.set(uid); + } + } + + pub(super) fn set_reuid(&self, ruid: Option, euid: Option) -> Result<()> { + self.check_uid_perm(ruid.as_ref(), euid.as_ref(), None, false)?; + + let should_set_suid = ruid.is_some() || euid.is_some_and(|euid| euid != self.ruid()); + + self.set_resuid_unchecked(ruid, euid, None); + + if should_set_suid { + self.suid.set(self.euid()); + } + + // FIXME: should we set fsuid here? The linux document for syscall `setfsuid` is contradictory + // with the document of syscall `setreuid`. The `setfsuid` document says the `fsuid` is always + // the same as `euid`, but `setreuid` does not mention the `fsuid` should be set. + self.fsuid.set(self.euid()); + + Ok(()) + } + + pub(super) fn set_resuid( + &self, + ruid: Option, + euid: Option, + suid: Option, + ) -> Result<()> { + self.check_uid_perm(ruid.as_ref(), euid.as_ref(), suid.as_ref(), true)?; + + self.set_resuid_unchecked(ruid, euid, suid); + + self.fsuid.set(self.euid()); + + Ok(()) + } + + pub(super) fn set_fsuid(&self, fsuid: Option) -> Result { + let old_fsuid = self.fsuid(); + + let Some(fsuid) = fsuid else { + return Ok(old_fsuid); + }; + + if self.is_privileged() { + self.fsuid.set(fsuid); + return Ok(old_fsuid); + } + + if fsuid != self.ruid() && fsuid != self.euid() && fsuid != self.suid() { + return_errno_with_message!( + Errno::EPERM, + "fsuid can only be one of old ruid, old euid and old suid." + ) + } + + self.fsuid.set(fsuid); + + Ok(old_fsuid) + } + + pub(super) fn set_euid(&self, euid: Uid) { + self.euid.set(euid); + } + + pub(super) fn set_suid(&self, suid: Uid) { + self.suid.set(suid); + } + + // For `setreuid`, ruid can *NOT* be set to old suid, + // while for `setresuid`, ruid can be set to old suid. + fn check_uid_perm( + &self, + ruid: Option<&Uid>, + euid: Option<&Uid>, + suid: Option<&Uid>, + ruid_may_be_old_suid: bool, + ) -> Result<()> { + if self.is_privileged() { + return Ok(()); + } + + if let Some(ruid) = ruid && *ruid != self.ruid() && *ruid != self.euid() && (!ruid_may_be_old_suid || *ruid != self.suid()) { + return_errno_with_message!(Errno::EPERM, "ruid can only be one of old ruid, old euid (and old suid)."); + } + + if let Some(euid) = euid && *euid != self.ruid() && *euid != self.euid() && *euid != self.suid() { + return_errno_with_message!(Errno::EPERM, "euid can only be one of old ruid, old euid and old suid.") + } + + if let Some(suid) = suid && *suid != self.ruid() && *suid != self.euid() && *suid != self.suid() { + return_errno_with_message!(Errno::EPERM, "suid can only be one of old ruid, old euid and old suid.") + } + + Ok(()) + } + + fn set_resuid_unchecked(&self, ruid: Option, euid: Option, suid: Option) { + if let Some(ruid) = ruid { + self.ruid.set(ruid); + } + + if let Some(euid) = euid { + self.euid.set(euid); + } + + if let Some(suid) = suid { + self.suid.set(suid); + } + } + + // ******* Gid methods ******* + + pub(super) fn rgid(&self) -> Gid { + self.rgid.get() + } + + pub(super) fn egid(&self) -> Gid { + self.egid.get() + } + + pub(super) fn sgid(&self) -> Gid { + self.sgid.get() + } + + pub(super) fn fsgid(&self) -> Gid { + self.fsgid.get() + } + + pub(super) fn set_gid(&self, gid: Gid) { + if self.is_privileged() { + self.rgid.set(gid); + self.egid.set(gid); + self.sgid.set(gid); + } else { + self.egid.set(gid); + } + } + + pub(super) fn set_regid(&self, rgid: Option, egid: Option) -> Result<()> { + self.check_gid_perm(rgid.as_ref(), egid.as_ref(), None, false)?; + + let should_set_sgid = rgid.is_some() || egid.is_some_and(|egid| egid != self.rgid()); + + self.set_resgid_unchecked(rgid, egid, None); + + if should_set_sgid { + self.sgid.set(self.egid()); + } + + self.fsgid.set(self.egid()); + + Ok(()) + } + + pub(super) fn set_resgid( + &self, + rgid: Option, + egid: Option, + sgid: Option, + ) -> Result<()> { + self.check_gid_perm(rgid.as_ref(), egid.as_ref(), sgid.as_ref(), true)?; + + self.set_resgid_unchecked(rgid, egid, sgid); + + self.fsgid.set(self.egid()); + + Ok(()) + } + + pub(super) fn set_fsgid(&self, fsgid: Option) -> Result { + let old_fsgid = self.fsgid(); + + let Some(fsgid) = fsgid else { + return Ok(old_fsgid); + }; + + if self.is_privileged() { + self.fsgid.set(fsgid); + return Ok(old_fsgid); + } + + if fsgid != self.rgid() && fsgid != self.egid() && fsgid != self.sgid() { + return_errno_with_message!( + Errno::EPERM, + "fsuid can only be one of old ruid, old euid and old suid." + ) + } + + self.fsgid.set(fsgid); + + Ok(old_fsgid) + } + + pub(super) fn set_egid(&self, egid: Gid) { + self.egid.set(egid); + } + + pub(super) fn set_sgid(&self, sgid: Gid) { + self.sgid.set(sgid); + } + + // For `setregid`, rgid can *NOT* be set to old sgid, + // while for `setresgid`, ruid can be set to old sgid. + fn check_gid_perm( + &self, + rgid: Option<&Gid>, + egid: Option<&Gid>, + sgid: Option<&Gid>, + rgid_may_be_old_sgid: bool, + ) -> Result<()> { + if self.is_privileged() { + return Ok(()); + } + + if let Some(rgid) = rgid && *rgid != self.rgid() && *rgid != self.egid() && (!rgid_may_be_old_sgid || *rgid != self.sgid()) { + return_errno_with_message!(Errno::EPERM, "rgid can only be one of old rgid, old egid (and old sgid)."); + } + + if let Some(egid) = egid && *egid != self.rgid() && *egid != self.egid() && *egid != self.sgid() { + return_errno_with_message!(Errno::EPERM, "egid can only be one of old rgid, old egid and old sgid.") + } + + if let Some(sgid) = sgid && *sgid != self.rgid() && *sgid != self.egid() && *sgid != self.sgid() { + return_errno_with_message!(Errno::EPERM, "sgid can only be one of old rgid, old egid and old sgid.") + } + + Ok(()) + } + + fn set_resgid_unchecked(&self, rgid: Option, egid: Option, sgid: Option) { + if let Some(rgid) = rgid { + self.rgid.set(rgid); + } + + if let Some(egid) = egid { + self.egid.set(egid); + } + + if let Some(sgid) = sgid { + self.sgid.set(sgid); + } + } + + // ******* Supplementary groups methods ******* + pub(super) fn groups(&self) -> RwLockReadGuard<'_, BTreeSet> { + self.supplementary_gids.read() + } + + pub(super) fn groups_mut(&self) -> RwLockWriteGuard<'_, BTreeSet> { + self.supplementary_gids.write() + } +} + +impl Clone for Credentials_ { + fn clone(&self) -> Self { + Self { + ruid: self.ruid.clone(), + euid: self.euid.clone(), + suid: self.suid.clone(), + fsuid: self.fsuid.clone(), + rgid: self.rgid.clone(), + egid: self.egid.clone(), + sgid: self.sgid.clone(), + fsgid: self.fsgid.clone(), + supplementary_gids: RwLock::new(self.supplementary_gids.read().clone()), + } + } +} diff --git a/services/libs/jinux-std/src/process/credentials/group.rs b/services/libs/jinux-std/src/process/credentials/group.rs new file mode 100644 index 00000000..9a0d6c5b --- /dev/null +++ b/services/libs/jinux-std/src/process/credentials/group.rs @@ -0,0 +1,50 @@ +use core::sync::atomic::{AtomicU32, Ordering}; + +use crate::prelude::*; + +#[derive(Debug, Clone, Copy, Pod, Default, PartialEq, Eq, PartialOrd, Ord)] +#[repr(C)] +pub struct Gid(u32); + +impl Gid { + pub const fn new(gid: u32) -> Self { + Self(gid) + } + + pub const fn new_root() -> Self { + Self(ROOT_GID) + } + + pub const fn as_u32(&self) -> u32 { + self.0 + } + + pub const fn is_root(&self) -> bool { + self.0 == ROOT_GID + } +} + +const ROOT_GID: u32 = 0; + +#[derive(Debug)] +pub(super) struct AtomicGid(AtomicU32); + +impl AtomicGid { + pub const fn new(gid: Gid) -> Self { + Self(AtomicU32::new(gid.as_u32())) + } + + pub fn set(&self, gid: Gid) { + self.0.store(gid.as_u32(), Ordering::Relaxed) + } + + pub fn get(&self) -> Gid { + Gid(self.0.load(Ordering::Relaxed)) + } +} + +impl Clone for AtomicGid { + fn clone(&self) -> Self { + Self(AtomicU32::new(self.0.load(Ordering::Relaxed))) + } +} diff --git a/services/libs/jinux-std/src/process/credentials/mod.rs b/services/libs/jinux-std/src/process/credentials/mod.rs new file mode 100644 index 00000000..e346aeb9 --- /dev/null +++ b/services/libs/jinux-std/src/process/credentials/mod.rs @@ -0,0 +1,45 @@ +mod credentials_; +mod group; +mod static_cap; +mod user; + +use crate::prelude::*; +use credentials_::Credentials_; +use jinux_rights::{FullOp, ReadOp, WriteOp}; + +pub use group::Gid; +pub use user::Uid; + +use super::posix_thread::PosixThreadExt; + +/// `Credentials` represents a set of associated numeric user ids (UIDs) and group identifiers (GIDs) +/// for a process. +/// These identifiers are as follows: +/// - real user ID and group ID; +/// - effective user ID and group ID; +/// - saved-set user ID and saved-set group ID; +/// - file system user ID and group ID (Linux-specific); +/// - supplementary group IDs. +pub struct Credentials(Arc, R); + +/// Gets read-only credentials of current thread. +/// +/// # Panic +/// +/// This method should only be called in process context. +pub fn credentials() -> Credentials { + let current_thread = current_thread!(); + let posix_thread = current_thread.as_posix_thread().unwrap(); + posix_thread.credentials() +} + +/// Gets write-only credentials of current thread. +/// +/// # Panic +/// +/// This method should only be called in process context. +pub fn credentials_mut() -> Credentials { + let current_thread = current_thread!(); + let posix_thread = current_thread.as_posix_thread().unwrap(); + posix_thread.credentials_mut() +} diff --git a/services/libs/jinux-std/src/process/credentials/static_cap.rs b/services/libs/jinux-std/src/process/credentials/static_cap.rs new file mode 100644 index 00000000..ad216284 --- /dev/null +++ b/services/libs/jinux-std/src/process/credentials/static_cap.rs @@ -0,0 +1,248 @@ +use super::credentials_::Credentials_; +use super::{Credentials, Gid, Uid}; +use crate::prelude::*; +use jinux_frame::sync::{RwLockReadGuard, RwLockWriteGuard}; +use jinux_rights::{Dup, Read, TRights, Write}; +use jinux_rights_proc::require; + +impl Credentials { + /// Creates a root `Credentials`. This method can only be used when creating the first process + pub fn new_root() -> Self { + let uid = Uid::new_root(); + let gid = Gid::new_root(); + let credentials_ = Arc::new(Credentials_::new(uid, gid)); + Self(credentials_, R::new()) + } + + /// Clones a new `Credentials` from an existing `Credentials`. + /// + /// This method requires the `Read` right. + #[require(R1 > Read)] + pub fn new_from(credentials: &Credentials) -> Self { + let credentials_ = Arc::new(credentials.0.as_ref().clone()); + + Self(credentials_, R::new()) + } + + /// Duplicates the capabilities. + /// + /// This method requires the `Dup` right. + #[require(R > Dup)] + pub fn dup(&self) -> Self { + Self(self.0.clone(), self.1) + } + + /// Restricts capabilities to a smaller set. + #[require(R > R1)] + pub fn restrict(self) -> Credentials { + let Credentials(credentials_, _) = self; + + Credentials(credentials_, R1::new()) + } + + // *********** Uid methods ********** + + /// Gets real user id. + /// + /// This method requies the `Read` right. + #[require(R > Read)] + pub fn ruid(&self) -> Uid { + self.0.ruid() + } + + /// Gets effective user id. + /// + /// This method requies the `Read` right. + #[require(R > Read)] + pub fn euid(&self) -> Uid { + self.0.euid() + } + + /// Gets saved-set user id. + /// + /// This method requies the `Read` right. + #[require(R > Read)] + pub fn suid(&self) -> Uid { + self.0.suid() + } + + /// Gets file system user id. + /// + /// This method requies the `Read` right. + #[require(R > Read)] + pub fn fsuid(&self) -> Uid { + self.0.fsuid() + } + + /// Sets uid. If self is privileged, sets the effective, real, saved-set user ids as `uid`, + /// Otherwise, sets effective user id as `uid`. + /// + /// This method requires the `Write` right. + #[require(R > Write)] + pub fn set_uid(&self, uid: Uid) { + self.0.set_uid(uid); + } + + /// Sets real, effective user ids as `ruid`, `euid` respectively. if `ruid` or `euid` + /// is `None`, the corresponding user id will leave unchanged. + /// + /// This method requires the `Write` right. + #[require(R > Write)] + pub fn set_reuid(&self, ruid: Option, euid: Option) -> Result<()> { + self.0.set_reuid(ruid, euid) + } + + /// Sets real, effective, saved-set user ids as `ruid`, `euid`, `suid` respectively. if + /// `ruid`, `euid` or `suid` is `None`, the corresponding user id will leave unchanged. + /// + /// This method requires the `Write` right. + #[require(R > Write)] + pub fn set_resuid( + &self, + ruid: Option, + euid: Option, + suid: Option, + ) -> Result<()> { + self.0.set_resuid(ruid, euid, suid) + } + + /// Sets file system user id as `fsuid`. Returns the original file system user id. + /// If `fsuid` is None, leaves file system user id unchanged. + /// + /// This method requires the `Write` right. + #[require(R > Write)] + pub fn set_fsuid(&self, fsuid: Option) -> Result { + self.0.set_fsuid(fsuid) + } + + /// Sets effective user id as `euid`. This method should only be used when executing a file + /// whose `setuid` bit is set. + /// + /// This method requires the `Write` right. + #[require(R > Write)] + pub fn set_euid(&self, euid: Uid) { + self.0.set_euid(euid); + } + + /// Sets saved-set user id as the same of effective user id. This method should only be used when + /// executing a new executable file. + /// + /// This method requires the `Write` right. + #[require(R > Write)] + pub fn reset_suid(&self) { + let euid = self.0.euid(); + self.0.set_suid(euid); + } + + // *********** Gid methods ********** + + /// Gets real group id. + /// + /// This method requies the `Read` right. + #[require(R > Read)] + pub fn rgid(&self) -> Gid { + self.0.rgid() + } + + /// Gets effective group id. + /// + /// This method requies the `Read` right. + #[require(R > Read)] + pub fn egid(&self) -> Gid { + self.0.egid() + } + + /// Gets saved-set group id. + /// + /// This method requies the `Read` right. + #[require(R > Read)] + pub fn sgid(&self) -> Gid { + self.0.sgid() + } + + /// Gets file system group id. + /// + /// This method requies the `Read` right. + #[require(R > Read)] + pub fn fsgid(&self) -> Gid { + self.0.fsgid() + } + + /// Sets gid. If self is privileged, sets the effective, real, saved-set group ids as `gid`, + /// Otherwise, sets effective group id as `gid`. + /// + /// This method requires the `Write` right. + #[require(R > Write)] + pub fn set_gid(&self, gid: Gid) { + self.0.set_gid(gid); + } + + /// Sets real, effective group ids as `rgid`, `egid` respectively. if `rgid` or `egid` + /// is `None`, the corresponding group id will leave unchanged. + /// + /// This method requires the `Write` right. + #[require(R > Write)] + pub fn set_regid(&self, rgid: Option, egid: Option) -> Result<()> { + self.0.set_regid(rgid, egid) + } + + /// Sets real, effective, saved-set group ids as `rgid`, `egid`, `sgid` respectively. if + /// `rgid`, `egid` or `sgid` is `None`, the corresponding group id will leave unchanged. + /// + /// This method requires the `Write` right. + #[require(R > Write)] + pub fn set_resgid( + &self, + rgid: Option, + egid: Option, + sgid: Option, + ) -> Result<()> { + self.0.set_resgid(rgid, egid, sgid) + } + + /// Sets file system group id as `fsgid`. Returns the original file system group id. + /// If `fsgid` is None, leaves file system group id unchanged. + /// + /// This method requires the `Write` right. + #[require(R > Write)] + pub fn set_fsgid(&self, fsgid: Option) -> Result { + self.0.set_fsgid(fsgid) + } + + /// Sets effective group id as `egid`. This method should only be used when executing a file + /// whose `setgid` bit is set. + /// + /// This method requires the `Write` right. + #[require(R > Write)] + pub fn set_egid(&self, egid: Gid) { + self.0.set_egid(egid); + } + + /// Sets saved-set group id as the same of effective group id. This method should only be used when + /// executing a new executable file. + /// + /// This method requires the `Write` right. + #[require(R > Write)] + pub fn reset_sgid(&self) { + let egid = self.0.egid(); + self.0.set_sgid(egid); + } + + // *********** Supplementary group methods ********** + + /// Acquires the read lock of supplementary group ids. + /// + /// This method requies the `Read` right. + #[require(R > Read)] + pub fn groups(&self) -> RwLockReadGuard<'_, BTreeSet> { + self.0.groups() + } + + /// Acquires the write lock of supplementary group ids. + /// + /// This method requires the `Write` right. + #[require(R > Write)] + pub fn groups_mut(&self) -> RwLockWriteGuard<'_, BTreeSet> { + self.0.groups_mut() + } +} diff --git a/services/libs/jinux-std/src/process/credentials/user.rs b/services/libs/jinux-std/src/process/credentials/user.rs new file mode 100644 index 00000000..a10e8db7 --- /dev/null +++ b/services/libs/jinux-std/src/process/credentials/user.rs @@ -0,0 +1,54 @@ +use core::sync::atomic::{AtomicU32, Ordering}; + +use crate::prelude::*; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Pod)] +#[repr(C)] +pub struct Uid(u32); + +const ROOT_UID: u32 = 0; + +impl Uid { + pub const fn new_root() -> Self { + Self(ROOT_UID) + } + + pub const fn new(uid: u32) -> Self { + Self(uid) + } + + pub const fn is_root(&self) -> bool { + self.0 == ROOT_UID + } + + pub const fn as_u32(&self) -> u32 { + self.0 + } +} + +#[derive(Debug)] +pub(super) struct AtomicUid(AtomicU32); + +impl AtomicUid { + pub const fn new(uid: Uid) -> Self { + Self(AtomicU32::new(uid.as_u32())) + } + + pub fn set(&self, uid: Uid) { + self.0.store(uid.as_u32(), Ordering::Release) + } + + pub fn get(&self) -> Uid { + Uid(self.0.load(Ordering::Acquire)) + } + + pub fn is_root(&self) -> bool { + self.get().is_root() + } +} + +impl Clone for AtomicUid { + fn clone(&self) -> Self { + Self(AtomicU32::new(self.0.load(Ordering::Acquire))) + } +}