Accept safe overlay work residue after unmount
This commit is contained in:
parent
e60baad993
commit
d38357a65d
4 changed files with 80 additions and 12 deletions
|
|
@ -34,10 +34,11 @@ use rustix::io::dup;
|
||||||
use rustix::thread::{ThreadNameSpaceType, move_into_thread_name_spaces};
|
use rustix::thread::{ThreadNameSpaceType, move_into_thread_name_spaces};
|
||||||
use storage::{
|
use storage::{
|
||||||
atomic_replace_at, create_directory, create_regular_file, descriptor_child_path,
|
atomic_replace_at, create_directory, create_regular_file, descriptor_child_path,
|
||||||
descriptor_path, directory_has_entries, host_path, inspect_directory, move_all_entries,
|
descriptor_path, host_path, inspect_directory, move_all_entries, move_selected_entries,
|
||||||
move_selected_entries, open_directory, open_exact_directory_path, open_mounted_directory,
|
open_directory, open_exact_directory_path, open_mounted_directory, open_or_create_quarantine,
|
||||||
open_or_create_quarantine, open_regular_file, path_from_host, remove_directory_contents,
|
open_regular_file, path_from_host, remove_directory_contents,
|
||||||
require_directory_descriptor_identity, require_same_descriptor, validate_relative_path,
|
require_directory_descriptor_identity, require_maintenance_work_shape, require_same_descriptor,
|
||||||
|
validate_relative_path,
|
||||||
};
|
};
|
||||||
|
|
||||||
const MOUNT_TIMEOUT: Duration = Duration::from_secs(5);
|
const MOUNT_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
|
|
@ -394,12 +395,7 @@ impl<'configuration> StateAnchorLayout<'configuration> {
|
||||||
|
|
||||||
fn require_maintenance_ready_allowing_quarantine(&self) -> Result<(), StateAnchorError> {
|
fn require_maintenance_ready_allowing_quarantine(&self) -> Result<(), StateAnchorError> {
|
||||||
self.require_unmounted()?;
|
self.require_unmounted()?;
|
||||||
if directory_has_entries(&self.work_descriptor)? {
|
require_maintenance_work_shape(&self.work_descriptor)
|
||||||
return Err(StateAnchorError::InvalidLayout(
|
|
||||||
"State Anchor work directory is not empty",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,14 @@ pub(super) fn remove_directory_contents(directory: impl AsFd) -> Result<(), Stat
|
||||||
}
|
}
|
||||||
|
|
||||||
fn directory_names(directory: impl AsFd) -> Result<Vec<OsString>, StateAnchorError> {
|
fn directory_names(directory: impl AsFd) -> Result<Vec<OsString>, StateAnchorError> {
|
||||||
let descriptor = rustix::io::dup(directory.as_fd()).map_err(io::Error::from)?;
|
let descriptor = openat2(
|
||||||
|
directory.as_fd(),
|
||||||
|
".",
|
||||||
|
OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
|
||||||
|
Mode::empty(),
|
||||||
|
confined_resolve_flags(),
|
||||||
|
)
|
||||||
|
.map_err(io::Error::from)?;
|
||||||
let mut buffer = [std::mem::MaybeUninit::uninit(); 8192];
|
let mut buffer = [std::mem::MaybeUninit::uninit(); 8192];
|
||||||
let mut raw = RawDir::new(descriptor, &mut buffer);
|
let mut raw = RawDir::new(descriptor, &mut buffer);
|
||||||
let mut names = Vec::new();
|
let mut names = Vec::new();
|
||||||
|
|
@ -228,6 +235,31 @@ pub(super) fn directory_has_entries(directory: impl AsFd) -> Result<bool, StateA
|
||||||
Ok(!directory_names(directory)?.is_empty())
|
Ok(!directory_names(directory)?.is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn require_maintenance_work_shape(directory: impl AsFd) -> Result<(), StateAnchorError> {
|
||||||
|
let names = directory_names(&directory)?;
|
||||||
|
if names.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if names.as_slice() != [OsString::from("work")] {
|
||||||
|
return Err(StateAnchorError::InvalidLayout(
|
||||||
|
"State Anchor work directory has unsupported entries",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let nested = open_directory(&directory, Path::new("work"))?;
|
||||||
|
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",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) 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()
|
if path.as_os_str().is_empty()
|
||||||
|| path
|
|| path
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::collections::BTreeMap;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::os::unix::ffi::OsStringExt;
|
use std::os::unix::ffi::OsStringExt;
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
|
|
@ -21,6 +21,45 @@ use lazaret_state_anchor::{
|
||||||
|
|
||||||
static SEQUENCE: AtomicU64 = AtomicU64::new(0);
|
static SEQUENCE: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn maintenance_accepts_exact_empty_fuse_overlayfs_work_shape() {
|
||||||
|
let tree = TestTree::new();
|
||||||
|
let configuration = configuration(&tree);
|
||||||
|
let layout = StateAnchorLayout::create(&configuration).unwrap();
|
||||||
|
let nested = domain_path(&configuration).join("state-anchor/work/work");
|
||||||
|
fs::create_dir(&nested).unwrap();
|
||||||
|
fs::set_permissions(&nested, fs::Permissions::from_mode(0o700)).unwrap();
|
||||||
|
|
||||||
|
layout.inspect_state().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn maintenance_refuses_mutated_fuse_overlayfs_work_shapes() {
|
||||||
|
let tree = TestTree::new();
|
||||||
|
let configuration = configuration(&tree);
|
||||||
|
let layout = StateAnchorLayout::create(&configuration).unwrap();
|
||||||
|
let work = domain_path(&configuration).join("state-anchor/work");
|
||||||
|
let nested = work.join("work");
|
||||||
|
fs::create_dir(&nested).unwrap();
|
||||||
|
fs::set_permissions(&nested, fs::Permissions::from_mode(0o755)).unwrap();
|
||||||
|
assert!(layout.inspect_state().is_err());
|
||||||
|
|
||||||
|
fs::set_permissions(&nested, fs::Permissions::from_mode(0o700)).unwrap();
|
||||||
|
fs::write(nested.join("unexpected"), b"").unwrap();
|
||||||
|
assert!(layout.inspect_state().is_err());
|
||||||
|
|
||||||
|
fs::remove_file(nested.join("unexpected")).unwrap();
|
||||||
|
fs::write(work.join("unexpected"), b"").unwrap();
|
||||||
|
assert!(layout.inspect_state().is_err());
|
||||||
|
|
||||||
|
fs::remove_file(work.join("unexpected")).unwrap();
|
||||||
|
fs::remove_dir(&nested).unwrap();
|
||||||
|
let foreign = tree.path.join("foreign-work");
|
||||||
|
fs::create_dir(&foreign).unwrap();
|
||||||
|
std::os::unix::fs::symlink(foreign, nested).unwrap();
|
||||||
|
assert!(layout.inspect_state().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn selected_reset_removes_exact_state_and_whiteout_without_touching_siblings() {
|
fn selected_reset_removes_exact_state_and_whiteout_without_touching_siblings() {
|
||||||
let tree = TestTree::new();
|
let tree = TestTree::new();
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,7 @@ fn run() -> io::Result<()> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
first.unmount().map_err(io::Error::other)?;
|
first.unmount().map_err(io::Error::other)?;
|
||||||
|
layout.inspect_state().map_err(io::Error::other)?;
|
||||||
|
|
||||||
let second = acquire(&layout, &artifact)?;
|
let second = acquire(&layout, &artifact)?;
|
||||||
if fs::read(layout.merged().join("usr/bin/installed-tool"))? != b"persistent"
|
if fs::read(layout.merged().join("usr/bin/installed-tool"))? != b"persistent"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue