141 lines
5.3 KiB
Rust
141 lines
5.3 KiB
Rust
//! Trust-chain regression checks touch only an explicitly owned target subtree.
|
|
use super::*;
|
|
use std::fs;
|
|
use std::os::unix::fs::{PermissionsExt, chown, symlink};
|
|
|
|
#[path = "../dever-tests/tests/support/temp.rs"]
|
|
mod temp;
|
|
|
|
#[test]
|
|
fn restricted_hosts_require_strict_profile_transitions_before_helper_selection() {
|
|
use std::io::{Error, ErrorKind};
|
|
for userns in [None, Some("0\n")] {
|
|
assert!(
|
|
!host_policy(|path| {
|
|
assert!(
|
|
path.ends_with("apparmor_restrict_unprivileged_userns"),
|
|
"unrestricted hosts must not read the strict flag"
|
|
);
|
|
userns
|
|
.map(str::to_owned)
|
|
.ok_or_else(|| Error::from(ErrorKind::NotFound))
|
|
})
|
|
.unwrap()
|
|
);
|
|
}
|
|
for strict in [None, Some("0\n"), Some("1\n")] {
|
|
let result = host_policy(|path| {
|
|
if path.ends_with("apparmor_restrict_unprivileged_userns") {
|
|
return Ok("1\n".into());
|
|
}
|
|
assert!(path.ends_with("apparmor_restrict_unprivileged_unconfined"));
|
|
strict
|
|
.map(str::to_owned)
|
|
.ok_or_else(|| Error::from(ErrorKind::NotFound))
|
|
});
|
|
if strict == Some("1\n") {
|
|
assert!(result.unwrap());
|
|
} else {
|
|
assert!(
|
|
result
|
|
.unwrap_err()
|
|
.contains("kernel.apparmor_restrict_unprivileged_unconfined=1")
|
|
);
|
|
}
|
|
}
|
|
for failure in [ErrorKind::PermissionDenied, ErrorKind::InvalidData] {
|
|
assert!(
|
|
host_policy(|_| Err(Error::from(failure)))
|
|
.unwrap_err()
|
|
.contains("cannot inspect")
|
|
);
|
|
}
|
|
assert!(
|
|
host_policy(|_| Ok("2\n".into()))
|
|
.unwrap_err()
|
|
.contains("invalid AppArmor kernel flag")
|
|
);
|
|
assert!(
|
|
host_policy(
|
|
|path| if path.ends_with("apparmor_restrict_unprivileged_userns") {
|
|
Ok("1".into())
|
|
} else {
|
|
Err(Error::from(ErrorKind::PermissionDenied))
|
|
}
|
|
)
|
|
.unwrap_err()
|
|
.contains("cannot inspect")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "requires root and the prepared static author bwrap; touches only owned target files"]
|
|
fn rejects_untrusted_helper_paths_bytes_modes_owners_and_capabilities() {
|
|
assert_eq!(rustix::process::geteuid().as_raw(), 0);
|
|
let target = Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
.join("../../target")
|
|
.canonicalize()
|
|
.unwrap();
|
|
let root = temp::TemporaryDirectory::new_in(&target);
|
|
let bytes = fs::read(target.join("sandbox-inputs/assets/bin/bwrap")).unwrap();
|
|
let hash = crate::validate_bwrap(&bytes).unwrap();
|
|
let directory = root.path().join(&hash);
|
|
fs::create_dir(&directory).unwrap();
|
|
let helper = directory.join("bwrap");
|
|
assert!(
|
|
verify_helper(&helper, &hash)
|
|
.unwrap_err()
|
|
.contains("missing")
|
|
);
|
|
fs::write(&helper, &bytes).unwrap();
|
|
fs::set_permissions(&helper, fs::Permissions::from_mode(0o755)).unwrap();
|
|
verify_helper(&helper, &hash).unwrap();
|
|
assert!(
|
|
verify_helper(&helper, &"0".repeat(64))
|
|
.unwrap_err()
|
|
.contains("differs")
|
|
);
|
|
for mode in [0o775, 0o4755, 0o2755, 0o644] {
|
|
fs::set_permissions(&helper, fs::Permissions::from_mode(mode)).unwrap();
|
|
assert!(verify_helper(&helper, &hash).is_err());
|
|
}
|
|
fs::set_permissions(&helper, fs::Permissions::from_mode(0o755)).unwrap();
|
|
chown(&helper, Some(65534), Some(65534)).unwrap();
|
|
assert!(verify_helper(&helper, &hash).is_err());
|
|
chown(&helper, Some(0), Some(0)).unwrap();
|
|
fs::set_permissions(&directory, fs::Permissions::from_mode(0o777)).unwrap();
|
|
assert!(verify_helper(&helper, &hash).is_err());
|
|
fs::set_permissions(&directory, fs::Permissions::from_mode(0o755)).unwrap();
|
|
let renamed = root.path().join("retained");
|
|
fs::rename(&directory, &renamed).unwrap();
|
|
symlink(&renamed, &directory).unwrap();
|
|
assert!(verify_helper(&helper, &hash).is_err());
|
|
fs::remove_file(&directory).unwrap();
|
|
fs::rename(&renamed, &directory).unwrap();
|
|
// VFS_CAP_REVISION_2 with only CAP_NET_ADMIN permitted; never execute it.
|
|
let mut capability = [0u8; 20];
|
|
capability[..4].copy_from_slice(&0x0200_0001u32.to_le_bytes());
|
|
capability[4..8].copy_from_slice(&(1u32 << 12).to_le_bytes());
|
|
rustix::fs::setxattr(
|
|
&helper,
|
|
"security.capability",
|
|
&capability,
|
|
rustix::fs::XattrFlags::empty(),
|
|
)
|
|
.unwrap();
|
|
assert!(
|
|
verify_helper(&helper, &hash)
|
|
.unwrap_err()
|
|
.contains("file capabilities")
|
|
);
|
|
rustix::fs::removexattr(&helper, "security.capability").unwrap();
|
|
verify_helper(&helper, &hash).unwrap();
|
|
// A sticky /tmp ancestor is acceptable for installer image fixtures, but
|
|
// cannot be part of the runtime's administrator-controlled attachment path.
|
|
let shared_ancestor = temp::TemporaryDirectory::new();
|
|
let shared_helper = shared_ancestor.path().join("bwrap");
|
|
fs::write(&shared_helper, &bytes).unwrap();
|
|
fs::set_permissions(&shared_helper, fs::Permissions::from_mode(0o755)).unwrap();
|
|
assert!(verify_helper(&shared_helper, &hash).is_err());
|
|
}
|