1use nix::sys::statvfs::FsFlags;
12use nix::sys::statvfs::statvfs;
13use reverie::Errno;
14use reverie::Pid;
15
16pub const READONLY_WARNING: &str =
18 "hermit: warning: /proc is mounted read-only; guest writes to procfs may fail with EROFS.\n";
19
20fn write_warning(warning: &str) {
21 if crate::nonwaiting_write::diagnostics_must_not_wait() {
28 crate::nonwaiting_write::write_without_waiting(libc::STDERR_FILENO, warning.as_bytes());
29 return;
30 }
31 let (blocked, original, pending) = unsafe {
40 let mut blocked = std::mem::zeroed::<libc::sigset_t>();
41 let mut original = std::mem::zeroed::<libc::sigset_t>();
42 let mut pending = std::mem::zeroed::<libc::sigset_t>();
43 libc::sigemptyset(&mut blocked);
44 libc::sigaddset(&mut blocked, libc::SIGPIPE);
45 if libc::sigprocmask(libc::SIG_BLOCK, &blocked, &mut original) == -1 {
46 return;
47 }
48 if libc::sigpending(&mut pending) == -1 {
49 libc::sigprocmask(libc::SIG_SETMASK, &original, std::ptr::null_mut());
50 return;
51 }
52 (blocked, original, pending)
53 };
54 let mut broken_pipe = false;
55 let mut remaining = warning.as_bytes();
56 while !remaining.is_empty() {
57 let written = unsafe {
59 libc::write(
60 libc::STDERR_FILENO,
61 remaining.as_ptr().cast(),
62 remaining.len(),
63 )
64 };
65 if written > 0 {
66 remaining = &remaining[written as usize..];
67 } else if written == -1 && Errno::last() == Errno::EINTR {
68 continue;
69 } else {
70 broken_pipe = written == -1 && Errno::last() == Errno::EPIPE;
71 break;
72 }
73 }
74 unsafe {
77 if broken_pipe && libc::sigismember(&pending, libc::SIGPIPE) == 0 {
78 let timeout = libc::timespec {
79 tv_sec: 0,
80 tv_nsec: 0,
81 };
82 while libc::sigtimedwait(&blocked, std::ptr::null_mut(), &timeout) == -1
83 && Errno::last() == Errno::EINTR
84 {}
85 }
86 libc::sigprocmask(libc::SIG_SETMASK, &original, std::ptr::null_mut());
87 }
88}
89
90pub fn warn_if_readonly_proc() {
92 let mut stats = std::mem::MaybeUninit::<libc::statfs64>::uninit();
93 if unsafe { libc::statfs64(c"/proc".as_ptr(), stats.as_mut_ptr()) } == -1 {
97 write_warning("hermit: warning: could not determine the /proc mount mode.\n");
98 } else if unsafe { stats.assume_init().f_flags } & libc::ST_RDONLY as libc::c_long != 0 {
99 write_warning(READONLY_WARNING);
100 }
101}
102
103pub(crate) fn guest_proc_readonly(pid: Pid) -> Option<bool> {
106 match statvfs(format!("/proc/{pid}/root/proc").as_str()) {
107 Ok(stats) => Some(stats.flags().contains(FsFlags::ST_RDONLY)),
108 Err(error) => {
109 write_warning(&format!(
110 "hermit: warning: could not determine the guest /proc mount mode: {error}\n"
111 ));
112 None
113 }
114 }
115}
116
117pub(crate) fn report_proc_mode(recorded: Option<bool>, actual: Option<bool>) {
118 if let (Some(recorded), Some(actual)) = (recorded, actual)
119 && recorded != actual
120 {
121 let mode = |readonly| if readonly { "read-only" } else { "read-write" };
122 write_warning(&format!(
123 "hermit: warning: /proc mount mode differs: recording was {}, replay is {}; \
124 guest procfs writes may behave differently.\n",
125 mode(recorded),
126 mode(actual)
127 ));
128 } else if actual == Some(true) {
129 write_warning(READONLY_WARNING);
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use std::os::fd::AsRawFd;
136 use std::os::fd::FromRawFd;
137 use std::os::fd::OwnedFd;
138
139 use reverie::process::Container;
140
141 #[test]
142 fn readonly_proc_warning_survives_closed_stderr() {
143 if crate::run_in_fresh_process(
144 "proc_mount::tests::readonly_proc_warning_survives_closed_stderr",
145 ) {
146 return;
147 }
148 let mut descriptors = [-1; 2];
149 assert_eq!(
151 unsafe { libc::pipe2(descriptors.as_mut_ptr(), libc::O_CLOEXEC) },
152 0,
153 "create stderr pipe: {}",
154 std::io::Error::last_os_error()
155 );
156 let (reader, writer) = unsafe {
158 (
159 OwnedFd::from_raw_fd(descriptors[0]),
160 OwnedFd::from_raw_fd(descriptors[1]),
161 )
162 };
163 drop(reader);
164
165 let observation = Container::new()
168 .run(|| {
169 unsafe {
172 let mut action = std::mem::zeroed::<libc::sigaction>();
173 action.sa_sigaction = libc::SIG_DFL;
174 libc::sigemptyset(&mut action.sa_mask);
175 let mut signals = std::mem::zeroed::<libc::sigset_t>();
176 libc::sigemptyset(&mut signals);
177 libc::sigaddset(&mut signals, libc::SIGPIPE);
178 if libc::dup2(writer.as_raw_fd(), libc::STDERR_FILENO) == -1
179 || libc::sigaction(libc::SIGPIPE, &action, std::ptr::null_mut()) == -1
180 || libc::sigprocmask(libc::SIG_UNBLOCK, &signals, std::ptr::null_mut())
181 == -1
182 {
183 return Err(*libc::__errno_location());
184 }
185
186 super::report_proc_mode(None, Some(true));
187
188 let mut mask = std::mem::zeroed::<libc::sigset_t>();
189 let mut pending = std::mem::zeroed::<libc::sigset_t>();
190 if libc::sigprocmask(libc::SIG_BLOCK, std::ptr::null(), &mut mask) == -1
191 || libc::sigpending(&mut pending) == -1
192 || libc::sigaction(libc::SIGPIPE, std::ptr::null(), &mut action) == -1
193 {
194 return Err(*libc::__errno_location());
195 }
196 Ok((
197 libc::getpid(),
198 action.sa_sigaction == libc::SIG_DFL,
199 libc::sigismember(&mask, libc::SIGPIPE) == 0,
200 libc::sigismember(&pending, libc::SIGPIPE) == 0,
201 ))
202 }
203 })
204 .expect("the warning must not terminate the child with SIGPIPE")
205 .expect("configure and inspect child stderr and signal state");
206 assert!(
207 observation.0 > 1,
208 "the child must not be PID namespace init"
209 );
210 assert_eq!(
211 (observation.1, observation.2, observation.3),
212 (true, true, true),
213 "the warning must preserve default/unblocked SIGPIPE and consume its own pending signal"
214 );
215 }
216}