PVF: fix unshare "could not create temporary directory"; refactor (#2663)

Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com>
This commit is contained in:
Marcin S
2023-12-13 21:38:27 +01:00
committed by GitHub
parent 30151bd357
commit c5cf395963
4 changed files with 78 additions and 65 deletions
+40 -63
View File
@@ -201,13 +201,20 @@ impl Artifacts {
/// Create an empty table and populate it with valid artifacts as [`ArtifactState::Prepared`],
/// if any. The existing caches will be checked by their file name to determine whether they are
/// valid, e.g., matching the current node version. The ones deemed invalid will be pruned.
///
/// Create the cache directory on-disk if it doesn't exist.
pub async fn new_and_prune(cache_path: &Path) -> Self {
let mut artifacts = Self { inner: HashMap::new() };
artifacts.insert_and_prune(cache_path).await;
let _ = artifacts.insert_and_prune(cache_path).await.map_err(|err| {
gum::error!(
target: LOG_TARGET,
"could not initialize artifacts cache: {err}",
)
});
artifacts
}
async fn insert_and_prune(&mut self, cache_path: &Path) {
async fn insert_and_prune(&mut self, cache_path: &Path) -> Result<(), String> {
async fn is_corrupted(path: &Path) -> bool {
let checksum = match tokio::fs::read(path).await {
Ok(bytes) => blake3::hash(&bytes),
@@ -237,24 +244,16 @@ impl Artifacts {
artifacts: &mut Artifacts,
entry: &tokio::fs::DirEntry,
cache_path: &Path,
) {
) -> Result<(), String> {
let file_type = entry.file_type().await;
let file_name = entry.file_name();
match file_type {
Ok(file_type) =>
if !file_type.is_file() {
return
return Ok(())
},
Err(err) => {
gum::warn!(
target: LOG_TARGET,
?err,
"unable to get file type for {:?}",
file_name,
);
return
},
Err(err) => return Err(format!("unable to get file type for {file_name:?}: {err}")),
}
if let Some(file_name) = file_name.to_str() {
@@ -262,73 +261,51 @@ impl Artifacts {
let path = cache_path.join(file_name);
if id.is_none() || is_corrupted(&path).await {
gum::warn!(
target: LOG_TARGET,
"discarding invalid artifact {:?}",
&path,
);
let _ = tokio::fs::remove_file(&path).await;
return
return Err(format!("invalid artifact {path:?}, file deleted"))
}
if let Some(id) = id {
gum::debug!(
target: LOG_TARGET,
"reusing existing {:?} for node version v{}",
&path,
NODE_VERSION,
);
artifacts.insert_prepared(id, path, SystemTime::now(), Default::default());
}
} else {
gum::warn!(
let id = id.expect("checked is_none() above; qed");
gum::debug!(
target: LOG_TARGET,
"non-Unicode file name {:?} found in {:?}",
file_name,
cache_path,
"reusing existing {:?} for node version v{}",
&path,
NODE_VERSION,
);
artifacts.insert_prepared(id, path, SystemTime::now(), Default::default());
Ok(())
} else {
Err(format!("non-Unicode file name {file_name:?} found in {cache_path:?}"))
}
}
// Make sure that the cache path directory and all its parents are created.
if let Err(err) = tokio::fs::create_dir_all(cache_path).await {
if err.kind() != io::ErrorKind::AlreadyExists {
gum::error!(
target: LOG_TARGET,
?err,
"failed to create dir {:?}",
cache_path,
);
return
return Err(format!("failed to create dir {cache_path:?}: {err}"))
}
}
let mut dir = match tokio::fs::read_dir(cache_path).await {
Ok(dir) => dir,
Err(err) => {
gum::error!(
target: LOG_TARGET,
?err,
"failed to read dir {:?}",
cache_path,
);
return
},
};
let mut dir = tokio::fs::read_dir(cache_path)
.await
.map_err(|err| format!("failed to read dir {cache_path:?}: {err}"))?;
loop {
match dir.next_entry().await {
Ok(Some(entry)) => insert_or_prune(self, &entry, cache_path).await,
Ok(None) => break,
Err(err) => {
gum::warn!(
target: LOG_TARGET,
?err,
"error processing artifacts in {:?}",
cache_path,
);
break
},
Ok(Some(entry)) =>
if let Err(err) = insert_or_prune(self, &entry, cache_path).await {
gum::warn!(
target: LOG_TARGET,
?cache_path,
"could not insert entry {:?} into the artifact cache: {}",
entry,
err,
)
},
Ok(None) => return Ok(()),
Err(err) =>
return Err(format!("error processing artifacts in {cache_path:?}: {err}")),
}
}
}
+3 -2
View File
@@ -217,6 +217,9 @@ pub async fn start(
) -> SubsystemResult<(ValidationHost, impl Future<Output = ()>)> {
gum::debug!(target: LOG_TARGET, ?config, "starting PVF validation host");
// Make sure the cache is initialized before doing anything else.
let artifacts = Artifacts::new_and_prune(&config.cache_path).await;
// Run checks for supported security features once per host startup. If some checks fail, warn
// if Secure Validator Mode is disabled and return an error otherwise.
let security_status = match security::check_security_status(&config).await {
@@ -260,8 +263,6 @@ pub async fn start(
let run_sweeper = sweeper_task(to_sweeper_rx);
let run_host = async move {
let artifacts = Artifacts::new_and_prune(&config.cache_path).await;
run(Inner {
cleanup_pulse_interval: Duration::from_secs(3600),
artifact_ttl: Duration::from_secs(3600 * 24),
+18
View File
@@ -445,3 +445,21 @@ async fn all_security_features_work() {
}
);
}
// Regression test to make sure the unshare-pivot-root capability does not depend on the PVF
// artifacts cache existing.
#[cfg(all(feature = "ci-only-tests", target_os = "linux"))]
#[tokio::test]
async fn nonexistant_cache_dir() {
let host = TestHost::new_with_config(|cfg| {
cfg.cache_path = cfg.cache_path.join("nonexistant_cache_dir");
})
.await;
assert!(host.security_status().await.can_unshare_user_namespace_and_change_root);
let _stats = host
.precheck_pvf(::adder::wasm_binary_unwrap(), Default::default())
.await
.unwrap();
}
@@ -0,0 +1,17 @@
title: "PVF: fix unshare 'could not create temporary directory'"
doc:
- audience: Node Operator
description: |
For validators: fixes the potential warning/error:
"Cannot unshare user namespace and change root, which are Linux-specific kernel security features: could not create a temporary directory in "/tmp/.tmpIcLriO".
migrations:
db: []
runtime: []
crates:
- name: polkadot-node-core-pvf
host_functions: []