Compose selected State Anchor paths over prepared roots
This commit is contained in:
parent
71b0266cf2
commit
4dd2b150d2
3 changed files with 1575 additions and 163 deletions
|
|
@ -276,6 +276,8 @@ impl<'configuration> StateAnchorLayout<'configuration> {
|
|||
work_path: Some(host_path(&self.work_label)),
|
||||
merged_path: Some(host_path(&self.merged_label)),
|
||||
daemon: PersistedHostProcessOwnership::unassigned(),
|
||||
user_namespace: None,
|
||||
mount_namespace: None,
|
||||
mount: None,
|
||||
}
|
||||
}
|
||||
|
|
@ -390,9 +392,15 @@ pub struct StateAnchorAcquisition<'path> {
|
|||
|
||||
pub struct StateAnchorLaunchAuthority {
|
||||
lower: OwnedFd,
|
||||
lower_label: PathBuf,
|
||||
persistent_root: OwnedFd,
|
||||
persistent_anchor: OwnedFd,
|
||||
persistent_anchor_label: PathBuf,
|
||||
runtime_root: OwnedFd,
|
||||
runtime_anchor: OwnedFd,
|
||||
runtime_anchor_label: PathBuf,
|
||||
expected_lower: (u64, u64),
|
||||
persistent_destinations: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl StateAnchorLaunchAuthority {
|
||||
|
|
@ -407,11 +415,59 @@ impl StateAnchorLaunchAuthority {
|
|||
layout.revalidate()?;
|
||||
let expected_lower = (artifact.device.get(), artifact.inode.get());
|
||||
let lower = open_exact_directory_path(&path_from_host(&artifact.path), expected_lower)?;
|
||||
Self::capture_open(
|
||||
layout,
|
||||
expected_lower,
|
||||
path_from_host(&artifact.path),
|
||||
lower,
|
||||
)
|
||||
}
|
||||
|
||||
/// Captures an already-confined prepared-lower authority for inheritance.
|
||||
///
|
||||
/// # Errors
|
||||
/// Refuses a changed layout or prepared-lower descriptor.
|
||||
pub fn capture_retained(
|
||||
layout: &StateAnchorLayout<'_>,
|
||||
artifact: &PreparedArtifact,
|
||||
lower_label: &Path,
|
||||
lower: impl AsFd,
|
||||
) -> Result<Self, StateAnchorError> {
|
||||
layout.revalidate()?;
|
||||
let expected_lower = (artifact.device.get(), artifact.inode.get());
|
||||
require_directory_descriptor_identity(&lower, expected_lower)?;
|
||||
Self::capture_open(layout, expected_lower, lower_label.to_owned(), lower)
|
||||
}
|
||||
|
||||
fn capture_open(
|
||||
layout: &StateAnchorLayout<'_>,
|
||||
expected_lower: (u64, u64),
|
||||
lower_label: PathBuf,
|
||||
lower: impl AsFd,
|
||||
) -> Result<Self, StateAnchorError> {
|
||||
Ok(Self {
|
||||
lower: inherit_for_exec(lower)?,
|
||||
lower_label,
|
||||
persistent_root: inherit_for_exec(&layout.persistent_root)?,
|
||||
persistent_anchor: inherit_for_exec(&layout.persistent_anchor)?,
|
||||
persistent_anchor_label: layout
|
||||
.upper_label
|
||||
.parent()
|
||||
.ok_or(StateAnchorError::InvalidLayout(
|
||||
"upper label has no State Anchor parent",
|
||||
))?
|
||||
.to_owned(),
|
||||
runtime_root: inherit_for_exec(&layout.runtime_root)?,
|
||||
runtime_anchor: inherit_for_exec(&layout.runtime_anchor)?,
|
||||
runtime_anchor_label: layout
|
||||
.merged_label
|
||||
.parent()
|
||||
.ok_or(StateAnchorError::InvalidLayout(
|
||||
"merged label has no State Anchor parent",
|
||||
))?
|
||||
.to_owned(),
|
||||
expected_lower,
|
||||
persistent_destinations: layout.persistent_destinations.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -425,21 +481,52 @@ impl StateAnchorLaunchAuthority {
|
|||
self.persistent_anchor.as_raw_fd()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn persistent_root_fd(&self) -> i32 {
|
||||
self.persistent_root.as_raw_fd()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn runtime_anchor_fd(&self) -> i32 {
|
||||
self.runtime_anchor.as_raw_fd()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn runtime_root_fd(&self) -> i32 {
|
||||
self.runtime_root.as_raw_fd()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn runtime_anchor_label(&self) -> &Path {
|
||||
&self.runtime_anchor_label
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn lower_label(&self) -> &Path {
|
||||
&self.lower_label
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn persistent_anchor_label(&self) -> &Path {
|
||||
&self.persistent_anchor_label
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn expected_lower(&self) -> (u64, u64) {
|
||||
self.expected_lower
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn persistent_destinations(&self) -> &[PathBuf] {
|
||||
&self.persistent_destinations
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RuntimeStateAnchorAuthority {
|
||||
lower: OwnedFd,
|
||||
persistent_anchor: OwnedFd,
|
||||
runtime_anchor: OwnedFd,
|
||||
merged_label: PathBuf,
|
||||
merged_descriptor: OwnedFd,
|
||||
}
|
||||
|
||||
|
|
@ -450,22 +537,67 @@ impl RuntimeStateAnchorAuthority {
|
|||
/// Refuses missing, replaced, or incorrectly typed descriptors and fixed children.
|
||||
pub fn from_inherited(
|
||||
lower_fd: i32,
|
||||
lower_label: &Path,
|
||||
persistent_root_fd: i32,
|
||||
persistent_anchor_fd: i32,
|
||||
persistent_anchor_label: &Path,
|
||||
runtime_root_fd: i32,
|
||||
runtime_anchor_fd: i32,
|
||||
runtime_anchor_label: &Path,
|
||||
expected_lower: (u64, u64),
|
||||
) -> Result<Self, StateAnchorError> {
|
||||
let lower = reopen_inherited_directory(lower_fd)?;
|
||||
require_directory_descriptor_identity(&lower, expected_lower)?;
|
||||
let persistent_anchor = reopen_inherited_directory(persistent_anchor_fd)?;
|
||||
let runtime_anchor = reopen_inherited_directory(runtime_anchor_fd)?;
|
||||
let retained_lower = reopen_inherited_directory(lower_fd)?;
|
||||
require_directory_descriptor_identity(&retained_lower, expected_lower)?;
|
||||
if !lower_label.is_absolute() {
|
||||
return Err(StateAnchorError::InvalidLayout(
|
||||
"prepared lower label is not absolute",
|
||||
));
|
||||
}
|
||||
let lower = open_exact_directory_path(lower_label, expected_lower)?;
|
||||
let persistent_root = reopen_inherited_directory(persistent_root_fd)?;
|
||||
let retained_persistent_anchor = reopen_inherited_directory(persistent_anchor_fd)?;
|
||||
if !persistent_anchor_label.is_absolute() {
|
||||
return Err(StateAnchorError::InvalidLayout(
|
||||
"persistent State Anchor label is not absolute",
|
||||
));
|
||||
}
|
||||
let retained_persistent =
|
||||
rustix::fs::fstat(&retained_persistent_anchor).map_err(io::Error::from)?;
|
||||
let confined_persistent_anchor =
|
||||
open_directory(&persistent_root, Path::new("state-anchor"))?;
|
||||
require_directory_descriptor_identity(
|
||||
&confined_persistent_anchor,
|
||||
(retained_persistent.st_dev, retained_persistent.st_ino),
|
||||
)?;
|
||||
let persistent_anchor = open_exact_directory_path(
|
||||
persistent_anchor_label,
|
||||
(retained_persistent.st_dev, retained_persistent.st_ino),
|
||||
)?;
|
||||
let runtime_root = reopen_inherited_directory(runtime_root_fd)?;
|
||||
let retained_runtime_anchor = reopen_inherited_directory(runtime_anchor_fd)?;
|
||||
if !runtime_anchor_label.is_absolute() {
|
||||
return Err(StateAnchorError::InvalidLayout(
|
||||
"runtime State Anchor label is not absolute",
|
||||
));
|
||||
}
|
||||
let confined_runtime_anchor = open_directory(&runtime_root, Path::new("state-anchor"))?;
|
||||
let retained = rustix::fs::fstat(&retained_runtime_anchor).map_err(io::Error::from)?;
|
||||
require_directory_descriptor_identity(
|
||||
&confined_runtime_anchor,
|
||||
(retained.st_dev, retained.st_ino),
|
||||
)?;
|
||||
let runtime_anchor =
|
||||
open_exact_directory_path(runtime_anchor_label, (retained.st_dev, retained.st_ino))?;
|
||||
for name in ["upper", "work"] {
|
||||
drop(open_directory(&persistent_anchor, Path::new(name))?);
|
||||
}
|
||||
let merged_descriptor = open_directory(&runtime_anchor, Path::new("merged"))?;
|
||||
let merged_label = runtime_anchor_label.join("merged");
|
||||
Ok(Self {
|
||||
lower,
|
||||
persistent_anchor,
|
||||
runtime_anchor,
|
||||
merged_label,
|
||||
merged_descriptor,
|
||||
})
|
||||
}
|
||||
|
|
@ -495,7 +627,6 @@ impl RuntimeStateAnchorAuthority {
|
|||
let baseline = mount_identity(&self.merged_descriptor)?;
|
||||
let lower_launch = inherit_for_exec(&self.lower)?;
|
||||
let persistent_launch = inherit_for_exec(&self.persistent_anchor)?;
|
||||
let runtime_launch = inherit_for_exec(&self.runtime_anchor)?;
|
||||
let target = TargetCommand::new("fuse-overlayfs")
|
||||
.argument("-f")
|
||||
.argument("-o")
|
||||
|
|
@ -504,7 +635,7 @@ impl RuntimeStateAnchorAuthority {
|
|||
&descriptor_child_path(&persistent_launch, "upper"),
|
||||
&descriptor_child_path(&persistent_launch, "work"),
|
||||
))
|
||||
.argument(descriptor_child_path(&runtime_launch, "merged"));
|
||||
.argument(&self.merged_label);
|
||||
let process =
|
||||
child_broker().acquire(expected_parent, ownership, &target, stage, acknowledge)?;
|
||||
let staged = StagedProcessOwnership::v1(
|
||||
|
|
@ -520,10 +651,8 @@ impl RuntimeStateAnchorAuthority {
|
|||
child_broker().reap(process)?;
|
||||
return Err(StateAnchorError::DaemonExited);
|
||||
}
|
||||
let observed = mount_identity(&open_mounted_directory(
|
||||
&self.runtime_anchor,
|
||||
Path::new("merged"),
|
||||
)?)?;
|
||||
let mounted = open_mounted_directory(&self.runtime_anchor, Path::new("merged"))?;
|
||||
let observed = mount_identity(&mounted)?;
|
||||
if observed.mount_id != baseline.mount_id {
|
||||
let readiness = VerifiedStateAnchorReadiness {
|
||||
staged,
|
||||
|
|
@ -534,7 +663,7 @@ impl RuntimeStateAnchorAuthority {
|
|||
while !process.has_exited()? {
|
||||
thread::yield_now();
|
||||
}
|
||||
let _ = rootless_unmount_anchor(&self.runtime_anchor);
|
||||
let _ = rootless_unmount_path(&self.merged_label);
|
||||
child_broker().reap(process)?;
|
||||
return Err(error.into());
|
||||
}
|
||||
|
|
@ -542,6 +671,7 @@ impl RuntimeStateAnchorAuthority {
|
|||
authority: self,
|
||||
process,
|
||||
mount: observed,
|
||||
merged: mounted,
|
||||
});
|
||||
}
|
||||
if Instant::now() >= deadline {
|
||||
|
|
@ -552,7 +682,7 @@ impl RuntimeStateAnchorAuthority {
|
|||
let merged = open_mounted_directory(&self.runtime_anchor, Path::new("merged"))?;
|
||||
let observed = mount_identity(&merged)?;
|
||||
if observed.mount_id != baseline.mount_id {
|
||||
let _ = rootless_unmount_anchor(&self.runtime_anchor);
|
||||
let _ = rootless_unmount_path(&self.merged_label);
|
||||
}
|
||||
child_broker().reap(process)?;
|
||||
return Err(StateAnchorError::MountTimeout);
|
||||
|
|
@ -566,6 +696,7 @@ pub struct RuntimeStateAnchorMount {
|
|||
authority: RuntimeStateAnchorAuthority,
|
||||
process: RetainedProcess,
|
||||
mount: MountIdentity,
|
||||
merged: OwnedFd,
|
||||
}
|
||||
|
||||
impl RuntimeStateAnchorMount {
|
||||
|
|
@ -574,27 +705,78 @@ impl RuntimeStateAnchorMount {
|
|||
self.mount
|
||||
}
|
||||
|
||||
/// Validates configured persistent subpaths beneath the exact mounted view.
|
||||
///
|
||||
/// # Errors
|
||||
/// Refuses missing paths, symlinks, nested mounts, or mount replacement.
|
||||
pub fn require_persistent_destinations(
|
||||
&self,
|
||||
destinations: &[PathBuf],
|
||||
) -> Result<(), StateAnchorError> {
|
||||
if mount_identity(&self.merged)? != self.mount {
|
||||
return Err(StateAnchorError::OwnershipMismatch);
|
||||
}
|
||||
for destination in destinations {
|
||||
if destination.as_os_str().is_empty()
|
||||
|| destination
|
||||
.components()
|
||||
.any(|component| !matches!(component, std::path::Component::Normal(_)))
|
||||
{
|
||||
return Err(StateAnchorError::InvalidLayout(
|
||||
"persistent destination is not normalized and relative",
|
||||
));
|
||||
}
|
||||
let descriptor = openat2(
|
||||
&self.merged,
|
||||
destination,
|
||||
OFlags::PATH | OFlags::DIRECTORY | OFlags::CLOEXEC | OFlags::NOFOLLOW,
|
||||
Mode::empty(),
|
||||
ResolveFlags::BENEATH
|
||||
| ResolveFlags::NO_MAGICLINKS
|
||||
| ResolveFlags::NO_SYMLINKS
|
||||
| ResolveFlags::NO_XDEV,
|
||||
)
|
||||
.map_err(io::Error::from)?;
|
||||
if !FileType::from_raw_mode(
|
||||
rustix::fs::fstat(&descriptor)
|
||||
.map_err(io::Error::from)?
|
||||
.st_mode,
|
||||
)
|
||||
.is_dir()
|
||||
{
|
||||
return Err(StateAnchorError::InvalidLayout(
|
||||
"persistent destination is not a directory",
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Unmounts and reaps only the retained exact RS-owned State Anchor.
|
||||
///
|
||||
/// # Errors
|
||||
/// Refuses mount replacement, unmount failure, or exact child cleanup failure.
|
||||
pub fn unmount(self) -> Result<(), StateAnchorError> {
|
||||
let observed = mount_identity(&open_mounted_directory(
|
||||
&self.authority.runtime_anchor,
|
||||
Path::new("merged"),
|
||||
)?)?;
|
||||
if observed != self.mount {
|
||||
let Self {
|
||||
authority,
|
||||
process,
|
||||
mount,
|
||||
merged,
|
||||
} = self;
|
||||
let observed = mount_identity(&merged)?;
|
||||
if observed != mount {
|
||||
return Err(StateAnchorError::OwnershipMismatch);
|
||||
}
|
||||
rootless_unmount_anchor(&self.authority.runtime_anchor)?;
|
||||
drop(merged);
|
||||
rootless_unmount_path(&authority.merged_label)?;
|
||||
let after = mount_identity(&open_directory(
|
||||
&self.authority.runtime_anchor,
|
||||
&authority.runtime_anchor,
|
||||
Path::new("merged"),
|
||||
)?)?;
|
||||
if after == self.mount {
|
||||
if after == mount {
|
||||
return Err(StateAnchorError::OwnershipMismatch);
|
||||
}
|
||||
child_broker().reap(self.process)?;
|
||||
child_broker().reap(process)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1022,9 +1204,13 @@ fn rootless_unmount(layout: &StateAnchorLayout<'_>) -> Result<(), StateAnchorErr
|
|||
fn rootless_unmount_anchor(runtime_anchor: impl AsFd) -> Result<(), StateAnchorError> {
|
||||
let inherited_anchor = inherit_for_exec(runtime_anchor)?;
|
||||
let path = descriptor_child_path(&inherited_anchor, "merged");
|
||||
rootless_unmount_path(&path)
|
||||
}
|
||||
|
||||
fn rootless_unmount_path(path: &Path) -> Result<(), StateAnchorError> {
|
||||
let status = std::process::Command::new("fusermount3")
|
||||
.arg("-u")
|
||||
.arg(&path)
|
||||
.arg(path)
|
||||
.status()?;
|
||||
if status.success() {
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::fs::{self, File};
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
use lazaret_core::configuration::{
|
||||
|
|
@ -80,22 +80,193 @@ fn inherited_runtime_authority_accepts_only_the_exact_prepared_lower() {
|
|||
let expected = launch.expected_lower();
|
||||
RuntimeStateAnchorAuthority::from_inherited(
|
||||
launch.lower_fd(),
|
||||
launch.lower_label(),
|
||||
launch.persistent_root_fd(),
|
||||
launch.persistent_anchor_fd(),
|
||||
launch.persistent_anchor_label(),
|
||||
launch.runtime_root_fd(),
|
||||
launch.runtime_anchor_fd(),
|
||||
launch.runtime_anchor_label(),
|
||||
expected,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
RuntimeStateAnchorAuthority::from_inherited(
|
||||
launch.lower_fd(),
|
||||
launch.lower_label(),
|
||||
launch.persistent_root_fd(),
|
||||
launch.persistent_anchor_fd(),
|
||||
launch.persistent_anchor_label(),
|
||||
launch.runtime_root_fd(),
|
||||
launch.runtime_anchor_fd(),
|
||||
launch.runtime_anchor_label(),
|
||||
(expected.0, expected.1 + 1),
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retained_prepared_lower_authority_refuses_a_replacement_descriptor() {
|
||||
let tree = TestTree::new();
|
||||
let configuration = configuration(&tree);
|
||||
let layout = StateAnchorLayout::create(&configuration).unwrap();
|
||||
let lower = tree.path.join("prepared-lower");
|
||||
let replacement = tree.path.join("replacement-lower");
|
||||
fs::create_dir(&lower).unwrap();
|
||||
fs::create_dir(&replacement).unwrap();
|
||||
let metadata = lower.metadata().unwrap();
|
||||
let artifact = PreparedArtifact {
|
||||
prepared_artifact_id: PreparedArtifactId::new([8; 32]),
|
||||
source_identity: SourceIdentity::new(b"fixture".to_vec()),
|
||||
selected_manifest_digest: SelectedManifestDigest::new([9; 32]),
|
||||
policy_id: PolicyId::new([10; 32]),
|
||||
id_map_digest: IdMapDigest::new([11; 32]),
|
||||
path: HostPath::new(b"artifacts/fixture".to_vec()),
|
||||
device: DeviceId::new(metadata.dev()),
|
||||
inode: InodeId::new(metadata.ino()),
|
||||
state: ArtifactState::Available,
|
||||
};
|
||||
let exact = File::open(&lower).unwrap();
|
||||
StateAnchorLaunchAuthority::capture_retained(&layout, &artifact, &lower, &exact).unwrap();
|
||||
let replacement = File::open(&replacement).unwrap();
|
||||
assert!(
|
||||
StateAnchorLaunchAuthority::capture_retained(&layout, &artifact, &lower, &replacement)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn namespace_local_rebound_refuses_a_replaced_state_anchor() {
|
||||
let tree = TestTree::new();
|
||||
let configuration = configuration(&tree);
|
||||
let layout = StateAnchorLayout::create(&configuration).unwrap();
|
||||
let lower = tree.path.join("prepared-lower");
|
||||
fs::create_dir(&lower).unwrap();
|
||||
let artifact = prepared_artifact(&lower);
|
||||
let launch = StateAnchorLaunchAuthority::capture(&layout, &artifact).unwrap();
|
||||
let anchor = launch.runtime_anchor_label().to_owned();
|
||||
fs::rename(&anchor, anchor.with_extension("retained")).unwrap();
|
||||
fs::create_dir(&anchor).unwrap();
|
||||
|
||||
assert!(
|
||||
RuntimeStateAnchorAuthority::from_inherited(
|
||||
launch.lower_fd(),
|
||||
launch.lower_label(),
|
||||
launch.persistent_root_fd(),
|
||||
launch.persistent_anchor_fd(),
|
||||
launch.persistent_anchor_label(),
|
||||
launch.runtime_root_fd(),
|
||||
launch.runtime_anchor_fd(),
|
||||
launch.runtime_anchor_label(),
|
||||
launch.expected_lower(),
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn namespace_local_rebound_refuses_a_replaced_prepared_lower() {
|
||||
let tree = TestTree::new();
|
||||
let configuration = configuration(&tree);
|
||||
let layout = StateAnchorLayout::create(&configuration).unwrap();
|
||||
let lower = tree.path.join("prepared-lower");
|
||||
fs::create_dir(&lower).unwrap();
|
||||
let artifact = prepared_artifact(&lower);
|
||||
let launch = StateAnchorLaunchAuthority::capture(&layout, &artifact).unwrap();
|
||||
fs::rename(&lower, lower.with_extension("retained")).unwrap();
|
||||
fs::create_dir(&lower).unwrap();
|
||||
|
||||
assert!(
|
||||
RuntimeStateAnchorAuthority::from_inherited(
|
||||
launch.lower_fd(),
|
||||
launch.lower_label(),
|
||||
launch.persistent_root_fd(),
|
||||
launch.persistent_anchor_fd(),
|
||||
launch.persistent_anchor_label(),
|
||||
launch.runtime_root_fd(),
|
||||
launch.runtime_anchor_fd(),
|
||||
launch.runtime_anchor_label(),
|
||||
launch.expected_lower(),
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn namespace_local_rebound_refuses_a_replaced_persistent_anchor() {
|
||||
let tree = TestTree::new();
|
||||
let configuration = configuration(&tree);
|
||||
let layout = StateAnchorLayout::create(&configuration).unwrap();
|
||||
let lower = tree.path.join("prepared-lower");
|
||||
fs::create_dir(&lower).unwrap();
|
||||
let artifact = prepared_artifact(&lower);
|
||||
let launch = StateAnchorLaunchAuthority::capture(&layout, &artifact).unwrap();
|
||||
let anchor = launch.persistent_anchor_label().to_owned();
|
||||
fs::rename(&anchor, anchor.with_extension("retained")).unwrap();
|
||||
fs::create_dir(&anchor).unwrap();
|
||||
|
||||
assert!(
|
||||
RuntimeStateAnchorAuthority::from_inherited(
|
||||
launch.lower_fd(),
|
||||
launch.lower_label(),
|
||||
launch.persistent_root_fd(),
|
||||
launch.persistent_anchor_fd(),
|
||||
launch.persistent_anchor_label(),
|
||||
launch.runtime_root_fd(),
|
||||
launch.runtime_anchor_fd(),
|
||||
launch.runtime_anchor_label(),
|
||||
launch.expected_lower(),
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn namespace_local_rebound_refuses_a_state_anchor_symlink() {
|
||||
let tree = TestTree::new();
|
||||
let configuration = configuration(&tree);
|
||||
let layout = StateAnchorLayout::create(&configuration).unwrap();
|
||||
let lower = tree.path.join("prepared-lower");
|
||||
fs::create_dir(&lower).unwrap();
|
||||
let artifact = prepared_artifact(&lower);
|
||||
let launch = StateAnchorLaunchAuthority::capture(&layout, &artifact).unwrap();
|
||||
let anchor = launch.runtime_anchor_label().to_owned();
|
||||
let retained = anchor.with_extension("retained");
|
||||
fs::rename(&anchor, &retained).unwrap();
|
||||
std::os::unix::fs::symlink(&retained, &anchor).unwrap();
|
||||
|
||||
assert!(
|
||||
RuntimeStateAnchorAuthority::from_inherited(
|
||||
launch.lower_fd(),
|
||||
launch.lower_label(),
|
||||
launch.persistent_root_fd(),
|
||||
launch.persistent_anchor_fd(),
|
||||
launch.persistent_anchor_label(),
|
||||
launch.runtime_root_fd(),
|
||||
launch.runtime_anchor_fd(),
|
||||
launch.runtime_anchor_label(),
|
||||
launch.expected_lower(),
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
fn prepared_artifact(lower: &Path) -> PreparedArtifact {
|
||||
let metadata = lower.metadata().unwrap();
|
||||
PreparedArtifact {
|
||||
prepared_artifact_id: PreparedArtifactId::new([8; 32]),
|
||||
source_identity: SourceIdentity::new(b"fixture".to_vec()),
|
||||
selected_manifest_digest: SelectedManifestDigest::new([9; 32]),
|
||||
policy_id: PolicyId::new([10; 32]),
|
||||
id_map_digest: IdMapDigest::new([11; 32]),
|
||||
path: HostPath::new(lower.as_os_str().as_encoded_bytes().to_vec()),
|
||||
device: DeviceId::new(metadata.dev()),
|
||||
inode: InodeId::new(metadata.ino()),
|
||||
state: ArtifactState::Available,
|
||||
}
|
||||
}
|
||||
|
||||
struct TestTree {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,8 +82,13 @@ fn run() -> io::Result<()> {
|
|||
StateAnchorLaunchAuthority::capture(&layout, &artifact).map_err(io::Error::other)?;
|
||||
let runtime_authority = RuntimeStateAnchorAuthority::from_inherited(
|
||||
launch.lower_fd(),
|
||||
launch.lower_label(),
|
||||
launch.persistent_root_fd(),
|
||||
launch.persistent_anchor_fd(),
|
||||
launch.persistent_anchor_label(),
|
||||
launch.runtime_root_fd(),
|
||||
launch.runtime_anchor_fd(),
|
||||
launch.runtime_anchor_label(),
|
||||
launch.expected_lower(),
|
||||
)
|
||||
.map_err(io::Error::other)?;
|
||||
|
|
|
|||
Loading…
Reference in a new issue