Restore persistent system path composition

This commit is contained in:
ms2k 2026-07-22 03:07:42 +09:00
commit 7aff8e542e
3 changed files with 338 additions and 30 deletions

View file

@ -113,6 +113,7 @@ impl Ipv4Prefix {
pub struct SandboxLayoutV1 {
protected_destinations: BTreeSet<String>,
nested_transient_overrides: BTreeSet<String>,
}
impl fmt::Debug for SandboxLayoutV1 {
@ -120,6 +121,10 @@ impl fmt::Debug for SandboxLayoutV1 {
formatter
.debug_struct("SandboxLayoutV1")
.field("protected_destinations", &self.protected_destinations)
.field(
"nested_transient_overrides",
&self.nested_transient_overrides,
)
.finish()
}
}
@ -132,9 +137,21 @@ impl SandboxLayoutV1 {
/// include `/workspace`.
pub fn from_protected_destinations(
destinations: impl IntoIterator<Item = String>,
) -> Result<Self, ConfigurationError> {
Self::from_destination_policy(destinations, [])
}
/// Builds protected destination classes supplied by sandbox-layout construction.
///
/// # Errors
/// Destinations must be normalized, non-root, unique across classes, and include
/// `/workspace`; nested overrides must be exact product-owned transient mounts.
pub fn from_destination_policy(
exclusive: impl IntoIterator<Item = String>,
nested_transient_overrides: impl IntoIterator<Item = String>,
) -> Result<Self, ConfigurationError> {
let mut protected_destinations = BTreeSet::new();
for destination in destinations {
for destination in exclusive {
validate_sandbox_path(&destination)?;
if !protected_destinations.insert(destination) {
return Err(ConfigurationError::new(
@ -143,6 +160,16 @@ impl SandboxLayoutV1 {
));
}
}
let mut nested = BTreeSet::new();
for destination in nested_transient_overrides {
validate_sandbox_path(&destination)?;
if !nested.insert(destination.clone()) || !protected_destinations.insert(destination) {
return Err(ConfigurationError::new(
ConfigurationErrorKind::InvalidPath,
"duplicate protected sandbox destination",
));
}
}
if !protected_destinations.contains("/workspace") {
return Err(ConfigurationError::new(
ConfigurationErrorKind::InvalidPath,
@ -151,6 +178,7 @@ impl SandboxLayoutV1 {
}
Ok(Self {
protected_destinations,
nested_transient_overrides: nested,
})
}
@ -158,6 +186,11 @@ impl SandboxLayoutV1 {
pub fn protected_destinations(&self) -> &BTreeSet<String> {
&self.protected_destinations
}
#[must_use]
pub fn nested_transient_overrides(&self) -> &BTreeSet<String> {
&self.nested_transient_overrides
}
}
pub struct ConfinedPathPlan {

View file

@ -265,6 +265,7 @@ fn validate_owned_directory(descriptor: &OwnedFd, uid: u32) -> Result<(), Config
pub(super) fn validate_persistent_destinations(
destinations: &[String],
protected: &BTreeSet<String>,
nested_transient_overrides: &BTreeSet<String>,
) -> Result<Vec<String>, ConfigurationError> {
require_pairwise_non_ancestral(
destinations.iter().map(String::as_str),
@ -272,11 +273,32 @@ pub(super) fn validate_persistent_destinations(
)?;
for destination in destinations {
validate_sandbox_path(destination)?;
require_clear_of_protected(destination, protected)?;
require_persistent_clear_of_protected(destination, protected, nested_transient_overrides)?;
}
Ok(destinations.to_vec())
}
fn require_persistent_clear_of_protected(
destination: &str,
protected: &BTreeSet<String>,
nested_transient_overrides: &BTreeSet<String>,
) -> Result<(), ConfigurationError> {
let conflict = protected.iter().any(|protected| {
destination == protected
|| sandbox_is_strict_descendant(destination, protected)
|| (sandbox_is_strict_descendant(protected, destination)
&& !nested_transient_overrides.contains(protected))
});
if conflict {
Err(ConfigurationError::new(
ConfigurationErrorKind::PathConflict,
"configured sandbox destination intersects a protected destination",
))
} else {
Ok(())
}
}
pub(super) fn require_clear_of_protected(
destination: &str,
protected: &BTreeSet<String>,

View file

@ -150,6 +150,7 @@ pub fn load_trusted_configuration(
let persistent_dirs = validate_persistent_destinations(
&selected.persistent_dirs,
request.sandbox_layout.protected_destinations(),
request.sandbox_layout.nested_transient_overrides(),
)?;
let (effective_allow_prefixes, effective_deny_prefixes) =
resolve_network(&configuration, selected)?;