1use crate::{Config, SecurityStatus, LOG_TARGET};
18use futures::join;
19use std::{fmt, path::Path};
20
21pub async fn check_security_status(config: &Config) -> Result<SecurityStatus, String> {
33 let Config { prepare_worker_program_path, secure_validator_mode, cache_path, .. } = config;
34
35 let (landlock, seccomp, change_root, secure_clone) = join!(
36 check_landlock(prepare_worker_program_path),
37 check_seccomp(prepare_worker_program_path),
38 check_can_unshare_user_namespace_and_change_root(prepare_worker_program_path, cache_path),
39 check_can_do_secure_clone(prepare_worker_program_path),
40 );
41
42 let full_security_status = FullSecurityStatus::new(
43 *secure_validator_mode,
44 landlock,
45 seccomp,
46 change_root,
47 secure_clone,
48 );
49 let security_status = full_security_status.as_partial();
50
51 if full_security_status.err_occurred() {
52 print_secure_mode_error_or_warning(&full_security_status);
53 if !full_security_status.all_errs_allowed() {
54 return Err("could not enable Secure Validator Mode; check logs".into())
55 }
56 }
57
58 if security_status.secure_validator_mode {
59 gum::info!(
60 target: LOG_TARGET,
61 "👮♀️ Running in Secure Validator Mode. \
62 It is highly recommended that you operate according to our security guidelines. \
63 \nMore information: https://wiki.polkadot.network/docs/maintain-guides-secure-validator#secure-validator-mode"
64 );
65 }
66
67 Ok(security_status)
68}
69
70struct FullSecurityStatus {
72 partial: SecurityStatus,
73 errs: Vec<SecureModeError>,
74}
75
76impl FullSecurityStatus {
77 fn new(
78 secure_validator_mode: bool,
79 landlock: SecureModeResult,
80 seccomp: SecureModeResult,
81 change_root: SecureModeResult,
82 secure_clone: SecureModeResult,
83 ) -> Self {
84 Self {
85 partial: SecurityStatus {
86 secure_validator_mode,
87 can_enable_landlock: landlock.is_ok(),
88 can_enable_seccomp: seccomp.is_ok(),
89 can_unshare_user_namespace_and_change_root: change_root.is_ok(),
90 can_do_secure_clone: secure_clone.is_ok(),
91 },
92 errs: [landlock, seccomp, change_root, secure_clone]
93 .into_iter()
94 .filter_map(|result| result.err())
95 .collect(),
96 }
97 }
98
99 fn as_partial(&self) -> SecurityStatus {
100 self.partial.clone()
101 }
102
103 fn err_occurred(&self) -> bool {
104 !self.errs.is_empty()
105 }
106
107 fn all_errs_allowed(&self) -> bool {
108 !self.partial.secure_validator_mode ||
109 self.errs.iter().all(|err| err.is_allowed_in_secure_mode(&self.partial))
110 }
111
112 fn errs_string(&self) -> String {
113 self.errs
114 .iter()
115 .map(|err| {
116 format!(
117 "\n - {}{}",
118 if err.is_allowed_in_secure_mode(&self.partial) { "Optional: " } else { "" },
119 err
120 )
121 })
122 .collect()
123 }
124}
125
126type SecureModeResult = std::result::Result<(), SecureModeError>;
127
128#[derive(Debug)]
130enum SecureModeError {
131 CannotEnableLandlock { err: String, abi: u8 },
132 CannotEnableSeccomp(String),
133 CannotUnshareUserNamespaceAndChangeRoot(String),
134 CannotDoSecureClone(String),
135}
136
137impl SecureModeError {
138 fn is_allowed_in_secure_mode(&self, security_status: &SecurityStatus) -> bool {
140 use SecureModeError::*;
141 match self {
142 CannotEnableLandlock { .. } =>
145 security_status.can_unshare_user_namespace_and_change_root,
146 CannotEnableSeccomp(_) => false,
148 CannotUnshareUserNamespaceAndChangeRoot(_) => security_status.can_enable_landlock,
151 CannotDoSecureClone(_) => true,
154 }
155 }
156}
157
158impl fmt::Display for SecureModeError {
159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160 use SecureModeError::*;
161 match self {
162 CannotEnableLandlock{err, abi} => write!(f, "Cannot enable landlock (ABI {abi}), a Linux 5.13+ kernel security feature: {err}"),
163 CannotEnableSeccomp(err) => write!(f, "Cannot enable seccomp, a Linux-specific kernel security feature: {err}"),
164 CannotUnshareUserNamespaceAndChangeRoot(err) => write!(f, "Cannot unshare user namespace and change root, which are Linux-specific kernel security features: {err}"),
165 CannotDoSecureClone(err) => write!(f, "Cannot call clone with all sandboxing flags, a Linux-specific kernel security features: {err}"),
166 }
167 }
168}
169
170fn print_secure_mode_error_or_warning(security_status: &FullSecurityStatus) {
172 let all_errs_allowed = security_status.all_errs_allowed();
173 let errs_string = security_status.errs_string();
174
175 if all_errs_allowed {
176 gum::warn!(
177 target: LOG_TARGET,
178 "{}{}",
179 crate::SECURE_MODE_WARNING,
180 errs_string,
181 );
182 } else {
183 gum::error!(
184 target: LOG_TARGET,
185 "{}{}{}",
186 crate::SECURE_MODE_ERROR,
187 errs_string,
188 crate::IGNORE_SECURE_MODE_TIP
189 );
190 }
191}
192
193async fn check_can_unshare_user_namespace_and_change_root(
199 prepare_worker_program_path: &Path,
200 cache_path: &Path,
201) -> SecureModeResult {
202 let cache_dir_tempdir = tempfile::Builder::new()
203 .prefix("check-can-unshare-")
204 .tempdir_in(cache_path)
205 .map_err(|err| {
206 SecureModeError::CannotUnshareUserNamespaceAndChangeRoot(format!(
207 "could not create a temporary directory in {:?}: {}",
208 cache_path, err
209 ))
210 })?;
211 spawn_process_for_security_check(
212 prepare_worker_program_path,
213 "--check-can-unshare-user-namespace-and-change-root",
214 &[cache_dir_tempdir.path()],
215 )
216 .await
217 .map_err(|err| SecureModeError::CannotUnshareUserNamespaceAndChangeRoot(err))
218}
219
220async fn check_landlock(prepare_worker_program_path: &Path) -> SecureModeResult {
226 let abi = polkadot_node_core_pvf_common::worker::security::landlock::LANDLOCK_ABI as u8;
227 spawn_process_for_security_check(
228 prepare_worker_program_path,
229 "--check-can-enable-landlock",
230 std::iter::empty::<&str>(),
231 )
232 .await
233 .map_err(|err| SecureModeError::CannotEnableLandlock { err, abi })
234}
235
236#[cfg(target_arch = "x86_64")]
243async fn check_seccomp(prepare_worker_program_path: &Path) -> SecureModeResult {
244 spawn_process_for_security_check(
245 prepare_worker_program_path,
246 "--check-can-enable-seccomp",
247 std::iter::empty::<&str>(),
248 )
249 .await
250 .map_err(|err| SecureModeError::CannotEnableSeccomp(err))
251}
252
253#[cfg(not(target_arch = "x86_64"))]
254async fn check_seccomp(_: &Path) -> SecureModeResult {
255 Err(SecureModeError::CannotEnableSeccomp(
256 "only supported on CPUs from the x86_64 family (usually Intel or AMD)".into(),
257 ))
258}
259
260async fn check_can_do_secure_clone(prepare_worker_program_path: &Path) -> SecureModeResult {
266 spawn_process_for_security_check(
267 prepare_worker_program_path,
268 "--check-can-do-secure-clone",
269 std::iter::empty::<&str>(),
270 )
271 .await
272 .map_err(|err| SecureModeError::CannotDoSecureClone(err))
273}
274
275async fn spawn_process_for_security_check<I, S>(
276 prepare_worker_program_path: &Path,
277 check_arg: &'static str,
278 extra_args: I,
279) -> Result<(), String>
280where
281 I: IntoIterator<Item = S>,
282 S: AsRef<std::ffi::OsStr>,
283{
284 let mut command = tokio::process::Command::new(prepare_worker_program_path);
285 command.env_clear();
288 if let Ok(value) = std::env::var("RUST_LOG") {
290 command.env("RUST_LOG", value);
291 }
292
293 match command.arg(check_arg).args(extra_args).output().await {
294 Ok(output) if output.status.success() => Ok(()),
295 Ok(output) => {
296 let stderr = std::str::from_utf8(&output.stderr)
297 .expect("child process writes a UTF-8 string to stderr; qed")
298 .trim();
299 if stderr.is_empty() {
300 Err("not available".into())
301 } else {
302 Err(format!("not available: {}", stderr))
303 }
304 },
305 Err(err) => Err(format!("could not start child process: {}", err)),
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312
313 #[test]
314 fn test_secure_mode_error_optionality() {
315 let err = SecureModeError::CannotEnableLandlock { err: String::new(), abi: 3 };
316 assert!(err.is_allowed_in_secure_mode(&SecurityStatus {
317 secure_validator_mode: true,
318 can_enable_landlock: false,
319 can_enable_seccomp: false,
320 can_unshare_user_namespace_and_change_root: true,
321 can_do_secure_clone: true,
322 }));
323 assert!(!err.is_allowed_in_secure_mode(&SecurityStatus {
324 secure_validator_mode: true,
325 can_enable_landlock: false,
326 can_enable_seccomp: true,
327 can_unshare_user_namespace_and_change_root: false,
328 can_do_secure_clone: false,
329 }));
330
331 let err = SecureModeError::CannotEnableSeccomp(String::new());
332 assert!(!err.is_allowed_in_secure_mode(&SecurityStatus {
333 secure_validator_mode: true,
334 can_enable_landlock: false,
335 can_enable_seccomp: false,
336 can_unshare_user_namespace_and_change_root: true,
337 can_do_secure_clone: true,
338 }));
339 assert!(!err.is_allowed_in_secure_mode(&SecurityStatus {
340 secure_validator_mode: true,
341 can_enable_landlock: false,
342 can_enable_seccomp: true,
343 can_unshare_user_namespace_and_change_root: false,
344 can_do_secure_clone: false,
345 }));
346
347 let err = SecureModeError::CannotUnshareUserNamespaceAndChangeRoot(String::new());
348 assert!(err.is_allowed_in_secure_mode(&SecurityStatus {
349 secure_validator_mode: true,
350 can_enable_landlock: true,
351 can_enable_seccomp: false,
352 can_unshare_user_namespace_and_change_root: false,
353 can_do_secure_clone: false,
354 }));
355 assert!(!err.is_allowed_in_secure_mode(&SecurityStatus {
356 secure_validator_mode: true,
357 can_enable_landlock: false,
358 can_enable_seccomp: true,
359 can_unshare_user_namespace_and_change_root: false,
360 can_do_secure_clone: false,
361 }));
362
363 let err = SecureModeError::CannotDoSecureClone(String::new());
364 assert!(err.is_allowed_in_secure_mode(&SecurityStatus {
365 secure_validator_mode: true,
366 can_enable_landlock: true,
367 can_enable_seccomp: true,
368 can_unshare_user_namespace_and_change_root: true,
369 can_do_secure_clone: true,
370 }));
371 assert!(err.is_allowed_in_secure_mode(&SecurityStatus {
372 secure_validator_mode: false,
373 can_enable_landlock: false,
374 can_enable_seccomp: false,
375 can_unshare_user_namespace_and_change_root: false,
376 can_do_secure_clone: false,
377 }));
378 }
379}