blob: f94c5ff1aec8b05320687266d1205b144be65789 [file]
// 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);
}
}