Replace lexical Rust fragments with real modules
This commit is contained in:
parent
f8849cdb5f
commit
247819250b
7 changed files with 3302 additions and 2671 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -307,7 +307,11 @@ struct TestTree {
|
|||
|
||||
impl TestTree {
|
||||
fn new() -> Self {
|
||||
let path = std::env::temp_dir().join(format!(
|
||||
let parent = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../target/test-artifacts/state-anchor-authority");
|
||||
fs::create_dir_all(&parent).unwrap();
|
||||
let parent = parent.canonicalize().unwrap();
|
||||
let path = parent.join(format!(
|
||||
"lazaret-state-anchor-authority-{}-{}",
|
||||
std::process::id(),
|
||||
SEQUENCE.fetch_add(1, Ordering::Relaxed)
|
||||
|
|
|
|||
|
|
@ -397,7 +397,11 @@ struct TestTree {
|
|||
|
||||
impl TestTree {
|
||||
fn new() -> io::Result<Self> {
|
||||
let path = std::env::temp_dir().join(format!(
|
||||
let parent = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../target/test-artifacts/state-anchor-lifecycle");
|
||||
fs::create_dir_all(&parent)?;
|
||||
let parent = parent.canonicalize()?;
|
||||
let path = parent.join(format!(
|
||||
"lazaret state-anchor,:{}-{}",
|
||||
std::process::id(),
|
||||
SEQUENCE.fetch_add(1, Ordering::Relaxed)
|
||||
|
|
|
|||
Loading…
Reference in a new issue