1use 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#[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
69pub fn proc_fs(
73 current_task: &CurrentTask,
74 options: FileSystemOptions,
75) -> Result<FileSystemHandle, Errno> {
76 ProcFs::new_fs(current_task, options)
77}
78
79#[derive(Debug, Default)]
81pub struct ProcFs {
82 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 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}