| // Copyright 2026 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use crate::security::PermissionFlags; |
| use starnix_uapi::open_flags::OpenFlags; |
| |
| /// Context or reason for an access permission check. |
| #[derive(Debug, Copy, Clone, PartialEq, Eq)] |
| pub enum CheckAccessReason { |
| /// Syscall `access(2)` or `faccessat(2)`. |
| Access, |
| /// Syscall `chdir(2)`. |
| Chdir, |
| /// Syscall `chroot(2)`. |
| Chroot, |
| /// Syscall `execve(2)`, `execveat(2)`, script interpreter, or ELF dynamic linker execution. |
| Exec, |
| /// Timestamp update (`utimensat(2)`). |
| ChangeTimestamps { now: bool }, |
| /// Internal VFS permission check. |
| InternalPermissionChecks, |
| } |
| |
| /// Configuration for access permission checks performed on filesystem nodes. |
| #[derive(Debug, Copy, Clone, PartialEq, Eq)] |
| pub struct AccessCheck { |
| permission_flags: PermissionFlags, |
| reason: CheckAccessReason, |
| } |
| |
| impl AccessCheck { |
| /// Internal VFS permission check for the specified access rights. |
| pub fn for_internal(permissions: impl Into<PermissionFlags>) -> Self { |
| Self { |
| permission_flags: permissions.into(), |
| reason: CheckAccessReason::InternalPermissionChecks, |
| } |
| } |
| |
| /// Check for execute (search) permission when changing working directory. |
| pub fn for_chdir() -> Self { |
| Self { permission_flags: PermissionFlags::EXEC, reason: CheckAccessReason::Chdir } |
| } |
| |
| /// Check for execute (search) permission when changing root directory. |
| pub fn for_chroot() -> Self { |
| Self { permission_flags: PermissionFlags::EXEC, reason: CheckAccessReason::Chroot } |
| } |
| |
| /// Check for specified access rights for `access(2)` / `faccessat(2)`. |
| pub fn for_access(permissions: impl Into<PermissionFlags>) -> Self { |
| Self { permission_flags: permissions.into(), reason: CheckAccessReason::Access } |
| } |
| |
| /// Permission flags to check. |
| pub fn permission_flags(&self) -> PermissionFlags { |
| self.permission_flags |
| } |
| |
| /// Reason for the access check. |
| pub fn reason(&self) -> CheckAccessReason { |
| self.reason |
| } |
| } |
| |
| /// Configuration for opening a file, bundling open flags with concrete permission checks. |
| #[derive(Debug, Copy, Clone, PartialEq, Eq)] |
| pub struct OpenAccessCheck { |
| open_flags: OpenFlags, |
| access_check: AccessCheck, |
| } |
| |
| impl From<OpenFlags> for OpenAccessCheck { |
| fn from(open_flags: OpenFlags) -> Self { |
| Self { |
| open_flags, |
| access_check: AccessCheck { |
| permission_flags: open_flags.into(), |
| reason: CheckAccessReason::InternalPermissionChecks, |
| }, |
| } |
| } |
| } |
| |
| impl OpenAccessCheck { |
| /// Creates an open check with explicit open flags and access check. |
| pub fn new(open_flags: OpenFlags, access_check: AccessCheck) -> Self { |
| Self { open_flags, access_check } |
| } |
| |
| /// Skips permission checks when opening a file with the specified flags. |
| pub fn skip(open_flags: OpenFlags) -> Self { |
| Self { |
| open_flags, |
| access_check: AccessCheck { |
| permission_flags: PermissionFlags::empty(), |
| reason: CheckAccessReason::InternalPermissionChecks, |
| }, |
| } |
| } |
| |
| /// Configures read-only flags with an execute permission check for `execve(2)` / `execveat(2)`. |
| pub fn for_exec() -> Self { |
| Self { |
| open_flags: OpenFlags::RDONLY, |
| access_check: AccessCheck { |
| permission_flags: PermissionFlags::EXEC, |
| reason: CheckAccessReason::Exec, |
| }, |
| } |
| } |
| |
| /// Open flags for the file. |
| pub fn open_flags(&self) -> OpenFlags { |
| self.open_flags |
| } |
| |
| /// Permission checks to perform when opening the file. |
| pub fn access_check(&self) -> AccessCheck { |
| self.access_check |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use starnix_uapi::file_mode::Access; |
| use starnix_uapi::open_flags::OpenFlags; |
| |
| #[::fuchsia::test] |
| fn test_access_check() { |
| let for_internal_access = AccessCheck::for_internal(Access::READ); |
| assert_eq!(for_internal_access.permission_flags(), PermissionFlags::READ); |
| assert_eq!(for_internal_access.reason(), CheckAccessReason::InternalPermissionChecks); |
| |
| let for_internal_perms = AccessCheck::for_internal(PermissionFlags::EXEC); |
| assert_eq!(for_internal_perms.permission_flags(), PermissionFlags::EXEC); |
| assert_eq!(for_internal_perms.reason(), CheckAccessReason::InternalPermissionChecks); |
| |
| let for_chdir = AccessCheck::for_chdir(); |
| assert_eq!(for_chdir.permission_flags(), PermissionFlags::EXEC); |
| assert_eq!(for_chdir.reason(), CheckAccessReason::Chdir); |
| |
| let for_chroot = AccessCheck::for_chroot(); |
| assert_eq!(for_chroot.permission_flags(), PermissionFlags::EXEC); |
| assert_eq!(for_chroot.reason(), CheckAccessReason::Chroot); |
| |
| let for_access = AccessCheck::for_access(Access::READ | Access::WRITE); |
| assert_eq!(for_access.permission_flags(), PermissionFlags::READ | PermissionFlags::WRITE); |
| assert_eq!(for_access.reason(), CheckAccessReason::Access); |
| } |
| |
| #[::fuchsia::test] |
| fn test_open_access_check() { |
| let from_flags = OpenAccessCheck::from(OpenFlags::RDWR); |
| assert_eq!(from_flags.open_flags(), OpenFlags::RDWR); |
| assert_eq!( |
| from_flags.access_check().permission_flags(), |
| PermissionFlags::READ | PermissionFlags::WRITE |
| ); |
| assert_eq!(from_flags.access_check().reason(), CheckAccessReason::InternalPermissionChecks); |
| |
| let skip_check = OpenAccessCheck::skip(OpenFlags::RDWR | OpenFlags::CREAT); |
| assert_eq!(skip_check.open_flags(), OpenFlags::RDWR | OpenFlags::CREAT); |
| assert_eq!(skip_check.access_check().permission_flags(), PermissionFlags::empty()); |
| assert_eq!(skip_check.access_check().reason(), CheckAccessReason::InternalPermissionChecks); |
| |
| let for_exec = OpenAccessCheck::for_exec(); |
| assert_eq!(for_exec.open_flags(), OpenFlags::RDONLY); |
| assert_eq!(for_exec.access_check().permission_flags(), PermissionFlags::EXEC); |
| assert_eq!(for_exec.access_check().reason(), CheckAccessReason::Exec); |
| |
| let custom = |
| OpenAccessCheck::new(OpenFlags::RDONLY, AccessCheck::for_internal(Access::READ)); |
| assert_eq!(custom.open_flags(), OpenFlags::RDONLY); |
| assert_eq!(custom.access_check().permission_flags(), PermissionFlags::READ); |
| } |
| } |