Skip to main content

starnix_core/vfs/
access_check.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::security::PermissionFlags;
6use starnix_uapi::open_flags::OpenFlags;
7
8/// Context or reason for an access permission check.
9#[derive(Debug, Copy, Clone, PartialEq, Eq)]
10pub enum CheckAccessReason {
11    /// Syscall `access(2)` or `faccessat(2)`.
12    Access,
13    /// Syscall `chdir(2)`.
14    Chdir,
15    /// Syscall `chroot(2)`.
16    Chroot,
17    /// Syscall `execve(2)`, `execveat(2)`, script interpreter, or ELF dynamic linker execution.
18    Exec,
19    /// Timestamp update (`utimensat(2)`).
20    ChangeTimestamps { now: bool },
21    /// Internal VFS permission check.
22    InternalPermissionChecks,
23}
24
25/// Configuration for access permission checks performed on filesystem nodes.
26#[derive(Debug, Copy, Clone, PartialEq, Eq)]
27pub struct AccessCheck {
28    permission_flags: PermissionFlags,
29    reason: CheckAccessReason,
30}
31
32impl AccessCheck {
33    /// Internal VFS permission check for the specified access rights.
34    pub fn for_internal(permissions: impl Into<PermissionFlags>) -> Self {
35        Self {
36            permission_flags: permissions.into(),
37            reason: CheckAccessReason::InternalPermissionChecks,
38        }
39    }
40
41    /// Check for execute (search) permission when changing working directory.
42    pub fn for_chdir() -> Self {
43        Self { permission_flags: PermissionFlags::EXEC, reason: CheckAccessReason::Chdir }
44    }
45
46    /// Check for execute (search) permission when changing root directory.
47    pub fn for_chroot() -> Self {
48        Self { permission_flags: PermissionFlags::EXEC, reason: CheckAccessReason::Chroot }
49    }
50
51    /// Check for specified access rights for `access(2)` / `faccessat(2)`.
52    pub fn for_access(permissions: impl Into<PermissionFlags>) -> Self {
53        Self { permission_flags: permissions.into(), reason: CheckAccessReason::Access }
54    }
55
56    /// Permission flags to check.
57    pub fn permission_flags(&self) -> PermissionFlags {
58        self.permission_flags
59    }
60
61    /// Reason for the access check.
62    pub fn reason(&self) -> CheckAccessReason {
63        self.reason
64    }
65}
66
67/// Configuration for opening a file, bundling open flags with concrete permission checks.
68#[derive(Debug, Copy, Clone, PartialEq, Eq)]
69pub struct OpenAccessCheck {
70    open_flags: OpenFlags,
71    access_check: AccessCheck,
72}
73
74impl From<OpenFlags> for OpenAccessCheck {
75    fn from(open_flags: OpenFlags) -> Self {
76        Self {
77            open_flags,
78            access_check: AccessCheck {
79                permission_flags: open_flags.into(),
80                reason: CheckAccessReason::InternalPermissionChecks,
81            },
82        }
83    }
84}
85
86impl OpenAccessCheck {
87    /// Creates an open check with explicit open flags and access check.
88    pub fn new(open_flags: OpenFlags, access_check: AccessCheck) -> Self {
89        Self { open_flags, access_check }
90    }
91
92    /// Skips permission checks when opening a file with the specified flags.
93    pub fn skip(open_flags: OpenFlags) -> Self {
94        Self {
95            open_flags,
96            access_check: AccessCheck {
97                permission_flags: PermissionFlags::empty(),
98                reason: CheckAccessReason::InternalPermissionChecks,
99            },
100        }
101    }
102
103    /// Configures read-only flags with an execute permission check for `execve(2)` / `execveat(2)`.
104    pub fn for_exec() -> Self {
105        Self {
106            open_flags: OpenFlags::RDONLY,
107            access_check: AccessCheck {
108                permission_flags: PermissionFlags::EXEC,
109                reason: CheckAccessReason::Exec,
110            },
111        }
112    }
113
114    /// Open flags for the file.
115    pub fn open_flags(&self) -> OpenFlags {
116        self.open_flags
117    }
118
119    /// Permission checks to perform when opening the file.
120    pub fn access_check(&self) -> AccessCheck {
121        self.access_check
122    }
123}
124
125#[cfg(test)]
126mod test {
127    use super::*;
128    use starnix_uapi::file_mode::Access;
129    use starnix_uapi::open_flags::OpenFlags;
130
131    #[::fuchsia::test]
132    fn test_access_check() {
133        let for_internal_access = AccessCheck::for_internal(Access::READ);
134        assert_eq!(for_internal_access.permission_flags(), PermissionFlags::READ);
135        assert_eq!(for_internal_access.reason(), CheckAccessReason::InternalPermissionChecks);
136
137        let for_internal_perms = AccessCheck::for_internal(PermissionFlags::EXEC);
138        assert_eq!(for_internal_perms.permission_flags(), PermissionFlags::EXEC);
139        assert_eq!(for_internal_perms.reason(), CheckAccessReason::InternalPermissionChecks);
140
141        let for_chdir = AccessCheck::for_chdir();
142        assert_eq!(for_chdir.permission_flags(), PermissionFlags::EXEC);
143        assert_eq!(for_chdir.reason(), CheckAccessReason::Chdir);
144
145        let for_chroot = AccessCheck::for_chroot();
146        assert_eq!(for_chroot.permission_flags(), PermissionFlags::EXEC);
147        assert_eq!(for_chroot.reason(), CheckAccessReason::Chroot);
148
149        let for_access = AccessCheck::for_access(Access::READ | Access::WRITE);
150        assert_eq!(for_access.permission_flags(), PermissionFlags::READ | PermissionFlags::WRITE);
151        assert_eq!(for_access.reason(), CheckAccessReason::Access);
152    }
153
154    #[::fuchsia::test]
155    fn test_open_access_check() {
156        let from_flags = OpenAccessCheck::from(OpenFlags::RDWR);
157        assert_eq!(from_flags.open_flags(), OpenFlags::RDWR);
158        assert_eq!(
159            from_flags.access_check().permission_flags(),
160            PermissionFlags::READ | PermissionFlags::WRITE
161        );
162        assert_eq!(from_flags.access_check().reason(), CheckAccessReason::InternalPermissionChecks);
163
164        let skip_check = OpenAccessCheck::skip(OpenFlags::RDWR | OpenFlags::CREAT);
165        assert_eq!(skip_check.open_flags(), OpenFlags::RDWR | OpenFlags::CREAT);
166        assert_eq!(skip_check.access_check().permission_flags(), PermissionFlags::empty());
167        assert_eq!(skip_check.access_check().reason(), CheckAccessReason::InternalPermissionChecks);
168
169        let for_exec = OpenAccessCheck::for_exec();
170        assert_eq!(for_exec.open_flags(), OpenFlags::RDONLY);
171        assert_eq!(for_exec.access_check().permission_flags(), PermissionFlags::EXEC);
172        assert_eq!(for_exec.access_check().reason(), CheckAccessReason::Exec);
173
174        let custom =
175            OpenAccessCheck::new(OpenFlags::RDONLY, AccessCheck::for_internal(Access::READ));
176        assert_eq!(custom.open_flags(), OpenFlags::RDONLY);
177        assert_eq!(custom.access_check().permission_flags(), PermissionFlags::READ);
178    }
179}