From 9c023db7b50dd8c40a8a89e04d748df3bd6615bf Mon Sep 17 00:00:00 2001 From: Jianfeng Jiang Date: Wed, 11 Feb 2026 11:18:48 +0000 Subject: [PATCH] Support nsfs --- kernel/src/fs/path/mount_namespace.rs | 32 ++- kernel/src/fs/procfs/pid/task/mod.rs | 6 +- kernel/src/fs/procfs/pid/task/ns.rs | 180 ++++++++++++ kernel/src/fs/pseudofs/mod.rs | 4 + kernel/src/fs/pseudofs/nsfs.rs | 365 ++++++++++++++++++++++++ kernel/src/net/uts_ns.rs | 44 ++- kernel/src/process/namespace/user_ns.rs | 59 +++- 7 files changed, 678 insertions(+), 12 deletions(-) create mode 100644 kernel/src/fs/procfs/pid/task/ns.rs create mode 100644 kernel/src/fs/pseudofs/nsfs.rs diff --git a/kernel/src/fs/path/mount_namespace.rs b/kernel/src/fs/path/mount_namespace.rs index d8629d57c..6b4bc9b0c 100644 --- a/kernel/src/fs/path/mount_namespace.rs +++ b/kernel/src/fs/path/mount_namespace.rs @@ -5,6 +5,7 @@ use spin::Once; use crate::{ fs::{ path::{Mount, Path, PathResolver}, + pseudofs::{NsCommonOps, NsFs, NsType}, ramfs::RamFs, }, prelude::*, @@ -22,6 +23,8 @@ pub struct MountNamespace { root: Arc, /// The user namespace that owns this mount namespace. owner: Arc, + /// The path in nsfs. + path: Path, } impl MountNamespace { @@ -36,7 +39,8 @@ impl MountNamespace { Arc::new_cyclic(|weak_self| { let root = Mount::new_root(rootfs, weak_self.clone()); - MountNamespace { root, owner } + let path = NsFs::new_path(weak_self.clone()); + MountNamespace { root, owner, path } }) }) } @@ -73,10 +77,11 @@ impl MountNamespace { let new_mnt_ns = Arc::new_cyclic(|weak_self| { let new_root = root_mount.clone_mount_tree(root_mount.root_dentry(), Some(weak_self), true); - + let path = NsFs::new_path(weak_self.clone()); MountNamespace { root: new_root, owner, + path, } }); @@ -134,3 +139,26 @@ impl Drop for MountNamespace { } } } + +impl NsCommonOps for MountNamespace { + const TYPE: NsType = NsType::Mnt; + + fn get_owner_user_ns(&self) -> Result<&Arc> { + Ok(&self.owner) + } + + fn get_parent(&self) -> Result> { + return_errno_with_message!( + Errno::EINVAL, + "mnt namespace does not have parent namespace" + ); + } + + fn path(&self) -> &Path { + &self.path + } + + fn as_any(&self) -> &dyn Any { + self + } +} diff --git a/kernel/src/fs/procfs/pid/task/mod.rs b/kernel/src/fs/procfs/pid/task/mod.rs index 54cbbfdd2..59055be4c 100644 --- a/kernel/src/fs/procfs/pid/task/mod.rs +++ b/kernel/src/fs/procfs/pid/task/mod.rs @@ -12,8 +12,8 @@ use crate::{ cgroup::CgroupFileOps, cmdline::CmdlineFileOps, comm::CommFileOps, environ::EnvironFileOps, exe::ExeSymOps, fd::FdDirOps, gid_map::GidMapFileOps, maps::MapsFileOps, mem::MemFileOps, mountinfo::MountInfoFileOps, - mounts::MountsFileOps, oom_score_adj::OomScoreAdjFileOps, stat::StatFileOps, - status::StatusFileOps, uid_map::UidMapFileOps, + mounts::MountsFileOps, ns::NsDirOps, oom_score_adj::OomScoreAdjFileOps, + stat::StatFileOps, status::StatusFileOps, uid_map::UidMapFileOps, }, template::{ DirOps, ProcDir, ProcDirBuilder, lookup_child_from_table, @@ -38,6 +38,7 @@ mod maps; mod mem; mod mountinfo; mod mounts; +mod ns; mod oom_score_adj; mod stat; mod status; @@ -111,6 +112,7 @@ impl TidDirOps { ("gid_map", GidMapFileOps::new_inode), ("mem", MemFileOps::new_inode), ("mountinfo", MountInfoFileOps::new_inode), + ("ns", NsDirOps::new_inode), ("oom_score_adj", OomScoreAdjFileOps::new_inode), ("stat", StatFileOps::new_inode), ("status", StatusFileOps::new_inode), diff --git a/kernel/src/fs/procfs/pid/task/ns.rs b/kernel/src/fs/procfs/pid/task/ns.rs new file mode 100644 index 000000000..441d2b599 --- /dev/null +++ b/kernel/src/fs/procfs/pid/task/ns.rs @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: MPL-2.0 + +use core::marker::PhantomData; + +use aster_util::slot_vec::SlotVec; +use ostd::sync::RwMutexUpgradeableGuard; + +use crate::{ + fs::{ + path::{MountNamespace, Path}, + procfs::{ + DirOps, ProcDir, ProcDirBuilder, ProcSymBuilder, SymOps, pid::TidDirOps, + template::ProcSym, + }, + pseudofs::NsCommonOps, + utils::{DirEntryVecExt, Inode, SymbolicLink, mkmod}, + }, + net::uts_ns::UtsNamespace, + prelude::*, + process::{NsProxy, UserNamespace, posix_thread::AsPosixThread}, +}; + +/// Represents the inode at `/proc/[pid]/task/[tid]/ns` (and also `/proc/[pid]/ns`). +pub(super) struct NsDirOps { + dir: TidDirOps, +} + +impl NsDirOps { + /// Creates a new directory inode for the `ns` directory. + pub fn new_inode(dir: &TidDirOps, parent: Weak) -> Arc { + ProcDirBuilder::new( + Self { dir: dir.clone() }, + // Reference: + mkmod!(u + r, a + x), + ) + .parent(parent) + .build() + .unwrap() + } +} + +impl NsDirOps { + /// The set of namespace entries that depend on the thread's [`NsProxy`]. + #[expect(clippy::type_complexity)] + const NS_PROXY_ENTRIES: &[(&str, fn(&NsProxy, Weak) -> Arc)] = &[ + ("uts", |proxy, parent| { + NsSymOps::new_inode(proxy.uts_ns(), parent) + }), + ("mnt", |proxy, parent| { + NsSymOps::new_inode(proxy.mnt_ns(), parent) + }), + ]; + + /// Looks up a namespace symlink backed by the thread's [`NsProxy`]. + fn lookup_ns_proxy_child( + &self, + name: &str, + parent: Weak, + ) -> Option>> { + let constructor = Self::NS_PROXY_ENTRIES + .iter() + .find(|(entry_name, _)| *entry_name == name) + .map(|(_, ctor)| ctor)?; + + let thread = self.dir.thread(); + let ns_proxy = thread.as_posix_thread().unwrap().ns_proxy().lock(); + let Some(ns_proxy) = ns_proxy.as_ref() else { + return Some(Err(Error::with_message( + Errno::ENOENT, + "the thread's namespace proxy no longer exists", + ))); + }; + + Some(Ok(constructor(ns_proxy, parent))) + } +} + +impl DirOps for NsDirOps { + fn lookup_child(&self, dir: &ProcDir, name: &str) -> Result> { + let inode = if let Some(result) = self.lookup_ns_proxy_child(name, dir.this_weak().clone()) + { + result? + } else if name == "user" { + let user_ns = self.dir.process_ref.user_ns().lock(); + NsSymOps::new_inode(&*user_ns, dir.this_weak().clone()) + } else { + return_errno_with_message!(Errno::ENOENT, "the file does not exist"); + }; + + let mut cached_children = dir.cached_children().write(); + cached_children.remove_entry_by_name(name); + cached_children.put((name.to_string(), inode.clone())); + Ok(inode) + } + + fn populate_children<'a>( + &self, + dir: &'a ProcDir, + ) -> RwMutexUpgradeableGuard<'a, SlotVec<(String, Arc)>> { + let mut cached_children = dir.cached_children().write(); + + let thread = self.dir.thread(); + let ns_proxy = thread.as_posix_thread().unwrap().ns_proxy().lock(); + + // Refresh NsProxy-backed entries: remove stale ones and re-add if the proxy is alive. + for &(name, constructor) in Self::NS_PROXY_ENTRIES { + cached_children.remove_entry_by_name(name); + if let Some(ns_proxy) = ns_proxy.as_ref() { + let inode = constructor(ns_proxy, dir.this_weak().clone()); + cached_children.put((name.to_string(), inode)); + } + } + + // The user namespace never changes, so only insert if absent. + cached_children.put_entry_if_not_found("user", || { + let user_ns = self.dir.process_ref.user_ns().lock(); + NsSymOps::new_inode(&*user_ns, dir.this_weak().clone()) + }); + + cached_children.downgrade() + } + + fn validate_child(&self, child: &dyn Inode) -> bool { + // The user namespace of a thread/process never changes, + // so a user-ns symlink is always valid. + if child.downcast_ref::>().is_some() { + return true; + } + + // Checks whether `child` still matches the corresponding namespace + // in the thread's current `NsProxy`. + + let thread = self.dir.thread(); + let ns_proxy = thread.as_posix_thread().unwrap().ns_proxy().lock(); + let Some(ns_proxy) = ns_proxy.as_ref() else { + return false; + }; + + if let Some(sym) = child.downcast_ref::>() { + return &sym.inner().ns_path == ns_proxy.uts_ns().path(); + } + + if let Some(sym) = child.downcast_ref::>() { + return &sym.inner().ns_path == ns_proxy.mnt_ns().path(); + } + + // TODO: Support additional namespace types. + false + } +} + +type NsSymlink = ProcSym>; + +/// Represents the inode at `/proc/[pid]/task/[tid]/ns/` (and also `/proc/[pid]/ns/`). +pub struct NsSymOps { + ns_path: Path, + phantom: PhantomData, +} + +impl NsSymOps { + /// Creates a new symlink inode pointing to the given namespace. + fn new_inode(ns: &Arc, parent: Weak) -> Arc { + ProcSymBuilder::new( + Self { + ns_path: ns.path().clone(), + phantom: PhantomData, + }, + mkmod!(a + rwx), + ) + .parent(parent) + .build() + .unwrap() + } +} + +impl SymOps for NsSymOps { + fn read_link(&self) -> Result { + Ok(SymbolicLink::Path(self.ns_path.clone())) + } +} diff --git a/kernel/src/fs/pseudofs/mod.rs b/kernel/src/fs/pseudofs/mod.rs index 046f57ecb..ddd2f1e0c 100644 --- a/kernel/src/fs/pseudofs/mod.rs +++ b/kernel/src/fs/pseudofs/mod.rs @@ -6,6 +6,7 @@ use core::{ }; pub use anon_inode_fs::AnonInodeFs; +pub use nsfs::{NsCommonOps, NsFile, NsFs, NsType}; pub use pidfdfs::PidfdFs; pub(super) use pipefs::PipeFs; use pipefs::PipeFsType; @@ -28,6 +29,7 @@ use crate::{ }; mod anon_inode_fs; +mod nsfs; mod pidfdfs; mod pipefs; mod sockfs; @@ -122,6 +124,7 @@ pub enum PseudoInodeType { Socket, AnonInode, Pidfd, + Ns, } impl From for InodeType { @@ -132,6 +135,7 @@ impl From for InodeType { PseudoInodeType::Socket => InodeType::Socket, PseudoInodeType::AnonInode => InodeType::Unknown, PseudoInodeType::Pidfd => InodeType::Unknown, + PseudoInodeType::Ns => InodeType::File, } } } diff --git a/kernel/src/fs/pseudofs/nsfs.rs b/kernel/src/fs/pseudofs/nsfs.rs new file mode 100644 index 000000000..431abfee7 --- /dev/null +++ b/kernel/src/fs/pseudofs/nsfs.rs @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: MPL-2.0 + +use alloc::format; +use core::time::Duration; + +use inherit_methods_macro::inherit_methods; +use ostd::task::Task; +use spin::Once; + +use crate::{ + events::IoEvents, + fs::{ + file_table::{FdFlags, FileDesc}, + inode_handle::{FileIo, InodeHandle}, + path::{Mount, Path}, + pseudofs::{PseudoFs, PseudoInode, PseudoInodeType}, + utils::{ + AccessMode, Extension, FileSystem, Inode, InodeIo, InodeMode, InodeType, Metadata, + StatusFlags, mkmod, + }, + }, + prelude::*, + process::{ + CloneFlags, Gid, Uid, UserNamespace, + signal::{PollHandle, Pollable}, + }, + util::ioctl::{RawIoctl, dispatch_ioctl}, +}; + +/// A pseudo filesystem for namespace files. +pub struct NsFs { + _private: (), +} + +impl NsFs { + /// Returns the singleton instance of the ns filesystem. + pub fn singleton() -> &'static Arc { + static NSFS: Once> = Once::new(); + PseudoFs::singleton(&NSFS, "nsfs", NSFS_MAGIC) + } + + /// Creates a pseudo [`Path`] for a namespace file. + pub fn new_path(ns: Weak) -> Path { + let ns_inode = { + let ino = Self::singleton().alloc_id(); + let fs = Arc::downgrade(Self::singleton()); + Arc::new(NsInode::new(ino, Uid::new_root(), Gid::new_root(), ns, fs)) + }; + + Path::new_pseudo(Self::mount_node().clone(), ns_inode, |inode| { + let inode = inode.downcast_ref::>().unwrap(); + inode.name().to_string() + }) + } + + /// Returns the pseudo mount node of the ns filesystem. + pub fn mount_node() -> &'static Arc { + static NSFS_MOUNT: Once> = Once::new(); + NSFS_MOUNT.call_once(|| Mount::new_pseudo(Self::singleton().clone())) + } +} + +/// An inode representing a namespace entry in [`NsFs`]. +struct NsInode { + common: PseudoInode, + ns: Weak, + name: String, +} + +impl NsInode { + fn new(ino: u64, uid: Uid, gid: Gid, ns: Weak, fs: Weak) -> Self { + let mode = mkmod!(a + r); + let common = PseudoInode::new(ino, PseudoInodeType::Ns, mode, uid, gid, fs); + let name = format!("{}:[{}]", T::NAME, ino); + + Self { common, ns, name } + } + + fn name(&self) -> &str { + &self.name + } +} + +#[inherit_methods(from = "self.common")] +impl Inode for NsInode { + fn size(&self) -> usize; + fn resize(&self, _new_size: usize) -> Result<()>; + fn metadata(&self) -> Metadata; + fn extension(&self) -> &Extension; + fn ino(&self) -> u64; + fn type_(&self) -> InodeType; + fn mode(&self) -> Result; + fn set_mode(&self, mode: InodeMode) -> Result<()>; + fn owner(&self) -> Result; + fn set_owner(&self, uid: Uid) -> Result<()>; + fn group(&self) -> Result; + fn set_group(&self, gid: Gid) -> Result<()>; + fn atime(&self) -> Duration; + fn set_atime(&self, time: Duration); + fn mtime(&self) -> Duration; + fn set_mtime(&self, time: Duration); + fn ctime(&self) -> Duration; + fn set_ctime(&self, time: Duration); + fn fs(&self) -> Arc; + + fn open( + &self, + access_mode: AccessMode, + _status_flags: StatusFlags, + ) -> Option>> { + // FIXME: This may not be the most appropriate place to check the access mode, + // but the check must not be bypassed even if the current process has the + // CAP_DAC_OVERRIDE capability. It is hard to find a better place for it, + // and an extra check here does no harm. + if access_mode.is_writable() { + return Some(Err(Error::with_message( + Errno::EPERM, + "ns files cannot be opened as writable", + ))); + } + + let ns = self + .ns + .upgrade() + .ok_or_else(|| Error::with_message(Errno::EPERM, "the namespace no longer exists")); + + Some(ns.map(|ns| Box::new(NsFile { ns }) as Box)) + } +} + +#[inherit_methods(from = "self.common")] +impl InodeIo for NsInode { + fn read_at( + &self, + _offset: usize, + _writer: &mut VmWriter, + _status: StatusFlags, + ) -> Result; + fn write_at( + &self, + _offset: usize, + _reader: &mut VmReader, + _status: StatusFlags, + ) -> Result; +} + +/// A file handle referencing a live namespace. +pub struct NsFile { + ns: Arc, +} + +impl NsFile { + /// Returns a reference to the underlying namespace. + pub fn ns(&self) -> &Arc { + &self.ns + } +} + +impl FileIo for NsFile { + fn check_seekable(&self) -> Result<()> { + return_errno_with_message!(Errno::ESPIPE, "ns files is not seekable"); + } + + fn is_offset_aware(&self) -> bool { + false + } + + fn ioctl(&self, raw_ioctl: RawIoctl) -> Result { + use ioctl_defs::*; + dispatch_ioctl!(match raw_ioctl { + _cmd @ GetUserNs => { + let user_ns = self.ns.get_owner_user_ns()?; + + let current = current!(); + let current_user_ns = current.user_ns().lock(); + if !current_user_ns.is_same_or_ancestor_of(user_ns) { + return_errno_with_message!( + Errno::EPERM, + "the owner user namespace is not an ancestor of the current namespace" + ); + } + + open_ns_as_file(user_ns.as_ref()) + } + _cmd @ GetParent => { + let parent = self.ns.get_parent()?; + open_ns_as_file(parent.as_ref()) + } + _cmd @ GetType => { + let clone_flags = CloneFlags::from(T::TYPE); + Ok(clone_flags.bits().cast_signed()) + } + cmd @ GetOwnerUid => { + let user_ns = self + .ns + .as_any() + .downcast_ref::() + .ok_or_else(|| { + Error::with_message( + Errno::EINVAL, + "the ns file does not correspond to a user namespace", + ) + })?; + let uid = user_ns.get_owner_uid()?; + cmd.write(&uid.into())?; + Ok(0) + } + // TODO: Support additional iotcl commands + _ => return_errno_with_message!(Errno::ENOTTY, "unsupported ioctl command"), + }) + } + + fn as_any(&self) -> &dyn Any { + self + } +} + +impl Pollable for NsFile { + fn poll(&self, mask: IoEvents, _poller: Option<&mut PollHandle>) -> IoEvents { + (IoEvents::IN | IoEvents::OUT | IoEvents::RDNORM) & mask + } +} + +impl InodeIo for NsFile { + fn read_at( + &self, + _offset: usize, + _writer: &mut VmWriter, + _status_flags: StatusFlags, + ) -> Result { + return_errno_with_message!(Errno::EINVAL, "ns files do not support read_at"); + } + + fn write_at( + &self, + _offset: usize, + _reader: &mut VmReader, + _status_flags: StatusFlags, + ) -> Result { + return_errno_with_message!(Errno::EINVAL, "ns files do not support write_at"); + } +} + +/// Opens a namespace as a file and returns the file descriptor. +fn open_ns_as_file(ns: &T) -> Result { + let path = ns.path(); + let inode_handle = InodeHandle::new(path.clone(), AccessMode::O_RDONLY, StatusFlags::empty())?; + + let current_task = Task::current().unwrap(); + let thread_local = current_task.as_thread_local().unwrap(); + let mut file_table_ref = thread_local.borrow_file_table_mut(); + let mut file_table = file_table_ref.unwrap().write(); + let fd = file_table.insert(Arc::new(inode_handle), FdFlags::CLOEXEC); + + Ok(fd) +} + +/// Common operations shared by all namespace types. +/// +/// Implementors represent a specific kind of namespace (e.g., UTS, mount, user) +/// and must provide the associated metadata and traversal methods required by +/// [`NsFs`] and [`NsFile`]. +pub trait NsCommonOps: Any + Send + Sync + 'static { + /// The human-readable name of this namespace kind (derived from [`Self::TYPE`]). + const NAME: &str = Self::TYPE.as_str(); + + /// The [`NsType`] discriminant for this namespace kind. + const TYPE: NsType; + + /// Returns the owner user namespace. + fn get_owner_user_ns(&self) -> Result<&Arc>; + + /// Returns the parent namespace, if one exists. + fn get_parent(&self) -> Result>; + + /// Returns the pseudo filesystem [`Path`] associated with this namespace. + fn path(&self) -> &Path; + + fn as_any(&self) -> &dyn Any; +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum NsType { + Uts, + User, + Mnt, + #[expect(unused)] + Pid, + #[expect(unused)] + Time, + #[expect(unused)] + Cgroup, + #[expect(unused)] + Ipc, + #[expect(unused)] + Net, +} + +impl NsType { + const fn as_str(&self) -> &'static str { + match self { + NsType::Uts => "uts", + NsType::User => "user", + NsType::Mnt => "mnt", + NsType::Pid => "pid", + NsType::Time => "time", + NsType::Cgroup => "cgroup", + NsType::Ipc => "ipc", + NsType::Net => "net", + } + } +} + +impl From for CloneFlags { + fn from(value: NsType) -> Self { + match value { + NsType::Uts => CloneFlags::CLONE_NEWUTS, + NsType::User => CloneFlags::CLONE_NEWUSER, + NsType::Mnt => CloneFlags::CLONE_NEWNS, + NsType::Pid => CloneFlags::CLONE_NEWPID, + NsType::Time => CloneFlags::CLONE_NEWTIME, + NsType::Cgroup => CloneFlags::CLONE_NEWCGROUP, + NsType::Ipc => CloneFlags::CLONE_NEWIPC, + NsType::Net => CloneFlags::CLONE_NEWNET, + } + } +} + +// Reference: +const NSFS_MAGIC: u64 = 0x6e736673; + +mod ioctl_defs { + use crate::util::ioctl::{InData, NoData, OutData, ioc}; + + // Legacy encoding ioctl commands + + /// Returns a file descriptor of the owner user namespace. + pub type GetUserNs = ioc!(NS_GET_USERNS, 0xb701, NoData); + /// Returns a file descriptor of the parent namespace. + pub type GetParent = ioc!(NS_GET_PARENT, 0xb702, NoData); + /// Gets the type of the namespace (e.g., user, pid, mnt, etc.). + pub type GetType = ioc!(NS_GET_NSTYPE, 0xb703, NoData); + /// Gets the user ID of the namespace owner. + /// + /// Only user namespace supports this operation. + pub type GetOwnerUid = ioc!(NS_GET_OWNER_ID, 0xb704, OutData); + + // Modern encoding ioctl commands + + #[expect(unused)] + /// Gets the ID of the mount namespace. + pub type GetMntNsId = ioc!(NS_GET_MNTNS_ID, 0xb7, 0x5, OutData); + /// Translates thread ID from the target PID namespace into the caller's PID namespace. + #[expect(unused)] + pub type GetTidFromPidNs = ioc!(NS_GET_PID_FROM_PIDNS, 0xb7, 0x6, InData); + /// Translates process ID from the target PID namespace into the caller's PID namespace. + #[expect(unused)] + pub type GetPidFromPidNs = ioc!(NS_GET_TGID_FROM_PIDNS, 0xb7, 0x7, InData); + /// Translates thread ID from the caller's PID namespace into the target PID namespace. + #[expect(unused)] + pub type GetTidInPidNs = ioc!(NS_GET_PID_IN_PIDNS, 0xb7, 0x8, InData); + /// Translates process ID from the caller's PID namespace into the target PID namespace. + #[expect(unused)] + pub type GetPidInPidNs = ioc!(NS_GET_TGID_IN_PIDNS, 0xb7, 0x9, InData); +} diff --git a/kernel/src/net/uts_ns.rs b/kernel/src/net/uts_ns.rs index 20ad677da..d43b3fb82 100644 --- a/kernel/src/net/uts_ns.rs +++ b/kernel/src/net/uts_ns.rs @@ -4,6 +4,10 @@ use ostd::{const_assert, sync::RwMutexReadGuard}; use spin::Once; use crate::{ + fs::{ + path::Path, + pseudofs::{NsCommonOps, NsFs, NsType}, + }, prelude::*, process::{UserNamespace, credentials::capabilities::CapSet, posix_thread::PosixThread}, util::padded, @@ -13,6 +17,7 @@ use crate::{ pub struct UtsNamespace { uts_name: RwMutex, owner: Arc, + path: Path, } impl UtsNamespace { @@ -33,11 +38,18 @@ impl UtsNamespace { }; let owner = UserNamespace::get_init_singleton().clone(); + Self::new(uts_name, owner) + }) + } - Arc::new(Self { + fn new(uts_name: UtsName, owner: Arc) -> Arc { + Arc::new_cyclic(|weak_self| { + let path = NsFs::new_path(weak_self.clone()); + Self { uts_name: RwMutex::new(uts_name), owner, - }) + path, + } }) } @@ -48,10 +60,7 @@ impl UtsNamespace { posix_thread: &PosixThread, ) -> Result> { owner.check_cap(CapSet::SYS_ADMIN, posix_thread)?; - Ok(Arc::new(Self { - uts_name: RwMutex::new(*self.uts_name.read()), - owner, - })) + Ok(Self::new(*self.uts_name.read(), owner)) } /// Returns the owner user namespace of the namespace. @@ -195,3 +204,26 @@ impl UtsName { } }; } + +impl NsCommonOps for UtsNamespace { + const TYPE: NsType = NsType::Uts; + + fn get_owner_user_ns(&self) -> Result<&Arc> { + Ok(&self.owner) + } + + fn get_parent(&self) -> Result> { + return_errno_with_message!( + Errno::EINVAL, + "UTS namespace does not have parent namespace" + ); + } + + fn path(&self) -> &Path { + &self.path + } + + fn as_any(&self) -> &dyn Any { + self + } +} diff --git a/kernel/src/process/namespace/user_ns.rs b/kernel/src/process/namespace/user_ns.rs index 623556352..bd6891f97 100644 --- a/kernel/src/process/namespace/user_ns.rs +++ b/kernel/src/process/namespace/user_ns.rs @@ -3,13 +3,18 @@ use spin::Once; use crate::{ + fs::{ + path::Path, + pseudofs::{NsCommonOps, NsFs, NsType}, + }, prelude::*, - process::{credentials::capabilities::CapSet, posix_thread::PosixThread}, + process::{Uid, credentials::capabilities::CapSet, posix_thread::PosixThread}, }; /// The user namespace. pub struct UserNamespace { _private: (), + path: Path, } impl UserNamespace { @@ -17,7 +22,14 @@ impl UserNamespace { pub fn get_init_singleton() -> &'static Arc { static INIT: Once> = Once::new(); - INIT.call_once(|| Arc::new(UserNamespace { _private: () })) + INIT.call_once(Self::new) + } + + fn new() -> Arc { + Arc::new_cyclic(|weak_self| { + let path = NsFs::new_path(weak_self.clone()); + Self { _private: (), path } + }) } /// Checks whether the thread has the required capability in this user namespace. @@ -37,4 +49,47 @@ impl UserNamespace { "the thread does not have the required capability" ) } + + /// Returns the owner UID of the user namespace. + pub fn get_owner_uid(&self) -> Result { + // FIXME: The owner of the user namespace is not yet tracked. + // Return the correct user ID once ownership tracking is implemented. + Ok(Uid::new_root()) + } + + /// Returns whether this namespace is the same as, or an ancestor of, the other namespace. + pub fn is_same_or_ancestor_of(self: &Arc, other: &Arc) -> bool { + // FIXME: Creating new user namespaces is not yet supported, + // so we simply check pointer equality. + // Once user namespace creation is implemented, + // this should walk up the ancestor chain to verify + // whether `self` is an ancestor of `other`. + Arc::ptr_eq(self, other) + } +} + +impl NsCommonOps for UserNamespace { + const TYPE: NsType = NsType::User; + + fn get_owner_user_ns(&self) -> Result<&Arc> { + return_errno_with_message!( + Errno::EPERM, + "a user namespace does not have an owner user namespace" + ); + } + + fn get_parent(&self) -> Result> { + return_errno_with_message!( + Errno::EPERM, + "getting the parent of a user namespace is not supported" + ); + } + + fn path(&self) -> &Path { + &self.path + } + + fn as_any(&self) -> &dyn Any { + self + } }