Skip to main content

starnix_modules_procfs/
fs.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::proc_directory::ProcDirectory;
6use starnix_core::mm::PAGE_SIZE;
7use starnix_core::task::{CurrentTask, Task, TaskContainer};
8use starnix_core::vfs::fs_args::MountParams;
9use starnix_core::vfs::{
10    CacheMode, FileSystem, FileSystemHandle, FileSystemOps, FileSystemOptions, FsNodeInfo, FsStr,
11    FsString,
12};
13use starnix_types::vfs::default_statfs;
14use starnix_uapi::auth::{FsCred, PTRACE_MODE_NOAUDIT, PTRACE_MODE_READ_FSCREDS};
15use starnix_uapi::errors::Errno;
16use starnix_uapi::file_mode::mode;
17use starnix_uapi::mount_flags::FileSystemFlags;
18use starnix_uapi::{PROC_SUPER_MAGIC, error, gid_t, statfs};
19use std::fmt::Write;
20use std::sync::atomic::{AtomicU64, Ordering};
21
22/// Modes for the `hidepid` mount option on `procfs`.
23#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
24#[repr(u8)]
25pub enum HidePid {
26    #[default]
27    Off = 0,
28    NoAccess = 1,
29    Invisible = 2,
30    Ptraceable = 4,
31}
32
33impl HidePid {
34    fn parse(s: &str) -> Result<Self, Errno> {
35        match s {
36            "0" | "off" => Ok(Self::Off),
37            "1" | "noaccess" => Ok(Self::NoAccess),
38            "2" | "invisible" => Ok(Self::Invisible),
39            "4" | "ptraceable" => Ok(Self::Ptraceable),
40            _ => error!(EINVAL),
41        }
42    }
43
44    fn from_u8(v: u8) -> Self {
45        match v {
46            1 => Self::NoAccess,
47            2 => Self::Invisible,
48            4 => Self::Ptraceable,
49            _ => Self::Off,
50        }
51    }
52
53    fn as_str(&self) -> Option<&'static str> {
54        match self {
55            Self::Off => None,
56            Self::NoAccess => Some("noaccess"),
57            Self::Invisible => Some("invisible"),
58            Self::Ptraceable => Some("ptraceable"),
59        }
60    }
61}
62
63#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
64struct ProcMountParams {
65    gid: Option<gid_t>,
66    hide_pid: Option<HidePid>,
67}
68
69/// Returns a new procfs instance.
70///
71/// Each mount of procfs creates a new instance with its own mount options.
72pub fn proc_fs(
73    current_task: &CurrentTask,
74    options: FileSystemOptions,
75) -> Result<FileSystemHandle, Errno> {
76    ProcFs::new_fs(current_task, options)
77}
78
79/// `ProcFs` is a filesystem that exposes runtime information about a `Kernel` instance.
80#[derive(Debug, Default)]
81pub struct ProcFs {
82    /// Packed mount options: lower 32 bits store `pid_gid` (`gid_t`), bits 32..40 store `hide_pid`.
83    mount_options: AtomicU64,
84}
85
86impl FileSystemOps for ProcFs {
87    fn statfs(&self, _fs: &FileSystem, _current_task: &CurrentTask) -> Result<statfs, Errno> {
88        Ok(default_statfs(PROC_SUPER_MAGIC))
89    }
90
91    fn name(&self) -> &'static FsStr {
92        "proc".into()
93    }
94
95    fn reconfigure(
96        &self,
97        fs: &FileSystem,
98        _current_task: &CurrentTask,
99        new_flags: FileSystemFlags,
100        params: &MountParams,
101    ) -> Result<(), Errno> {
102        self.apply_mount_params(params)?;
103        fs.options.flags.store(new_flags, Ordering::Relaxed);
104        Ok(())
105    }
106
107    fn show_options(&self, _fs: &FileSystem) -> Result<FsString, Errno> {
108        let (pid_gid, hide_pid) = self.load_mount_options();
109        let mut opts = String::new();
110        if pid_gid != 0 {
111            let _ = write!(&mut opts, ",gid={pid_gid}");
112        }
113        if let Some(hide_pid) = hide_pid.as_str() {
114            let _ = write!(&mut opts, ",hidepid={hide_pid}");
115        }
116        Ok(opts.into())
117    }
118
119    fn default_blksize(&self) -> Option<usize> {
120        Some(*PAGE_SIZE as usize)
121    }
122}
123
124impl ProcFs {
125    /// Creates a new instance of `ProcFs` for the given `kernel`.
126    pub fn new_fs(
127        current_task: &CurrentTask,
128        options: FileSystemOptions,
129    ) -> Result<FileSystemHandle, Errno> {
130        let params = Self::parse_mount_params(&options.params)?;
131        let proc_fs = ProcFs::default();
132        proc_fs.set_mount_params(params);
133        let kernel = current_task.kernel();
134        let fs = FileSystem::new(kernel, CacheMode::Uncached, proc_fs, options)?;
135        let root_ino = fs.allocate_ino();
136        fs.create_root_with_info(
137            root_ino,
138            ProcDirectory::new(kernel, &fs),
139            FsNodeInfo::new(mode!(IFDIR, 0o555), FsCred::root()),
140        );
141        Ok(fs)
142    }
143
144    fn pack_mount_options(pid_gid: gid_t, hide_pid: HidePid) -> u64 {
145        (pid_gid as u64) | ((hide_pid as u64) << 32)
146    }
147
148    fn unpack_mount_options(raw: u64) -> (gid_t, HidePid) {
149        let pid_gid = raw as gid_t;
150        let hide_pid = HidePid::from_u8((raw >> 32) as u8);
151        (pid_gid, hide_pid)
152    }
153
154    pub fn load_mount_options(&self) -> (gid_t, HidePid) {
155        Self::unpack_mount_options(self.mount_options.load(Ordering::Relaxed))
156    }
157
158    fn parse_mount_params(params: &MountParams) -> Result<ProcMountParams, Errno> {
159        let gid = params.get_as::<gid_t>(b"gid")?;
160        let hide_pid = params.get_with(b"hidepid", HidePid::parse)?;
161        Ok(ProcMountParams { gid, hide_pid })
162    }
163
164    fn set_mount_params(&self, params: ProcMountParams) {
165        let _ = self.mount_options.try_update(Ordering::Relaxed, Ordering::Relaxed, |raw| {
166            let (cur_gid, cur_hide_pid) = Self::unpack_mount_options(raw);
167            let new_gid = params.gid.unwrap_or(cur_gid);
168            let new_hide_pid = params.hide_pid.unwrap_or(cur_hide_pid);
169            Some(Self::pack_mount_options(new_gid, new_hide_pid))
170        });
171    }
172
173    fn apply_mount_params(&self, params: &MountParams) -> Result<(), Errno> {
174        let parsed = Self::parse_mount_params(params)?;
175        self.set_mount_params(parsed);
176        Ok(())
177    }
178
179    pub fn has_pid_permissions_with_opts(
180        &self,
181        current_task: &CurrentTask,
182        target_task: &Task,
183        hide_pid_min: HidePid,
184        pid_gid: gid_t,
185        hide_pid: HidePid,
186    ) -> bool {
187        if current_task.pid == target_task.pid {
188            return true;
189        }
190        if hide_pid != HidePid::Ptraceable {
191            if hide_pid < hide_pid_min {
192                return true;
193            }
194            let creds = current_task.current_creds();
195            if creds.fsgid == pid_gid || creds.groups.contains(&pid_gid) {
196                return true;
197            }
198        }
199        current_task
200            .check_ptrace_access_mode(PTRACE_MODE_READ_FSCREDS | PTRACE_MODE_NOAUDIT, target_task)
201            .is_ok()
202    }
203
204    pub fn has_pid_permissions(
205        &self,
206        current_task: &CurrentTask,
207        target_task: &Task,
208        hide_pid_min: HidePid,
209    ) -> bool {
210        let (pid_gid, hide_pid) = self.load_mount_options();
211        self.has_pid_permissions_with_opts(
212            current_task,
213            target_task,
214            hide_pid_min,
215            pid_gid,
216            hide_pid,
217        )
218    }
219
220    pub fn check_pid_access(
221        &self,
222        current_task: &CurrentTask,
223        target: &TaskContainer,
224        hide_pid_min: HidePid,
225    ) -> Result<(), Errno> {
226        let (pid_gid, hide_pid) = self.load_mount_options();
227        let Ok(target_task) = target.get_task() else {
228            if hide_pid >= HidePid::Invisible {
229                return error!(ENOENT);
230            }
231            return error!(ESRCH);
232        };
233        if !self.has_pid_permissions_with_opts(
234            current_task,
235            &target_task,
236            hide_pid_min,
237            pid_gid,
238            hide_pid,
239        ) {
240            if hide_pid >= HidePid::Invisible {
241                return error!(ENOENT);
242            }
243            return error!(EPERM);
244        }
245        Ok(())
246    }
247}