Replace lexical Rust fragments with real modules

This commit is contained in:
ms2k 2026-07-19 05:12:57 +09:00
commit 247819250b
5 changed files with 3302 additions and 2671 deletions

View file

@ -32,6 +32,13 @@ use rustix::fs::{
};
use rustix::io::dup;
use rustix::thread::{ThreadNameSpaceType, move_into_thread_name_spaces};
use storage::{
atomic_replace_at, create_directory, create_regular_file, descriptor_child_path,
descriptor_path, directory_has_entries, host_path, inspect_directory, move_all_entries,
move_selected_entries, open_directory, open_exact_directory_path, open_mounted_directory,
open_or_create_quarantine, open_regular_file, path_from_host, remove_directory_contents,
require_directory_descriptor_identity, require_same_descriptor, validate_relative_path,
};
const MOUNT_TIMEOUT: Duration = Duration::from_secs(5);
const MOUNT_POLL_INTERVAL: Duration = Duration::from_millis(2);
@ -565,7 +572,24 @@ impl<'layout, 'configuration> StateAnchorSandboxView<'layout, 'configuration> {
}
}
include!("lib/authority.rs");
include!("lib/mount.rs");
include!("lib/storage.rs");
include!("lib/tests.rs");
#[path = "lib/authority.rs"]
mod authority;
#[path = "lib/mount.rs"]
mod mount;
#[path = "lib/storage.rs"]
mod storage;
pub use authority::{
RuntimeStateAnchorAbsenceAuthority, RuntimeStateAnchorAuthority, RuntimeStateAnchorInherited,
RuntimeStateAnchorMount, StateAnchorAcquisition, StateAnchorLaunchAuthority,
VerifiedStateAnchorReadiness,
};
pub use mount::{
FuseOverlayfsMount, dispatch_private_state_anchor_recovery, recover_private_state_anchor_mount,
unmount_after_coordinator_loss,
};
pub use storage::{observe_mount_absence, require_mount_absent};
#[cfg(test)]
#[path = "lib/tests.rs"]
mod tests;

View file

@ -1,7 +1,20 @@
use super::storage::{
descriptor_child_path, descriptor_path, inherit_for_exec, open_directory,
open_exact_directory_path, open_mounted_directory, overlay_options, path_from_host,
reopen_inherited_directory, require_directory_descriptor_identity, rootless_unmount_path,
};
use super::{
AsFd, AsRawFd, FileType, HostProcessOwnershipSlot, Instant, MOUNT_POLL_INTERVAL, MOUNT_TIMEOUT,
Mode, MountIdentity, OFlags, OwnedFd, Path, PathBuf, PreparedArtifact,
ProcessOwnershipCommitAck, ProcessOwnershipContext, ResolveFlags, RetainedProcess,
StagedProcessOwnership, StateAnchorError, StateAnchorLayout, TargetCommand, child_broker, io,
mount_identity, openat2, thread,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct VerifiedStateAnchorReadiness {
staged: StagedProcessOwnership,
mount: MountIdentity,
pub(super) staged: StagedProcessOwnership,
pub(super) mount: MountIdentity,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]

View file

@ -1,3 +1,20 @@
use super::storage::{
descriptor_child_path, descriptor_path, has_mount_record, inherit_for_exec,
open_exact_directory_path, open_final_mount_directory, overlay_options, require_mount_record,
rootless_unmount,
};
use super::{
AsFd, DeviceId, HostProcessOwnershipSlot, InodeId, Instant, MOUNT_POLL_INTERVAL, MOUNT_TIMEOUT,
Mode, MountIdentity, NamespaceIdentity, OFlags, OsString, OwnedFd,
PRIVATE_STATE_ANCHOR_RECOVERY_ROLE, Path, PathBuf, PersistedHostProcessOwnership,
ProcessOwnershipCommitAck, ResolveFlags, RetainedProcess, StagedProcessOwnership,
StateAnchorAcquisition, StateAnchorError, StateAnchorLayout, TargetCommand,
ThreadNameSpaceType, VerifiedStateAnchorReadiness, child_broker, io, mount_identity,
move_into_thread_name_spaces, namespace_identity, openat2, private_seqpacket_pair,
receive_descriptors, receive_packet, require_pidfs_magic, send_descriptors, send_packet,
thread,
};
pub struct FuseOverlayfsMount<'layout, 'configuration> {
process: RetainedProcess,
mount: MountIdentity,
@ -276,7 +293,7 @@ fn run_private_state_anchor_recovery(arguments: &[OsString]) -> io::Result<()> {
send_packet(stdout, &observed)
}
trait PrivateRecoveryOperations {
pub(super) trait PrivateRecoveryOperations {
fn validate_pidfs(&mut self) -> io::Result<()>;
fn enter_user_namespace(&mut self) -> io::Result<()>;
fn observe_user_namespace(&mut self) -> io::Result<NamespaceIdentity>;
@ -287,7 +304,7 @@ trait PrivateRecoveryOperations {
fn observe_after_unmount(&mut self) -> io::Result<MountIdentity>;
}
fn execute_private_recovery(
pub(super) fn execute_private_recovery(
operations: &mut impl PrivateRecoveryOperations,
expected_user: NamespaceIdentity,
expected_mount_namespace: NamespaceIdentity,

View file

@ -1,3 +1,11 @@
use super::{
AsFd, AsRawFd, AtFlags, Component, File, FileType, HostPath, Mode, MountIdentity, OFlags,
Ordering, OsStrExt, OsString, OsStringExt, OwnedFd, Path, PathBuf, RESET_QUARANTINE, RawDir,
RenameFlags, ResolveFlags, StateAnchorEntry, StateAnchorEntryKind, StateAnchorError,
StateAnchorLayout, TEMPORARY_SEQUENCE, TargetCommand, Write, child_broker, dup, io, mkdirat,
mount_identity, openat2, renameat, renameat_with, statat, unlinkat,
};
/// Independently verifies that no mount is attached at the exact merged path.
///
/// # Errors
@ -22,7 +30,7 @@ pub fn observe_mount_absence(
}
}
fn inspect_directory(
pub(super) fn inspect_directory(
directory: impl AsFd,
prefix: &Path,
entries: &mut Vec<StateAnchorEntry>,
@ -73,7 +81,7 @@ fn inspect_directory(
Ok(())
}
fn open_or_create_quarantine(parent: impl AsFd) -> Result<OwnedFd, StateAnchorError> {
pub(super) fn open_or_create_quarantine(parent: impl AsFd) -> Result<OwnedFd, StateAnchorError> {
match mkdirat(parent.as_fd(), RESET_QUARANTINE, Mode::from_raw_mode(0o700)) {
Ok(()) => rustix::fs::fsync(parent.as_fd()).map_err(io::Error::from)?,
Err(rustix::io::Errno::EXIST) => {}
@ -82,14 +90,17 @@ fn open_or_create_quarantine(parent: impl AsFd) -> Result<OwnedFd, StateAnchorEr
open_directory(parent, Path::new(RESET_QUARANTINE))
}
fn move_all_entries(upper: impl AsFd, quarantine: impl AsFd) -> Result<(), StateAnchorError> {
pub(super) fn move_all_entries(
upper: impl AsFd,
quarantine: impl AsFd,
) -> Result<(), StateAnchorError> {
for name in directory_names(&upper)? {
move_if_present(&upper, Path::new(&name), &quarantine, Path::new(&name))?;
}
Ok(())
}
fn move_selected_entries(
pub(super) fn move_selected_entries(
upper: impl AsFd,
quarantine: impl AsFd,
relative: &Path,
@ -170,7 +181,7 @@ fn entry_exists(parent: impl AsFd, path: &Path) -> Result<bool, StateAnchorError
}
}
fn remove_directory_contents(directory: impl AsFd) -> Result<(), StateAnchorError> {
pub(super) fn remove_directory_contents(directory: impl AsFd) -> Result<(), StateAnchorError> {
for name in directory_names(&directory)? {
let path = Path::new(&name);
let descriptor = openat2(
@ -213,11 +224,11 @@ fn directory_names(directory: impl AsFd) -> Result<Vec<OsString>, StateAnchorErr
Ok(names)
}
fn directory_has_entries(directory: impl AsFd) -> Result<bool, StateAnchorError> {
pub(super) fn directory_has_entries(directory: impl AsFd) -> Result<bool, StateAnchorError> {
Ok(!directory_names(directory)?.is_empty())
}
fn validate_relative_path(path: &Path) -> Result<(), StateAnchorError> {
pub(super) fn validate_relative_path(path: &Path) -> Result<(), StateAnchorError> {
if path.as_os_str().is_empty()
|| path
.components()
@ -231,7 +242,7 @@ fn validate_relative_path(path: &Path) -> Result<(), StateAnchorError> {
}
}
fn create_directory(parent: impl AsFd, name: &str) -> Result<(), StateAnchorError> {
pub(super) fn create_directory(parent: impl AsFd, name: &str) -> Result<(), StateAnchorError> {
match mkdirat(parent.as_fd(), name, Mode::from_raw_mode(0o700)) {
Ok(()) | Err(rustix::io::Errno::EXIST) => {}
Err(error) => return Err(io::Error::from(error).into()),
@ -240,7 +251,7 @@ fn create_directory(parent: impl AsFd, name: &str) -> Result<(), StateAnchorErro
Ok(())
}
fn open_directory(parent: impl AsFd, path: &Path) -> Result<OwnedFd, StateAnchorError> {
pub(super) fn open_directory(parent: impl AsFd, path: &Path) -> Result<OwnedFd, StateAnchorError> {
let descriptor = openat2(
parent.as_fd(),
path,
@ -261,7 +272,7 @@ fn open_directory(parent: impl AsFd, path: &Path) -> Result<OwnedFd, StateAnchor
Ok(descriptor)
}
fn open_final_mount_directory(parent: impl AsFd, path: &Path) -> io::Result<OwnedFd> {
pub(super) fn open_final_mount_directory(parent: impl AsFd, path: &Path) -> io::Result<OwnedFd> {
let mut components = path.components();
if !matches!(components.next(), Some(Component::Normal(_))) || components.next().is_some() {
return Err(io::Error::other(
@ -271,7 +282,10 @@ fn open_final_mount_directory(parent: impl AsFd, path: &Path) -> io::Result<Owne
open_mounted_directory(parent, path).map_err(io::Error::other)
}
fn open_mounted_directory(parent: impl AsFd, path: &Path) -> Result<OwnedFd, StateAnchorError> {
pub(super) fn open_mounted_directory(
parent: impl AsFd,
path: &Path,
) -> Result<OwnedFd, StateAnchorError> {
let descriptor = openat2(
parent.as_fd(),
path,
@ -283,7 +297,7 @@ fn open_mounted_directory(parent: impl AsFd, path: &Path) -> Result<OwnedFd, Sta
Ok(descriptor)
}
fn open_exact_directory_path(
pub(super) fn open_exact_directory_path(
path: &Path,
expected: (u64, u64),
) -> Result<OwnedFd, StateAnchorError> {
@ -299,7 +313,7 @@ fn open_exact_directory_path(
Ok(descriptor)
}
fn require_directory_descriptor_identity(
pub(super) fn require_directory_descriptor_identity(
descriptor: impl AsFd,
expected: (u64, u64),
) -> Result<(), StateAnchorError> {
@ -313,7 +327,7 @@ fn require_directory_descriptor_identity(
}
}
fn require_same_descriptor(
pub(super) fn require_same_descriptor(
observed: impl AsFd,
expected: impl AsFd,
) -> Result<(), StateAnchorError> {
@ -326,7 +340,7 @@ fn require_same_descriptor(
}
}
fn create_regular_file(parent: impl AsFd, name: &str) -> Result<(), StateAnchorError> {
pub(super) fn create_regular_file(parent: impl AsFd, name: &str) -> Result<(), StateAnchorError> {
match openat2(
parent.as_fd(),
name,
@ -341,7 +355,10 @@ fn create_regular_file(parent: impl AsFd, name: &str) -> Result<(), StateAnchorE
Ok(())
}
fn open_regular_file(parent: impl AsFd, name: &str) -> Result<OwnedFd, StateAnchorError> {
pub(super) fn open_regular_file(
parent: impl AsFd,
name: &str,
) -> Result<OwnedFd, StateAnchorError> {
let descriptor = openat2(
parent.as_fd(),
name,
@ -360,7 +377,11 @@ fn open_regular_file(parent: impl AsFd, name: &str) -> Result<OwnedFd, StateAnch
}
}
fn atomic_replace_at(parent: impl AsFd, name: &str, bytes: &[u8]) -> Result<(), StateAnchorError> {
pub(super) fn atomic_replace_at(
parent: impl AsFd,
name: &str,
bytes: &[u8],
) -> Result<(), StateAnchorError> {
let temporary_name = loop {
let sequence = TEMPORARY_SEQUENCE.fetch_add(1, Ordering::Relaxed);
let candidate =
@ -406,19 +427,19 @@ const fn confined_resolve_flags() -> ResolveFlags {
.union(ResolveFlags::NO_XDEV)
}
fn descriptor_path(descriptor: impl AsFd) -> PathBuf {
pub(super) fn descriptor_path(descriptor: impl AsFd) -> PathBuf {
PathBuf::from(format!("/proc/self/fd/{}", descriptor.as_fd().as_raw_fd()))
}
fn descriptor_child_path(descriptor: impl AsFd, child: &str) -> PathBuf {
pub(super) fn descriptor_child_path(descriptor: impl AsFd, child: &str) -> PathBuf {
descriptor_path(descriptor).join(child)
}
fn inherit_for_exec(descriptor: impl AsFd) -> Result<OwnedFd, StateAnchorError> {
pub(super) fn inherit_for_exec(descriptor: impl AsFd) -> Result<OwnedFd, StateAnchorError> {
dup(descriptor).map_err(io::Error::from).map_err(Into::into)
}
fn reopen_inherited_directory(descriptor: i32) -> Result<OwnedFd, StateAnchorError> {
pub(super) fn reopen_inherited_directory(descriptor: i32) -> Result<OwnedFd, StateAnchorError> {
if descriptor < 0 {
return Err(StateAnchorError::InvalidLayout(
"inherited directory descriptor is invalid",
@ -436,7 +457,7 @@ fn reopen_inherited_directory(descriptor: i32) -> Result<OwnedFd, StateAnchorErr
}
}
fn overlay_options(lower: &Path, upper: &Path, work: &Path) -> OsString {
pub(super) fn overlay_options(lower: &Path, upper: &Path, work: &Path) -> OsString {
let mut bytes = b"lowerdir=".to_vec();
append_fuse_option_path(&mut bytes, lower);
bytes.extend_from_slice(b",upperdir=");
@ -458,7 +479,7 @@ fn append_fuse_option_path(target: &mut Vec<u8>, path: &Path) {
}
}
fn rootless_unmount(layout: &StateAnchorLayout<'_>) -> Result<(), StateAnchorError> {
pub(super) fn rootless_unmount(layout: &StateAnchorLayout<'_>) -> Result<(), StateAnchorError> {
rootless_unmount_anchor(&layout.runtime_anchor)
}
@ -468,7 +489,7 @@ fn rootless_unmount_anchor(runtime_anchor: impl AsFd) -> Result<(), StateAnchorE
rootless_unmount_path(&path)
}
fn rootless_unmount_path(path: &Path) -> Result<(), StateAnchorError> {
pub(super) fn rootless_unmount_path(path: &Path) -> Result<(), StateAnchorError> {
child_broker()
.run_transient(
&TargetCommand::new("fusermount3")
@ -479,7 +500,7 @@ fn rootless_unmount_path(path: &Path) -> Result<(), StateAnchorError> {
.map_err(StateAnchorError::from)
}
fn require_mount_record(
pub(super) fn require_mount_record(
layout: &StateAnchorLayout<'_>,
expected: MountIdentity,
) -> Result<(), StateAnchorError> {
@ -490,7 +511,7 @@ fn require_mount_record(
}
}
fn has_mount_record(
pub(super) fn has_mount_record(
layout: &StateAnchorLayout<'_>,
expected: MountIdentity,
) -> Result<bool, StateAnchorError> {
@ -550,10 +571,10 @@ fn decode_mount_field(bytes: &[u8]) -> Vec<u8> {
decoded
}
fn path_from_host(path: &HostPath) -> PathBuf {
pub(super) fn path_from_host(path: &HostPath) -> PathBuf {
PathBuf::from(OsString::from_vec(path.as_bytes().to_vec()))
}
fn host_path(path: &Path) -> HostPath {
pub(super) fn host_path(path: &Path) -> HostPath {
HostPath::new(path.as_os_str().as_bytes().to_vec())
}

View file

@ -1,123 +1,188 @@
#[cfg(test)]
mod recovery_tests {
use super::*;
use std::os::unix::fs::symlink;
use super::mount::{PrivateRecoveryOperations, execute_private_recovery};
use super::storage::open_final_mount_directory;
use super::*;
use std::os::unix::fs::symlink;
struct FailpointRecovery {
fail_at: Option<usize>,
boundary: usize,
events: Vec<&'static str>,
user: NamespaceIdentity,
mount_namespace: NamespaceIdentity,
mount: MountIdentity,
observed: MountIdentity,
}
struct FailpointRecovery {
fail_at: Option<usize>,
boundary: usize,
events: Vec<&'static str>,
user: NamespaceIdentity,
mount_namespace: NamespaceIdentity,
mount: MountIdentity,
observed: MountIdentity,
}
impl FailpointRecovery {
fn boundary(&mut self, name: &'static str) -> io::Result<()> {
self.boundary += 1;
self.events.push(name);
if self.fail_at == Some(self.boundary) {
Err(io::Error::other("injected recovery boundary failure"))
} else {
Ok(())
}
impl FailpointRecovery {
fn boundary(&mut self, name: &'static str) -> io::Result<()> {
self.boundary += 1;
self.events.push(name);
if self.fail_at == Some(self.boundary) {
Err(io::Error::other("injected recovery boundary failure"))
} else {
Ok(())
}
}
}
impl PrivateRecoveryOperations for FailpointRecovery {
fn validate_pidfs(&mut self) -> io::Result<()> {
self.boundary("pidfs")
}
fn enter_user_namespace(&mut self) -> io::Result<()> {
self.boundary("enter-user")
}
fn observe_user_namespace(&mut self) -> io::Result<NamespaceIdentity> {
self.boundary("observe-user")?;
Ok(self.user)
}
fn enter_mount_namespace(&mut self) -> io::Result<()> {
self.boundary("enter-mount")
}
fn observe_mount_namespace(&mut self) -> io::Result<NamespaceIdentity> {
self.boundary("observe-mount")?;
Ok(self.mount_namespace)
}
fn verify_mount(&mut self) -> io::Result<MountIdentity> {
self.boundary("verify-mount")?;
Ok(self.mount)
}
fn unmount(&mut self) -> io::Result<()> {
self.boundary("unmount")
}
fn observe_after_unmount(&mut self) -> io::Result<MountIdentity> {
self.boundary("observe-absence")?;
Ok(self.observed)
}
impl PrivateRecoveryOperations for FailpointRecovery {
fn validate_pidfs(&mut self) -> io::Result<()> {
self.boundary("pidfs")
}
fn fixture(fail_at: Option<usize>) -> FailpointRecovery {
FailpointRecovery {
fail_at,
boundary: 0,
events: Vec::new(),
user: NamespaceIdentity {
device: DeviceId::new(1),
inode: InodeId::new(2),
},
mount_namespace: NamespaceIdentity {
device: DeviceId::new(3),
inode: InodeId::new(4),
},
mount: MountIdentity {
mount_id: lazaret_core::identity::MountId::new(5),
device: DeviceId::new(6),
inode: InodeId::new(7),
},
observed: MountIdentity {
mount_id: lazaret_core::identity::MountId::new(8),
device: DeviceId::new(6),
inode: InodeId::new(7),
},
}
fn enter_user_namespace(&mut self) -> io::Result<()> {
self.boundary("enter-user")
}
#[test]
fn private_recovery_failpoints_refuse_before_every_later_boundary() {
for boundary in 1..=8 {
let mut operations = fixture(Some(boundary));
let expected_user = operations.user;
let expected_mount_namespace = operations.mount_namespace;
let expected_mount = operations.mount;
let result = execute_private_recovery(
&mut operations,
expected_user,
expected_mount_namespace,
expected_mount,
);
assert!(result.is_err());
assert_eq!(operations.events.len(), boundary);
}
let mut operations = fixture(None);
fn observe_user_namespace(&mut self) -> io::Result<NamespaceIdentity> {
self.boundary("observe-user")?;
Ok(self.user)
}
fn enter_mount_namespace(&mut self) -> io::Result<()> {
self.boundary("enter-mount")
}
fn observe_mount_namespace(&mut self) -> io::Result<NamespaceIdentity> {
self.boundary("observe-mount")?;
Ok(self.mount_namespace)
}
fn verify_mount(&mut self) -> io::Result<MountIdentity> {
self.boundary("verify-mount")?;
Ok(self.mount)
}
fn unmount(&mut self) -> io::Result<()> {
self.boundary("unmount")
}
fn observe_after_unmount(&mut self) -> io::Result<MountIdentity> {
self.boundary("observe-absence")?;
Ok(self.observed)
}
}
fn fixture(fail_at: Option<usize>) -> FailpointRecovery {
FailpointRecovery {
fail_at,
boundary: 0,
events: Vec::new(),
user: NamespaceIdentity {
device: DeviceId::new(1),
inode: InodeId::new(2),
},
mount_namespace: NamespaceIdentity {
device: DeviceId::new(3),
inode: InodeId::new(4),
},
mount: MountIdentity {
mount_id: lazaret_core::identity::MountId::new(5),
device: DeviceId::new(6),
inode: InodeId::new(7),
},
observed: MountIdentity {
mount_id: lazaret_core::identity::MountId::new(8),
device: DeviceId::new(6),
inode: InodeId::new(7),
},
}
}
#[test]
fn private_recovery_failpoints_refuse_before_every_later_boundary() {
for boundary in 1..=8 {
let mut operations = fixture(Some(boundary));
let expected_user = operations.user;
let expected_mount_namespace = operations.mount_namespace;
let expected_mount = operations.mount;
assert_eq!(
let result = execute_private_recovery(
&mut operations,
expected_user,
expected_mount_namespace,
expected_mount,
);
assert!(result.is_err());
assert_eq!(operations.events.len(), boundary);
}
let mut operations = fixture(None);
let expected_user = operations.user;
let expected_mount_namespace = operations.mount_namespace;
let expected_mount = operations.mount;
assert_eq!(
execute_private_recovery(
&mut operations,
expected_user,
expected_mount_namespace,
expected_mount,
)
.unwrap(),
operations.observed
);
assert_eq!(
operations.events,
[
"pidfs",
"enter-user",
"observe-user",
"enter-mount",
"observe-mount",
"verify-mount",
"unmount",
"observe-absence",
]
);
}
#[test]
fn final_mount_open_refuses_intermediate_and_link_traversal() {
let parent = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../target/test-artifacts/state-anchor-unit");
std::fs::create_dir_all(&parent).unwrap();
let parent = parent.canonicalize().unwrap();
let root = parent.join(format!(
"lazaret-final-mount-open-{}-{}",
std::process::id(),
TEMPORARY_SEQUENCE.fetch_add(1, Ordering::Relaxed)
));
std::fs::create_dir_all(root.join("nested/final")).unwrap();
symlink("nested", root.join("link")).unwrap();
let parent = File::open(&root).unwrap();
let descriptor = File::open(root.join("nested")).unwrap();
symlink(
format!("/proc/self/fd/{}", descriptor.as_raw_fd()),
root.join("magic"),
)
.unwrap();
assert!(open_final_mount_directory(&parent, Path::new("nested/final")).is_err());
assert!(open_final_mount_directory(&parent, Path::new("link")).is_err());
assert!(open_final_mount_directory(&parent, Path::new("magic")).is_err());
assert!(open_final_mount_directory(&parent, Path::new("nested")).is_ok());
std::fs::remove_dir_all(root).unwrap();
}
#[test]
fn private_recovery_refuses_wrong_or_absent_final_mount_before_action() {
for observed_mount_id in [4, 8] {
let mut operations = fixture(None);
operations.mount.mount_id = lazaret_core::identity::MountId::new(observed_mount_id);
let expected_user = operations.user;
let expected_mount_namespace = operations.mount_namespace;
let expected_mount = MountIdentity {
mount_id: lazaret_core::identity::MountId::new(5),
device: DeviceId::new(6),
inode: InodeId::new(7),
};
assert!(
execute_private_recovery(
&mut operations,
expected_user,
expected_mount_namespace,
expected_mount,
)
.unwrap(),
operations.observed
.is_err()
);
assert_eq!(
operations.events,
@ -128,69 +193,7 @@ mod recovery_tests {
"enter-mount",
"observe-mount",
"verify-mount",
"unmount",
"observe-absence",
]
);
}
#[test]
fn final_mount_open_refuses_intermediate_and_link_traversal() {
let root = std::env::temp_dir().join(format!(
"lazaret-final-mount-open-{}-{}",
std::process::id(),
TEMPORARY_SEQUENCE.fetch_add(1, Ordering::Relaxed)
));
std::fs::create_dir_all(root.join("nested/final")).unwrap();
symlink("nested", root.join("link")).unwrap();
let parent = File::open(&root).unwrap();
let descriptor = File::open(root.join("nested")).unwrap();
symlink(
format!("/proc/self/fd/{}", descriptor.as_raw_fd()),
root.join("magic"),
)
.unwrap();
assert!(open_final_mount_directory(&parent, Path::new("nested/final")).is_err());
assert!(open_final_mount_directory(&parent, Path::new("link")).is_err());
assert!(open_final_mount_directory(&parent, Path::new("magic")).is_err());
assert!(open_final_mount_directory(&parent, Path::new("nested")).is_ok());
std::fs::remove_dir_all(root).unwrap();
}
#[test]
fn private_recovery_refuses_wrong_or_absent_final_mount_before_action() {
for observed_mount_id in [4, 8] {
let mut operations = fixture(None);
operations.mount.mount_id = lazaret_core::identity::MountId::new(observed_mount_id);
let expected_user = operations.user;
let expected_mount_namespace = operations.mount_namespace;
let expected_mount = MountIdentity {
mount_id: lazaret_core::identity::MountId::new(5),
device: DeviceId::new(6),
inode: InodeId::new(7),
};
assert!(
execute_private_recovery(
&mut operations,
expected_user,
expected_mount_namespace,
expected_mount,
)
.is_err()
);
assert_eq!(
operations.events,
[
"pidfs",
"enter-user",
"observe-user",
"enter-mount",
"observe-mount",
"verify-mount",
]
);
}
}
}