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