diff --git a/tests/contest/contest/src/main.rs b/tests/contest/contest/src/main.rs index e18f639af..8060519a4 100644 --- a/tests/contest/contest/src/main.rs +++ b/tests/contest/contest/src/main.rs @@ -22,6 +22,7 @@ use crate::tests::mounts_recursive::get_mounts_recursive_test; use crate::tests::no_pivot::get_no_pivot_test; use crate::tests::pidfile::get_pidfile_test; use crate::tests::process_rlimits::get_process_rlimits_test; +use crate::tests::process_user::get_process_user_test; use crate::tests::readonly_paths::get_ro_paths_test; use crate::tests::scheduler::get_scheduler_test; use crate::tests::seccomp::get_seccomp_test; @@ -115,6 +116,7 @@ fn main() -> Result<()> { let scheduler = get_scheduler_test(); let io_priority_test = get_io_priority_test(); let devices = get_devices_test(); + let process_user = get_process_user_test(); let process_rlimtis = get_process_rlimits_test(); let no_pivot = get_no_pivot_test(); @@ -140,6 +142,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(process_user)); tm.add_test_group(Box::new(process_rlimtis)); tm.add_test_group(Box::new(no_pivot)); diff --git a/tests/contest/contest/src/tests/mod.rs b/tests/contest/contest/src/tests/mod.rs index b33210152..68cca2e1d 100644 --- a/tests/contest/contest/src/tests/mod.rs +++ b/tests/contest/contest/src/tests/mod.rs @@ -12,6 +12,7 @@ pub mod mounts_recursive; pub mod no_pivot; pub mod pidfile; pub mod process_rlimits; +pub mod process_user; pub mod readonly_paths; pub mod scheduler; pub mod seccomp; diff --git a/tests/contest/contest/src/tests/process_user/mod.rs b/tests/contest/contest/src/tests/process_user/mod.rs new file mode 100644 index 000000000..495aaba6f --- /dev/null +++ b/tests/contest/contest/src/tests/process_user/mod.rs @@ -0,0 +1,2 @@ +mod process_user_test; +pub use process_user_test::get_process_user_test; diff --git a/tests/contest/contest/src/tests/process_user/process_user_test.rs b/tests/contest/contest/src/tests/process_user/process_user_test.rs new file mode 100644 index 000000000..825cb3254 --- /dev/null +++ b/tests/contest/contest/src/tests/process_user/process_user_test.rs @@ -0,0 +1,56 @@ +use anyhow::{Context, Ok, Result}; +use oci_spec::runtime::{ProcessBuilder, Spec, SpecBuilder, UserBuilder}; +use rand::Rng; +use test_framework::{test_result, Test, TestGroup, TestResult}; + +use crate::utils::test_inside_container; + +// Generates a Vec with a random number of elements (between 5 and 15), +// where each element is a random u32 value between 0 and 65535. +fn generate_unique_random_vec() -> Vec { + let mut rng = rand::thread_rng(); + let vec_size = rng.gen_range(5..=10); + let mut ret = Vec::new(); + while ret.len() < vec_size { + let rand = rng.gen_range(100..=200); + if !ret.contains(&rand) { + ret.push(rand); + } + } + ret +} + +fn create_spec() -> Result { + let umask = 0o002; + let user = UserBuilder::default() + .uid(10u32) + .gid(10u32) + .additional_gids(generate_unique_random_vec()) + .umask(umask as u32) + .build()?; + + let spec = SpecBuilder::default() + .process( + ProcessBuilder::default() + .args(vec!["runtimetest".to_string(), "process_user".to_string()]) + .user(user) + .build() + .expect("error in creating process config"), + ) + .build() + .context("failed to build spec")?; + Ok(spec) +} +fn process_user_test() -> TestResult { + let spec = test_result!(create_spec()); + test_inside_container(spec, &|_| Ok(())) +} + +pub fn get_process_user_test() -> TestGroup { + let mut process_user_test_group = TestGroup::new("process_user"); + + let test = Test::new("process_user_test", Box::new(process_user_test)); + process_user_test_group.add(vec![Box::new(test)]); + + process_user_test_group +} diff --git a/tests/contest/runtimetest/src/main.rs b/tests/contest/runtimetest/src/main.rs index cbb96226c..7e98f0847 100644 --- a/tests/contest/runtimetest/src/main.rs +++ b/tests/contest/runtimetest/src/main.rs @@ -44,6 +44,7 @@ fn main() { "io_priority_class_be" => tests::test_io_priority_class(&spec, IoprioClassBe), "io_priority_class_idle" => tests::test_io_priority_class(&spec, IoprioClassIdle), "devices" => tests::validate_devices(&spec), + "process_user" => tests::validate_process_user(&spec), "process_rlimits" => tests::validate_process_rlimits(&spec), "no_pivot" => tests::validate_rootfs(), _ => eprintln!("error due to unexpected execute test name: {execute_test}"), diff --git a/tests/contest/runtimetest/src/tests.rs b/tests/contest/runtimetest/src/tests.rs index 57b9e7796..8b358b285 100644 --- a/tests/contest/runtimetest/src/tests.rs +++ b/tests/contest/runtimetest/src/tests.rs @@ -7,8 +7,9 @@ use anyhow::{bail, Result}; use nix::errno::Errno; use nix::libc; use nix::sys::resource::{getrlimit, Resource}; +use nix::sys::stat::{umask, Mode}; use nix::sys::utsname; -use nix::unistd::getcwd; +use nix::unistd::{getcwd, getgid, getgroups, getuid, Gid, Uid}; use oci_spec::runtime::IOPriorityClass::{self, IoprioClassBe, IoprioClassIdle, IoprioClassRt}; use oci_spec::runtime::{ LinuxDevice, LinuxDeviceType, LinuxSchedulerPolicy, PosixRlimit, PosixRlimitType, Spec, @@ -549,6 +550,65 @@ pub fn test_io_priority_class(spec: &Spec, io_priority_class: IOPriorityClass) { } } +pub fn validate_process_user(spec: &Spec) { + let process = spec.process().as_ref().unwrap(); + let expected_uid = Uid::from(process.user().uid()); + let expected_gid = Gid::from(process.user().gid()); + let expected_umask = Mode::from_bits(process.user().umask().unwrap()).unwrap(); + + let uid = getuid(); + let gid = getgid(); + // The umask function not only gets the current mask, but also has the ability to set a new mask, + // so we need to set it back after getting the latest value. + let current_umask = umask(nix::sys::stat::Mode::empty()); + umask(current_umask); + + if expected_uid != uid { + eprintln!("error due to uid want {}, got {}", expected_uid, uid) + } + + if expected_gid != gid { + eprintln!("error due to gid want {}, got {}", expected_gid, gid) + } + + if let Err(e) = validate_additional_gids(process.user().additional_gids().as_ref().unwrap()) { + eprintln!("error additional gids {e}"); + } + + if expected_umask != current_umask { + eprintln!( + "error due to umask want {:?}, got {:?}", + expected_umask, current_umask + ) + } +} + +// validate_additional_gids function is used to validate additional groups of user +fn validate_additional_gids(expected_gids: &Vec) -> Result<()> { + let current_gids = getgroups().unwrap(); + + if expected_gids.len() != current_gids.len() { + bail!( + "error : additional group mismatch, want {:?}, got {:?}", + expected_gids, + current_gids + ); + } + + for gid in expected_gids { + if !current_gids.contains(&Gid::from_raw(*gid)) { + bail!( + "error : additional gid {} is not in current groups, expected {:?}, got {:?}", + gid, + expected_gids, + current_gids + ); + } + } + + Ok(()) +} + pub fn validate_process_rlimits(spec: &Spec) { let process = spec.process().as_ref().unwrap(); let spec_rlimits: &Vec = process.rlimits().as_ref().unwrap();