diff --git a/Cargo.lock b/Cargo.lock index 450e0be..7a8dab2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -164,6 +164,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bitflags" version = "1.3.2" @@ -430,6 +436,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "core-foundation" version = "0.9.4" @@ -664,6 +676,33 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "data-encoding" version = "2.10.0" @@ -679,6 +718,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "digest" version = "0.10.7" @@ -809,6 +858,31 @@ dependencies = [ "linux-raw-sys 0.9.4", ] +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.15.0" @@ -875,6 +949,23 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1153,6 +1244,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "http" version = "1.4.0" @@ -2009,6 +2106,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.32" @@ -2339,6 +2446,15 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.44" @@ -2405,6 +2521,15 @@ dependencies = [ "libc", ] +[[package]] +name = "seccompiler" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345a3e4dddf721a478089d4697b83c6c0a8f5bf16086f6c13397e4534eb6e2e5" +dependencies = [ + "libc", +] + [[package]] name = "semver" version = "1.0.27" @@ -2532,6 +2657,15 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "slab" version = "0.4.12" @@ -2650,6 +2784,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "sptr" version = "0.3.2" @@ -2662,6 +2806,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.117" @@ -2713,6 +2863,17 @@ dependencies = [ "winx", ] +[[package]] +name = "tar" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "target-lexicon" version = "0.13.3" @@ -3821,6 +3982,7 @@ dependencies = [ "toml 0.8.23", "tracing", "tracing-subscriber", + "weft-ipc-types", ] [[package]] @@ -3841,6 +4003,22 @@ dependencies = [ "wayland-backend", "wayland-scanner", "wayland-server", + "weft-ipc-types", +] + +[[package]] +name = "weft-ipc-types" +version = "0.1.0" +dependencies = [ + "rmp-serde", + "serde", +] + +[[package]] +name = "weft-mount-helper" +version = "0.1.0" +dependencies = [ + "anyhow", ] [[package]] @@ -3848,8 +4026,14 @@ name = "weft-pack" version = "0.1.0" dependencies = [ "anyhow", + "ed25519-dalek", + "hex", + "rand 0.8.5", "serde", + "sha2", + "tar", "toml 0.8.23", + "zstd", ] [[package]] @@ -3857,6 +4041,9 @@ name = "weft-runtime" version = "0.1.0" dependencies = [ "anyhow", + "cap-std", + "libc", + "seccompiler", "tracing", "tracing-subscriber", "wasmtime", @@ -4475,6 +4662,16 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix 1.1.4", +] + [[package]] name = "xcursor" version = "0.3.10" @@ -4581,6 +4778,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + [[package]] name = "zerotrie" version = "0.2.3" diff --git a/Cargo.toml b/Cargo.toml index ef21caa..e6e17e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "crates/weft-build-meta", "crates/weft-compositor", "crates/weft-ipc-types", + "crates/weft-mount-helper", "crates/weft-pack", "crates/weft-runtime", "crates/weft-servo-shell", diff --git a/crates/weft-ipc-types/src/lib.rs b/crates/weft-ipc-types/src/lib.rs index 5b2e604..753ef2b 100644 --- a/crates/weft-ipc-types/src/lib.rs +++ b/crates/weft-ipc-types/src/lib.rs @@ -83,7 +83,10 @@ impl std::fmt::Display for FrameDecodeError { Self::TooShort => write!(f, "frame buffer shorter than 4-byte header"), Self::TooLong(n) => write!(f, "frame length {n} exceeds MAX_FRAME_LEN"), Self::LengthMismatch { declared, actual } => { - write!(f, "declared length {declared} != actual body length {actual}") + write!( + f, + "declared length {declared} != actual body length {actual}" + ) } Self::Deserialize(e) => write!(f, "MessagePack deserialize error: {e}"), } @@ -113,7 +116,11 @@ mod tests { let frame = frame_encode(&msg).unwrap(); let decoded: AppdToCompositor = frame_decode(&frame).unwrap(); match decoded { - AppdToCompositor::AppSurfaceCreated { app_id, session_id, pid } => { + AppdToCompositor::AppSurfaceCreated { + app_id, + session_id, + pid, + } => { assert_eq!(app_id, "com.example.app"); assert_eq!(session_id, 42); assert_eq!(pid, 1234); @@ -184,6 +191,9 @@ mod tests { frame[2] = bad_len[2]; frame[3] = bad_len[3]; let result: Result = frame_decode(&frame); - assert!(matches!(result, Err(FrameDecodeError::LengthMismatch { .. }))); + assert!(matches!( + result, + Err(FrameDecodeError::LengthMismatch { .. }) + )); } } diff --git a/crates/weft-mount-helper/Cargo.toml b/crates/weft-mount-helper/Cargo.toml new file mode 100644 index 0000000..f8b9946 --- /dev/null +++ b/crates/weft-mount-helper/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "weft-mount-helper" +version.workspace = true +edition.workspace = true +rust-version.workspace = true + +[[bin]] +name = "weft-mount-helper" +path = "src/main.rs" + +[dependencies] +anyhow = "1.0" diff --git a/crates/weft-mount-helper/src/main.rs b/crates/weft-mount-helper/src/main.rs new file mode 100644 index 0000000..a17a914 --- /dev/null +++ b/crates/weft-mount-helper/src/main.rs @@ -0,0 +1,162 @@ +use std::path::Path; + +use anyhow::Context; + +fn main() -> anyhow::Result<()> { + let args: Vec = std::env::args().collect(); + match args.get(1).map(String::as_str) { + Some("mount") => { + let img = args.get(2).context( + "usage: weft-mount-helper mount ", + )?; + let hash_dev = args.get(3).context("missing ")?; + let root_hash = args.get(4).context("missing ")?; + let mountpoint = args.get(5).context("missing ")?; + cmd_mount( + Path::new(img), + Path::new(hash_dev), + root_hash, + Path::new(mountpoint), + )?; + } + Some("umount") => { + let mountpoint = args + .get(2) + .context("usage: weft-mount-helper umount ")?; + cmd_umount(Path::new(mountpoint))?; + } + _ => { + eprintln!("usage:"); + eprintln!(" weft-mount-helper mount "); + eprintln!(" weft-mount-helper umount "); + std::process::exit(1); + } + } + Ok(()) +} + +fn effective_uid() -> Option { + let status = std::fs::read_to_string("/proc/self/status").ok()?; + status + .lines() + .find(|l| l.starts_with("Uid:")) + .and_then(|l| l.split_whitespace().nth(2)) + .and_then(|s| s.parse().ok()) +} + +fn require_root() -> anyhow::Result<()> { + match effective_uid() { + Some(0) => Ok(()), + Some(uid) => anyhow::bail!("weft-mount-helper must run as root (euid={uid})"), + None => { + anyhow::bail!("weft-mount-helper must run as root (could not read /proc/self/status)") + } + } +} + +fn device_name(mountpoint: &Path) -> String { + let s = mountpoint.to_string_lossy(); + let sanitized: String = s + .chars() + .map(|c| if c.is_ascii_alphanumeric() { c } else { '-' }) + .collect(); + let suffix: String = sanitized + .trim_matches('-') + .chars() + .take(26) + .collect::() + .trim_end_matches('-') + .to_string(); + format!("weft-{suffix}") +} + +fn cmd_mount( + img: &Path, + hash_dev: &Path, + root_hash: &str, + mountpoint: &Path, +) -> anyhow::Result<()> { + require_root()?; + let dev_name = device_name(mountpoint); + + let status = std::process::Command::new("veritysetup") + .args([ + "open", + &img.to_string_lossy(), + &dev_name, + &hash_dev.to_string_lossy(), + root_hash, + ]) + .status() + .context("spawn veritysetup; ensure cryptsetup-bin is installed")?; + if !status.success() { + anyhow::bail!("veritysetup open failed with status {status}"); + } + + let mapper_dev = format!("/dev/mapper/{dev_name}"); + let status = std::process::Command::new("mount") + .args([ + "-t", + "erofs", + "-o", + "ro", + &mapper_dev, + &mountpoint.to_string_lossy(), + ]) + .status() + .context("spawn mount")?; + if !status.success() { + let _ = std::process::Command::new("veritysetup") + .args(["close", &dev_name]) + .status(); + anyhow::bail!("mount failed with status {status}"); + } + + eprintln!("mounted: {} -> {}", img.display(), mountpoint.display()); + Ok(()) +} + +fn cmd_umount(mountpoint: &Path) -> anyhow::Result<()> { + require_root()?; + let dev_name = device_name(mountpoint); + + let status = std::process::Command::new("umount") + .arg(mountpoint) + .status() + .context("spawn umount")?; + if !status.success() { + anyhow::bail!("umount failed with status {status}"); + } + + let status = std::process::Command::new("veritysetup") + .args(["close", &dev_name]) + .status() + .context("spawn veritysetup close; ensure cryptsetup-bin is installed")?; + if !status.success() { + anyhow::bail!("veritysetup close failed with status {status}"); + } + + eprintln!("unmounted: {}", mountpoint.display()); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn device_name_sanitizes_path() { + let mp = Path::new("/run/weft/mounts/com.example.myapp"); + let name = device_name(mp); + assert!(name.starts_with("weft-")); + assert!(name.chars().all(|c| c.is_ascii_alphanumeric() || c == '-')); + assert!(name.len() <= 31); + } + + #[test] + fn device_name_truncates_long_paths() { + let mp = Path::new("/run/weft/mounts/com.example.averylongappidthatexceedsthemaximum"); + let name = device_name(mp); + assert!(name.len() <= 31); + } +} diff --git a/crates/weft-pack/src/main.rs b/crates/weft-pack/src/main.rs index c6ad57f..c8f164b 100644 --- a/crates/weft-pack/src/main.rs +++ b/crates/weft-pack/src/main.rs @@ -127,7 +127,9 @@ fn main() -> anyhow::Result<()> { eprintln!(" weft-pack install install package to app store"); eprintln!(" weft-pack uninstall remove installed package"); eprintln!(" weft-pack list list installed packages"); - eprintln!(" weft-pack build-image [--out ] create EROFS image with mkfs.erofs"); + eprintln!( + " weft-pack build-image [--out ] create EROFS image with mkfs.erofs" + ); eprintln!(" weft-pack build-verity [--out ] add dm-verity hash tree"); eprintln!(" weft-pack bundle [--out ] create .app.tar.zst archive"); eprintln!(" weft-pack unbundle [--out ] extract .app.tar.zst"); @@ -406,7 +408,11 @@ fn build_verity(img: &Path, hash_out: Option<&Path>) -> anyhow::Result<()> { anyhow::bail!("{} already exists", hash_path.display()); } let output = std::process::Command::new("veritysetup") - .args(["format", &img.to_string_lossy(), &hash_path.to_string_lossy()]) + .args([ + "format", + &img.to_string_lossy(), + &hash_path.to_string_lossy(), + ]) .output() .context("spawn veritysetup; ensure cryptsetup-bin is installed")?; if !output.status.success() { diff --git a/scripts/wsl-test.sh b/scripts/wsl-test.sh index 8262efc..fc8c1a4 100644 --- a/scripts/wsl-test.sh +++ b/scripts/wsl-test.sh @@ -38,5 +38,9 @@ echo "" echo "==> cargo test -p weft-pack" cargo test -p weft-pack -- --test-threads=1 2>&1 +echo "" +echo "==> cargo test -p weft-mount-helper" +cargo test -p weft-mount-helper -- --test-threads=1 2>&1 + echo "" echo "ALL DONE"