mirror of
https://github.com/uutils/coreutils
synced 2024-12-30 06:53:23 +00:00
090be5bb94
The ToDo list was updated to mark `chcon` as done. Building and testing `chcon` requires enabling the `feat_selinux` feature. If `make` is used for building, then please specify `SELINUX_ENABLED=1` if building and testing on a system where SELinux is not enabled.
469 lines
13 KiB
Rust
469 lines
13 KiB
Rust
// spell-checker:ignore (jargon) xattributes
|
|
|
|
#![cfg(feature = "feat_selinux")]
|
|
|
|
use std::ffi::CString;
|
|
use std::path::Path;
|
|
use std::{io, iter, str};
|
|
|
|
use crate::common::util::*;
|
|
|
|
#[test]
|
|
fn version() {
|
|
new_ucmd!().arg("--version").succeeds();
|
|
new_ucmd!().arg("-V").succeeds();
|
|
}
|
|
|
|
#[test]
|
|
fn help() {
|
|
new_ucmd!().fails();
|
|
new_ucmd!().arg("--help").succeeds();
|
|
new_ucmd!().arg("-h").fails(); // -h is NOT --help, it is actually --no-dereference.
|
|
}
|
|
|
|
#[test]
|
|
fn reference_errors() {
|
|
for args in &[
|
|
&["--verbose", "--reference"] as &[&str],
|
|
&["--verbose", "--reference=/dev/null"],
|
|
&["--verbose", "--reference=/inexistent", "/dev/null"],
|
|
] {
|
|
new_ucmd!().args(args).fails();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn recursive_errors() {
|
|
for args in &[
|
|
&["--verbose", "-P"] as &[&str],
|
|
&["--verbose", "-H"],
|
|
&["--verbose", "-L"],
|
|
&["--verbose", "--recursive", "-P", "--dereference"],
|
|
&["--verbose", "--recursive", "-H", "--no-dereference"],
|
|
&["--verbose", "--recursive", "-L", "--no-dereference"],
|
|
] {
|
|
new_ucmd!().args(args).fails();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn valid_context() {
|
|
let (dir, mut cmd) = at_and_ucmd!();
|
|
dir.touch("a.tmp");
|
|
dir.symlink_file("a.tmp", "la.tmp");
|
|
|
|
let la_context = get_file_context(dir.plus("a.tmp")).unwrap();
|
|
let new_la_context = "guest_u:object_r:etc_t:s0:c42";
|
|
|
|
cmd.args(&["--verbose", new_la_context])
|
|
.arg(dir.plus("la.tmp"))
|
|
.succeeds();
|
|
assert_eq!(get_file_context(dir.plus("la.tmp")).unwrap(), la_context);
|
|
assert_eq!(
|
|
get_file_context(dir.plus("a.tmp")).unwrap().as_deref(),
|
|
Some(new_la_context)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn valid_context_on_valid_symlink() {
|
|
let (dir, mut cmd) = at_and_ucmd!();
|
|
dir.touch("a.tmp");
|
|
dir.symlink_file("a.tmp", "la.tmp");
|
|
|
|
let a_context = get_file_context(dir.plus("a.tmp")).unwrap();
|
|
let new_la_context = "guest_u:object_r:etc_t:s0:c42";
|
|
|
|
cmd.args(&["--verbose", "--no-dereference", new_la_context])
|
|
.arg(dir.plus("la.tmp"))
|
|
.succeeds();
|
|
assert_eq!(
|
|
get_file_context(dir.plus("la.tmp")).unwrap().as_deref(),
|
|
Some(new_la_context)
|
|
);
|
|
assert_eq!(get_file_context(dir.plus("a.tmp")).unwrap(), a_context);
|
|
}
|
|
|
|
#[test]
|
|
fn valid_context_on_broken_symlink() {
|
|
let (dir, mut cmd) = at_and_ucmd!();
|
|
dir.symlink_file("a.tmp", "la.tmp");
|
|
|
|
let new_la_context = "guest_u:object_r:etc_t:s0:c42";
|
|
|
|
cmd.args(&["--verbose", "--no-dereference", new_la_context])
|
|
.arg(dir.plus("la.tmp"))
|
|
.succeeds();
|
|
assert_eq!(
|
|
get_file_context(dir.plus("la.tmp")).unwrap().as_deref(),
|
|
Some(new_la_context)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn valid_context_with_prior_xattributes() {
|
|
let (dir, mut cmd) = at_and_ucmd!();
|
|
dir.touch("a.tmp");
|
|
|
|
let a_context = get_file_context(dir.plus("a.tmp")).unwrap();
|
|
if a_context.is_none() {
|
|
set_file_context(dir.plus("a.tmp"), "unconfined_u:object_r:user_tmp_t:s0").unwrap();
|
|
}
|
|
let new_la_context = "guest_u:object_r:etc_t:s0:c42";
|
|
|
|
cmd.args(&["--verbose", new_la_context])
|
|
.arg(dir.plus("a.tmp"))
|
|
.succeeds();
|
|
assert_eq!(
|
|
get_file_context(dir.plus("a.tmp")).unwrap().as_deref(),
|
|
Some(new_la_context)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn valid_context_directory() {
|
|
let (dir, mut cmd) = at_and_ucmd!();
|
|
dir.mkdir("a");
|
|
dir.symlink_dir("a", "la");
|
|
|
|
let b_path = Path::new("a").join("b.txt");
|
|
dir.touch(b_path.to_str().unwrap());
|
|
|
|
let la_context = get_file_context(dir.plus("la")).unwrap();
|
|
let b_context = get_file_context(dir.plus(b_path.to_str().unwrap())).unwrap();
|
|
|
|
let new_la_context = "guest_u:object_r:etc_t:s0:c42";
|
|
|
|
cmd.args(&["--verbose", new_la_context])
|
|
.arg(dir.plus("la"))
|
|
.succeeds();
|
|
assert_eq!(get_file_context(dir.plus("la")).unwrap(), la_context);
|
|
assert_eq!(
|
|
get_file_context(dir.plus("a")).unwrap().as_deref(),
|
|
Some(new_la_context)
|
|
);
|
|
assert_eq!(
|
|
get_file_context(dir.plus(b_path.to_str().unwrap())).unwrap(),
|
|
b_context
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn valid_context_directory_recursive() {
|
|
let (dir, mut cmd) = at_and_ucmd!();
|
|
dir.mkdir("a");
|
|
dir.symlink_dir("a", "la");
|
|
|
|
let b_path = Path::new("a").join("b.txt");
|
|
dir.touch(b_path.to_str().unwrap());
|
|
|
|
let a_context = get_file_context(dir.plus("a")).unwrap();
|
|
let b_context = get_file_context(dir.plus(b_path.to_str().unwrap())).unwrap();
|
|
|
|
let new_la_context = "guest_u:object_r:etc_t:s0:c42";
|
|
|
|
// -P (default): do not traverse any symbolic links.
|
|
cmd.args(&["--verbose", "--recursive", new_la_context])
|
|
.arg(dir.plus("la"))
|
|
.succeeds();
|
|
assert_eq!(
|
|
get_file_context(dir.plus("la")).unwrap().as_deref(),
|
|
Some(new_la_context)
|
|
);
|
|
assert_eq!(get_file_context(dir.plus("a")).unwrap(), a_context);
|
|
assert_eq!(
|
|
get_file_context(dir.plus(b_path.to_str().unwrap())).unwrap(),
|
|
b_context
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn valid_context_directory_recursive_follow_args_dir_symlinks() {
|
|
let (dir, mut cmd) = at_and_ucmd!();
|
|
dir.mkdir("a");
|
|
dir.symlink_dir("a", "la");
|
|
|
|
let b_path = Path::new("a").join("b.txt");
|
|
dir.touch(b_path.to_str().unwrap());
|
|
|
|
let la_context = get_file_context(dir.plus("la")).unwrap();
|
|
let new_la_context = "guest_u:object_r:etc_t:s0:c42";
|
|
|
|
/*
|
|
let lc_path = Path::new("a").join("lc");
|
|
dir.symlink_dir("c", lc_path.to_str().unwrap());
|
|
assert_eq!(
|
|
get_file_context(dir.plus(lc_path.to_str().unwrap())).unwrap(),
|
|
None
|
|
);
|
|
*/
|
|
|
|
// -H: if a command line argument is a symbolic link to a directory, traverse it.
|
|
cmd.args(&["--verbose", "--recursive", "-H", new_la_context])
|
|
.arg(dir.plus("la"))
|
|
.succeeds();
|
|
assert_eq!(
|
|
get_file_context(dir.plus("a")).unwrap().as_deref(),
|
|
Some(new_la_context)
|
|
);
|
|
assert_eq!(
|
|
get_file_context(dir.plus(b_path.to_str().unwrap()))
|
|
.unwrap()
|
|
.as_deref(),
|
|
Some(new_la_context)
|
|
);
|
|
assert_eq!(get_file_context(dir.plus("la")).unwrap(), la_context);
|
|
/*
|
|
assert_eq!(
|
|
get_file_context(dir.plus(lc_path.to_str().unwrap()))
|
|
.unwrap()
|
|
.as_deref(),
|
|
Some(new_la_context)
|
|
);
|
|
*/
|
|
}
|
|
|
|
#[test]
|
|
fn valid_context_directory_recursive_follow_all_symlinks() {
|
|
let (dir, mut cmd) = at_and_ucmd!();
|
|
dir.mkdir("a");
|
|
dir.symlink_dir("a", "la");
|
|
|
|
let b_path = Path::new("a").join("b.txt");
|
|
dir.touch(b_path.to_str().unwrap());
|
|
|
|
let c_path = Path::new("a").join("c");
|
|
dir.touch(c_path.to_str().unwrap());
|
|
|
|
let lc_path = Path::new("a").join("lc");
|
|
dir.symlink_dir(c_path.to_str().unwrap(), lc_path.to_str().unwrap());
|
|
|
|
let la_context = get_file_context(dir.plus("la")).unwrap();
|
|
let lc_context = get_file_context(dir.plus(lc_path.to_str().unwrap())).unwrap();
|
|
|
|
let new_la_context = "guest_u:object_r:etc_t:s0:c42";
|
|
|
|
// -L: traverse every symbolic link to a directory encountered.
|
|
cmd.args(&["--verbose", "--recursive", "-L", new_la_context])
|
|
.arg(dir.plus("la"))
|
|
.succeeds();
|
|
assert_eq!(get_file_context(dir.plus("la")).unwrap(), la_context);
|
|
assert_eq!(
|
|
get_file_context(dir.plus("a")).unwrap().as_deref(),
|
|
Some(new_la_context)
|
|
);
|
|
assert_eq!(
|
|
get_file_context(dir.plus(b_path.to_str().unwrap()))
|
|
.unwrap()
|
|
.as_deref(),
|
|
Some(new_la_context)
|
|
);
|
|
assert_eq!(
|
|
get_file_context(dir.plus(lc_path.to_str().unwrap())).unwrap(),
|
|
lc_context
|
|
);
|
|
assert_eq!(
|
|
get_file_context(dir.plus(c_path.to_str().unwrap()))
|
|
.unwrap()
|
|
.as_deref(),
|
|
Some(new_la_context)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn user_role_range_type() {
|
|
let (dir, mut cmd) = at_and_ucmd!();
|
|
|
|
dir.touch("a.tmp");
|
|
let a_context = get_file_context(dir.plus("a.tmp")).unwrap();
|
|
if a_context.is_none() {
|
|
set_file_context(dir.plus("a.tmp"), "unconfined_u:object_r:user_tmp_t:s0").unwrap();
|
|
}
|
|
|
|
cmd.args(&[
|
|
"--verbose",
|
|
"--user=guest_u",
|
|
"--role=object_r",
|
|
"--type=etc_t",
|
|
"--range=s0:c42",
|
|
])
|
|
.arg(dir.plus("a.tmp"))
|
|
.succeeds();
|
|
|
|
assert_eq!(
|
|
get_file_context(dir.plus("a.tmp")).unwrap().as_deref(),
|
|
Some("guest_u:object_r:etc_t:s0:c42")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn user_change() {
|
|
let (dir, mut cmd) = at_and_ucmd!();
|
|
|
|
dir.touch("a.tmp");
|
|
let a_context = get_file_context(dir.plus("a.tmp")).unwrap();
|
|
let new_a_context = if let Some(a_context) = a_context {
|
|
let mut components: Vec<_> = a_context.split(':').collect();
|
|
components[0] = "guest_u";
|
|
components.join(":")
|
|
} else {
|
|
set_file_context(dir.plus("a.tmp"), "unconfined_u:object_r:user_tmp_t:s0").unwrap();
|
|
String::from("guest_u:object_r:user_tmp_t:s0")
|
|
};
|
|
|
|
cmd.args(&["--verbose", "--user=guest_u"])
|
|
.arg(dir.plus("a.tmp"))
|
|
.succeeds();
|
|
assert_eq!(
|
|
get_file_context(dir.plus("a.tmp")).unwrap(),
|
|
Some(new_a_context)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn role_change() {
|
|
let (dir, mut cmd) = at_and_ucmd!();
|
|
|
|
dir.touch("a.tmp");
|
|
let a_context = get_file_context(dir.plus("a.tmp")).unwrap();
|
|
let new_a_context = if let Some(a_context) = a_context {
|
|
let mut components: Vec<_> = a_context.split(':').collect();
|
|
components[1] = "system_r";
|
|
components.join(":")
|
|
} else {
|
|
set_file_context(dir.plus("a.tmp"), "unconfined_u:object_r:user_tmp_t:s0").unwrap();
|
|
String::from("unconfined_u:system_r:user_tmp_t:s0")
|
|
};
|
|
|
|
cmd.args(&["--verbose", "--role=system_r"])
|
|
.arg(dir.plus("a.tmp"))
|
|
.succeeds();
|
|
assert_eq!(
|
|
get_file_context(dir.plus("a.tmp")).unwrap(),
|
|
Some(new_a_context)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn type_change() {
|
|
let (dir, mut cmd) = at_and_ucmd!();
|
|
|
|
dir.touch("a.tmp");
|
|
let a_context = get_file_context(dir.plus("a.tmp")).unwrap();
|
|
let new_a_context = if let Some(a_context) = a_context {
|
|
let mut components: Vec<_> = a_context.split(':').collect();
|
|
components[2] = "etc_t";
|
|
components.join(":")
|
|
} else {
|
|
set_file_context(dir.plus("a.tmp"), "unconfined_u:object_r:user_tmp_t:s0").unwrap();
|
|
String::from("unconfined_u:object_r:etc_t:s0")
|
|
};
|
|
|
|
cmd.args(&["--verbose", "--type=etc_t"])
|
|
.arg(dir.plus("a.tmp"))
|
|
.succeeds();
|
|
assert_eq!(
|
|
get_file_context(dir.plus("a.tmp")).unwrap(),
|
|
Some(new_a_context)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn range_change() {
|
|
let (dir, mut cmd) = at_and_ucmd!();
|
|
|
|
dir.touch("a.tmp");
|
|
let a_context = get_file_context(dir.plus("a.tmp")).unwrap();
|
|
let new_a_context = if let Some(a_context) = a_context {
|
|
a_context
|
|
.split(':')
|
|
.take(3)
|
|
.chain(iter::once("s0:c42"))
|
|
.collect::<Vec<_>>()
|
|
.join(":")
|
|
} else {
|
|
set_file_context(dir.plus("a.tmp"), "unconfined_u:object_r:user_tmp_t:s0").unwrap();
|
|
String::from("unconfined_u:object_r:user_tmp_t:s0:c42")
|
|
};
|
|
|
|
cmd.args(&["--verbose", "--range=s0:c42"])
|
|
.arg(dir.plus("a.tmp"))
|
|
.succeeds();
|
|
assert_eq!(
|
|
get_file_context(dir.plus("a.tmp")).unwrap(),
|
|
Some(new_a_context)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn valid_reference() {
|
|
let (dir, mut cmd) = at_and_ucmd!();
|
|
|
|
dir.touch("a.tmp");
|
|
let new_a_context = "guest_u:object_r:etc_t:s0:c42";
|
|
set_file_context(dir.plus("a.tmp"), new_a_context).unwrap();
|
|
|
|
dir.touch("b.tmp");
|
|
let b_context = get_file_context(dir.plus("b.tmp")).unwrap();
|
|
assert_ne!(b_context.as_deref(), Some(new_a_context));
|
|
|
|
cmd.arg("--verbose")
|
|
.arg(format!("--reference={}", dir.plus_as_string("a.tmp")))
|
|
.arg(dir.plus("b.tmp"))
|
|
.succeeds();
|
|
assert_eq!(
|
|
get_file_context(dir.plus("b.tmp")).unwrap().as_deref(),
|
|
Some(new_a_context)
|
|
);
|
|
}
|
|
|
|
fn get_file_context(path: impl AsRef<Path>) -> Result<Option<String>, selinux::errors::Error> {
|
|
let path = path.as_ref();
|
|
match selinux::SecurityContext::of_path(path, false, false) {
|
|
Err(r) => {
|
|
println!("get_file_context failed: '{}': {}.", path.display(), &r);
|
|
Err(r)
|
|
}
|
|
|
|
Ok(None) => {
|
|
println!(
|
|
"get_file_context: '{}': No SELinux context defined.",
|
|
path.display()
|
|
);
|
|
Ok(None)
|
|
}
|
|
|
|
Ok(Some(context)) => {
|
|
let bytes = context
|
|
.as_bytes()
|
|
.splitn(2, |&b| b == 0_u8)
|
|
.next()
|
|
.unwrap_or_default();
|
|
let context = String::from_utf8(bytes.into()).unwrap_or_default();
|
|
println!("get_file_context: '{}' => '{}'.", context, path.display());
|
|
Ok(Some(context))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn set_file_context(path: impl AsRef<Path>, context: &str) -> Result<(), selinux::errors::Error> {
|
|
let c_context = CString::new(context.as_bytes()).map_err(|_r| selinux::errors::Error::IO {
|
|
source: io::Error::from(io::ErrorKind::InvalidInput),
|
|
operation: "CString::new",
|
|
})?;
|
|
|
|
let path = path.as_ref();
|
|
let r =
|
|
selinux::SecurityContext::from_c_str(&c_context, false).set_for_path(path, false, false);
|
|
if let Err(r) = &r {
|
|
println!(
|
|
"set_file_context failed: '{}' => '{}': {}.",
|
|
context,
|
|
path.display(),
|
|
r
|
|
)
|
|
} else {
|
|
println!("set_file_context: '{}' => '{}'.", context, path.display())
|
|
}
|
|
r
|
|
}
|