Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Readonly paths #582

Merged
merged 12 commits into from
Jan 9, 2022
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/integration_test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ tar = "0.4"
test_framework = { path = "../test_framework" }
uuid = "0.8"
which = "4.2.2"
libc = "0.2.112"

[dependencies.clap]
version = "=3.0.0-beta.5"
Expand Down
3 changes: 3 additions & 0 deletions crates/integration_test/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod utils;
use crate::tests::lifecycle::{ContainerCreate, ContainerLifecycle};
use crate::tests::linux_ns_itype::get_ns_itype_tests;
use crate::tests::pidfile::get_pidfile_test;
use crate::tests::readonly_paths::get_ro_paths_test;
use crate::tests::seccomp_notify::get_seccomp_notify_test;
use crate::tests::tlb::get_tlb_test;
use crate::utils::support::set_runtime_path;
Expand Down Expand Up @@ -79,6 +80,7 @@ fn main() -> Result<()> {
let cgroup_v1_network = cgroups::network::get_test_group();
let cgroup_v1_blkio = cgroups::blkio::get_test_group();
let seccomp_notify = get_seccomp_notify_test();
let ro_paths = get_ro_paths_test();

tm.add_test_group(&cl);
tm.add_test_group(&cc);
Expand All @@ -92,6 +94,7 @@ fn main() -> Result<()> {
tm.add_test_group(&cgroup_v1_network);
tm.add_test_group(&cgroup_v1_blkio);
tm.add_test_group(&seccomp_notify);
tm.add_test_group(&ro_paths);

tm.add_cleanup(Box::new(cgroups::cleanup_v1));
tm.add_cleanup(Box::new(cgroups::cleanup_v2));
Expand Down
1 change: 1 addition & 0 deletions crates/integration_test/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ pub mod cgroups;
pub mod lifecycle;
pub mod linux_ns_itype;
pub mod pidfile;
pub mod readonly_paths;
pub mod seccomp_notify;
pub mod tlb;
2 changes: 2 additions & 0 deletions crates/integration_test/src/tests/readonly_paths/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod readonly_paths;
pub use readonly_paths::get_ro_paths_test;
275 changes: 275 additions & 0 deletions crates/integration_test/src/tests/readonly_paths/readonly_paths.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
use crate::utils::test_inside_container;
use anyhow::bail;
use oci_spec::runtime::LinuxBuilder;
use oci_spec::runtime::{Spec, SpecBuilder};
use std::path::PathBuf;
use test_framework::{Test, TestGroup, TestResult};

fn get_spec(readonly_paths: Vec<String>) -> Spec {
SpecBuilder::default()
.linux(
LinuxBuilder::default()
.readonly_paths(readonly_paths)
.build()
.expect("could not build"),
)
.build()
.unwrap()
}

fn check_readonly_paths() -> TestResult {
// here we abbreviate 'readonly' as ro for variable names,
// purely for ease of writing

let ro_dir = "readonly_dir";
let ro_subdir = "readonly_subdir";
let ro_file = "readonly_file";

// in the runtime-tools tests, they start these with a '/',
// but in that case, when joined with any path later,
// the '/' takes preference, and path is not actually joined
// eg : (test).join(t1) = test/t1
// (test).join(.t1) = /t1
// which is not what we want, so we leave them without '/'
let ro_dir_top = PathBuf::from(ro_dir);
let ro_file_top = PathBuf::from(ro_file);

let ro_dir_sub = ro_dir_top.join(ro_subdir);
let ro_file_sub = ro_dir_top.join(ro_file);
let ro_file_sub_sub = ro_dir_sub.join(ro_file);

let root = PathBuf::from("/");

let ro_paths = vec![
root.join(&ro_dir_top).to_string_lossy().to_string(),
root.join(&ro_file_top).to_string_lossy().to_string(),
root.join(&ro_dir_sub).to_string_lossy().to_string(),
root.join(&ro_file_sub).to_string_lossy().to_string(),
root.join(&ro_file_sub_sub).to_string_lossy().to_string(),
];

let spec = get_spec(ro_paths);
test_inside_container(spec, &|bundle| {
use std::{fs, io};
let bundle_path = bundle.as_ref();
let test_dir = bundle_path.join(&ro_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(&ro_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(&ro_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(&ro_file);
match fs::File::create(&test_file) {
io::Result::Ok(_) => { /*This is expected*/ }
io::Result::Err(e) => {
YJDoc2 marked this conversation as resolved.
Show resolved Hide resolved
bail!(e)
}
}

Ok(())
})
}

fn check_readonly_rel_path() -> TestResult {
let ro_rel_path = "readonly_relpath";
let ro_paths = vec![ro_rel_path.to_string()];
let spec = get_spec(ro_paths);

test_inside_container(spec, &|bundle| {
use std::{fs, io};
let bundle_path = bundle.as_ref();
let test_file = bundle_path.join(ro_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 {
return Ok(());
} else {
bail!("expected not found error, got {:?}", err);
}
}
}
})
}

fn check_readonly_symlinks() -> TestResult {
let root = PathBuf::from("/");
let ro_symlink = "readonly_symlink";
let ro_paths = vec![root.join(&ro_symlink).to_string_lossy().to_string()];

let spec = get_spec(ro_paths);

test_inside_container(spec, &|bundle| {
use std::{fs, io};
let bundle_path = bundle.as_ref();
let test_file = bundle_path.join(ro_symlink);

match std::os::unix::fs::symlink("../readonly_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 symlink for {:?} should have given error, found {:?} instead",
test_file,
md
)
}
io::Result::Err(e) => {
let err = e.kind();
if let io::ErrorKind::NotFound = err {
return Ok(());
} else {
bail!("expected not found error, got {:?}", err);
}
}
}
})
}

fn test_node(mode: u32) -> TestResult {
let root = PathBuf::from("/");
let ro_device = "readonly_device";
let ro_paths = vec![root.join(&ro_device).to_string_lossy().to_string()];

let spec = get_spec(ro_paths);

test_inside_container(spec, &|bundle| {
use std::{fs, io};

let bundle_path = bundle.as_ref();
let test_file = bundle_path.join(&ro_device);
// NOTE
// yes, I know using unsafe willy-nilly is a bad idea,
// especially given that OpenOptionsExt in std::os::unix::fs does provide a method
// to set mode in open options, like this :

// use std::os::unix::fs::OpenOptionsExt;
// let mut opts = fs::OpenOptions::new();
// opts.mode(mode);
// opts.create(true);
// if let io::Result::Err(e) = opts.open(&test_file) {
// bail!(
// "could not create device node at {:?} with mode {}, got error {:?}",
// test_file,
// mode ^ 0o666,
// e
// );
// }

// but that gives OsErr 22, invalid arguments.
// That is why we directly use mknod from lib here

let _path = test_file.to_string_lossy().as_ptr() as *const i8;
let r = unsafe { libc::mknod(_path, mode, 0) };
if r != 0 {
bail!(
"error in creating a device node at {:?} with mode {:?}, got return code {}",
test_file,
mode,
r
);
}

YJDoc2 marked this conversation as resolved.
Show resolved Hide resolved
match fs::metadata(&test_file) {
io::Result::Ok(_) => Ok(()),
io::Result::Err(e) => {
bail!("error in creating device node, {:?}", e)
}
}
})
}

fn check_readonly_device_nodes() -> TestResult {
let modes = [
libc::S_IFBLK | 0o666,
libc::S_IFCHR | 0o666,
libc::S_IFIFO | 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_ro_paths_test<'a>() -> TestGroup<'a> {
let ro_paths = Test::new("readonly_paths", Box::new(check_readonly_paths));
let ro_rel_paths = Test::new("readonly_rel_paths", Box::new(check_readonly_rel_path));
let ro_symlinks = Test::new("readonly_symlinks", Box::new(check_readonly_symlinks));
// let ro_device_nodes = Test::new(
// "readonly_device_nodes",
// Box::new(check_readonly_device_nodes),
// );
let ro_device_nodes_blk = Test::new(
"readonly_device_nodes_blk",
Box::new(|| test_node(libc::S_IFBLK | 0o666)),
);
let ro_device_nodes_chr = Test::new(
"readonly_device_node_chr",
Box::new(|| test_node(libc::S_IFCHR | 0o666)),
);

let ro_device_nodes_fifo = Test::new(
"readonly_device_nodes_fifo",
Box::new(|| test_node(libc::S_IFIFO | 0o666)),
);
YJDoc2 marked this conversation as resolved.
Show resolved Hide resolved
let mut tg = TestGroup::new("readonly_paths");
tg.add(vec![
Box::new(ro_paths),
Box::new(ro_rel_paths),
Box::new(ro_symlinks),
// Box::new(ro_device_nodes),
Box::new(ro_device_nodes_blk),
Box::new(ro_device_nodes_chr),
Box::new(ro_device_nodes_fifo),
]);
tg
}
4 changes: 2 additions & 2 deletions crates/integration_test/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ pub use support::{
};
pub use temp_dir::{create_temp_dir, TempDir};
pub use test_utils::{
create_container, delete_container, get_state, kill_container, test_outside_container,
ContainerData, State,
create_container, delete_container, get_state, kill_container, test_inside_container,
test_outside_container, ContainerData, State,
};
Loading