Add support for group-based permission checking in ext2

This commit is contained in:
anbo225
2024-12-12 16:59:16 +08:00
committed by Tate, Hongliang Tian
parent 6cea8d2a8c
commit e75b6320ad
16 changed files with 153 additions and 42 deletions

View File

@ -18,7 +18,7 @@ use super::{
}; };
use crate::{ use crate::{
fs::utils::{Extension, FallocMode, InodeMode, Metadata}, fs::utils::{Extension, FallocMode, InodeMode, Metadata},
process::{Gid, Uid}, process::{posix_thread::AsPosixThread, Gid, Uid},
}; };
/// Max length of file name. /// Max length of file name.
@ -2075,11 +2075,12 @@ impl TryFrom<RawInode> for InodeDesc {
impl InodeDesc { impl InodeDesc {
pub fn new(type_: InodeType, perm: FilePerm) -> Dirty<Self> { pub fn new(type_: InodeType, perm: FilePerm) -> Dirty<Self> {
let now = now(); let now = now();
let credentials = current_thread!().as_posix_thread().unwrap().credentials();
Dirty::new_dirty(Self { Dirty::new_dirty(Self {
type_, type_,
perm, perm,
uid: 0, uid: credentials.fsuid().into(),
gid: 0, gid: credentials.fsgid().into(),
size: 0, size: 0,
atime: now, atime: now,
ctime: now, ctime: now,

View File

@ -134,9 +134,6 @@ impl FsResolver {
let parent = lookup_ctx let parent = lookup_ctx
.parent() .parent()
.ok_or_else(|| Error::with_message(Errno::ENOENT, "parent not found"))?; .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 tail_file_name = lookup_ctx.tail_file_name().unwrap();
let new_dentry = let new_dentry =

View File

@ -8,14 +8,8 @@ use crate::{prelude::*, process::signal::Pollable};
impl InodeHandle<Rights> { impl InodeHandle<Rights> {
pub fn new(dentry: Dentry, access_mode: AccessMode, status_flags: StatusFlags) -> Result<Self> { pub fn new(dentry: Dentry, access_mode: AccessMode, status_flags: StatusFlags) -> Result<Self> {
let inode_mode = dentry.inode().mode()?; let inode = dentry.inode();
if access_mode.is_readable() && !inode_mode.is_readable() { inode.check_permission(access_mode.into())?;
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");
}
Self::new_unchecked_access(dentry, access_mode, status_flags) Self::new_unchecked_access(dentry, access_mode, status_flags)
} }

View File

@ -15,7 +15,9 @@ use ostd::sync::RwMutexWriteGuard;
use crate::{ use crate::{
fs::{ fs::{
path::mount::MountNode, path::mount::MountNode,
utils::{FileSystem, Inode, InodeMode, InodeType, Metadata, MknodType, NAME_MAX}, utils::{
FileSystem, Inode, InodeMode, InodeType, Metadata, MknodType, Permission, NAME_MAX,
},
}, },
prelude::*, prelude::*,
process::{Gid, Uid}, process::{Gid, Uid},
@ -461,6 +463,13 @@ impl Dentry {
/// Creates a new `Dentry` to represent the child directory of a file system. /// 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<Self> { pub fn new_fs_child(&self, name: &str, type_: InodeType, mode: InodeMode) -> Result<Self> {
if self
.inode()
.check_permission(Permission::MAY_WRITE)
.is_err()
{
return_errno!(Errno::EACCES);
}
let new_child_dentry = self.inner.create(name, type_, mode)?; let new_child_dentry = self.inner.create(name, type_, mode)?;
Ok(Self::new(self.mount_node.clone(), new_child_dentry)) Ok(Self::new(self.mount_node.clone(), new_child_dentry))
} }
@ -474,7 +483,7 @@ impl Dentry {
if self.type_() != InodeType::Dir { if self.type_() != InodeType::Dir {
return_errno!(Errno::ENOTDIR); return_errno!(Errno::ENOTDIR);
} }
if !self.mode()?.is_executable() { if self.inode().check_permission(Permission::MAY_EXEC).is_err() {
return_errno!(Errno::EACCES); return_errno!(Errno::EACCES);
} }
if name.len() > NAME_MAX { if name.len() > NAME_MAX {

View File

@ -6,13 +6,14 @@ use core::{any::TypeId, time::Duration};
use aster_rights::Full; use aster_rights::Full;
use core2::io::{Error as IoError, ErrorKind as IoErrorKind, Result as IoResult, Write}; 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::{ use crate::{
events::IoEvents, events::IoEvents,
fs::device::{Device, DeviceType}, fs::device::{Device, DeviceType},
prelude::*, prelude::*,
process::{signal::PollHandle, Gid, Uid}, process::{posix_thread::AsPosixThread, signal::PollHandle, Gid, Uid},
time::clocks::RealTimeCoarseClock, time::clocks::RealTimeCoarseClock,
vm::vmo::Vmo, vm::vmo::Vmo,
}; };
@ -80,6 +81,43 @@ impl From<DeviceType> 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<AccessMode> 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! { bitflags! {
pub struct InodeMode: u16 { pub struct InodeMode: u16 {
/// set-user-ID /// set-user-ID
@ -110,18 +148,42 @@ bitflags! {
} }
impl InodeMode { impl InodeMode {
pub fn is_readable(&self) -> bool { pub fn is_owner_readable(&self) -> bool {
self.contains(Self::S_IRUSR) self.contains(Self::S_IRUSR)
} }
pub fn is_writable(&self) -> bool { pub fn is_owner_writable(&self) -> bool {
self.contains(Self::S_IWUSR) self.contains(Self::S_IWUSR)
} }
pub fn is_executable(&self) -> bool { pub fn is_owner_executable(&self) -> bool {
self.contains(Self::S_IXUSR) 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 { pub fn has_sticky_bit(&self) -> bool {
self.contains(Self::S_ISVTX) self.contains(Self::S_ISVTX)
} }
@ -434,6 +496,47 @@ pub trait Inode: Any + Sync + Send {
fn extension(&self) -> Option<&Extension> { fn extension(&self) -> Option<&Extension> {
None 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 { impl dyn Inode {

View File

@ -11,7 +11,7 @@ pub use falloc_mode::FallocMode;
pub use file_creation_mask::FileCreationMask; pub use file_creation_mask::FileCreationMask;
pub use flock::{FlockItem, FlockList, FlockType}; pub use flock::{FlockItem, FlockList, FlockType};
pub use fs::{FileSystem, FsFlags, SuperBlock}; 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 ioctl::IoctlCmd;
pub use page_cache::{PageCache, PageCacheBackend}; pub use page_cache::{PageCache, PageCacheBackend};
pub use random_test::{generate_random_operation, new_fs_in_memory}; pub use random_test::{generate_random_operation, new_fs_in_memory};

View File

@ -4,7 +4,7 @@ use crate::{
fs::{ fs::{
fs_resolver::{split_path, FsPath}, fs_resolver::{split_path, FsPath},
path::Dentry, path::Dentry,
utils::{InodeMode, InodeType}, utils::{InodeMode, InodeType, Permission},
}, },
prelude::*, prelude::*,
process::posix_thread::AsPosixThread, process::posix_thread::AsPosixThread,
@ -19,7 +19,11 @@ pub fn lookup_socket_file(path: &str) -> Result<Dentry> {
fs.lookup(&fs_path)? 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") return_errno_with_message!(Errno::EACCES, "the socket file cannot be read or written")
} }

View File

@ -12,6 +12,7 @@ use crate::{
fs::{ fs::{
fs_resolver::{FsPath, FsResolver, AT_FDCWD}, fs_resolver::{FsPath, FsResolver, AT_FDCWD},
path::Dentry, path::Dentry,
utils::Permission,
}, },
prelude::*, 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"); 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"); return_errno_with_message!(Errno::EACCES, "the dentry is not executable");
} }

View File

@ -5,7 +5,7 @@ use crate::{
fs::{ fs::{
file_table::FileDesc, file_table::FileDesc,
fs_resolver::{FsPath, AT_FDCWD}, fs_resolver::{FsPath, AT_FDCWD},
utils::PATH_MAX, utils::{Permission, PATH_MAX},
}, },
prelude::*, prelude::*,
}; };
@ -81,19 +81,19 @@ pub fn do_faccessat(
return Ok(SyscallReturn::Return(0)); return Ok(SyscallReturn::Return(0));
} }
let inode_mode = dentry.mode()?; let inode = dentry.inode();
// FIXME: The current implementation is dummy // FIXME: The current implementation is dummy
if mode.contains(AccessMode::R_OK) && !inode_mode.is_readable() { if mode.contains(AccessMode::R_OK) {
return_errno_with_message!(Errno::EACCES, "Read permission denied"); inode.check_permission(Permission::MAY_READ)?;
} }
if mode.contains(AccessMode::W_OK) && !inode_mode.is_writable() { if mode.contains(AccessMode::W_OK) {
return_errno_with_message!(Errno::EACCES, "Write permission denied"); inode.check_permission(Permission::MAY_WRITE)?;
} }
if mode.contains(AccessMode::X_OK) && !inode_mode.is_executable() { if mode.contains(AccessMode::X_OK) {
return_errno_with_message!(Errno::EACCES, "Execute permission denied"); inode.check_permission(Permission::MAY_EXEC)?;
} }
Ok(SyscallReturn::Return(0)) Ok(SyscallReturn::Return(0))

View File

@ -7,8 +7,6 @@
# Please keep the list sorted by name. # Please keep the list sorted by name.
TESTS ?= \ TESTS ?= \
access_test \ access_test \
alarm_test \
chmod_test \
chown_test \ chown_test \
creat_test \ creat_test \
dup_test \ dup_test \

View File

@ -1 +1,4 @@
AccessTest.UsrReadWrite AccessTest.UsrReadWrite
AccessTest.NoPerms
AccessTest.UsrReadOnly
AccessTest.UsrReadExec

View File

@ -1,2 +1,3 @@
MkdirTest.HonorsUmask MkdirTest.HonorsUmask
MkdirTest.HonorsUmask2 MkdirTest.HonorsUmask2
MkdirTest.FailsOnDirWithoutWritePerms

View File

@ -2,3 +2,4 @@ CreateTest.ChmodReadToWriteBetweenOpens_NoRandomSave
CreateTest.ChmodWriteToReadBetweenOpens_NoRandomSave CreateTest.ChmodWriteToReadBetweenOpens_NoRandomSave
CreateTest.CreateWithReadFlagNotAllowedByMode_NoRandomSave CreateTest.CreateWithReadFlagNotAllowedByMode_NoRandomSave
CreateTest.CreateWithWriteFlagNotAllowedByMode_NoRandomSave CreateTest.CreateWithWriteFlagNotAllowedByMode_NoRandomSave
CreateTest.CreateFailsOnDirWithoutWritePerms

View File

@ -1,3 +0,0 @@
AccessTest.NoPerms
AccessTest.UsrReadOnly
AccessTest.UsrReadExec

View File

@ -1 +0,0 @@
MkdirTest.FailsOnDirWithoutWritePerms

View File

@ -2,4 +2,3 @@ CreateTest.HonorsUmask_NoRandomSave
CreateTest.CreatWithOTrunc CreateTest.CreatWithOTrunc
CreateTest.CreatDirWithOTruncAndReadOnly CreateTest.CreatDirWithOTruncAndReadOnly
CreateTest.CreateFailsOnUnpermittedDir CreateTest.CreateFailsOnUnpermittedDir
CreateTest.CreateFailsOnDirWithoutWritePerms