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