Refine some access checks

This commit is contained in:
Ruihan Li 2025-11-05 23:26:30 +08:00 committed by Tate, Hongliang Tian
parent b4385ddc3d
commit eaeba41f5e
5 changed files with 151 additions and 141 deletions

View File

@ -9,26 +9,32 @@ use crate::{fs::file_handle::Mappable, prelude::*, process::signal::Pollable};
impl InodeHandle<Rights> {
pub fn new(path: Path, access_mode: AccessMode, status_flags: StatusFlags) -> Result<Self> {
let inode = path.inode();
inode.check_permission(access_mode.into())?;
if !status_flags.contains(StatusFlags::O_PATH) {
// "Opening a file or directory with the O_PATH flag requires no permissions on the
// object itself".
// Reference: <https://man7.org/linux/man-pages/man2/openat.2.html>
inode.check_permission(access_mode.into())?;
}
Self::new_unchecked_access(path, access_mode, status_flags)
}
pub fn new_unchecked_access(
path: Path,
access_mode: AccessMode,
mut access_mode: AccessMode,
status_flags: StatusFlags,
) -> Result<Self> {
let inode = path.inode();
if inode.type_() == InodeType::Dir && access_mode.is_writable() {
return_errno_with_message!(Errno::EISDIR, "directory cannot open to write");
}
// TODO: Add more checks to ensure that certain behaviors
// of files opened with `O_PATH` are prohibited.
let file_io = if status_flags.contains(StatusFlags::O_PATH) {
None
let (file_io, rights) = if status_flags.contains(StatusFlags::O_PATH) {
// We follow Linux to report O_RDONLY later (e.g., in `/proc/[pid]/fdinfo/[n]`).
access_mode = AccessMode::O_RDONLY;
(None, Rights::empty())
} else if inode.type_() == InodeType::Dir && access_mode.is_writable() {
return_errno_with_message!(Errno::EISDIR, "a directory cannot be opened writable");
} else {
inode.open(access_mode, status_flags).transpose()?
let file_io = inode.open(access_mode, status_flags).transpose()?;
let rights = Rights::from(access_mode);
(file_io, rights)
};
let inner = Arc::new(InodeHandle_ {
@ -38,7 +44,7 @@ impl InodeHandle<Rights> {
access_mode,
status_flags: AtomicU32::new(status_flags.bits()),
});
Ok(Self(inner, Rights::from(access_mode)))
Ok(Self(inner, rights))
}
#[expect(dead_code)]
@ -53,10 +59,53 @@ impl InodeHandle<Rights> {
pub fn readdir(&self, visitor: &mut dyn DirentVisitor) -> Result<usize> {
if !self.1.contains(Rights::READ) {
return_errno_with_message!(Errno::EBADF, "file is not readable");
return_errno_with_message!(Errno::EBADF, "the file is not opened readable");
}
self.0.readdir(visitor)
}
pub fn test_range_lock(&self, lock: RangeLockItem) -> Result<RangeLockItem> {
if self.1.is_empty() {
return_errno_with_message!(Errno::EBADF, "the file is opened as a path");
}
self.0.test_range_lock(lock)
}
pub fn set_range_lock(&self, lock: &RangeLockItem, is_nonblocking: bool) -> Result<()> {
match lock.type_() {
RangeLockType::ReadLock => {
if !self.1.contains(Rights::READ) {
return_errno_with_message!(Errno::EBADF, "the file is not opened readable");
}
}
RangeLockType::WriteLock => {
if !self.1.contains(Rights::WRITE) {
return_errno_with_message!(Errno::EBADF, "the file is not opened writable");
}
}
RangeLockType::Unlock => {
if self.1.is_empty() {
return_errno_with_message!(Errno::EBADF, "the file is opened as a path");
}
}
}
self.0.set_range_lock(lock, is_nonblocking)
}
pub fn set_flock(&self, lock: FlockItem, is_nonblocking: bool) -> Result<()> {
if self.1.is_empty() {
return_errno_with_message!(Errno::EBADF, "the file is opened as a path");
}
self.0.set_flock(lock, is_nonblocking)
}
pub fn unlock_flock(&self) -> Result<()> {
if self.1.is_empty() {
return_errno_with_message!(Errno::EBADF, "the file is opened as a path");
}
self.0.unlock_flock(self);
Ok(())
}
}
impl Clone for InodeHandle<Rights> {
@ -72,7 +121,6 @@ impl Pollable for InodeHandle<Rights> {
#[inherit_methods(from = "self.0")]
impl FileLike for InodeHandle<Rights> {
fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result<i32>;
fn status_flags(&self) -> StatusFlags;
fn access_mode(&self) -> AccessMode;
fn metadata(&self) -> Metadata;
@ -82,40 +130,55 @@ impl FileLike for InodeHandle<Rights> {
fn set_owner(&self, uid: Uid) -> Result<()>;
fn group(&self) -> Result<Gid>;
fn set_group(&self, gid: Gid) -> Result<()>;
fn seek(&self, seek_from: SeekFrom) -> Result<usize>;
fn mappable(&self) -> Result<Mappable>;
fn read(&self, writer: &mut VmWriter) -> Result<usize> {
if !self.1.contains(Rights::READ) {
return_errno_with_message!(Errno::EBADF, "file is not readable");
return_errno_with_message!(Errno::EBADF, "the file is not opened readable");
}
self.0.read(writer)
}
fn write(&self, reader: &mut VmReader) -> Result<usize> {
if !self.1.contains(Rights::WRITE) {
return_errno_with_message!(Errno::EBADF, "file is not writable");
return_errno_with_message!(Errno::EBADF, "the file is not opened writable");
}
self.0.write(reader)
}
fn read_at(&self, offset: usize, writer: &mut VmWriter) -> Result<usize> {
if !self.1.contains(Rights::READ) {
return_errno_with_message!(Errno::EBADF, "file is not readable");
return_errno_with_message!(Errno::EBADF, "the file is not opened readable");
}
self.0.read_at(offset, writer)
}
fn write_at(&self, offset: usize, reader: &mut VmReader) -> Result<usize> {
if !self.1.contains(Rights::WRITE) {
return_errno_with_message!(Errno::EBADF, "file is not writable");
return_errno_with_message!(Errno::EBADF, "the file is not opened writable");
}
self.0.write_at(offset, reader)
}
fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result<i32> {
if self.1.is_empty() {
return_errno_with_message!(Errno::EBADF, "the file is opened as a path");
}
self.0.ioctl(cmd, arg)
}
fn mappable(&self) -> Result<Mappable> {
if self.1.is_empty() {
return_errno_with_message!(Errno::EBADF, "the file is opened as a path");
}
self.0.mappable()
}
fn resize(&self, new_size: usize) -> Result<()> {
if self.1.is_empty() {
return_errno_with_message!(Errno::EBADF, "the file is opened as a path");
}
if !self.1.contains(Rights::WRITE) {
return_errno_with_message!(Errno::EINVAL, "file is not writable");
return_errno_with_message!(Errno::EINVAL, "the file is not opened writable");
}
self.0.resize(new_size)
}
@ -125,9 +188,16 @@ impl FileLike for InodeHandle<Rights> {
Ok(())
}
fn seek(&self, seek_from: SeekFrom) -> Result<usize> {
if self.1.is_empty() {
return_errno_with_message!(Errno::EBADF, "the file is opened as a path");
}
self.0.seek(seek_from)
}
fn fallocate(&self, mode: FallocMode, offset: usize, len: usize) -> Result<()> {
if !self.1.contains(Rights::WRITE) {
return_errno_with_message!(Errno::EBADF, "file is not writable");
return_errno_with_message!(Errno::EBADF, "the file is not opened writable");
}
self.0.fallocate(mode, offset, len)
}

View File

@ -188,21 +188,20 @@ impl InodeHandle_ {
}
}
fn test_range_lock(&self, lock: RangeLockItem) -> Result<RangeLockItem> {
let mut req_lock = lock.clone();
if let Some(extension) = self.path.inode().extension() {
if let Some(range_lock_list) = extension.get::<RangeLockList>() {
req_lock = range_lock_list.test_lock(lock);
} else {
// The range lock could be placed if there is no lock list
req_lock.set_type(RangeLockType::Unlock);
}
} else {
debug!("Inode extension is not supported, the lock could be placed");
// Some file systems may not support range lock like procfs and sysfs
// Returns Ok if extension is not supported.
req_lock.set_type(RangeLockType::Unlock);
}
fn test_range_lock(&self, mut lock: RangeLockItem) -> Result<RangeLockItem> {
let Some(extension) = self.path.inode().extension() else {
// Range locks are not supported. So nothing is locked.
lock.set_type(RangeLockType::Unlock);
return Ok(lock);
};
let Some(range_lock_list) = extension.get::<RangeLockList>() else {
// The lock list is not present. So nothing is locked.
lock.set_type(RangeLockType::Unlock);
return Ok(lock);
};
let req_lock = range_lock_list.test_lock(lock);
Ok(req_lock)
}
@ -212,20 +211,17 @@ impl InodeHandle_ {
return Ok(());
}
self.check_range_lock_with_access_mode(lock)?;
if let Some(extension) = self.path.inode().extension() {
let range_lock_list = match extension.get::<RangeLockList>() {
Some(list) => list,
None => extension.get_or_put_default::<RangeLockList>(),
};
let Some(extension) = self.path.inode().extension() else {
// TODO: Figure out whether range locks are supported on all inodes.
warn!("the inode does not have support for range locks; this operation will fail");
return_errno_with_message!(Errno::ENOLCK, "range locks are not supported");
};
range_lock_list.set_lock(lock, is_nonblocking)
} else {
debug!("Inode extension is not supported, let the lock could be acquired");
// Some file systems may not support range lock like procfs and sysfs
// Returns Ok if extension is not supported.
Ok(())
}
let range_lock_list = match extension.get::<RangeLockList>() {
Some(list) => list,
None => extension.get_or_put_default::<RangeLockList>(),
};
range_lock_list.set_lock(lock, is_nonblocking)
}
fn release_range_locks(&self) {
@ -241,51 +237,32 @@ impl InodeHandle_ {
}
fn unlock_range_lock(&self, lock: &RangeLockItem) {
if let Some(extension) = self.path.inode().extension() {
if let Some(range_lock_list) = extension.get::<RangeLockList>() {
range_lock_list.unlock(lock);
}
if let Some(extension) = self.path.inode().extension()
&& let Some(range_lock_list) = extension.get::<RangeLockList>()
{
range_lock_list.unlock(lock);
}
}
fn check_range_lock_with_access_mode(&self, lock: &RangeLockItem) -> Result<()> {
match lock.type_() {
RangeLockType::ReadLock => {
if !self.access_mode().is_readable() {
return_errno_with_message!(Errno::EBADF, "file not readable");
}
}
RangeLockType::WriteLock => {
if !self.access_mode().is_writable() {
return_errno_with_message!(Errno::EBADF, "file not writable");
}
}
_ => (),
}
Ok(())
}
fn set_flock(&self, lock: FlockItem, is_nonblocking: bool) -> Result<()> {
if let Some(extension) = self.path.inode().extension() {
let flock_list = match extension.get::<FlockList>() {
Some(list) => list,
None => extension.get_or_put_default::<FlockList>(),
};
let Some(extension) = self.path.inode().extension() else {
// TODO: Figure out whether flocks are supported on all inodes.
warn!("the inode does not have support for flocks; this operation will fail");
return_errno_with_message!(Errno::ENOLCK, "flocks are not supported");
};
flock_list.set_lock(lock, is_nonblocking)
} else {
debug!("Inode extension is not supported, let the lock could be acquired");
// Some file systems may not support flock like procfs and sysfs
// Returns Ok if extension is not supported.
Ok(())
}
let flock_list = match extension.get::<FlockList>() {
Some(list) => list,
None => extension.get_or_put_default::<FlockList>(),
};
flock_list.set_lock(lock, is_nonblocking)
}
fn unlock_flock<R>(&self, req_owner: &InodeHandle<R>) {
if let Some(extension) = self.path.inode().extension() {
if let Some(flock_list) = extension.get::<FlockList>() {
flock_list.unlock(req_owner);
}
if let Some(extension) = self.path.inode().extension()
&& let Some(flock_list) = extension.get::<FlockList>()
{
flock_list.unlock(req_owner);
}
}
}
@ -309,7 +286,7 @@ impl Debug for InodeHandle_ {
.field("offset", &self.offset())
.field("access_mode", &self.access_mode())
.field("status_flags", &self.status_flags())
.finish()
.finish_non_exhaustive()
}
}
@ -319,26 +296,6 @@ impl<R> InodeHandle<R> {
&self.0.path
}
pub fn test_range_lock(&self, lock: RangeLockItem) -> Result<RangeLockItem> {
self.0.test_range_lock(lock)
}
pub fn set_range_lock(&self, lock: &RangeLockItem, is_nonblocking: bool) -> Result<()> {
self.0.set_range_lock(lock, is_nonblocking)
}
pub fn release_range_locks(&self) {
self.0.release_range_locks()
}
pub fn set_flock(&self, lock: FlockItem, is_nonblocking: bool) -> Result<()> {
self.0.set_flock(lock, is_nonblocking)
}
pub fn unlock_flock(&self) {
self.0.unlock_flock(self);
}
pub fn offset(&self) -> usize {
self.0.offset()
}
@ -346,8 +303,8 @@ impl<R> InodeHandle<R> {
impl<R> Drop for InodeHandle<R> {
fn drop(&mut self) {
self.release_range_locks();
self.unlock_flock();
self.0.release_range_locks();
self.0.unlock_flock(self);
}
}

View File

@ -8,11 +8,11 @@ use crate::prelude::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum AccessMode {
/// read only
/// Read only
O_RDONLY = 0,
/// write only
/// Write only
O_WRONLY = 1,
/// read write
/// Read write
O_RDWR = 2,
}
@ -29,28 +29,11 @@ impl AccessMode {
impl AccessMode {
pub fn from_u32(flags: u32) -> Result<Self> {
let bits = (flags & 0b11) as u8;
if bits > Self::O_RDWR as u8 {
return_errno_with_message!(Errno::EINVAL, "invalid bits for access mode");
}
Ok(match bits {
0 => Self::O_RDONLY,
1 => Self::O_WRONLY,
2 => Self::O_RDWR,
_ => unreachable!(),
})
}
}
impl From<Rights> for AccessMode {
fn from(rights: Rights) -> AccessMode {
if rights.contains(Rights::READ) && rights.contains(Rights::WRITE) {
AccessMode::O_RDWR
} else if rights.contains(Rights::READ) {
AccessMode::O_RDONLY
} else if rights.contains(Rights::WRITE) {
AccessMode::O_WRONLY
} else {
panic!("invalid rights");
match bits {
0 => Ok(Self::O_RDONLY),
1 => Ok(Self::O_WRONLY),
2 => Ok(Self::O_RDWR),
_ => return_errno_with_message!(Errno::EINVAL, "the bits are not a valid access mode"),
}
}
}

View File

@ -17,7 +17,7 @@ pub fn sys_flock(fd: FileDesc, ops: i32, ctx: &Context) -> Result<SyscallReturn>
let inode_file = file.as_inode_or_err()?;
let ops: FlockOps = FlockOps::from_i32(ops)?;
if ops.contains(FlockOps::LOCK_UN) {
inode_file.unlock_flock();
inode_file.unlock_flock()?;
} else {
let is_nonblocking = ops.contains(FlockOps::LOCK_NB);
let flock = {

View File

@ -108,7 +108,7 @@ fn do_sys_mmap(
if offset != 0 {
return_errno_with_message!(
Errno::EINVAL,
"offset must be zero for anonymous mapping"
"the offset must be zero for anonymous mapping"
);
}
@ -126,11 +126,11 @@ fn do_sys_mmap(
let access_mode = file.access_mode();
if vm_perms.contains(VmPerms::READ) && !access_mode.is_readable() {
return_errno!(Errno::EACCES);
return_errno_with_message!(Errno::EACCES, "the file is not opened readable");
}
if option.typ() == MMapType::Shared && !access_mode.is_writable() {
if vm_perms.contains(VmPerms::WRITE) {
return_errno!(Errno::EACCES);
return_errno_with_message!(Errno::EACCES, "the file is not opened writable");
}
vm_may_perms.remove(VmPerms::MAY_WRITE);
}
@ -152,14 +152,14 @@ fn do_sys_mmap(
fn check_option(addr: Vaddr, size: usize, option: &MMapOptions) -> Result<()> {
if option.typ() == MMapType::File {
return_errno_with_message!(Errno::EINVAL, "Invalid mmap type");
return_errno_with_message!(Errno::EINVAL, "invalid mmap type");
}
let map_end = addr.checked_add(size).ok_or(Errno::EINVAL)?;
if option.flags().contains(MMapFlags::MAP_FIXED)
&& !(is_userspace_vaddr(addr) && is_userspace_vaddr(map_end - 1))
{
return_errno_with_message!(Errno::EINVAL, "Invalid mmap fixed addr");
return_errno_with_message!(Errno::EINVAL, "invalid mmap fixed address");
}
Ok(())