Skip to content

Commit c1eb342

Browse files
authored
PVF: more filesystem sandboxing (#1373)
1 parent de71fec commit c1eb342

File tree

24 files changed

+1531
-615
lines changed

24 files changed

+1531
-615
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

polkadot/node/core/pvf/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ license.workspace = true
88

99
[dependencies]
1010
always-assert = "0.1"
11+
cfg-if = "1.0"
1112
futures = "0.3.21"
1213
futures-timer = "3.0.2"
1314
gum = { package = "tracing-gum", path = "../../gum" }

polkadot/node/core/pvf/common/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ edition.workspace = true
77
license.workspace = true
88

99
[dependencies]
10+
cfg-if = "1.0"
1011
cpu-time = "1.0.0"
1112
futures = "0.3.21"
1213
gum = { package = "tracing-gum", path = "../../../gum" }

polkadot/node/core/pvf/common/src/error.rs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,17 @@ pub enum PrepareError {
4444
/// The response from the worker is received, but the file cannot be renamed (moved) to the
4545
/// final destination location. This state is reported by the validation host (not by the
4646
/// worker).
47-
RenameTmpFileErr(String),
47+
RenameTmpFileErr {
48+
err: String,
49+
// Unfortunately `PathBuf` doesn't implement `Encode`/`Decode`, so we do a fallible
50+
// conversion to `Option<String>`.
51+
src: Option<String>,
52+
dest: Option<String>,
53+
},
54+
/// The response from the worker is received, but the worker cache could not be cleared. The
55+
/// worker has to be killed to avoid jobs having access to data from other jobs. This state is
56+
/// reported by the validation host (not by the worker).
57+
ClearWorkerDir(String),
4858
}
4959

5060
impl PrepareError {
@@ -58,7 +68,11 @@ impl PrepareError {
5868
use PrepareError::*;
5969
match self {
6070
Prevalidation(_) | Preparation(_) | Panic(_) => true,
61-
TimedOut | IoErr(_) | CreateTmpFileErr(_) | RenameTmpFileErr(_) => false,
71+
TimedOut |
72+
IoErr(_) |
73+
CreateTmpFileErr(_) |
74+
RenameTmpFileErr { .. } |
75+
ClearWorkerDir(_) => false,
6276
// Can occur due to issues with the PVF, but also due to local errors.
6377
RuntimeConstruction(_) => false,
6478
}
@@ -76,7 +90,9 @@ impl fmt::Display for PrepareError {
7690
TimedOut => write!(f, "prepare: timeout"),
7791
IoErr(err) => write!(f, "prepare: io error while receiving response: {}", err),
7892
CreateTmpFileErr(err) => write!(f, "prepare: error creating tmp file: {}", err),
79-
RenameTmpFileErr(err) => write!(f, "prepare: error renaming tmp file: {}", err),
93+
RenameTmpFileErr { err, src, dest } =>
94+
write!(f, "prepare: error renaming tmp file ({:?} -> {:?}): {}", src, dest, err),
95+
ClearWorkerDir(err) => write!(f, "prepare: error clearing worker cache: {}", err),
8096
}
8197
}
8298
}
@@ -89,8 +105,17 @@ impl fmt::Display for PrepareError {
89105
pub enum InternalValidationError {
90106
/// Some communication error occurred with the host.
91107
HostCommunication(String),
108+
/// Host could not create a hard link to the artifact path.
109+
CouldNotCreateLink(String),
92110
/// Could not find or open compiled artifact file.
93111
CouldNotOpenFile(String),
112+
/// Host could not clear the worker cache after a job.
113+
CouldNotClearWorkerDir {
114+
err: String,
115+
// Unfortunately `PathBuf` doesn't implement `Encode`/`Decode`, so we do a fallible
116+
// conversion to `Option<String>`.
117+
path: Option<String>,
118+
},
94119
/// An error occurred in the CPU time monitor thread. Should be totally unrelated to
95120
/// validation.
96121
CpuTimeMonitorThread(String),
@@ -104,8 +129,18 @@ impl fmt::Display for InternalValidationError {
104129
match self {
105130
HostCommunication(err) =>
106131
write!(f, "validation: some communication error occurred with the host: {}", err),
132+
CouldNotCreateLink(err) => write!(
133+
f,
134+
"validation: host could not create a hard link to the artifact path: {}",
135+
err
136+
),
107137
CouldNotOpenFile(err) =>
108138
write!(f, "validation: could not find or open compiled artifact file: {}", err),
139+
CouldNotClearWorkerDir { err, path } => write!(
140+
f,
141+
"validation: host could not clear the worker cache ({:?}) after a job: {}",
142+
path, err
143+
),
109144
CpuTimeMonitorThread(err) =>
110145
write!(f, "validation: an error occurred in the CPU time monitor thread: {}", err),
111146
NonDeterministicPrepareError(err) => write!(f, "validation: prepare: {}", err),

polkadot/node/core/pvf/common/src/execute.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub struct Handshake {
2929
}
3030

3131
/// The response from an execution job on the worker.
32-
#[derive(Encode, Decode)]
32+
#[derive(Debug, Encode, Decode)]
3333
pub enum Response {
3434
/// The job completed successfully.
3535
Ok {

polkadot/node/core/pvf/common/src/lib.rs

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub mod executor_intf;
2222
pub mod prepare;
2323
pub mod pvf;
2424
pub mod worker;
25+
pub mod worker_dir;
2526

2627
pub use cpu_time::ProcessTime;
2728

@@ -30,8 +31,11 @@ pub use sp_tracing;
3031

3132
const LOG_TARGET: &str = "parachain::pvf-common";
3233

33-
use std::mem;
34-
use tokio::io::{self, AsyncRead, AsyncReadExt as _, AsyncWrite, AsyncWriteExt as _};
34+
use std::{
35+
io::{Read, Write},
36+
mem,
37+
};
38+
use tokio::io;
3539

3640
#[cfg(feature = "test-utils")]
3741
pub mod tests {
@@ -41,20 +45,31 @@ pub mod tests {
4145
pub const TEST_PREPARATION_TIMEOUT: Duration = Duration::from_secs(30);
4246
}
4347

44-
/// Write some data prefixed by its length into `w`.
45-
pub async fn framed_send(w: &mut (impl AsyncWrite + Unpin), buf: &[u8]) -> io::Result<()> {
48+
/// Status of security features on the current system.
49+
#[derive(Debug, Clone, Default)]
50+
pub struct SecurityStatus {
51+
/// Whether the landlock features we use are fully available on this system.
52+
pub can_enable_landlock: bool,
53+
// Whether we are able to unshare the user namespace and change the filesystem root.
54+
pub can_unshare_user_namespace_and_change_root: bool,
55+
}
56+
57+
/// Write some data prefixed by its length into `w`. Sync version of `framed_send` to avoid
58+
/// dependency on tokio.
59+
pub fn framed_send_blocking(w: &mut (impl Write + Unpin), buf: &[u8]) -> io::Result<()> {
4660
let len_buf = buf.len().to_le_bytes();
47-
w.write_all(&len_buf).await?;
48-
w.write_all(buf).await?;
61+
w.write_all(&len_buf)?;
62+
w.write_all(buf)?;
4963
Ok(())
5064
}
5165

52-
/// Read some data prefixed by its length from `r`.
53-
pub async fn framed_recv(r: &mut (impl AsyncRead + Unpin)) -> io::Result<Vec<u8>> {
66+
/// Read some data prefixed by its length from `r`. Sync version of `framed_recv` to avoid
67+
/// dependency on tokio.
68+
pub fn framed_recv_blocking(r: &mut (impl Read + Unpin)) -> io::Result<Vec<u8>> {
5469
let mut len_buf = [0u8; mem::size_of::<usize>()];
55-
r.read_exact(&mut len_buf).await?;
70+
r.read_exact(&mut len_buf)?;
5671
let len = usize::from_le_bytes(len_buf);
5772
let mut buf = vec![0; len];
58-
r.read_exact(&mut buf).await?;
73+
r.read_exact(&mut buf)?;
5974
Ok(buf)
6075
}

0 commit comments

Comments
 (0)