Skip to content

Commit 7e5e7e0

Browse files
committed
sandlock-core: parse a :ro suffix on mount specs for read-only mounts
Signed-off-by: Cong Wang <cwang@multikernel.io>
1 parent fd5b28b commit 7e5e7e0

1 file changed

Lines changed: 30 additions & 6 deletions

File tree

crates/sandlock-core/src/profile.rs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,8 @@ pub fn parse_input(input: ProfileInput) -> Result<(Sandbox, ProgramSpec), Sandlo
180180
for p in input.filesystem.deny.iter() { b = b.fs_deny(p); }
181181
if let Some(c) = input.filesystem.chroot { b = b.chroot(c); }
182182
for spec in input.filesystem.mount.iter() {
183-
let (virt, host) = parse_mount_spec(spec)?;
184-
b = b.fs_mount(virt, host);
183+
let (virt, host, read_only) = parse_mount_spec(spec)?;
184+
b = if read_only { b.fs_mount_ro(virt, host) } else { b.fs_mount(virt, host) };
185185
}
186186
if let Some(s) = input.filesystem.on_exit.as_deref() { b = b.on_exit(parse_branch_action(s)?); }
187187
if let Some(s) = input.filesystem.on_error.as_deref() { b = b.on_error(parse_branch_action(s)?); }
@@ -250,17 +250,27 @@ fn parse_branch_action(s: &str) -> Result<crate::sandbox::BranchAction, Sandlock
250250
}
251251

252252
/// Parses a `"VIRTUAL:HOST"` mount spec string into a `(virtual, host)` pair.
253-
fn parse_mount_spec(s: &str) -> Result<(PathBuf, PathBuf), SandlockError> {
253+
/// Parse a `VIRTUAL:HOST` mount spec, with an optional trailing `:ro` (or the
254+
/// default `:rw`) selecting a read-only mount. Returns
255+
/// `(virtual_path, host_path, read_only)`.
256+
pub fn parse_mount_spec(s: &str) -> Result<(PathBuf, PathBuf, bool), SandlockError> {
254257
use crate::error::SandboxError;
255-
let (virt, host) = s.split_once(':').ok_or_else(|| SandlockError::Sandbox(SandboxError::Invalid(
256-
format!("invalid mount spec {s:?}; expected \"VIRTUAL:HOST\""),
258+
let (body, read_only) = if let Some(b) = s.strip_suffix(":ro") {
259+
(b, true)
260+
} else if let Some(b) = s.strip_suffix(":rw") {
261+
(b, false)
262+
} else {
263+
(s, false)
264+
};
265+
let (virt, host) = body.split_once(':').ok_or_else(|| SandlockError::Sandbox(SandboxError::Invalid(
266+
format!("invalid mount spec {s:?}; expected \"VIRTUAL:HOST[:ro]\""),
257267
)))?;
258268
if virt.is_empty() || host.is_empty() {
259269
return Err(SandlockError::Sandbox(SandboxError::Invalid(
260270
format!("invalid mount spec {s:?}; both VIRTUAL and HOST must be non-empty"),
261271
)));
262272
}
263-
Ok((PathBuf::from(virt), PathBuf::from(host)))
273+
Ok((PathBuf::from(virt), PathBuf::from(host), read_only))
264274
}
265275

266276
/// Parses an RFC3339 timestamp string into `SystemTime`.
@@ -400,6 +410,20 @@ mod tests {
400410
assert_eq!(policy.extra_deny_syscalls, vec!["ptrace".to_string()]);
401411
}
402412

413+
#[test]
414+
fn parse_mount_spec_ro_suffix() {
415+
let (v, h, ro) = parse_mount_spec("/work:/host").unwrap();
416+
assert_eq!((v.to_str().unwrap(), h.to_str().unwrap(), ro), ("/work", "/host", false));
417+
let (_, _, ro) = parse_mount_spec("/work:/host:rw").unwrap();
418+
assert!(!ro);
419+
let (v, h, ro) = parse_mount_spec("/work:/host:ro").unwrap();
420+
assert_eq!((v.to_str().unwrap(), h.to_str().unwrap(), ro), ("/work", "/host", true));
421+
// a host path containing colons still parses; only a trailing :ro/:rw is an option
422+
let (_, h, ro) = parse_mount_spec("/v:/a:b:ro").unwrap();
423+
assert_eq!((h.to_str().unwrap(), ro), ("/a:b", true));
424+
assert!(parse_mount_spec("nocolon").is_err());
425+
}
426+
403427
#[test]
404428
fn parse_mount_spec_rejects_missing_colon() {
405429
let toml = r#"

0 commit comments

Comments
 (0)