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 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_id::DeviceId;
136    use starnix_uapi::file_mode::FileMode;
137
138    fn create_fs_node(current_task: &crate::task::CurrentTask) -> FsNodeHandle {
139        current_task
140            .fs()
141            .root()
142            .create_node(current_task, "foo".into(), FileMode::IFREG, DeviceId::NONE)
143            .expect("create_node")
144            .entry
145            .node
146            .clone()
147    }
148
149    #[derive(Debug)]
150    struct FileWriteGuard {
151        mode: FileWriteGuardMode,
152        node: FsNodeHandle,
153    }
154
155    impl FileWriteGuard {
156        pub fn new(node: &FsNodeHandle, mode: FileWriteGuardMode) -> Result<FileWriteGuard, Errno> {
157            let mut state = node.write_guard_state.lock();
158            state.acquire(mode)?;
159            Ok(FileWriteGuard { mode, node: node.clone() })
160        }
161    }
162
163    impl Drop for FileWriteGuard {
164        fn drop(&mut self) {
165            let mut state = self.node.write_guard_state.lock();
166            state.release(self.mode);
167        }
168    }
169
170    #[::fuchsia::test]
171    async fn test_write_exec_locking() {
172        spawn_kernel_and_run(async |current_task| {
173            let fs_node = create_fs_node(current_task);
174
175            let write_guard = FileWriteGuard::new(&fs_node, FileWriteGuardMode::WriteFile)
176                .expect("FsNode::lock failed unexpectedly");
177
178            assert_eq!(
179                FileWriteGuard::new(&fs_node, FileWriteGuardMode::ExecMapping).unwrap_err(),
180                errno!(ETXTBSY)
181            );
182
183            let write_mapping_guard =
184                FileWriteGuard::new(&fs_node, FileWriteGuardMode::WriteMapping)
185                    .expect("FsNode::lock failed unexpectedly");
186
187            assert_eq!(
188                FileWriteGuard::new(&fs_node, FileWriteGuardMode::ExecMapping).unwrap_err(),
189                errno!(ETXTBSY)
190            );
191
192            std::mem::drop(write_guard);
193
194            assert_eq!(
195                FileWriteGuard::new(&fs_node, FileWriteGuardMode::ExecMapping).unwrap_err(),
196                errno!(ETXTBSY)
197            );
198
199            std::mem::drop(write_mapping_guard);
200
201            let exec_guard = FileWriteGuard::new(&fs_node, FileWriteGuardMode::ExecMapping)
202                .expect("FsNode::lock failed unexpectedly");
203
204            assert_eq!(
205                FileWriteGuard::new(&fs_node, FileWriteGuardMode::WriteFile).unwrap_err(),
206                errno!(ETXTBSY)
207            );
208
209            std::mem::drop(exec_guard);
210
211            FileWriteGuard::new(&fs_node, FileWriteGuardMode::WriteFile)
212                .expect("FsNode::lock failed unexpectedly");
213        })
214        .await;
215    }
216
217    #[::fuchsia::test]
218    async fn test_no_seals() {
219        let mut state = FileWriteGuardState::default();
220
221        // By default seals are not enabled.
222        assert_eq!(state.try_add_seal(SealFlags::WRITE), error!(EINVAL));
223        assert_eq!(state.check_no_seal(SealFlags::WRITE), Ok(()));
224        assert_eq!(state.get_seals(), error!(EINVAL));
225    }
226
227    #[::fuchsia::test]
228    async fn test_seals() {
229        spawn_kernel_and_run(async |current_task| {
230            let fs_node = create_fs_node(current_task);
231
232            {
233                let mut state = fs_node.write_guard_state.lock();
234
235                state.enable_sealing(SealFlags::empty());
236
237                assert_eq!(state.check_no_seal(SealFlags::WRITE), Ok(()));
238                assert_eq!(state.get_seals(), Ok(SealFlags::empty()));
239
240                // Apply WRITE seal.
241                assert_eq!(state.try_add_seal(SealFlags::WRITE), Ok(()));
242                assert_eq!(state.check_no_seal(SealFlags::WRITE), error!(EPERM));
243                assert_eq!(state.get_seals(), Ok(SealFlags::WRITE));
244            }
245
246            // Files with WRITE seal can be opened for write.
247            let file_guard = FileWriteGuard::new(&fs_node, FileWriteGuardMode::WriteFile)
248                .expect("lock(WriteFile) failed");
249
250            // Files with WRITE seal cannot be mapped.
251            assert_eq!(
252                FileWriteGuard::new(&fs_node, FileWriteGuardMode::WriteMapping).unwrap_err(),
253                errno!(EPERM)
254            );
255
256            std::mem::drop(file_guard);
257        })
258        .await;
259    }
260
261    #[::fuchsia::test]
262    async fn test_seals_block_when_mapped() {
263        spawn_kernel_and_run(async |current_task| {
264            let fs_node = create_fs_node(current_task);
265            fs_node.write_guard_state.lock().enable_sealing(SealFlags::empty());
266
267            let _write_guard = FileWriteGuard::new(&fs_node, FileWriteGuardMode::WriteFile)
268                .expect("FsNode::lock failed unexpectedly");
269            let write_mapping_guard =
270                FileWriteGuard::new(&fs_node, FileWriteGuardMode::WriteMapping)
271                    .expect("FsNode::lock failed unexpectedly");
272
273            // Should fail since the file is mapped.
274            {
275                let mut state = fs_node.write_guard_state.lock();
276                assert_eq!(state.try_add_seal(SealFlags::WRITE), error!(EBUSY));
277                assert_eq!(state.check_no_seal(SealFlags::WRITE), Ok(()));
278            }
279
280            std::mem::drop(write_mapping_guard);
281
282            // Should succeed after file is unmapped.
283            {
284                let mut state = fs_node.write_guard_state.lock();
285                assert_eq!(state.try_add_seal(SealFlags::WRITE), Ok(()));
286                assert_eq!(state.check_no_seal(SealFlags::WRITE), error!(EPERM));
287            }
288        })
289        .await;
290    }
291
292    #[::fuchsia::test]
293    async fn test_seals_sealed() {
294        spawn_kernel_and_run(async |current_task| {
295            let fs_node = create_fs_node(current_task);
296            let mut state = fs_node.write_guard_state.lock();
297
298            state.enable_sealing(SealFlags::SEAL);
299
300            assert_eq!(state.get_seals(), Ok(SealFlags::SEAL));
301
302            assert_eq!(state.try_add_seal(SealFlags::WRITE), error!(EPERM));
303            assert_eq!(state.get_seals(), Ok(SealFlags::SEAL));
304        })
305        .await;
306    }
307}