use crate::backend::linux::cgroup_run::{cgroup_for_run, finish};
use crate::backend::linux::launch;
use crate::backend::linux::{cgroup, plan_build, sys};
use crate::contract::backend::Backend;
use crate::contract::capability::{Enforcement, EvidenceClaim, FsAccess, PathSet, SupportVerdict};
use crate::contract::ids::BackendId;
use crate::contract::plan::{BoundaryPlan, BoundaryRequirement, Workload};
use crate::contract::report::{BoundaryReportBody, ObservedFact, Outcome};
use crate::contract::secret::{MapSecretResolver, SecretResolver};
use crate::contract::support::{
BackendProfile, BackendProfileSnapshot, RequirementKind, SupportMatrix,
};
use std::collections::BTreeMap;
const LANDLOCK_ABI_FLOOR: i64 = 1;
pub struct LinuxBackend {
id: BackendId,
support: SupportMatrix,
landlock_abi: i64,
launcher_path: Option<std::path::PathBuf>,
pub(super) cgroup_base: Option<std::path::PathBuf>,
cgroup_pids_peak: bool,
netns_available: bool,
seccomp_available: bool,
pub(super) secret_resolver: std::sync::Arc<dyn SecretResolver + Send + Sync>,
}
pub(super) fn default_secret_resolver() -> std::sync::Arc<dyn SecretResolver + Send + Sync> {
std::sync::Arc::new(MapSecretResolver::new())
}
fn probe_cgroup() -> (Option<std::path::PathBuf>, bool) {
let Some(base) = cgroup::probe_controller_base(&["pids"]) else {
return (None, false);
};
let caps = cgroup::probe_leaf_caps(&base);
if caps.atomic_kill {
(Some(base), caps.pids_peak)
} else {
(None, false)
}
}
impl LinuxBackend {
pub const ID: &'static str = "linux";
#[must_use]
pub fn new() -> Self {
let (cgroup_base, cgroup_pids_peak) = probe_cgroup();
Self {
id: BackendId::new(Self::ID),
support: super::support_matrix(),
landlock_abi: sys::probe_landlock_abi(),
launcher_path: None,
cgroup_base,
cgroup_pids_peak,
netns_available: launch::unprivileged_userns_available(),
seccomp_available: super::seccomp::seccomp_filter_available(),
secret_resolver: default_secret_resolver(),
}
}
#[must_use]
pub fn with_secret_resolver(
mut self,
resolver: std::sync::Arc<dyn SecretResolver + Send + Sync>,
) -> Self {
self.secret_resolver = resolver;
self
}
#[must_use]
pub fn with_launcher_path(launcher_path: std::path::PathBuf) -> Self {
let (cgroup_base, cgroup_pids_peak) = probe_cgroup();
Self {
id: BackendId::new(Self::ID),
support: super::support_matrix(),
landlock_abi: sys::probe_landlock_abi(),
launcher_path: Some(launcher_path),
cgroup_base,
cgroup_pids_peak,
netns_available: launch::unprivileged_userns_available(),
seccomp_available: super::seccomp::seccomp_filter_available(),
secret_resolver: default_secret_resolver(),
}
}
#[cfg(test)]
fn with_abi_for_test(landlock_abi: i64) -> Self {
Self {
id: BackendId::new(Self::ID),
support: super::support_matrix(),
landlock_abi,
launcher_path: None,
cgroup_base: None,
cgroup_pids_peak: false,
netns_available: false,
seccomp_available: false,
secret_resolver: default_secret_resolver(),
}
}
#[cfg(test)]
fn with_cgroup_for_test(pids_peak: bool) -> Self {
Self {
id: BackendId::new(Self::ID),
support: super::support_matrix(),
landlock_abi: LANDLOCK_ABI_FLOOR,
launcher_path: None,
cgroup_base: Some(std::path::PathBuf::from("/sys/fs/cgroup/test-placeholder")),
cgroup_pids_peak: pids_peak,
netns_available: false,
seccomp_available: false,
secret_resolver: default_secret_resolver(),
}
}
fn filesystem_enforced(&self) -> bool {
self.landlock_abi >= LANDLOCK_ABI_FLOOR
}
fn network_deny_all_enforced(&self) -> bool {
self.netns_available
}
fn child_spawn_deny_enforced(&self) -> bool {
self.seccomp_available
}
fn child_spawn_descendants_enforced(&self) -> bool {
self.cgroup_base.is_some()
}
fn ceiling(&self) -> BackendProfile {
let mut ceiling = BTreeMap::new();
if self.filesystem_enforced() {
ceiling.insert(
RequirementKind::Filesystem,
SupportVerdict::new(
Enforcement::Enforced,
[
EvidenceClaim::AllowedActions,
EvidenceClaim::DeniedAttempts,
EvidenceClaim::FilesystemDelta,
EvidenceClaim::MechanismAttestation,
]
.into_iter()
.collect(),
),
);
}
ceiling.insert(
RequirementKind::LaunchWorkload,
SupportVerdict::new(
Enforcement::Enforced,
[EvidenceClaim::TerminalOutcome, EvidenceClaim::ProcessTree]
.into_iter()
.collect(),
),
);
ceiling.insert(
RequirementKind::CaptureStreams,
SupportVerdict::new(
Enforcement::Enforced,
[EvidenceClaim::CapturedStreams].into_iter().collect(),
),
);
ceiling.insert(
RequirementKind::Environment,
SupportVerdict::new(
Enforcement::Enforced,
[EvidenceClaim::MechanismAttestation].into_iter().collect(),
),
);
ceiling.insert(
RequirementKind::InheritedFdsNone,
SupportVerdict::new(
Enforcement::Enforced,
[EvidenceClaim::MechanismAttestation].into_iter().collect(),
),
);
if self.network_deny_all_enforced() {
ceiling.insert(
RequirementKind::NetworkDenyAll,
SupportVerdict::new(
Enforcement::Enforced,
[
EvidenceClaim::DeniedAttempts,
EvidenceClaim::MechanismAttestation,
]
.into_iter()
.collect(),
),
);
}
self.insert_child_spawn_ceiling(&mut ceiling);
if self.cgroup_base.is_some() {
ceiling.insert(
RequirementKind::Kill,
SupportVerdict::new(
Enforcement::Enforced,
[EvidenceClaim::MechanismAttestation].into_iter().collect(),
),
);
}
BackendProfile::from_ceiling(ceiling)
}
fn insert_child_spawn_ceiling(&self, ceiling: &mut BTreeMap<RequirementKind, SupportVerdict>) {
if self.child_spawn_deny_enforced() {
ceiling.insert(
RequirementKind::ChildSpawnDenyNewTasks,
SupportVerdict::new(
Enforcement::Enforced,
[
EvidenceClaim::DeniedAttempts,
EvidenceClaim::ProcessTree,
EvidenceClaim::MechanismAttestation,
]
.into_iter()
.collect(),
),
);
}
if self.child_spawn_descendants_enforced() {
ceiling.insert(
RequirementKind::ChildSpawnAllowDescendants,
SupportVerdict::new(
Enforcement::Enforced,
[
EvidenceClaim::ProcessTree,
EvidenceClaim::MechanismAttestation,
]
.into_iter()
.collect(),
),
);
}
}
}
#[cfg(feature = "dangerous-test-hooks")]
#[path = "backend_impl_proof.rs"]
mod proof_hooks;
#[path = "backend_impl_env.rs"]
mod env_lowering;
use env_lowering::lower_environment;
#[path = "backend_impl_fds.rs"]
mod fds_lowering;
use fds_lowering::lower_inherited_fds;
#[path = "backend_impl_net.rs"]
mod net_lowering;
use net_lowering::{lower_network, NetLowering};
#[path = "backend_impl_childspawn.rs"]
mod child_spawn_lowering;
use child_spawn_lowering::{lower_child_spawn, ChildTaskLowering};
#[path = "backend_impl_budget.rs"]
mod budget_profile;
use budget_profile::observed_budget_profile;
impl Default for LinuxBackend {
fn default() -> Self {
Self::new()
}
}
impl Backend for LinuxBackend {
fn id(&self) -> BackendId {
self.id.clone()
}
fn support(&self) -> &SupportMatrix {
&self.support
}
fn probe(&self) -> BackendProfileSnapshot {
let mut probed = BTreeMap::new();
probed.insert("landlock_abi".to_string(), self.landlock_abi.to_string());
probed.insert(
"filesystem_confinement".to_string(),
if self.filesystem_enforced() {
"landlock".to_string()
} else {
"unsupported-below-abi-floor".to_string()
},
);
BackendProfileSnapshot {
backend: self.id.clone(),
probed,
budget: observed_budget_profile(self.cgroup_base.is_some(), self.cgroup_pids_peak),
}
}
fn profile(&self, _snap: &BackendProfileSnapshot) -> BackendProfile {
self.ceiling()
}
fn classify(&self, req: &BoundaryRequirement, profile: &BackendProfile) -> SupportVerdict {
self.support.classify(req, profile)
}
fn mechanism(&self, requirement: &BoundaryRequirement, enforcement: Enforcement) -> String {
let primitive = match RequirementKind::of(requirement) {
RequirementKind::Filesystem => "landlock",
RequirementKind::LaunchWorkload => "process_spawn",
RequirementKind::CaptureStreams => "pipe_capture",
RequirementKind::Kill => "cgroup_kill",
RequirementKind::Environment => "explicit_env",
RequirementKind::InheritedFdsNone => "fd_scrub",
RequirementKind::NetworkDenyAll => "empty_netns",
RequirementKind::ChildSpawnDenyNewTasks => "seccomp_deny_tasks",
RequirementKind::ChildSpawnAllowDescendants => "cgroup_descendant_boundary",
RequirementKind::NetworkAllowList
| RequirementKind::ChildSpawnAllowThreads
| RequirementKind::InheritedFdsOnly
| RequirementKind::TempRoot
| RequirementKind::ExposePath
| RequirementKind::CommitArtifact
| RequirementKind::DiscardArtifact
| RequirementKind::ListOutputs => "none/unimplemented-this-chunk",
};
format!("{}:{primitive}:{enforcement:?}", self.id)
}
fn execute(&self, plan: &BoundaryPlan) -> BoundaryReportBody {
execute_confined(self, plan)
}
}
fn execute_confined(backend: &LinuxBackend, plan: &BoundaryPlan) -> BoundaryReportBody {
let mut observed = Vec::new();
let (exe, args) = match &plan.workload {
Workload::Process { exe, args } => (exe.clone(), args.clone()),
Workload::Wasm { module_ref } => {
observed.push(ObservedFact {
kind: "workload_unsupported".to_string(),
detail: format!("linux backend cannot run wasm module {module_ref}"),
});
return fail_closed(backend, plan, Outcome::Unsupported, observed);
}
};
let fs = filesystem_capability(plan);
if fs.is_some() && !backend.filesystem_enforced() {
observed.push(ObservedFact {
kind: "filesystem_confinement_unavailable".to_string(),
detail: format!(
"landlock abi {} below floor {LANDLOCK_ABI_FLOOR}; refusing to run unconfined",
backend.landlock_abi
),
});
return fail_closed(backend, plan, Outcome::Unsupported, observed);
}
let envp = match lower_environment(backend, plan, observed) {
Ok((envp, facts)) => {
observed = facts;
envp
}
Err(facts) => return fail_closed(backend, plan, Outcome::Unsupported, facts),
};
observed = match lower_inherited_fds(backend, plan, observed) {
Ok(facts) => facts,
Err(facts) => return fail_closed(backend, plan, Outcome::Unsupported, facts),
};
let deny_network = match lower_network(backend, plan, observed) {
Ok(NetLowering {
deny_network,
observed: facts,
}) => {
observed = facts;
deny_network
}
Err(facts) => return fail_closed(backend, plan, Outcome::Unsupported, facts),
};
let deny_new_tasks = match lower_child_spawn(backend, plan, observed) {
Ok(ChildTaskLowering {
deny_new_tasks,
observed: facts,
}) => {
observed = facts;
deny_new_tasks
}
Err(facts) => return fail_closed(backend, plan, Outcome::Unsupported, facts),
};
run_prepared(
backend,
plan,
&exe,
&args,
fs.as_ref(),
LoweredLaunch {
envp,
deny_network,
deny_new_tasks,
},
observed,
)
}
struct LoweredLaunch {
envp: Vec<(String, String)>,
deny_network: bool,
deny_new_tasks: bool,
}
fn run_prepared(
backend: &LinuxBackend,
plan: &BoundaryPlan,
exe: &str,
args: &[String],
fs: Option<&(FsAccess, PathSet)>,
lowered: LoweredLaunch,
observed: Vec<ObservedFact>,
) -> BoundaryReportBody {
let LoweredLaunch {
envp,
deny_network,
deny_new_tasks,
} = lowered;
let (cgroup_leaf, cgroup_dir_fd, mut observed) = match cgroup_for_run(backend, plan, observed) {
Ok(triple) => triple,
Err(observed) => return fail_closed(backend, plan, Outcome::SupervisorFault, observed),
};
let prepared = match plan_build::prepare_launch(plan_build::LaunchInputs {
exe,
args,
plan,
fs,
cgroup_dir_fd,
envp,
deny_network,
deny_new_tasks,
}) {
Ok(prepared) => prepared,
Err(detail) => {
observed.push(ObservedFact {
kind: "launch_plan_construction_failed".to_string(),
detail,
});
return finish(
cgroup_leaf,
fail_closed(backend, plan, Outcome::SupervisorFault, observed),
);
}
};
let plan_build::Prepared {
launch_plan,
authority,
read_roots,
write_roots,
confined,
} = prepared;
if confined {
observed.push(ObservedFact {
kind: "filesystem_confined".to_string(),
detail: format!(
"landlock abi {} (launcher restrict_self): read-roots {read_roots:?}, \
write-roots {write_roots:?}",
backend.landlock_abi
),
});
}
let launcher_path = match resolve_launcher(backend) {
Ok(path) => path,
Err(detail) => {
observed.push(ObservedFact {
kind: "launcher_unresolvable".to_string(),
detail,
});
return finish(
cgroup_leaf,
fail_closed(backend, plan, Outcome::Unsupported, observed),
);
}
};
attest_launcher(&launcher_path, &mut observed);
let report = match launch::run_launcher(&launcher_path, &launch_plan, authority) {
Ok(obs) => {
let process_peak = cgroup_leaf
.as_ref()
.and_then(|l| l.peak_pids().ok().flatten());
map_observation(backend, plan, exe, confined, &obs, observed, process_peak)
}
Err(error) => {
observed.push(ObservedFact {
kind: "launcher_harness_fault".to_string(),
detail: format!("linux launcher harness fault: {error}"),
});
fail_closed(backend, plan, Outcome::SupervisorFault, observed)
}
};
finish(cgroup_leaf, report)
}
#[path = "backend_impl_resolve.rs"]
mod launcher_resolve;
use launcher_resolve::{attest_launcher, resolve_launcher};
#[path = "backend_impl_report.rs"]
mod report_mapping;
use report_mapping::{fail_closed, filesystem_capability, map_observation};
#[cfg(test)]
#[path = "backend_impl_tests.rs"]
mod tests;