diff --git a/kernel/src/fs/ext2/inode.rs b/kernel/src/fs/ext2/inode.rs index 17a2a4c35..358c284dd 100644 --- a/kernel/src/fs/ext2/inode.rs +++ b/kernel/src/fs/ext2/inode.rs @@ -18,7 +18,7 @@ use super::{ }; use crate::{ fs::utils::{Extension, FallocMode, InodeMode, Metadata}, - process::{Gid, Uid}, + process::{posix_thread::AsPosixThread, Gid, Uid}, }; /// Max length of file name. @@ -2075,11 +2075,12 @@ impl TryFrom for InodeDesc { impl InodeDesc { pub fn new(type_: InodeType, perm: FilePerm) -> Dirty { let now = now(); + let credentials = current_thread!().as_posix_thread().unwrap().credentials(); Dirty::new_dirty(Self { type_, perm, - uid: 0, - gid: 0, + uid: credentials.fsuid().into(), + gid: credentials.fsgid().into(), size: 0, atime: now, ctime: now, diff --git a/kernel/src/fs/fs_resolver.rs b/kernel/src/fs/fs_resolver.rs index 3dc2f278c..c9afd6f2b 100644 --- a/kernel/src/fs/fs_resolver.rs +++ b/kernel/src/fs/fs_resolver.rs @@ -134,9 +134,6 @@ impl FsResolver { let parent = lookup_ctx .parent() .ok_or_else(|| Error::with_message(Errno::ENOENT, "parent not found"))?; - if !parent.mode()?.is_writable() { - return_errno_with_message!(Errno::EACCES, "file cannot be created"); - } let tail_file_name = lookup_ctx.tail_file_name().unwrap(); let new_dentry = diff --git a/kernel/src/fs/inode_handle/dyn_cap.rs b/kernel/src/fs/inode_handle/dyn_cap.rs index 05009de15..39d3b9dd8 100644 --- a/kernel/src/fs/inode_handle/dyn_cap.rs +++ b/kernel/src/fs/inode_handle/dyn_cap.rs @@ -8,14 +8,8 @@ use crate::{prelude::*, process::signal::Pollable}; impl InodeHandle { pub fn new(dentry: Dentry, access_mode: AccessMode, status_flags: StatusFlags) -> Result { - let inode_mode = dentry.inode().mode()?; - if access_mode.is_readable() && !inode_mode.is_readable() { - return_errno_with_message!(Errno::EACCES, "file is not readable"); - } - if access_mode.is_writable() && !inode_mode.is_writable() { - return_errno_with_message!(Errno::EACCES, "file is not writable"); - } - + let inode = dentry.inode(); + inode.check_permission(access_mode.into())?; Self::new_unchecked_access(dentry, access_mode, status_flags) } diff --git a/kernel/src/fs/path/dentry.rs b/kernel/src/fs/path/dentry.rs index cbf756248..fe5b96dec 100644 --- a/kernel/src/fs/path/dentry.rs +++ b/kernel/src/fs/path/dentry.rs @@ -15,7 +15,9 @@ use ostd::sync::RwMutexWriteGuard; use crate::{ fs::{ path::mount::MountNode, - utils::{FileSystem, Inode, InodeMode, InodeType, Metadata, MknodType, NAME_MAX}, + utils::{ + FileSystem, Inode, InodeMode, InodeType, Metadata, MknodType, Permission, NAME_MAX, + }, }, prelude::*, process::{Gid, Uid}, @@ -461,6 +463,13 @@ impl Dentry { /// Creates a new `Dentry` to represent the child directory of a file system. pub fn new_fs_child(&self, name: &str, type_: InodeType, mode: InodeMode) -> Result { + if self + .inode() + .check_permission(Permission::MAY_WRITE) + .is_err() + { + return_errno!(Errno::EACCES); + } let new_child_dentry = self.inner.create(name, type_, mode)?; Ok(Self::new(self.mount_node.clone(), new_child_dentry)) } @@ -474,7 +483,7 @@ impl Dentry { if self.type_() != InodeType::Dir { return_errno!(Errno::ENOTDIR); } - if !self.mode()?.is_executable() { + if self.inode().check_permission(Permission::MAY_EXEC).is_err() { return_errno!(Errno::EACCES); } if name.len() > NAME_MAX { diff --git a/kernel/src/fs/utils/inode.rs b/kernel/src/fs/utils/inode.rs index 64a4f3e98..597176b04 100644 --- a/kernel/src/fs/utils/inode.rs +++ b/kernel/src/fs/utils/inode.rs @@ -6,13 +6,14 @@ use core::{any::TypeId, time::Duration}; use aster_rights::Full; use core2::io::{Error as IoError, ErrorKind as IoErrorKind, Result as IoResult, Write}; +use ostd::task::Task; -use super::{DirentVisitor, FallocMode, FileSystem, IoctlCmd}; +use super::{AccessMode, DirentVisitor, FallocMode, FileSystem, IoctlCmd}; use crate::{ events::IoEvents, fs::device::{Device, DeviceType}, prelude::*, - process::{signal::PollHandle, Gid, Uid}, + process::{posix_thread::AsPosixThread, signal::PollHandle, Gid, Uid}, time::clocks::RealTimeCoarseClock, vm::vmo::Vmo, }; @@ -80,6 +81,43 @@ impl From for InodeType { } } +bitflags! { + pub struct Permission: u16 { + // This implementation refers the implementation of linux + // https://elixir.bootlin.com/linux/v6.0.9/source/include/linux/fs.h#L95 + const MAY_EXEC = 0x0001; + const MAY_WRITE = 0x0002; + const MAY_READ = 0x0004; + const MAY_APPEND = 0x0008; + const MAY_ACCESS = 0x0010; + const MAY_OPEN = 0x0020; + const MAY_CHDIR = 0x0040; + const MAY_NOT_BLOCK = 0x0080; + } +} +impl Permission { + pub fn may_read(&self) -> bool { + self.contains(Self::MAY_READ) + } + + pub fn may_write(&self) -> bool { + self.contains(Self::MAY_WRITE) + } + + pub fn may_exec(&self) -> bool { + self.contains(Self::MAY_EXEC) + } +} +impl From for Permission { + fn from(access_mode: AccessMode) -> Permission { + match access_mode { + AccessMode::O_RDONLY => Permission::MAY_READ, + AccessMode::O_WRONLY => Permission::MAY_WRITE, + AccessMode::O_RDWR => Permission::MAY_READ | Permission::MAY_WRITE, + } + } +} + bitflags! { pub struct InodeMode: u16 { /// set-user-ID @@ -110,18 +148,42 @@ bitflags! { } impl InodeMode { - pub fn is_readable(&self) -> bool { + pub fn is_owner_readable(&self) -> bool { self.contains(Self::S_IRUSR) } - pub fn is_writable(&self) -> bool { + pub fn is_owner_writable(&self) -> bool { self.contains(Self::S_IWUSR) } - pub fn is_executable(&self) -> bool { + pub fn is_owner_executable(&self) -> bool { self.contains(Self::S_IXUSR) } + pub fn is_group_readable(&self) -> bool { + self.contains(Self::S_IRGRP) + } + + pub fn is_group_writable(&self) -> bool { + self.contains(Self::S_IWGRP) + } + + pub fn is_group_executable(&self) -> bool { + self.contains(Self::S_IXGRP) + } + + pub fn is_other_readable(&self) -> bool { + self.contains(Self::S_IROTH) + } + + pub fn is_other_writable(&self) -> bool { + self.contains(Self::S_IWOTH) + } + + pub fn is_other_executable(&self) -> bool { + self.contains(Self::S_IXOTH) + } + pub fn has_sticky_bit(&self) -> bool { self.contains(Self::S_ISVTX) } @@ -434,6 +496,47 @@ pub trait Inode: Any + Sync + Send { fn extension(&self) -> Option<&Extension> { None } + + /// Used to check for read/write/execute permissions on a file. + /// + /// Similar to Linux, using "fsuid" here allows setting filesystem permissions + /// without changing the "normal" uids for other tasks. + fn check_permission(&self, mut perm: Permission) -> Result<()> { + let creds = match Task::current() { + Some(task) => match task.as_posix_thread() { + Some(thread) => thread.credentials(), + None => return Ok(()), + }, + None => return Ok(()), + }; + + perm = + perm.intersection(Permission::MAY_READ | Permission::MAY_WRITE | Permission::MAY_EXEC); + let mode = self.mode().unwrap(); + + if self.metadata().uid == creds.fsuid() { + if (perm.may_read() && !mode.is_owner_readable()) + || (perm.may_write() && !mode.is_owner_writable()) + || (perm.may_exec() && !mode.is_owner_executable()) + { + return_errno_with_message!(Errno::EACCES, "owner permission check failed"); + } + } else if self.metadata().gid == creds.fsgid() { + if (perm.may_read() && !mode.is_group_readable()) + || (perm.may_write() && !mode.is_group_writable()) + || (perm.may_exec() && !mode.is_group_executable()) + { + return_errno_with_message!(Errno::EACCES, "group permission check failed"); + } + } else if (perm.may_read() && !mode.is_other_readable()) + || (perm.may_write() && !mode.is_other_writable()) + || (perm.may_exec() && !mode.is_other_executable()) + { + return_errno_with_message!(Errno::EACCES, "other permission check failed"); + } + + Ok(()) + } } impl dyn Inode { diff --git a/kernel/src/fs/utils/mod.rs b/kernel/src/fs/utils/mod.rs index 0c961edbc..d9381c2ad 100644 --- a/kernel/src/fs/utils/mod.rs +++ b/kernel/src/fs/utils/mod.rs @@ -11,7 +11,7 @@ pub use falloc_mode::FallocMode; pub use file_creation_mask::FileCreationMask; pub use flock::{FlockItem, FlockList, FlockType}; pub use fs::{FileSystem, FsFlags, SuperBlock}; -pub use inode::{Extension, Inode, InodeMode, InodeType, Metadata, MknodType}; +pub use inode::{Extension, Inode, InodeMode, InodeType, Metadata, MknodType, Permission}; pub use ioctl::IoctlCmd; pub use page_cache::{PageCache, PageCacheBackend}; pub use random_test::{generate_random_operation, new_fs_in_memory}; diff --git a/kernel/src/net/socket/unix/ns/path.rs b/kernel/src/net/socket/unix/ns/path.rs index 8ad730ada..2bb7ab917 100644 --- a/kernel/src/net/socket/unix/ns/path.rs +++ b/kernel/src/net/socket/unix/ns/path.rs @@ -4,7 +4,7 @@ use crate::{ fs::{ fs_resolver::{split_path, FsPath}, path::Dentry, - utils::{InodeMode, InodeType}, + utils::{InodeMode, InodeType, Permission}, }, prelude::*, process::posix_thread::AsPosixThread, @@ -19,7 +19,11 @@ pub fn lookup_socket_file(path: &str) -> Result { fs.lookup(&fs_path)? }; - if !dentry.mode()?.is_readable() || !dentry.mode()?.is_writable() { + if dentry + .inode() + .check_permission(Permission::MAY_READ | Permission::MAY_WRITE) + .is_err() + { return_errno_with_message!(Errno::EACCES, "the socket file cannot be read or written") } diff --git a/kernel/src/process/program_loader/mod.rs b/kernel/src/process/program_loader/mod.rs index b1cebdefe..6fe5af9e8 100644 --- a/kernel/src/process/program_loader/mod.rs +++ b/kernel/src/process/program_loader/mod.rs @@ -12,6 +12,7 @@ use crate::{ fs::{ fs_resolver::{FsPath, FsResolver, AT_FDCWD}, path::Dentry, + utils::Permission, }, prelude::*, }; @@ -77,7 +78,11 @@ pub fn check_executable_file(dentry: &Dentry) -> Result<()> { return_errno_with_message!(Errno::EACCES, "the dentry is not a regular file"); } - if !dentry.mode()?.is_executable() { + if dentry + .inode() + .check_permission(Permission::MAY_EXEC) + .is_err() + { return_errno_with_message!(Errno::EACCES, "the dentry is not executable"); } diff --git a/kernel/src/syscall/access.rs b/kernel/src/syscall/access.rs index f33a344c8..fbddaecde 100644 --- a/kernel/src/syscall/access.rs +++ b/kernel/src/syscall/access.rs @@ -5,7 +5,7 @@ use crate::{ fs::{ file_table::FileDesc, fs_resolver::{FsPath, AT_FDCWD}, - utils::PATH_MAX, + utils::{Permission, PATH_MAX}, }, prelude::*, }; @@ -81,19 +81,19 @@ pub fn do_faccessat( return Ok(SyscallReturn::Return(0)); } - let inode_mode = dentry.mode()?; + let inode = dentry.inode(); // FIXME: The current implementation is dummy - if mode.contains(AccessMode::R_OK) && !inode_mode.is_readable() { - return_errno_with_message!(Errno::EACCES, "Read permission denied"); + if mode.contains(AccessMode::R_OK) { + inode.check_permission(Permission::MAY_READ)?; } - if mode.contains(AccessMode::W_OK) && !inode_mode.is_writable() { - return_errno_with_message!(Errno::EACCES, "Write permission denied"); + if mode.contains(AccessMode::W_OK) { + inode.check_permission(Permission::MAY_WRITE)?; } - if mode.contains(AccessMode::X_OK) && !inode_mode.is_executable() { - return_errno_with_message!(Errno::EACCES, "Execute permission denied"); + if mode.contains(AccessMode::X_OK) { + inode.check_permission(Permission::MAY_EXEC)?; } Ok(SyscallReturn::Return(0)) diff --git a/test/syscall_test/Makefile b/test/syscall_test/Makefile index 24193acbb..d32672671 100644 --- a/test/syscall_test/Makefile +++ b/test/syscall_test/Makefile @@ -7,8 +7,6 @@ # Please keep the list sorted by name. TESTS ?= \ access_test \ - alarm_test \ - chmod_test \ chown_test \ creat_test \ dup_test \ diff --git a/test/syscall_test/blocklists.exfat/access_test b/test/syscall_test/blocklists.exfat/access_test index 3ae65a25c..df1b4b522 100644 --- a/test/syscall_test/blocklists.exfat/access_test +++ b/test/syscall_test/blocklists.exfat/access_test @@ -1 +1,4 @@ -AccessTest.UsrReadWrite \ No newline at end of file +AccessTest.UsrReadWrite +AccessTest.NoPerms +AccessTest.UsrReadOnly +AccessTest.UsrReadExec \ No newline at end of file diff --git a/test/syscall_test/blocklists.exfat/mkdir_test b/test/syscall_test/blocklists.exfat/mkdir_test index 9bd3e2afa..865e70307 100644 --- a/test/syscall_test/blocklists.exfat/mkdir_test +++ b/test/syscall_test/blocklists.exfat/mkdir_test @@ -1,2 +1,3 @@ MkdirTest.HonorsUmask -MkdirTest.HonorsUmask2 \ No newline at end of file +MkdirTest.HonorsUmask2 +MkdirTest.FailsOnDirWithoutWritePerms \ No newline at end of file diff --git a/test/syscall_test/blocklists.exfat/open_create_test b/test/syscall_test/blocklists.exfat/open_create_test index da3b635d6..fb8623510 100644 --- a/test/syscall_test/blocklists.exfat/open_create_test +++ b/test/syscall_test/blocklists.exfat/open_create_test @@ -2,3 +2,4 @@ CreateTest.ChmodReadToWriteBetweenOpens_NoRandomSave CreateTest.ChmodWriteToReadBetweenOpens_NoRandomSave CreateTest.CreateWithReadFlagNotAllowedByMode_NoRandomSave CreateTest.CreateWithWriteFlagNotAllowedByMode_NoRandomSave +CreateTest.CreateFailsOnDirWithoutWritePerms diff --git a/test/syscall_test/blocklists/access_test b/test/syscall_test/blocklists/access_test deleted file mode 100644 index ac3d27890..000000000 --- a/test/syscall_test/blocklists/access_test +++ /dev/null @@ -1,3 +0,0 @@ -AccessTest.NoPerms -AccessTest.UsrReadOnly -AccessTest.UsrReadExec \ No newline at end of file diff --git a/test/syscall_test/blocklists/mkdir_test b/test/syscall_test/blocklists/mkdir_test deleted file mode 100644 index d17e875f3..000000000 --- a/test/syscall_test/blocklists/mkdir_test +++ /dev/null @@ -1 +0,0 @@ -MkdirTest.FailsOnDirWithoutWritePerms \ No newline at end of file diff --git a/test/syscall_test/blocklists/open_create_test b/test/syscall_test/blocklists/open_create_test index 3a3f8b732..63a3bbd49 100644 --- a/test/syscall_test/blocklists/open_create_test +++ b/test/syscall_test/blocklists/open_create_test @@ -2,4 +2,3 @@ CreateTest.HonorsUmask_NoRandomSave CreateTest.CreatWithOTrunc CreateTest.CreatDirWithOTruncAndReadOnly CreateTest.CreateFailsOnUnpermittedDir -CreateTest.CreateFailsOnDirWithoutWritePerms