use std::path::{Path, PathBuf};
use super::{
developer_toolchain_system_read_roots, network_name_service_read_roots,
proc_runtime_reads_are_contained, push_rule, read_only_access, standard_device_rules,
system_read_roots, workspace_access, AccessFs, FilesystemProfile, FsAccess,
ProcessFilesystemScope,
};
use crate::orchestration::{CapabilityPolicy, SandboxProfile};
use crate::stdlib::sandbox::{
policy_allows_child_writes, process_sandbox_developer_toolchain_read_roots,
process_sandbox_package_manager_config_read_roots, process_sandbox_policy_read_roots,
process_sandbox_policy_write_roots, process_sandbox_readonly_roots, process_sandbox_roots,
sandbox_rejection, warn_once,
};
use crate::VmError;
pub(super) fn filesystem_profile(
program: &str,
policy: &CapabilityPolicy,
handled_access_fs: FsAccess,
process_scope: ProcessFilesystemScope,
) -> Result<FilesystemProfile, VmError> {
let mut profile = FilesystemProfile {
rules: Vec::new(),
symlinks: std::collections::BTreeMap::new(),
handled_access_fs,
read_deny_roots: super::super::process_sandbox_read_deny_roots(policy),
unheld_read_denials: Vec::new(),
};
for (path, access) in standard_device_rules() {
push_rule(&mut profile, path, access, true)?;
}
for path in system_read_roots() {
push_rule(&mut profile, path, read_only_access(), true)?;
}
for path in network_name_service_read_roots(policy) {
push_rule(&mut profile, path, AccessFs::ReadFile.into(), true)?;
}
if policy.process_sandbox.allow_process_self_introspection {
if matches!(process_scope, ProcessFilesystemScope::Host)
&& !proc_runtime_reads_are_contained()
{
return Err(sandbox_rejection(
"process self-introspection needs a kernel that keeps a sandboxed task from inspecting its neighbours; this host permits it, so the grant would widen past the process it names"
.to_string(),
));
}
push_rule(
&mut profile,
PathBuf::from("/proc"),
AccessFs::ReadFile | AccessFs::ReadDir,
true,
)?;
} else if proc_runtime_reads_are_contained()
|| matches!(process_scope, ProcessFilesystemScope::PrivatePidNamespace)
{
push_rule(
&mut profile,
PathBuf::from("/proc"),
AccessFs::ReadFile.into(),
true,
)?;
}
let program_path = std::path::Path::new(program);
if program_path.is_absolute() {
push_rule(
&mut profile,
program_path.to_path_buf(),
AccessFs::ReadFile | AccessFs::Execute,
true,
)?;
}
for root in process_sandbox_developer_toolchain_read_roots(policy) {
push_rule(&mut profile, root, read_only_access(), true)?;
}
for root in developer_toolchain_system_read_roots(policy) {
push_rule(&mut profile, root, read_only_access(), true)?;
}
for grant in super::super::read_roots::path_grants::process_sandbox_path_entry_grants(policy) {
push_rule(&mut profile, grant.root, read_only_access(), true)?;
}
let workspace_access = workspace_access(policy);
for root in process_sandbox_roots(policy) {
push_rule(&mut profile, root, workspace_access, false)?;
}
for root in process_sandbox_readonly_roots(policy) {
push_rule(&mut profile, root, read_only_access(), false)?;
}
for root in process_sandbox_policy_read_roots(policy) {
push_rule(&mut profile, root, read_only_access(), false)?;
}
for root in process_sandbox_package_manager_config_read_roots(policy) {
push_rule(&mut profile, root, read_only_access(), true)?;
}
let toolchain_cache_roots =
super::super::process_sandbox_developer_toolchain_cache_roots(policy);
let toolchain_cache_access = if policy_allows_child_writes(policy) {
workspace_access
} else {
read_only_access()
};
for root in toolchain_cache_roots {
push_rule(&mut profile, root, toolchain_cache_access, true)?;
}
if policy_allows_child_writes(policy) {
for root in process_sandbox_policy_write_roots(policy) {
push_rule(&mut profile, root, workspace_access, false)?;
}
}
if !policy.process_sandbox.unix_socket_roots.is_empty() {
for root in super::super::process_sandbox_unix_socket_roots(policy) {
push_rule(
&mut profile,
root,
AccessFs::MakeSock
| AccessFs::ReadFile
| AccessFs::ReadDir
| AccessFs::WriteFile
| AccessFs::MakeDir
| AccessFs::RemoveFile,
true,
)?;
}
}
report_unheld_read_denials(policy, &profile.unheld_read_denials)?;
Ok(profile)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) struct UnheldReadDenial {
pub(super) denied: PathBuf,
pub(super) writable_grant: PathBuf,
}
pub(super) fn keep_writable_grant(profile: &mut FilesystemProfile, grant: &Path) -> bool {
if profile
.read_deny_roots
.iter()
.any(|denied| grant.starts_with(denied))
{
return false;
}
for denied in &profile.read_deny_roots {
let unheld = UnheldReadDenial {
denied: denied.clone(),
writable_grant: grant.to_path_buf(),
};
if denied.starts_with(grant) && !profile.unheld_read_denials.contains(&unheld) {
profile.unheld_read_denials.push(unheld);
}
}
true
}
fn report_unheld_read_denials(
policy: &CapabilityPolicy,
unheld: &[UnheldReadDenial],
) -> Result<(), VmError> {
if unheld.is_empty() {
return Ok(());
}
let pairs = unheld
.iter()
.map(|entry| {
format!(
"'{}' inside writable root '{}'",
entry.denied.display(),
entry.writable_grant.display()
)
})
.collect::<Vec<_>>()
.join(", ");
let fact = format!(
"credential read denials are not enforced for {pairs}: the writable root is granted \
whole, so a child can read the denied path"
);
if policy.sandbox_profile == SandboxProfile::OsHardened {
return Err(sandbox_rejection(format!(
"{fact}; the requested sandbox profile requires every denial to hold. Move the \
denied path out of the writable root, or grant that root read-only."
)));
}
warn_once(
&format!("sandbox_unheld_read_denial:{pairs}"),
&format!("{fact}. The writable roots keep their full access."),
);
Ok(())
}
pub(crate) fn installed_write_grants(
policy: &CapabilityPolicy,
) -> Option<super::super::child_write::LandlockWriteGrants> {
let support = super::landlock_support::host_support()?;
let profile = filesystem_profile(
"/bin/sh",
policy,
support.handled_access(),
ProcessFilesystemScope::Host,
)
.ok()?;
let refused_outside_grants = support.handled_access() | AccessFs::Refer | AccessFs::Truncate;
Some(super::super::child_write::LandlockWriteGrants {
rules: profile
.filesystem_rules()
.map(|(path, access)| (path.to_path_buf(), access.bits()))
.collect(),
handled_access: refused_outside_grants.bits(),
write_access: LANDLOCK_WRITE_ACCESS.bits(),
})
}
impl FilesystemProfile {
fn filesystem_rules(&self) -> impl Iterator<Item = (&std::path::Path, FsAccess)> + '_ {
self.rules
.iter()
.map(|rule| (rule.path.as_path(), rule.allowed_access))
}
}
pub(super) const LANDLOCK_WRITE_ACCESS: FsAccess = super::landlock_support::make_bitflags!(AccessFs::{
WriteFile
| RemoveDir
| RemoveFile
| MakeChar
| MakeDir
| MakeReg
| MakeSock
| MakeFifo
| MakeBlock
| MakeSym
| Refer
| Truncate
});