diff --git a/flake.nix b/flake.nix index 4a5cb2b..1942c8b 100644 --- a/flake.nix +++ b/flake.nix @@ -41,7 +41,8 @@ in !(pkgs.lib.hasPrefix "userland/" relative || pkgs.lib.hasPrefix "spikes/" relative) && (craneLib.filterCargoSources path type || pkgs.lib.hasSuffix ".wit" path || pkgs.lib.hasSuffix ".witx" path - || pkgs.lib.hasSuffix ".js" path); + || pkgs.lib.hasSuffix ".js" path || pkgs.lib.hasSuffix ".html" path + || pkgs.lib.hasSuffix ".wasm" path); }; v8Archive = pkgs.fetchurl { # deno_core enables rusty_v8's simdutf feature. diff --git a/host/src/world.rs b/host/src/world.rs index b77674e..2a6f79d 100644 --- a/host/src/world.rs +++ b/host/src/world.rs @@ -5,6 +5,7 @@ //! - `host/`: host state outside the sandbox (harness storage, later the vault). No guest sees it. use sausage_vfs::{Generated, Host, Layer, Memory, Mounts, Process}; use std::{ + os::unix::fs::PermissionsExt, path::{Path, PathBuf}, sync::Arc, }; @@ -21,13 +22,19 @@ pub struct World { fn copy_tree(source: &Path, destination: &Path) -> std::io::Result<()> { std::fs::create_dir_all(destination)?; + let mut permissions = std::fs::metadata(destination)?.permissions(); + permissions.set_mode(permissions.mode() | 0o200); + std::fs::set_permissions(destination, permissions)?; for entry in std::fs::read_dir(source)? { let entry = entry?; let target = destination.join(entry.file_name()); if entry.file_type()?.is_dir() { copy_tree(&entry.path(), &target)?; } else { - std::fs::copy(entry.path(), target)?; + std::fs::copy(entry.path(), &target)?; + let mut permissions = std::fs::metadata(&target)?.permissions(); + permissions.set_mode(permissions.mode() | 0o200); + std::fs::set_permissions(&target, permissions)?; } } Ok(()) @@ -85,3 +92,38 @@ impl World { }) } } + +#[cfg(test)] +#[test] +fn readonly_bundle_copies_are_writable() -> std::io::Result<()> { + use std::fs; + let bundle = tempfile::tempdir()?; + let world = tempfile::tempdir()?; + for (name, mode) in [("bin/tool.wasm", 0o555), ("src/lib.rs", 0o444)] { + let path = bundle.path().join(name); + fs::create_dir_all(path.parent().unwrap())?; + fs::write(&path, b"original")?; + fs::set_permissions(path, fs::Permissions::from_mode(mode))?; + } + for path in [bundle.path().join("bin"), bundle.path().join("src"), bundle.path().to_owned(), world.path().to_owned()] { + fs::set_permissions(path, fs::Permissions::from_mode(0o555))?; + } + let result = copy_tree(bundle.path(), world.path()); + // Restore source directories so the temporary directory can remove its files. + for path in [bundle.path(), &bundle.path().join("bin"), &bundle.path().join("src")] { + fs::set_permissions(path, fs::Permissions::from_mode(0o755))?; + } + result?; + assert_eq!(fs::metadata(world.path())?.permissions().mode() & 0o777, 0o755); + for (name, mode) in [("bin/tool.wasm", 0o555), ("src/lib.rs", 0o444)] { + let source = bundle.path().join(name); + let target = world.path().join(name); + assert_eq!(fs::metadata(&source)?.permissions().mode() & 0o777, mode); + assert_eq!(fs::metadata(&target)?.permissions().mode() & 0o777, mode | 0o200); + fs::write(&target, b"changed")?; + assert_eq!(fs::read(source)?, b"original"); + } + fs::write(world.path().join("bin/added.wasm"), b"new")?; + fs::write(world.path().join("src/added.rs"), b"new")?; + Ok(()) +}