From 063d5583f5b59acda5957899b34949d77ce62ce2 Mon Sep 17 00:00:00 2001 From: nayuta-ai Date: Wed, 9 Oct 2024 01:31:13 +0900 Subject: [PATCH] linux_masked_paths integration test --- tests/contest/contest/src/main.rs | 3 + .../tests/linux_masked_paths/masked_paths.rs | 256 ++++++++++++++++++ .../src/tests/linux_masked_paths/mod.rs | 3 + tests/contest/contest/src/tests/mod.rs | 1 + tests/contest/runtimetest/src/main.rs | 1 + tests/contest/runtimetest/src/tests.rs | 31 +++ 6 files changed, 295 insertions(+) create mode 100644 tests/contest/contest/src/tests/linux_masked_paths/masked_paths.rs create mode 100644 tests/contest/contest/src/tests/linux_masked_paths/mod.rs diff --git a/tests/contest/contest/src/main.rs b/tests/contest/contest/src/main.rs index 8049457c0..c378044b7 100644 --- a/tests/contest/contest/src/main.rs +++ b/tests/contest/contest/src/main.rs @@ -17,6 +17,7 @@ use crate::tests::hostname::get_hostname_test; use crate::tests::intel_rdt::get_intel_rdt_test; use crate::tests::io_priority::get_io_priority_test; use crate::tests::lifecycle::{ContainerCreate, ContainerLifecycle}; +use crate::tests::linux_masked_paths::get_linux_masked_paths_tests; use crate::tests::linux_ns_itype::get_ns_itype_tests; use crate::tests::mounts_recursive::get_mounts_recursive_test; use crate::tests::pidfile::get_pidfile_test; @@ -113,6 +114,7 @@ fn main() -> Result<()> { let scheduler = get_scheduler_test(); let io_priority_test = get_io_priority_test(); let devices = get_devices_test(); + let masked_paths = get_linux_masked_paths_tests(); tm.add_test_group(Box::new(cl)); tm.add_test_group(Box::new(cc)); @@ -136,6 +138,7 @@ fn main() -> Result<()> { tm.add_test_group(Box::new(sysctl)); tm.add_test_group(Box::new(scheduler)); tm.add_test_group(Box::new(devices)); + tm.add_test_group(Box::new(masked_paths)); tm.add_test_group(Box::new(io_priority_test)); tm.add_cleanup(Box::new(cgroups::cleanup_v1)); diff --git a/tests/contest/contest/src/tests/linux_masked_paths/masked_paths.rs b/tests/contest/contest/src/tests/linux_masked_paths/masked_paths.rs new file mode 100644 index 000000000..21c2e44ff --- /dev/null +++ b/tests/contest/contest/src/tests/linux_masked_paths/masked_paths.rs @@ -0,0 +1,256 @@ +use std::path::PathBuf; + +use anyhow::{anyhow, bail}; +use nix::sys::stat::SFlag; +use oci_spec::runtime::{LinuxBuilder, ProcessBuilder, Spec, SpecBuilder}; +use test_framework::{Test, TestGroup, TestResult}; + +use crate::utils::test_inside_container; + +fn get_spec(masked_paths: Vec) -> Spec { + SpecBuilder::default() + .linux( + LinuxBuilder::default() + .masked_paths(masked_paths) + .build() + .expect("could not build"), + ) + .process( + ProcessBuilder::default() + .args(vec!["runtimetest".to_string(), "masked_paths".to_string()]) + .build() + .unwrap(), + ) + .build() + .unwrap() +} + +fn check_masked_paths() -> TestResult { + let masked_dir = "masked-dir"; + let masked_subdir = "masked-subdir"; + let masked_file = "masked-file"; + + let masked_dir_top = PathBuf::from(masked_dir); + let masked_file_top = PathBuf::from(masked_file); + + let masked_dir_sub = masked_dir_top.join(masked_subdir); + let masked_file_sub = masked_dir_top.join(masked_file); + let masked_file_sub_sub = masked_dir_sub.join(masked_file); + + let root = PathBuf::from("/"); + + let masked_paths = vec![ + root.join(&masked_dir_top).to_string_lossy().to_string(), + root.join(&masked_file_top).to_string_lossy().to_string(), + root.join(&masked_dir_sub).to_string_lossy().to_string(), + root.join(&masked_file_sub).to_string_lossy().to_string(), + root.join(&masked_file_sub_sub) + .to_string_lossy() + .to_string(), + ]; + + let spec = get_spec(masked_paths); + + test_inside_container(spec, &|bundle_path| { + use std::{fs, io}; + let test_dir = bundle_path.join(&masked_dir_sub); + + match fs::create_dir_all(&test_dir) { + io::Result::Ok(_) => { /*This is expected*/ } + io::Result::Err(e) => { + bail!(e) + } + } + + match fs::File::create(test_dir.join("tmp")) { + io::Result::Ok(_) => { /*This is expected*/ } + io::Result::Err(e) => { + bail!(e) + } + } + + let test_sub_sub_file = bundle_path.join(&masked_file_sub_sub); + match fs::File::create(test_sub_sub_file) { + io::Result::Ok(_) => { /*This is expected*/ } + io::Result::Err(e) => { + bail!(e) + } + } + + let test_sub_file = bundle_path.join(&masked_file_sub); + match fs::File::create(test_sub_file) { + io::Result::Ok(_) => { /*This is expected*/ } + io::Result::Err(e) => { + bail!(e) + } + } + + let test_file = bundle_path.join(masked_file); + match fs::File::create(test_file) { + io::Result::Ok(_) => { /*This is expected*/ } + io::Result::Err(e) => { + bail!(e) + } + } + + Ok(()) + }) +} + +fn check_masked_rel_paths() -> TestResult { + // Deliberately set a relative path to be masked, + // and expect an error + let masked_rel_path = "masked_rel_path"; + let masked_paths = vec![masked_rel_path.to_string()]; + let spec = get_spec(masked_paths); + + test_inside_container(spec, &|bundle_path| { + use std::{fs, io}; + let test_file = bundle_path.join(masked_rel_path); + match fs::metadata(&test_file) { + io::Result::Ok(md) => { + bail!( + "reading path {:?} should have given error, found {:?} instead", + test_file, + md + ) + } + io::Result::Err(e) => { + let err = e.kind(); + if let io::ErrorKind::NotFound = err { + Ok(()) + } else { + bail!("expected not found error, got {:?}", err); + } + } + } + }) +} + +fn check_masked_symlinks() -> TestResult { + // Deliberately create a masked symlink that points an invalid file, + // and expect an error. + let root = PathBuf::from("/"); + let masked_symlink = "masked_symlink"; + let masked_paths = vec![root.join(masked_symlink).to_string_lossy().to_string()]; + let spec = get_spec(masked_paths); + + let res = test_inside_container(spec, &|bundle_path| { + use std::{fs, io}; + let test_file = bundle_path.join(masked_symlink); + // ln -s .. /masked-symlink ; readlink -f /masked-symlink; ls -L /masked-symlink + match std::os::unix::fs::symlink("../masked_symlink", &test_file) { + io::Result::Ok(_) => { /* This is expected */ } + io::Result::Err(e) => { + bail!("error in creating symlink, to {:?} {:?}", test_file, e); + } + } + + let r_path = match fs::read_link(&test_file) { + io::Result::Ok(p) => p, + io::Result::Err(e) => { + bail!("error in reading symlink at {:?} : {:?}", test_file, e); + } + }; + + match fs::metadata(r_path) { + io::Result::Ok(md) => { + bail!( + "reading path {:?} should have given error, found {:?} instead", + test_file, + md + ) + } + io::Result::Err(e) => { + let err = e.kind(); + if let io::ErrorKind::NotFound = err { + Ok(()) + } else { + bail!("expected not found error, got {:?}", err); + } + } + } + }); + + if let TestResult::Passed = res { + TestResult::Failed(anyhow!( + "expected error in container creation with invalid symlink, found no error" + )) + } else { + TestResult::Passed + } +} + +fn test_node(mode: u32) -> TestResult { + let root = PathBuf::from("/"); + let masked_device = "masked_device"; + let masked_paths = vec![root.join(masked_device).to_string_lossy().to_string()]; + let spec = get_spec(masked_paths); + + test_inside_container(spec, &|bundle_path| { + use std::os::unix::fs::OpenOptionsExt; + use std::{fs, io}; + let test_file = bundle_path.join(masked_device); + + let mut opts = fs::OpenOptions::new(); + opts.mode(mode); + opts.create(true); + if let io::Result::Err(e) = fs::OpenOptions::new() + .mode(mode) + .create(true) + .write(true) + .open(&test_file) + { + bail!( + "could not create file {:?} with mode {:?} : {:?}", + test_file, + mode ^ 0o666, + e + ); + } + + match fs::metadata(&test_file) { + io::Result::Ok(_) => Ok(()), + io::Result::Err(e) => { + let err = e.kind(); + if let io::ErrorKind::NotFound = err { + bail!("error in creating device node, {:?}", e) + } else { + Ok(()) + } + } + } + }) +} + +fn check_masked_device_nodes() -> TestResult { + let modes = [ + SFlag::S_IFBLK.bits() | 0o666, + SFlag::S_IFCHR.bits() | 0o666, + SFlag::S_IFIFO.bits() | 0o666, + ]; + for mode in modes { + let res = test_node(mode); + if let TestResult::Failed(_) = res { + return res; + } + std::thread::sleep(std::time::Duration::from_millis(1000)); + } + TestResult::Passed +} + +pub fn get_linux_masked_paths_tests() -> TestGroup { + let mut tg = TestGroup::new("masked_paths"); + let masked_paths_test = Test::new("masked_paths", Box::new(check_masked_paths)); + let masked_rel_paths_test = Test::new("masked_rel_paths", Box::new(check_masked_rel_paths)); + let masked_symlinks_test = Test::new("masked_symlinks", Box::new(check_masked_symlinks)); + let masked_device_nodes_test = + Test::new("masked_device_nodes", Box::new(check_masked_device_nodes)); + tg.add(vec![ + Box::new(masked_paths_test), + Box::new(masked_rel_paths_test), + Box::new(masked_symlinks_test), + Box::new(masked_device_nodes_test), + ]); + tg +} diff --git a/tests/contest/contest/src/tests/linux_masked_paths/mod.rs b/tests/contest/contest/src/tests/linux_masked_paths/mod.rs new file mode 100644 index 000000000..30e6af832 --- /dev/null +++ b/tests/contest/contest/src/tests/linux_masked_paths/mod.rs @@ -0,0 +1,3 @@ +mod masked_paths; + +pub use masked_paths::get_linux_masked_paths_tests; diff --git a/tests/contest/contest/src/tests/mod.rs b/tests/contest/contest/src/tests/mod.rs index 1fee606b1..c57a30c7f 100644 --- a/tests/contest/contest/src/tests/mod.rs +++ b/tests/contest/contest/src/tests/mod.rs @@ -7,6 +7,7 @@ pub mod hostname; pub mod intel_rdt; pub mod io_priority; pub mod lifecycle; +pub mod linux_masked_paths; pub mod linux_ns_itype; pub mod mounts_recursive; pub mod pidfile; diff --git a/tests/contest/runtimetest/src/main.rs b/tests/contest/runtimetest/src/main.rs index 95780bd48..5117a29b9 100644 --- a/tests/contest/runtimetest/src/main.rs +++ b/tests/contest/runtimetest/src/main.rs @@ -33,6 +33,7 @@ fn main() { "hello_world" => tests::hello_world(&spec), ////////// ANCHOR_END: example_runtimetest_main "readonly_paths" => tests::validate_readonly_paths(&spec), + "masked_paths" => tests::validate_masked_paths(&spec), "set_host_name" => tests::validate_hostname(&spec), "mounts_recursive" => tests::validate_mounts_recursive(&spec), "domainname_test" => tests::validate_domainname(&spec), diff --git a/tests/contest/runtimetest/src/tests.rs b/tests/contest/runtimetest/src/tests.rs index 40f5ad29c..d9c9e5013 100644 --- a/tests/contest/runtimetest/src/tests.rs +++ b/tests/contest/runtimetest/src/tests.rs @@ -545,3 +545,34 @@ pub fn test_io_priority_class(spec: &Spec, io_priority_class: IOPriorityClass) { eprintln!("error ioprio_get expected priority {expected_priority:?}, got {priority}") } } + +pub fn validate_masked_paths(spec: &Spec) { + let linux = spec.linux().as_ref().unwrap(); + let masked_paths = match linux.masked_paths() { + Some(p) => p, + None => { + eprintln!("in readonly paths, expected some readonly paths to be set, found none"); + return; + } + }; + + if masked_paths.is_empty() { + return; + } + + // TODO when https://github.com/rust-lang/rust/issues/86442 stabilizes, + // change manual matching of i32 to e.kind() and match statement + for path in masked_paths { + if let std::io::Result::Err(e) = test_read_access(path) { + let errno = Errno::from_raw(e.raw_os_error().unwrap()); + if errno == Errno::ENOENT { + /* This is expected */ + } else { + eprintln!("in masked paths, error in testing read access for path {path} : {e:?}"); + return; + } + } else { + /* Expected */ + } + } +}