Skip to main content

starnix_core/vfs/
fs_context.rs

1// Copyright 2021 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;
6use crate::task::{CurrentTask, MountsWriteToken};
7use crate::vfs::{ActiveNamespaceNode, CheckAccessReason, Namespace, NamespaceNode};
8use starnix_logging::log_trace;
9use starnix_sync::{FileOpsCore, LockDepRwLock, LockEqualOrBefore, Locked};
10use starnix_uapi::auth::CAP_SYS_CHROOT;
11use starnix_uapi::errno;
12use starnix_uapi::errors::Errno;
13use starnix_uapi::file_mode::{Access, FileMode};
14use std::sync::Arc;
15
16/// The mutable state for an FsContext.
17///
18/// This state is cloned in FsContext::fork.
19#[derive(Debug, Clone)]
20struct FsContextState {
21    /// The namespace tree for this FsContext.
22    ///
23    /// This field owns the mount table for this FsContext.
24    namespace: Arc<Namespace>,
25
26    /// The root of the namespace tree for this FsContext.
27    ///
28    /// Operations on the file system are typically either relative to this
29    /// root or to the cwd().
30    root: ActiveNamespaceNode,
31
32    /// The current working directory.
33    cwd: ActiveNamespaceNode,
34
35    // See <https://man7.org/linux/man-pages/man2/umask.2.html>
36    umask: FileMode,
37}
38
39impl FsContextState {
40    fn set_namespace(
41        &mut self,
42        new_ns: Arc<Namespace>,
43        mounts_guard: &MountsWriteToken,
44    ) -> Result<(), Errno> {
45        log_trace!("updating namespace");
46        let new_root = Namespace::translate_node(self.root.to_passive(), &new_ns, mounts_guard)
47            .ok_or_else(|| errno!(EINVAL))?;
48        let new_cwd = Namespace::translate_node(self.cwd.to_passive(), &new_ns, mounts_guard)
49            .ok_or_else(|| errno!(EINVAL))?;
50
51        // Only perform a mutation if the rebased nodes both exist in the target namespace.
52        self.root = new_root.into_active();
53        self.cwd = new_cwd.into_active();
54        self.namespace = new_ns;
55        log_trace!("namespace update succeeded");
56        Ok(())
57    }
58}
59
60/// The file system context associated with a task.
61///
62/// File system operations, such as opening a file or mounting a directory, are
63/// performed using this context.
64#[derive(Debug)]
65pub struct FsContext {
66    state: LockDepRwLock<FsContextState, starnix_sync::FsContextStateLock>,
67}
68
69impl FsContext {
70    /// Create an FsContext for the given namespace.
71    ///
72    /// The root and cwd of the FsContext are initialized to the root of the
73    /// namespace.
74    pub fn new(namespace: Arc<Namespace>) -> Arc<FsContext> {
75        let root = namespace.root();
76        Arc::new(FsContext {
77            state: FsContextState {
78                namespace,
79                root: root.clone().into_active(),
80                cwd: root.into_active(),
81                umask: FileMode::DEFAULT_UMASK,
82            }
83            .into(),
84        })
85    }
86
87    pub fn fork(&self) -> Arc<FsContext> {
88        // A child process created via fork(2) inherits its parent's umask.
89        // The umask is left unchanged by execve(2).
90        //
91        // See <https://man7.org/linux/man-pages/man2/umask.2.html>
92
93        Arc::new(FsContext { state: self.state.read().clone().into() })
94    }
95
96    /// Returns a reference to the current working directory.
97    pub fn cwd(&self) -> NamespaceNode {
98        let state = self.state.read();
99        state.cwd.to_passive()
100    }
101
102    /// Returns the root.
103    pub fn root(&self) -> NamespaceNode {
104        let state = self.state.read();
105        state.root.to_passive()
106    }
107
108    /// Change the current working directory.
109    pub fn chdir<L>(
110        &self,
111        locked: &mut Locked<L>,
112        current_task: &CurrentTask,
113        name: NamespaceNode,
114    ) -> Result<(), Errno>
115    where
116        L: LockEqualOrBefore<FileOpsCore>,
117    {
118        name.check_access(locked, current_task, Access::EXEC, CheckAccessReason::Chdir)?;
119        let mut state = self.state.write();
120        state.cwd = name.into_active();
121        Ok(())
122    }
123
124    /// Change the root.
125    pub fn chroot<L>(
126        &self,
127        locked: &mut Locked<L>,
128        current_task: &CurrentTask,
129        name: NamespaceNode,
130    ) -> Result<(), Errno>
131    where
132        L: LockEqualOrBefore<FileOpsCore>,
133    {
134        name.check_access(locked, current_task, Access::EXEC, CheckAccessReason::Chroot)
135            .map_err(|_| errno!(EACCES))?;
136        security::check_task_capable(current_task, CAP_SYS_CHROOT)?;
137
138        let mut state = self.state.write();
139        state.root = name.into_active();
140        Ok(())
141    }
142
143    pub fn umask(&self) -> FileMode {
144        self.state.read().umask
145    }
146
147    pub fn apply_umask(&self, mode: FileMode) -> FileMode {
148        let umask = self.state.read().umask;
149        mode & !umask
150    }
151
152    pub fn set_umask(&self, umask: FileMode) -> FileMode {
153        let mut state = self.state.write();
154        let old_umask = state.umask;
155
156        // umask() sets the calling process's file mode creation mask
157        // (umask) to mask & 0o777 (i.e., only the file permission bits of
158        // mask are used), and returns the previous value of the mask.
159        //
160        // See <https://man7.org/linux/man-pages/man2/umask.2.html>
161        state.umask = umask & FileMode::from_bits(0o777);
162
163        old_umask
164    }
165
166    pub fn set_namespace(&self, new_ns: Arc<Namespace>) -> Result<(), Errno> {
167        let mut state = self.state.write();
168        let kernel = state.namespace.kernel();
169        let mounts_guard = kernel.mounts_lock.lock();
170        state.set_namespace(new_ns, &mounts_guard)?;
171        Ok(())
172    }
173
174    pub fn unshare_namespace(&self) {
175        let mut state = self.state.write();
176        let kernel = state.namespace.kernel();
177        let mounts_guard = kernel.mounts_lock.lock();
178
179        let cloned = state.namespace.clone_namespace(&mounts_guard);
180        state
181            .set_namespace(cloned, &mounts_guard)
182            .expect("nodes should exist in the cloned namespace");
183    }
184
185    pub fn namespace(&self) -> Arc<Namespace> {
186        Arc::clone(&self.state.read().namespace)
187    }
188}
189
190#[cfg(test)]
191mod test {
192    use crate::fs::tmpfs::TmpFs;
193    use crate::testing::{spawn_kernel_and_run, spawn_kernel_and_run_with_pkgfs};
194    use crate::vfs::{FsContext, Namespace};
195    use starnix_uapi::file_mode::FileMode;
196    use starnix_uapi::open_flags::OpenFlags;
197
198    #[::fuchsia::test]
199    async fn test_umask() {
200        spawn_kernel_and_run(async |locked, current_task| {
201            let kernel = current_task.kernel();
202            let fs = FsContext::new(Namespace::new(TmpFs::new_fs(locked, &kernel)));
203
204            assert_eq!(FileMode::from_bits(0o22), fs.set_umask(FileMode::from_bits(0o3020)));
205            assert_eq!(FileMode::from_bits(0o646), fs.apply_umask(FileMode::from_bits(0o666)));
206            assert_eq!(FileMode::from_bits(0o3646), fs.apply_umask(FileMode::from_bits(0o3666)));
207            assert_eq!(FileMode::from_bits(0o20), fs.set_umask(FileMode::from_bits(0o11)));
208        })
209        .await;
210    }
211
212    #[::fuchsia::test]
213    async fn test_chdir() {
214        spawn_kernel_and_run_with_pkgfs(async |locked, current_task| {
215            assert_eq!("/", current_task.fs().cwd().path_escaping_chroot());
216
217            let bin = current_task
218                .open_file(locked, "bin".into(), OpenFlags::RDONLY)
219                .expect("missing bin directory");
220            current_task
221                .fs()
222                .chdir(locked, &current_task, bin.name.to_passive())
223                .expect("Failed to chdir");
224            assert_eq!("/bin", current_task.fs().cwd().path_escaping_chroot());
225
226            // Now that we have changed directories to bin, we're opening a file
227            // relative to that directory, which doesn't exist.
228            assert!(current_task.open_file(locked, "bin".into(), OpenFlags::RDONLY).is_err());
229
230            // However, bin still exists in the root directory.
231            assert!(current_task.open_file(locked, "/bin".into(), OpenFlags::RDONLY).is_ok());
232
233            let previous_directory = current_task
234                .open_file(locked, "..".into(), OpenFlags::RDONLY)
235                .expect("failed to open ..")
236                .name
237                .to_passive();
238            current_task
239                .fs()
240                .chdir(locked, &current_task, previous_directory)
241                .expect("Failed to chdir");
242            assert_eq!("/", current_task.fs().cwd().path_escaping_chroot());
243
244            // Now bin exists again because we've gone back to the root.
245            assert!(current_task.open_file(locked, "bin".into(), OpenFlags::RDONLY).is_ok());
246
247            // Repeating the .. doesn't do anything because we're already at the root.
248            let previous_directory = current_task
249                .open_file(locked, "..".into(), OpenFlags::RDONLY)
250                .expect("failed to open ..")
251                .name
252                .to_passive();
253            current_task
254                .fs()
255                .chdir(locked, &current_task, previous_directory)
256                .expect("Failed to chdir");
257            assert_eq!("/", current_task.fs().cwd().path_escaping_chroot());
258            assert!(current_task.open_file(locked, "bin".into(), OpenFlags::RDONLY).is_ok());
259        })
260        .await;
261    }
262}