Skip to content

Commit

Permalink
linux_masked_paths integration test
Browse files Browse the repository at this point in the history
  • Loading branch information
nayuta-ai committed Oct 9, 2024
1 parent 38ae405 commit 063d558
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 0 deletions.
3 changes: 3 additions & 0 deletions tests/contest/contest/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand All @@ -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));
Expand Down
256 changes: 256 additions & 0 deletions tests/contest/contest/src/tests/linux_masked_paths/masked_paths.rs
Original file line number Diff line number Diff line change
@@ -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<String>) -> 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
}
3 changes: 3 additions & 0 deletions tests/contest/contest/src/tests/linux_masked_paths/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod masked_paths;

pub use masked_paths::get_linux_masked_paths_tests;
1 change: 1 addition & 0 deletions tests/contest/contest/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions tests/contest/runtimetest/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
31 changes: 31 additions & 0 deletions tests/contest/runtimetest/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
}
}
}

0 comments on commit 063d558

Please sign in to comment.