Normalize inactive State Anchor work storage

This commit is contained in:
ms2k 2026-07-23 21:44:16 +09:00
commit 42e197510e
4 changed files with 133 additions and 11 deletions

View file

@ -33,10 +33,10 @@ 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, 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,
atomic_replace_at, clear_work_directory, create_directory, create_regular_file,
descriptor_child_path, descriptor_path, 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_maintenance_work_shape, require_same_descriptor,
validate_relative_path,
};
@ -272,6 +272,15 @@ impl<'configuration> StateAnchorLayout<'configuration> {
self.revalidate()
}
/// Clears the private fuse-overlayfs work directory after proving the anchor inactive.
///
/// # Errors
/// Refuses changed authority, a remaining mount, or descriptor-confined cleanup failure.
pub fn normalize_inactive_work_directory(&self) -> Result<(), StateAnchorError> {
self.require_unmounted()?;
clear_work_directory(&self.work_descriptor)
}
/// Atomically replaces sandbox runtime files in host-private transient storage.
///
/// # Errors

View file

@ -1,7 +1,8 @@
use super::storage::{
descriptor_child_path, descriptor_path, inherit_for_exec, open_directory,
clear_work_directory, 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,
reopen_inherited_directory, require_directory_descriptor_identity, require_same_descriptor,
rootless_unmount_path,
};
use super::{
AsFd, AsRawFd, FileType, HostProcessOwnershipSlot, Instant, MOUNT_POLL_INTERVAL, MOUNT_TIMEOUT,
@ -161,6 +162,7 @@ pub struct RuntimeStateAnchorAuthority {
lower: OwnedFd,
persistent_anchor: OwnedFd,
upper: OwnedFd,
work: OwnedFd,
runtime_anchor: OwnedFd,
merged_label: PathBuf,
merged_descriptor: OwnedFd,
@ -233,13 +235,14 @@ impl RuntimeStateAnchorAuthority {
inherited.runtime_anchor_label,
(retained.st_dev, retained.st_ino),
)?;
drop(open_directory(&persistent_anchor, Path::new("work"))?);
let work = open_directory(&persistent_anchor, Path::new("work"))?;
let merged_descriptor = open_directory(&runtime_anchor, Path::new("merged"))?;
let merged_label = inherited.runtime_anchor_label.join("merged");
Ok(Self {
lower,
persistent_anchor,
upper,
work,
runtime_anchor,
merged_label,
merged_descriptor,
@ -495,6 +498,9 @@ impl RuntimeStateAnchorMount {
return Err(StateAnchorError::OwnershipMismatch);
}
child_broker().reap(process)?;
let observed_work = open_directory(&authority.persistent_anchor, Path::new("work"))?;
require_same_descriptor(&observed_work, &authority.work)?;
clear_work_directory(&authority.work)?;
Ok(RuntimeStateAnchorAbsenceAuthority {
runtime_anchor: authority.runtime_anchor,
mount,

View file

@ -151,6 +151,7 @@ impl<'layout, 'configuration> FuseOverlayfsMount<'layout, 'configuration> {
}
self.layout.require_unmounted()?;
child_broker().reap(self.process)?;
self.layout.normalize_inactive_work_directory()?;
Ok(())
}
@ -165,6 +166,7 @@ impl<'layout, 'configuration> FuseOverlayfsMount<'layout, 'configuration> {
}
self.layout.require_unmounted()?;
child_broker().reap(self.process)?;
self.layout.normalize_inactive_work_directory()?;
Ok(())
}
}

View file

@ -6,6 +6,8 @@ use super::{
mount_identity, openat2, renameat, renameat_with, statat, unlinkat,
};
const FUSE_OVERLAYFS_NESTED_WORK: &str = "work";
/// Independently verifies that no mount is attached at the exact merged path.
///
/// # Errors
@ -240,18 +242,34 @@ pub(super) fn require_maintenance_work_shape(directory: impl AsFd) -> Result<(),
if names.is_empty() {
return Ok(());
}
if names.as_slice() != [OsString::from("work")] {
if names.as_slice() != [OsString::from(FUSE_OVERLAYFS_NESTED_WORK)] {
return Err(StateAnchorError::InvalidLayout(
"State Anchor work directory has unsupported entries",
));
}
let nested = open_directory(&directory, Path::new("work"))?;
let nested = open_directory(&directory, Path::new(FUSE_OVERLAYFS_NESTED_WORK))?;
require_nested_work_authority(&directory, &nested)?;
if directory_has_entries(&nested)? {
return Err(StateAnchorError::InvalidLayout(
"State Anchor work directory has unsupported entries",
));
}
Ok(())
}
pub(super) fn clear_work_directory(directory: impl AsFd) -> Result<(), StateAnchorError> {
remove_directory_contents(directory)
}
fn require_nested_work_authority(
directory: impl AsFd,
nested: impl AsFd,
) -> Result<(), StateAnchorError> {
let parent = rustix::fs::fstat(&directory).map_err(io::Error::from)?;
let child = rustix::fs::fstat(&nested).map_err(io::Error::from)?;
if child.st_mode & 0o7777 != 0o700
|| child.st_uid != parent.st_uid
|| child.st_gid != parent.st_gid
|| directory_has_entries(&nested)?
{
return Err(StateAnchorError::InvalidLayout(
"State Anchor work directory has unsupported entries",