referrerpolicy=no-referrer-when-downgrade

polkadot_node_core_pvf/
security.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17use crate::{Config, SecurityStatus, LOG_TARGET};
18use futures::join;
19use std::{fmt, path::Path};
20
21/// Run checks for supported security features.
22///
23/// # Returns
24///
25/// Returns the set of security features that we were able to enable. If an error occurs while
26/// enabling a security feature we set the corresponding status to `false`.
27///
28/// # Errors
29///
30/// Returns an error only if we could not fully enforce the security level required by the current
31/// configuration.
32pub 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
70/// Contains the full security status including error states.
71struct 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/// Errors related to enabling Secure Validator Mode.
129#[derive(Debug)]
130enum SecureModeError {
131	CannotEnableLandlock { err: String, abi: u8 },
132	CannotEnableSeccomp(String),
133	CannotUnshareUserNamespaceAndChangeRoot(String),
134	CannotDoSecureClone(String),
135}
136
137impl SecureModeError {
138	/// Whether this error is allowed with Secure Validator Mode enabled.
139	fn is_allowed_in_secure_mode(&self, security_status: &SecurityStatus) -> bool {
140		use SecureModeError::*;
141		match self {
142			// Landlock is present on relatively recent Linuxes. This is optional if the unshare
143			// capability is present, providing FS sandboxing a different way.
144			CannotEnableLandlock { .. } =>
145				security_status.can_unshare_user_namespace_and_change_root,
146			// seccomp should be present on all modern Linuxes unless it's been disabled.
147			CannotEnableSeccomp(_) => false,
148			// Should always be present on modern Linuxes. If not, Landlock also provides FS
149			// sandboxing, so don't enforce this.
150			CannotUnshareUserNamespaceAndChangeRoot(_) => security_status.can_enable_landlock,
151			// We have not determined the kernel requirements for this capability, and it's also not
152			// necessary for FS or networking restrictions.
153			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
170/// Print an error if Secure Validator Mode and some mandatory errors occurred, warn otherwise.
171fn 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
193/// Check if we can change root to a new, sandboxed root and return an error if not.
194///
195/// We do this check by spawning a new process and trying to sandbox it. To get as close as possible
196/// to running the check in a worker, we try it... in a worker. The expected return status is 0 on
197/// success and -1 on failure.
198async 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
220/// Check if landlock is supported and return an error if not.
221///
222/// We do this check by spawning a new process and trying to sandbox it. To get as close as possible
223/// to running the check in a worker, we try it... in a worker. The expected return status is 0 on
224/// success and -1 on failure.
225async 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/// Check if seccomp is supported and return an error if not.
237///
238/// We do this check by spawning a new process and trying to sandbox it. To get as close as possible
239/// to running the check in a worker, we try it... in a worker. The expected return status is 0 on
240/// success and -1 on failure.
241
242#[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
260/// Check if we can call `clone` with all sandboxing flags, and return an error if not.
261///
262/// We do this check by spawning a new process and trying to sandbox it. To get as close as possible
263/// to running the check in a worker, we try it... in a worker. The expected return status is 0 on
264/// success and -1 on failure.
265async 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	// Clear env vars. (In theory, running checks with different env vars could result in different
286	// outcomes of the checks.)
287	command.env_clear();
288	// Add back any env vars we want to keep.
289	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}