Confine package ownership changes to private mounts

This commit is contained in:
ms2k 2026-07-27 03:31:58 +09:00
commit 8334fd379f
3 changed files with 402 additions and 25 deletions

View file

@ -275,8 +275,23 @@ impl IgnoredWorkspace {
let stat = match backing {
Classification::Host => host,
Classification::Isolated => isolated,
}
.ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))?;
};
let (backing, stat) = if let Some(stat) = stat {
(backing, stat)
} else if backing == Classification::Isolated
&& host.as_ref().is_some_and(|stat| {
stat.st_mode & libc::S_IFMT == libc::S_IFDIR
&& self
.provenance
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.matches(path, &crate::migration::ObjectStamp::from_stat(stat))
})
{
(Classification::Host, host.expect("checked host directory"))
} else {
return Err(io::Error::from_raw_os_error(libc::ENOENT));
};
Ok(ResolvedEntry {
path: path.to_path_buf(),
backing,

View file

@ -81,6 +81,63 @@ fn disabled_isolation_routes_ignored_paths_to_the_host_backing() {
);
}
#[test]
fn newly_ignored_created_directory_remains_a_safe_traversal_shell() {
let fixture = Fixture::new();
repository(&fixture.workspace);
let filesystem = IgnoredWorkspace::open(&fixture.workspace, &fixture.isolated, None).unwrap();
for path in ["environment", "environment/bin"] {
fs::create_dir(fixture.workspace.join(path)).unwrap();
filesystem
.record_created(Classification::Host, std::path::Path::new(path))
.unwrap();
}
fs::write(fixture.workspace.join("environment/.gitignore"), b"*\n").unwrap();
filesystem
.record_created(
Classification::Host,
std::path::Path::new("environment/.gitignore"),
)
.unwrap();
let path = std::path::Path::new("environment/bin");
let snapshot = filesystem.classifier.snapshot_for(path);
let entry = filesystem.resolve(&snapshot, path).unwrap();
assert_eq!(entry.backing, Classification::Host);
assert_eq!(
filesystem.classify(&snapshot, &path.join("python3"), false),
Classification::Isolated
);
}
#[test]
fn ignored_host_directory_requires_current_creation_provenance() {
let fixture = Fixture::new();
repository(&fixture.workspace);
fs::write(fixture.workspace.join(".gitignore"), b"hidden/\nchanged/\n").unwrap();
fs::create_dir(fixture.workspace.join("hidden")).unwrap();
fs::create_dir(fixture.workspace.join("changed")).unwrap();
let filesystem = IgnoredWorkspace::open(&fixture.workspace, &fixture.isolated, None).unwrap();
let hidden = std::path::Path::new("hidden");
let snapshot = filesystem.classifier.snapshot_for(hidden);
let Err(error) = filesystem.resolve(&snapshot, hidden) else {
panic!("unproven host directory became visible");
};
assert_eq!(error.raw_os_error(), Some(libc::ENOENT));
filesystem
.record_created(Classification::Host, std::path::Path::new("changed"))
.unwrap();
fs::write(fixture.workspace.join("changed/external"), b"outside").unwrap();
let changed = std::path::Path::new("changed");
let snapshot = filesystem.classifier.snapshot_for(changed);
let Err(error) = filesystem.resolve(&snapshot, changed) else {
panic!("changed host directory retained creation provenance");
};
assert_eq!(error.raw_os_error(), Some(libc::ENOENT));
}
#[test]
#[ignore = "requires a usable host FUSE device"]
fn physical_router_preserves_lock_domains_across_transition() {

View file

@ -86,7 +86,7 @@ impl Provenance {
self.objects.extend(moved);
}
fn matches(&self, path: &Path, stamp: &ObjectStamp) -> bool {
pub(crate) fn matches(&self, path: &Path, stamp: &ObjectStamp) -> bool {
self.objects.get(path) == Some(stamp)
}
}