mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-20 17:25:41 +00:00
PVF: more filesystem sandboxing (#1373)
This commit is contained in:
@@ -7,6 +7,7 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "1.0"
|
||||
futures = "0.3.21"
|
||||
gum = { package = "tracing-gum", path = "../../../gum" }
|
||||
libc = "0.2.139"
|
||||
|
||||
@@ -33,25 +33,24 @@ use parity_scale_codec::{Decode, Encode};
|
||||
use polkadot_node_core_pvf_common::{
|
||||
error::{PrepareError, PrepareResult},
|
||||
executor_intf::Executor,
|
||||
framed_recv, framed_send,
|
||||
framed_recv_blocking, framed_send_blocking,
|
||||
prepare::{MemoryStats, PrepareJobKind, PrepareStats},
|
||||
pvf::PvfPrepData,
|
||||
worker::{
|
||||
bytes_to_path, cpu_time_monitor_loop,
|
||||
security::LandlockStatus,
|
||||
stringify_panic_payload,
|
||||
cpu_time_monitor_loop, stringify_panic_payload,
|
||||
thread::{self, WaitOutcome},
|
||||
worker_event_loop,
|
||||
worker_event_loop, WorkerKind,
|
||||
},
|
||||
ProcessTime,
|
||||
worker_dir, ProcessTime, SecurityStatus,
|
||||
};
|
||||
use polkadot_primitives::ExecutorParams;
|
||||
use std::{
|
||||
os::unix::net::UnixStream,
|
||||
path::PathBuf,
|
||||
sync::{mpsc::channel, Arc},
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::{io, net::UnixStream};
|
||||
use tokio::io;
|
||||
|
||||
/// Contains the bytes for a successfully compiled artifact.
|
||||
pub struct CompiledArtifact(Vec<u8>);
|
||||
@@ -69,36 +68,34 @@ impl AsRef<[u8]> for CompiledArtifact {
|
||||
}
|
||||
}
|
||||
|
||||
async fn recv_request(stream: &mut UnixStream) -> io::Result<(PvfPrepData, PathBuf)> {
|
||||
let pvf = framed_recv(stream).await?;
|
||||
fn recv_request(stream: &mut UnixStream) -> io::Result<PvfPrepData> {
|
||||
let pvf = framed_recv_blocking(stream)?;
|
||||
let pvf = PvfPrepData::decode(&mut &pvf[..]).map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("prepare pvf recv_request: failed to decode PvfPrepData: {}", e),
|
||||
)
|
||||
})?;
|
||||
let tmp_file = framed_recv(stream).await?;
|
||||
let tmp_file = bytes_to_path(&tmp_file).ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"prepare pvf recv_request: non utf-8 artifact path".to_string(),
|
||||
)
|
||||
})?;
|
||||
Ok((pvf, tmp_file))
|
||||
Ok(pvf)
|
||||
}
|
||||
|
||||
async fn send_response(stream: &mut UnixStream, result: PrepareResult) -> io::Result<()> {
|
||||
framed_send(stream, &result.encode()).await
|
||||
fn send_response(stream: &mut UnixStream, result: PrepareResult) -> io::Result<()> {
|
||||
framed_send_blocking(stream, &result.encode())
|
||||
}
|
||||
|
||||
/// The entrypoint that the spawned prepare worker should start with.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// The `socket_path` specifies the path to the socket used to communicate with the host. The
|
||||
/// `node_version`, if `Some`, is checked against the worker version. A mismatch results in
|
||||
/// immediate worker termination. `None` is used for tests and in other situations when version
|
||||
/// check is not necessary.
|
||||
/// - `worker_dir_path`: specifies the path to the worker-specific temporary directory.
|
||||
///
|
||||
/// - `node_version`: if `Some`, is checked against the `worker_version`. A mismatch results in
|
||||
/// immediate worker termination. `None` is used for tests and in other situations when version
|
||||
/// check is not necessary.
|
||||
///
|
||||
/// - `worker_version`: see above
|
||||
///
|
||||
/// - `security_status`: contains the detected status of security features.
|
||||
///
|
||||
/// # Flow
|
||||
///
|
||||
@@ -119,20 +116,23 @@ async fn send_response(stream: &mut UnixStream, result: PrepareResult) -> io::Re
|
||||
/// 7. Send the result of preparation back to the host. If any error occurred in the above steps, we
|
||||
/// send that in the `PrepareResult`.
|
||||
pub fn worker_entrypoint(
|
||||
socket_path: &str,
|
||||
worker_dir_path: PathBuf,
|
||||
node_version: Option<&str>,
|
||||
worker_version: Option<&str>,
|
||||
security_status: SecurityStatus,
|
||||
) {
|
||||
worker_event_loop(
|
||||
"prepare",
|
||||
socket_path,
|
||||
WorkerKind::Prepare,
|
||||
worker_dir_path,
|
||||
node_version,
|
||||
worker_version,
|
||||
|mut stream| async move {
|
||||
&security_status,
|
||||
|mut stream, worker_dir_path| async move {
|
||||
let worker_pid = std::process::id();
|
||||
let temp_artifact_dest = worker_dir::prepare_tmp_artifact(&worker_dir_path);
|
||||
|
||||
loop {
|
||||
let (pvf, temp_artifact_dest) = recv_request(&mut stream).await?;
|
||||
let pvf = recv_request(&mut stream)?;
|
||||
gum::debug!(
|
||||
target: LOG_TARGET,
|
||||
%worker_pid,
|
||||
@@ -172,14 +172,6 @@ pub fn worker_entrypoint(
|
||||
let prepare_thread = thread::spawn_worker_thread(
|
||||
"prepare thread",
|
||||
move || {
|
||||
// Try to enable landlock.
|
||||
#[cfg(target_os = "linux")]
|
||||
let landlock_status = polkadot_node_core_pvf_common::worker::security::landlock::try_restrict_thread()
|
||||
.map(LandlockStatus::from_ruleset_status)
|
||||
.map_err(|e| e.to_string());
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let landlock_status: Result<LandlockStatus, String> = Ok(LandlockStatus::NotEnforced);
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut result = prepare_artifact(pvf, cpu_time_start);
|
||||
|
||||
@@ -200,7 +192,7 @@ pub fn worker_entrypoint(
|
||||
});
|
||||
}
|
||||
|
||||
(result, landlock_status)
|
||||
result
|
||||
},
|
||||
Arc::clone(&condvar),
|
||||
WaitOutcome::Finished,
|
||||
@@ -213,20 +205,20 @@ pub fn worker_entrypoint(
|
||||
let _ = cpu_time_monitor_tx.send(());
|
||||
|
||||
match prepare_thread.join().unwrap_or_else(|err| {
|
||||
(
|
||||
Err(PrepareError::Panic(stringify_panic_payload(err))),
|
||||
Ok(LandlockStatus::Unavailable),
|
||||
)
|
||||
Err(PrepareError::Panic(stringify_panic_payload(err)))
|
||||
}) {
|
||||
(Err(err), _) => {
|
||||
Err(err) => {
|
||||
// Serialized error will be written into the socket.
|
||||
Err(err)
|
||||
},
|
||||
(Ok(ok), landlock_status) => {
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let (artifact, cpu_time_elapsed) = ok;
|
||||
#[cfg(target_os = "linux")]
|
||||
let (artifact, cpu_time_elapsed, max_rss) = ok;
|
||||
Ok(ok) => {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
let (artifact, cpu_time_elapsed, max_rss) = ok;
|
||||
} else {
|
||||
let (artifact, cpu_time_elapsed) = ok;
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the memory stats worker and get its observed memory stats.
|
||||
#[cfg(any(target_os = "linux", feature = "jemalloc-allocator"))]
|
||||
@@ -242,16 +234,6 @@ pub fn worker_entrypoint(
|
||||
max_rss: extract_max_rss_stat(max_rss, worker_pid),
|
||||
};
|
||||
|
||||
// Log if landlock threw an error.
|
||||
if let Err(err) = landlock_status {
|
||||
gum::warn!(
|
||||
target: LOG_TARGET,
|
||||
%worker_pid,
|
||||
"error enabling landlock: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
|
||||
// Write the serialized artifact into a temp file.
|
||||
//
|
||||
// PVF host only keeps artifacts statuses in its memory,
|
||||
@@ -300,7 +282,13 @@ pub fn worker_entrypoint(
|
||||
),
|
||||
};
|
||||
|
||||
send_response(&mut stream, result).await?;
|
||||
gum::trace!(
|
||||
target: LOG_TARGET,
|
||||
%worker_pid,
|
||||
"worker: sending response to host: {:?}",
|
||||
result
|
||||
);
|
||||
send_response(&mut stream, result)?;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user