Skip to main content

starnix_core/vfs/
file_write_guard.rs

1// Copyright 2023 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::vfs::FsNode;
6use starnix_uapi::errors::Errno;
7use starnix_uapi::seal_flags::SealFlags;
8use starnix_uapi::{errno, error};
9
10#[derive(Copy, Clone, Debug, Eq, PartialEq)]
11pub enum FileWriteGuardMode {
12    // File open for write.
13    WriteFile,
14
15    // Writable mapping.
16    WriteMapping,
17
18    // Mapped for execution.
19    ExecMapping,
20}
21
22// Tracks FileWriteGuard state for FsNode instances. Used to implement executable write blocking
23// (see `ETXTBSY`) and file seals (see `memfd_create`).
24// Note that this is not related to the `flock` file state, which is stored in `FlockInfo`.
25#[derive(Default)]
26pub struct FileWriteGuardState {
27    // Positive values indicate number of write locks, negative - execution locks.
28    // This implies that write and exec locks cannot be held similtaneously.
29    write_exec_locks: isize,
30
31    // Number of WriteMapping guards.
32    num_write_mappings: usize,
33
34    // Seals are not allowed by default.
35    seals: Option<SealFlags>,
36}
37
38impl FileWriteGuardState {
39    pub fn acquire(&mut self, mode: FileWriteGuardMode) -> Result<(), Errno> {
40        match mode {
41            FileWriteGuardMode::WriteFile => {
42                if self.write_exec_locks < 0 {
43                    return error!(ETXTBSY);
44                }
45
46                // Do not check write seals: file with a write seals can be opened,
47                // but `write()` will fail.
48
49                self.write_exec_locks += 1;
50            }
51            FileWriteGuardMode::WriteMapping => {
52                self.check_no_seal(SealFlags::WRITE | SealFlags::FUTURE_WRITE)?;
53
54                // File must be open for write in order to be mapped.
55                assert!(self.write_exec_locks > 0);
56
57                self.write_exec_locks += 1;
58                self.num_write_mappings += 1;
59            }
60            FileWriteGuardMode::ExecMapping => {
61                if self.write_exec_locks > 0 {
62                    return error!(ETXTBSY);
63                }
64                self.write_exec_locks -= 1;
65            }
66        }
67        Ok(())
68    }
69
70    pub fn release(&mut self, mode: FileWriteGuardMode) {
71        match mode {
72            FileWriteGuardMode::WriteFile => {
73                assert!(self.write_exec_locks > 0);
74                self.write_exec_locks -= 1;
75            }
76            FileWriteGuardMode::WriteMapping => {
77                assert!(self.write_exec_locks > 0);
78                self.write_exec_locks -= 1;
79                assert!(self.num_write_mappings > 0);
80                self.num_write_mappings -= 1;
81            }
82            FileWriteGuardMode::ExecMapping => {
83                assert!(self.write_exec_locks < 0);
84                self.write_exec_locks += 1;
85            }
86        };
87    }
88
89    pub fn enable_sealing(&mut self, initial_seals: SealFlags) {
90        self.seals = Some(initial_seals);
91    }
92
93    /// Add a new seal to the current set, if allowed.
94    pub fn try_add_seal(&mut self, flags: SealFlags) -> Result<(), Errno> {
95        if let Some(seals) = self.seals.as_mut() {
96            if seals.contains(SealFlags::SEAL) {
97                // More seals cannot be added.
98                return error!(EPERM);
99            }
100
101            // Write seal cannot be added when we have writable mappings.
102            if flags.contains(SealFlags::WRITE) && self.num_write_mappings > 0 {
103                return error!(EBUSY);
104            }
105
106            seals.insert(flags);
107
108            Ok(())
109        } else {
110            // Seals are not allowed for this file.
111            error!(EINVAL)
112        }
113    }
114
115    /// Fails with EPERM if the current seal flags contain any of the given `flags`.
116    pub fn check_no_seal(&self, flags: SealFlags) -> Result<(), Errno> {
117        if let Some(seals) = self.seals.as_ref() {
118            if seals.intersects(flags) {
119                return error!(EPERM);
120            }
121        }
122        Ok(())
123    }
124
125    /// Returns current seals.
126    pub fn get_seals(&self) -> Result<SealFlags, Errno> {
127        self.seals.ok_or_else(|| errno!(EINVAL))
128    }
129}
130
131/// RAII guard that holds a [`FileWriteGuardMode`] reservation on an [`FsNode`] and releases it on
132/// drop without keeping the [`FileWriteGuardState`] mutex locked.
133#[derive(Debug)]
134#[must_use]
135pub struct FileWriteGuardRef<'a> {
136    node: &'a FsNode,
137    mode: FileWriteGuardMode,
138}
139
140impl<'a> FileWriteGuardRef<'a> {
141    /// Acquires a [`FileWriteGuardMode`] reservation on `node`.
142    pub(in crate::vfs) fn new(node: &'a FsNode, mode: FileWriteGuardMode) -> Result<Self, Errno> {
143        node.write_guard_state.lock().acquire(mode)?;
144        Ok(Self { node, mode })
145    }
146}
147
148impl Drop for FileWriteGuardRef<'_> {
149    fn drop(&mut self) {
150        self.node.write_guard_state.lock().release(self.mode);
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157    use crate::testing::spawn_kernel_and_run;
158    use crate::vfs::FsNodeHandle;
159    use starnix_uapi::device_id::DeviceId;
160    use starnix_uapi::file_mode::FileMode;
161
162    fn create_fs_node(current_task: &crate::task::CurrentTask) -> FsNodeHandle {
163        current_task
164            .fs()
165            .root()
166            .create_node(current_task, "foo".into(), FileMode::IFREG, DeviceId::NONE)
167            .expect("create_node")
168            .entry
169            .node
170            .clone()
171    }
172
173    #[::fuchsia::test]
174    async fn test_write_exec_locking() {
175        spawn_kernel_and_run(async |current_task| {
176            let fs_node = create_fs_node(current_task);
177
178            let write_guard = fs_node
179                .create_write_guard(FileWriteGuardMode::WriteFile)
180                .expect("FsNode::lock failed unexpectedly");
181
182            assert_eq!(
183                fs_node.create_write_guard(FileWriteGuardMode::ExecMapping).unwrap_err(),
184                errno!(ETXTBSY)
185            );
186
187            let write_mapping_guard = fs_node
188                .create_write_guard(FileWriteGuardMode::WriteMapping)
189                .expect("FsNode::lock failed unexpectedly");
190
191            assert_eq!(
192                fs_node.create_write_guard(FileWriteGuardMode::ExecMapping).unwrap_err(),
193                errno!(ETXTBSY)
194            );
195
196            std::mem::drop(write_guard);
197
198            assert_eq!(
199                fs_node.create_write_guard(FileWriteGuardMode::ExecMapping).unwrap_err(),
200                errno!(ETXTBSY)
201            );
202
203            std::mem::drop(write_mapping_guard);
204
205            let exec_guard = fs_node
206                .create_write_guard(FileWriteGuardMode::ExecMapping)
207                .expect("FsNode::lock failed unexpectedly");
208
209            assert_eq!(
210                fs_node.create_write_guard(FileWriteGuardMode::WriteFile).unwrap_err(),
211                errno!(ETXTBSY)
212            );
213
214            std::mem::drop(exec_guard);
215
216            let _write_guard = fs_node
217                .create_write_guard(FileWriteGuardMode::WriteFile)
218                .expect("FsNode::lock failed unexpectedly");
219        })
220        .await;
221    }
222
223    #[::fuchsia::test]
224    async fn test_no_seals() {
225        let mut state = FileWriteGuardState::default();
226
227        // By default seals are not enabled.
228        assert_eq!(state.try_add_seal(SealFlags::WRITE), error!(EINVAL));
229        assert_eq!(state.check_no_seal(SealFlags::WRITE), Ok(()));
230        assert_eq!(state.get_seals(), error!(EINVAL));
231    }
232
233    #[::fuchsia::test]
234    async fn test_seals() {
235        spawn_kernel_and_run(async |current_task| {
236            let fs_node = create_fs_node(current_task);
237
238            {
239                let mut state = fs_node.write_guard_state.lock();
240
241                state.enable_sealing(SealFlags::empty());
242
243                assert_eq!(state.check_no_seal(SealFlags::WRITE), Ok(()));
244                assert_eq!(state.get_seals(), Ok(SealFlags::empty()));
245
246                // Apply WRITE seal.
247                assert_eq!(state.try_add_seal(SealFlags::WRITE), Ok(()));
248                assert_eq!(state.check_no_seal(SealFlags::WRITE), error!(EPERM));
249                assert_eq!(state.get_seals(), Ok(SealFlags::WRITE));
250            }
251
252            // Files with WRITE seal can be opened for write.
253            let file_guard = fs_node
254                .create_write_guard(FileWriteGuardMode::WriteFile)
255                .expect("lock(WriteFile) failed");
256
257            // Files with WRITE seal cannot be mapped.
258            assert_eq!(
259                fs_node.create_write_guard(FileWriteGuardMode::WriteMapping).unwrap_err(),
260                errno!(EPERM)
261            );
262
263            std::mem::drop(file_guard);
264        })
265        .await;
266    }
267
268    #[::fuchsia::test]
269    async fn test_seals_block_when_mapped() {
270        spawn_kernel_and_run(async |current_task| {
271            let fs_node = create_fs_node(current_task);
272            fs_node.write_guard_state.lock().enable_sealing(SealFlags::empty());
273
274            let _write_guard = fs_node
275                .create_write_guard(FileWriteGuardMode::WriteFile)
276                .expect("FsNode::lock failed unexpectedly");
277            let write_mapping_guard = fs_node
278                .create_write_guard(FileWriteGuardMode::WriteMapping)
279                .expect("FsNode::lock failed unexpectedly");
280
281            // Should fail since the file is mapped.
282            {
283                let mut state = fs_node.write_guard_state.lock();
284                assert_eq!(state.try_add_seal(SealFlags::WRITE), error!(EBUSY));
285                assert_eq!(state.check_no_seal(SealFlags::WRITE), Ok(()));
286            }
287
288            std::mem::drop(write_mapping_guard);
289
290            // Should succeed after file is unmapped.
291            {
292                let mut state = fs_node.write_guard_state.lock();
293                assert_eq!(state.try_add_seal(SealFlags::WRITE), Ok(()));
294                assert_eq!(state.check_no_seal(SealFlags::WRITE), error!(EPERM));
295            }
296        })
297        .await;
298    }
299
300    #[::fuchsia::test]
301    async fn test_seals_sealed() {
302        spawn_kernel_and_run(async |current_task| {
303            let fs_node = create_fs_node(current_task);
304            let mut state = fs_node.write_guard_state.lock();
305
306            state.enable_sealing(SealFlags::SEAL);
307
308            assert_eq!(state.get_seals(), Ok(SealFlags::SEAL));
309
310            assert_eq!(state.try_add_seal(SealFlags::WRITE), error!(EPERM));
311            assert_eq!(state.get_seals(), Ok(SealFlags::SEAL));
312        })
313        .await;
314    }
315}