diff --git a/RELEASES.md b/RELEASES.md index c2a60a8657bf..8c943931beb6 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,14 @@ +## 24.0.11 + +Released 2026-06-24. + +### Fixed + +* WASI hard links and renames check wasmtime-wasi's FilePerms for destination + [GHSA-4ch3-9j33-3pmj](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-4ch3-9j33-3pmj) + +-------------------------------------------------------------------------------- + ## 24.0.10 Released 2026-06-15. diff --git a/crates/test-programs/src/bin/preview1_file_hardlink_across_perms.rs b/crates/test-programs/src/bin/preview1_file_hardlink_across_perms.rs new file mode 100644 index 000000000000..bb5f7c011171 --- /dev/null +++ b/crates/test-programs/src/bin/preview1_file_hardlink_across_perms.rs @@ -0,0 +1,94 @@ +use std::env; +use std::process; +use test_programs::preview1::open_scratch_directory; +use wasi as wasip1; + +const RW_ALIAS_FILENAME: &str = "alias.txt"; +const RO_TEST_FILENAME: &str = "test.txt"; +const RO_EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; + +unsafe fn test_ro_file_has_expected_contents(dir_fd: wasip1::Fd) { + // Open a file for reading + let file_fd = wasip1::path_open(dir_fd, 0, RO_TEST_FILENAME, 0, wasip1::RIGHTS_FD_READ, 0, 0) + .expect("opening test.txt for reading"); + + // Read the file's contents + let buffer = &mut [0u8; 100]; + let nread = wasip1::fd_read( + file_fd, + &[wasip1::Iovec { + buf: buffer.as_mut_ptr(), + buf_len: buffer.len(), + }], + ) + .expect("reading file content"); + + // The file should be as created by the test harness + assert_eq!(nread, RO_EXPECTED_CONTENTS.len(), "expected untouched file"); + assert_eq!( + &buffer[..nread], + RO_EXPECTED_CONTENTS, + "expected untouched file contents" + ); + + wasip1::fd_close(file_fd).expect("closing the file"); +} + +unsafe fn test_file_hardlink_across_perms(rw_dir_fd: wasip1::Fd, ro_dir_fd: wasip1::Fd) { + // Check test preconditions. + test_ro_file_has_expected_contents(ro_dir_fd); + + // Creating a hard link of the read-only file into a Descriptor under + // which files are read-writable would allow the read-only file to be + // written to. So, this must fail with perm: + let err = wasip1::path_link(ro_dir_fd, 0, RO_TEST_FILENAME, rw_dir_fd, RW_ALIAS_FILENAME); + assert!( + err.is_err(), + "path_link should fail because link source readonly, dest is readwrite" + ); + assert_eq!( + err.err().unwrap(), + wasip1::ERRNO_PERM, + "path_link should fail with ERRNO_PERM" + ); + + // Check that contents of link dest did not change + test_ro_file_has_expected_contents(ro_dir_fd); +} + +fn main() { + let mut args = env::args(); + let prog = args.next().unwrap(); + let arg = if let Some(arg) = args.next() { + arg + } else { + eprintln!("usage: {prog} "); + process::exit(1); + }; + + // Open read-write scratch directory + let rw_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("failed to open scratch directory: {err}"); + process::exit(1) + } + }; + + // This test program requires a special preopen at the path "readonly", + // which the host enforces as read-only. Unlike other test programs, this + // directory's path not passed in as an argument, because modifications to + // the testing harness would be too invasive. + let ro_dir_fd = match open_scratch_directory("readonly") { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("failed to open readonly preopen: {err}"); + process::exit(1) + } + }; + + // Run the tests. + unsafe { + test_file_hardlink_across_perms(rw_dir_fd, ro_dir_fd); + } +} diff --git a/crates/test-programs/src/bin/preview1_file_rename_across_perms.rs b/crates/test-programs/src/bin/preview1_file_rename_across_perms.rs new file mode 100644 index 000000000000..b38c5cace7e9 --- /dev/null +++ b/crates/test-programs/src/bin/preview1_file_rename_across_perms.rs @@ -0,0 +1,103 @@ +use std::env; +use std::process; +use test_programs::preview1::open_scratch_directory; +use wasi as wasip1; + +const RW_ALIAS_FILENAME: &str = "alias.txt"; +const RO_TEST_FILENAME: &str = "test.txt"; +const RO_EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; + +unsafe fn test_ro_file_has_expected_contents(dir_fd: wasip1::Fd) { + // Open a file for reading + let file_fd = wasip1::path_open(dir_fd, 0, RO_TEST_FILENAME, 0, wasip1::RIGHTS_FD_READ, 0, 0) + .expect("opening test.txt for reading"); + + // Read the file's contents + let buffer = &mut [0u8; 100]; + let nread = wasip1::fd_read( + file_fd, + &[wasip1::Iovec { + buf: buffer.as_mut_ptr(), + buf_len: buffer.len(), + }], + ) + .expect("reading file content"); + + // The file should be as created by the test harness + assert_eq!(nread, RO_EXPECTED_CONTENTS.len(), "expected untouched file"); + assert_eq!( + &buffer[..nread], + RO_EXPECTED_CONTENTS, + "expected untouched file contents" + ); + + wasip1::fd_close(file_fd).expect("closing the file"); +} + +unsafe fn test_file_rename_across_perms(rw_dir_fd: wasip1::Fd, ro_dir_fd: wasip1::Fd) { + // Check test preconditions. + test_ro_file_has_expected_contents(ro_dir_fd); + + // Create a hardlink inside the file ro dir so there are two files pointing to + // the read-only file. + match wasip1::path_link(ro_dir_fd, 0, RO_TEST_FILENAME, ro_dir_fd, RW_ALIAS_FILENAME) { + // The readonly dir isnt recreated fresh per test mode in the p2 + // runner, so just allow this to fail with exists because its very + // tedious to restructure everything to fix this properly + Ok(()) | Err(wasip1::ERRNO_EXIST) => {} + _ => panic!("should be possible to create link inside ro file domain"), + } + + // Renaming that file into the file rw dir should fail with permissions + // error, otherwise it would permit opening the ro file as rw + let err = wasip1::path_rename(ro_dir_fd, RW_ALIAS_FILENAME, rw_dir_fd, RW_ALIAS_FILENAME); + assert!( + err.is_err(), + "path_rename should fail because link source readonly, dest is readwrite" + ); + assert_eq!( + err.err().unwrap(), + wasip1::ERRNO_PERM, + "path_rename should fail with ERRNO_PERM" + ); + + // Check that contents of link dest did not change + test_ro_file_has_expected_contents(ro_dir_fd); +} + +fn main() { + let mut args = env::args(); + let prog = args.next().unwrap(); + let arg = if let Some(arg) = args.next() { + arg + } else { + eprintln!("usage: {prog} "); + process::exit(1); + }; + + // Open read-write scratch directory + let rw_dir_fd = match open_scratch_directory(&arg) { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("failed to open scratch directory: {err}"); + process::exit(1) + } + }; + + // This test program requires a special preopen at the path "readonly", + // which the host enforces as read-only. Unlike other test programs, this + // directory's path not passed in as an argument, because modifications to + // the testing harness would be too invasive. + let ro_dir_fd = match open_scratch_directory("readonly") { + Ok(dir_fd) => dir_fd, + Err(err) => { + eprintln!("failed to open readonly preopen: {err}"); + process::exit(1) + } + }; + + // Run the tests. + unsafe { + test_file_rename_across_perms(rw_dir_fd, ro_dir_fd); + } +} diff --git a/crates/test-programs/src/bin/preview1_file_truncation_readonly.rs b/crates/test-programs/src/bin/preview1_file_truncation_readonly.rs index 2c1a63d15932..918857498a56 100644 --- a/crates/test-programs/src/bin/preview1_file_truncation_readonly.rs +++ b/crates/test-programs/src/bin/preview1_file_truncation_readonly.rs @@ -18,7 +18,7 @@ unsafe fn test_file_has_expected_contents(dir_fd: wasi::Fd) { ) .expect("reading file content"); - const EXPECTED_CONTENTS: &[u8] = b"truncation test file\n"; + const EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; // The file should be as created by the test harness, not truncated. assert_eq!(nread, EXPECTED_CONTENTS.len(), "expected untouched file"); assert_eq!( diff --git a/crates/test-programs/src/bin/preview2_file_hardlink_across_perms.rs b/crates/test-programs/src/bin/preview2_file_hardlink_across_perms.rs new file mode 100644 index 000000000000..5270619ec8df --- /dev/null +++ b/crates/test-programs/src/bin/preview2_file_hardlink_across_perms.rs @@ -0,0 +1,84 @@ +use test_programs::wasi::filesystem::types::{ + Descriptor, DescriptorFlags, ErrorCode, OpenFlags, PathFlags, +}; + +const RW_ALIAS_FILENAME: &str = "alias.txt"; +const RO_TEST_FILENAME: &str = "test.txt"; +const RO_EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; + +unsafe fn test_ro_file_has_expected_contents(dir: &Descriptor) { + // Open a file for reading + let file = dir + .open_at( + PathFlags::empty(), + RO_TEST_FILENAME, + OpenFlags::empty(), + DescriptorFlags::READ, + ) + .expect("open test.txt for reading"); + + // Read the file's contents + let stream = file.read_via_stream(0).unwrap(); + let read = stream.blocking_read(100).expect("reading test.txt content"); + + drop(stream); + drop(file); + assert_eq!( + read, RO_EXPECTED_CONTENTS, + "expected untouched file contents" + ); +} + +unsafe fn test_file_hardlink_across_perms(rw_dir: &Descriptor, ro_dir: &Descriptor) { + // Check test preconditions. + test_ro_file_has_expected_contents(ro_dir); + + // Creating a hard link of the read-only file into a Descriptor under + // which files are read-writable would allow the read-only file to be + // written to. So, this must fail with perm: + let err = ro_dir.link_at( + PathFlags::empty(), + RO_TEST_FILENAME, + rw_dir, + RW_ALIAS_FILENAME, + ); + assert!( + err.is_err(), + "link_at should fail because link source is readonly, and dest is readwrite" + ); + assert_eq!( + err.err().unwrap(), + ErrorCode::NotPermitted, + "link_at should fail with NotPermitted" + ); + + // Check that contents of link dest did not change + test_ro_file_has_expected_contents(ro_dir); +} + +fn main() { + let args = test_programs::wasi::cli::environment::get_arguments(); + if args.len() != 2 { + panic!("usage: scratch directory argument required"); + } + let preopens = test_programs::wasi::filesystem::preopens::get_directories(); + let rw_path = &args[1]; + let (rw_dir, _) = preopens + .iter() + .find(|(_, path)| path == rw_path) + .expect("find preopen specified by argument"); + + // This test program requires a special preopen at the path "readonly", + // which the host enforces as read-only. Unlike other test programs, this + // directory's path not passed in as an argument, because modifications to + // the testing harness would be too invasive. + let (ro_dir, _) = preopens + .iter() + .find(|(_, path)| path == "readonly") + .expect("find preopen named readonly"); + + // Run the tests. + unsafe { + test_file_hardlink_across_perms(rw_dir, ro_dir); + } +} diff --git a/crates/test-programs/src/bin/preview2_file_rename_across_perms.rs b/crates/test-programs/src/bin/preview2_file_rename_across_perms.rs new file mode 100644 index 000000000000..e1ae35ecc38c --- /dev/null +++ b/crates/test-programs/src/bin/preview2_file_rename_across_perms.rs @@ -0,0 +1,94 @@ +use test_programs::wasi::filesystem::preopens; +use test_programs::wasi::filesystem::types::{ + Descriptor, DescriptorFlags, ErrorCode, OpenFlags, PathFlags, +}; + +const RW_ALIAS_FILENAME: &str = "alias.txt"; +const RO_TEST_FILENAME: &str = "test.txt"; +const RO_EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; + +unsafe fn test_ro_file_has_expected_contents(dir: &Descriptor) { + // Open a file for reading + let file = dir + .open_at( + PathFlags::empty(), + RO_TEST_FILENAME, + OpenFlags::empty(), + DescriptorFlags::READ, + ) + .expect("open test.txt for reading"); + + // Read the file's contents + let stream = file.read_via_stream(0).unwrap(); + let read = stream.blocking_read(100).expect("reading test.txt content"); + + drop(stream); + drop(file); + assert_eq!( + read, RO_EXPECTED_CONTENTS, + "expected untouched file contents" + ); +} + +unsafe fn test_file_hardlink_across_perms(rw_dir: &Descriptor, ro_dir: &Descriptor) { + // Check test preconditions. + test_ro_file_has_expected_contents(ro_dir); + + // Create a hardlink inside the file ro dir so there are two files pointing to + // the read-only file. + match ro_dir.link_at( + PathFlags::empty(), + RO_TEST_FILENAME, + ro_dir, + RW_ALIAS_FILENAME, + ) { + // The readonly dir isnt recreated fresh per test mode in the p2 + // runner, so just allow this to fail with exists because its very + // tedious to restructure everything to fix this properly + Ok(()) | Err(ErrorCode::Exist) => {} + _ => panic!("should be possible to create link inside ro file domain"), + } + + // Renaming that file into the file rw dir should fail with permissions + // error, otherwise it would permit opening the ro file as rw + let err = ro_dir.rename_at(RW_ALIAS_FILENAME, rw_dir, RW_ALIAS_FILENAME); + assert!( + err.is_err(), + "rename_at should fail because link source is file readonly, and dest is file readwrite" + ); + assert_eq!( + err.err().unwrap(), + ErrorCode::NotPermitted, + "rename_at should fail with NotPermitted" + ); + + // Check that contents of link dest did not change + test_ro_file_has_expected_contents(ro_dir); +} + +fn main() { + let args = test_programs::wasi::cli::environment::get_arguments(); + if args.len() != 2 { + panic!("usage: scratch directory argument required"); + } + let preopens = preopens::get_directories(); + let rw_path = &args[1]; + let (rw_dir, _) = preopens + .iter() + .find(|(_, path)| path == rw_path) + .expect("find preopen specified by argument"); + + // This test program requires a special preopen at the path "readonly", + // which the host enforces as read-only. Unlike other test programs, this + // directory's path not passed in as an argument, because modifications to + // the testing harness would be too invasive. + let (ro_dir, _) = preopens + .iter() + .find(|(_, path)| path == "readonly") + .expect("find preopen named readonly"); + + // Run the tests. + unsafe { + test_file_hardlink_across_perms(rw_dir, ro_dir); + } +} diff --git a/crates/test-programs/src/bin/preview2_file_truncation_readonly.rs b/crates/test-programs/src/bin/preview2_file_truncation_readonly.rs index d51a16edeb42..38865a8a997a 100644 --- a/crates/test-programs/src/bin/preview2_file_truncation_readonly.rs +++ b/crates/test-programs/src/bin/preview2_file_truncation_readonly.rs @@ -21,7 +21,7 @@ fn test_file_has_expected_contents(dir: &Descriptor) { drop(stream); drop(file); - const EXPECTED_CONTENTS: &[u8] = b"truncation test file\n"; + const EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; // The file should not be empty due to truncation assert_eq!(read, EXPECTED_CONTENTS, "expected untouched file contents"); } diff --git a/crates/wasi-common/tests/all/async_.rs b/crates/wasi-common/tests/all/async_.rs index f89da3a57f52..64d265fdbc29 100644 --- a/crates/wasi-common/tests/all/async_.rs +++ b/crates/wasi-common/tests/all/async_.rs @@ -303,3 +303,15 @@ fn preview1_file_truncation_readonly() { "blank placeholder test to satisfy assert_test_exists. This test exercises wasmtime-wasi functionality is not relevant to wasi-common" ); } +#[test] +fn preview1_file_hardlink_across_perms() { + println!( + "blank placeholder test to satisfy assert_test_exists. This test exercises wasmtime-wasi functionality is not relevant to wasi-common" + ); +} +#[test] +fn preview1_file_rename_across_perms() { + println!( + "blank placeholder test to satisfy assert_test_exists. This test exercises wasmtime-wasi functionality is not relevant to wasi-common" + ); +} diff --git a/crates/wasi-common/tests/all/sync.rs b/crates/wasi-common/tests/all/sync.rs index 4d3cb11bb8ec..74ea3088c592 100644 --- a/crates/wasi-common/tests/all/sync.rs +++ b/crates/wasi-common/tests/all/sync.rs @@ -292,3 +292,15 @@ fn preview1_file_truncation_readonly() { "blank placeholder test to satisfy assert_test_exists. This test exercises wasmtime-wasi functionality is not relevant to wasi-common" ); } +#[test] +fn preview1_file_hardlink_across_perms() { + println!( + "blank placeholder test to satisfy assert_test_exists. This test exercises wasmtime-wasi functionality is not relevant to wasi-common" + ); +} +#[test] +fn preview1_file_rename_across_perms() { + println!( + "blank placeholder test to satisfy assert_test_exists. This test exercises wasmtime-wasi functionality is not relevant to wasi-common" + ); +} diff --git a/crates/wasi/src/host/filesystem.rs b/crates/wasi/src/host/filesystem.rs index 0372ec9c8d7c..5f59f91a73b9 100644 --- a/crates/wasi/src/host/filesystem.rs +++ b/crates/wasi/src/host/filesystem.rs @@ -488,6 +488,9 @@ where if symlink_follow(old_path_flags) { return Err(ErrorCode::Invalid.into()); } + if old_dir.perms != new_dir.perms || old_dir.file_perms != new_dir.file_perms { + return Err(ErrorCode::NotPermitted.into()); + } let new_dir_handle = std::sync::Arc::clone(&new_dir.dir); old_dir .spawn_blocking(move |d| d.hard_link(&old_path, &new_dir_handle, &new_path)) @@ -698,6 +701,9 @@ where if !new_dir.perms.contains(DirPerms::MUTATE) { return Err(ErrorCode::NotPermitted.into()); } + if old_dir.perms != new_dir.perms || old_dir.file_perms != new_dir.file_perms { + return Err(ErrorCode::NotPermitted.into()); + } let new_dir_handle = std::sync::Arc::clone(&new_dir.dir); Ok(old_dir .spawn_blocking(move |d| d.rename(&old_path, &new_dir_handle, &new_path)) diff --git a/crates/wasi/tests/all/async_.rs b/crates/wasi/tests/all/async_.rs index 19c84830908f..19b9390d0aa8 100644 --- a/crates/wasi/tests/all/async_.rs +++ b/crates/wasi/tests/all/async_.rs @@ -431,27 +431,27 @@ async fn preview2_file_read_write() { #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn preview1_file_truncation_readonly() { - file_truncation_readonly(PREVIEW1_FILE_TRUNCATION_READONLY_COMPONENT).await + run_with_readonly_testfile(PREVIEW1_FILE_TRUNCATION_READONLY_COMPONENT).await } #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn preview2_file_truncation_readonly() { - file_truncation_readonly(PREVIEW2_FILE_TRUNCATION_READONLY_COMPONENT).await + run_with_readonly_testfile(PREVIEW2_FILE_TRUNCATION_READONLY_COMPONENT).await } -async fn file_truncation_readonly(component_path: &str) { +async fn run_with_readonly_testfile(component_path: &str) { use std::path::PathBuf; use wasmtime_wasi::{DirPerms, FilePerms}; - let prefix = "wasi_components_truncation_readonly_ro_"; + let prefix = "wasi_components_ro_"; let tempdir = tempfile::Builder::new() .prefix(prefix) .tempdir() .expect("create readonly tempdir"); const FILENAME: &str = "test.txt"; - const EXPECTED_CONTENTS: &[u8] = b"truncation test file\n"; + const EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; let mut file: PathBuf = PathBuf::from(tempdir.path()); file.push(FILENAME); - std::fs::write(&file, EXPECTED_CONTENTS).expect("write truncation test file"); + std::fs::write(&file, EXPECTED_CONTENTS).expect("write read only test file"); run(component_path, |b| { b.preopened_dir( @@ -463,8 +463,25 @@ async fn file_truncation_readonly(component_path: &str) { .unwrap(); }) .await - .expect("run p1_file_truncation_readonly guest"); + .expect("run guest"); - let contents = std::fs::read(&file).expect("read truncation test file"); + let contents = std::fs::read(&file).expect("read read only test file"); assert_eq!(EXPECTED_CONTENTS, contents); } + +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn preview1_file_hardlink_across_perms() { + run_with_readonly_testfile(PREVIEW1_FILE_HARDLINK_ACROSS_PERMS_COMPONENT).await +} +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn preview2_file_hardlink_across_perms() { + run_with_readonly_testfile(PREVIEW2_FILE_HARDLINK_ACROSS_PERMS_COMPONENT).await +} +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn preview1_file_rename_across_perms() { + run_with_readonly_testfile(PREVIEW1_FILE_RENAME_ACROSS_PERMS_COMPONENT).await +} +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn preview2_file_rename_across_perms() { + run_with_readonly_testfile(PREVIEW2_FILE_RENAME_ACROSS_PERMS_COMPONENT).await +} diff --git a/crates/wasi/tests/all/preview1.rs b/crates/wasi/tests/all/preview1.rs index d23b94e52507..b675108f4566 100644 --- a/crates/wasi/tests/all/preview1.rs +++ b/crates/wasi/tests/all/preview1.rs @@ -283,21 +283,25 @@ async fn preview1_sleep_quickly_but_lots() { #[test_log::test(tokio::test(flavor = "multi_thread"))] async fn preview1_file_truncation_readonly() { + run_with_readonly_testfile(PREVIEW1_FILE_TRUNCATION_READONLY).await +} + +async fn run_with_readonly_testfile(component_path: &str) { use std::path::PathBuf; use wasmtime_wasi::{DirPerms, FilePerms}; - let prefix = format!("wasi_components_truncation_readonly_ro_"); + let prefix = format!("wasi_components_ro_"); let tempdir = tempfile::Builder::new() .prefix(&prefix) .tempdir() .expect("create readonly tempdir"); const FILENAME: &str = "test.txt"; - const EXPECTED_CONTENTS: &[u8] = b"truncation test file\n"; + const EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; let mut file: PathBuf = PathBuf::from(tempdir.path()); file.push(FILENAME); std::fs::write(&file, EXPECTED_CONTENTS).expect("write truncation test file"); - run(PREVIEW1_FILE_TRUNCATION_READONLY, |b| { + run(component_path, |b| { b.preopened_dir( tempdir.path(), "readonly", @@ -307,8 +311,17 @@ async fn preview1_file_truncation_readonly() { .unwrap(); }) .await - .expect("run p1_file_truncation_readonly guest"); + .expect("run guest"); let contents = std::fs::read(&file).expect("read truncation test file"); assert_eq!(EXPECTED_CONTENTS, contents); } + +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn preview1_file_hardlink_across_perms() { + run_with_readonly_testfile(PREVIEW1_FILE_HARDLINK_ACROSS_PERMS).await +} +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn preview1_file_rename_across_perms() { + run_with_readonly_testfile(PREVIEW1_FILE_RENAME_ACROSS_PERMS).await +} diff --git a/crates/wasi/tests/all/sync.rs b/crates/wasi/tests/all/sync.rs index 9d510bd9a000..0f7fff0f1080 100644 --- a/crates/wasi/tests/all/sync.rs +++ b/crates/wasi/tests/all/sync.rs @@ -348,27 +348,27 @@ fn preview2_file_read_write() { #[test_log::test] fn preview1_file_truncation_readonly() { - file_truncation_readonly(PREVIEW1_FILE_TRUNCATION_READONLY_COMPONENT) + run_with_readonly_testfile(PREVIEW1_FILE_TRUNCATION_READONLY_COMPONENT) } #[test_log::test] fn preview2_file_truncation_readonly() { - file_truncation_readonly(PREVIEW2_FILE_TRUNCATION_READONLY_COMPONENT) + run_with_readonly_testfile(PREVIEW2_FILE_TRUNCATION_READONLY_COMPONENT) } -fn file_truncation_readonly(component_path: &str) { +fn run_with_readonly_testfile(component_path: &str) { use std::path::PathBuf; use wasmtime_wasi::{DirPerms, FilePerms}; - let prefix = "wasi_components_truncation_readonly_ro_"; + let prefix = "wasi_components_ro_"; let tempdir = tempfile::Builder::new() .prefix(prefix) .tempdir() .expect("create readonly tempdir"); const FILENAME: &str = "test.txt"; - const EXPECTED_CONTENTS: &[u8] = b"truncation test file\n"; + const EXPECTED_CONTENTS: &[u8] = b"read only test file\n"; let mut file: PathBuf = PathBuf::from(tempdir.path()); file.push(FILENAME); - std::fs::write(&file, EXPECTED_CONTENTS).expect("write truncation test file"); + std::fs::write(&file, EXPECTED_CONTENTS).expect("write read only test file"); run(component_path, |b| { b.preopened_dir( @@ -379,8 +379,25 @@ fn file_truncation_readonly(component_path: &str) { ) .unwrap(); }) - .expect("run p1_file_truncation_readonly guest"); + .expect("run guest"); - let contents = std::fs::read(&file).expect("read truncation test file"); + let contents = std::fs::read(&file).expect("read read only test file"); assert_eq!(EXPECTED_CONTENTS, contents); } + +#[test_log::test] +fn preview1_file_hardlink_across_perms() { + run_with_readonly_testfile(PREVIEW1_FILE_HARDLINK_ACROSS_PERMS_COMPONENT) +} +#[test_log::test] +fn preview2_file_hardlink_across_perms() { + run_with_readonly_testfile(PREVIEW2_FILE_HARDLINK_ACROSS_PERMS_COMPONENT) +} +#[test_log::test] +fn preview1_file_rename_across_perms() { + run_with_readonly_testfile(PREVIEW1_FILE_RENAME_ACROSS_PERMS_COMPONENT) +} +#[test_log::test] +fn preview2_file_rename_across_perms() { + run_with_readonly_testfile(PREVIEW2_FILE_RENAME_ACROSS_PERMS_COMPONENT) +}