mirror of
https://github.com/lahfir/agent-desktop.git
synced 2026-07-30 02:39:18 +00:00
fix: route windows session and trace directory creation through the private-file seam
This commit is contained in:
parent
51683d5541
commit
1e3a240020
4 changed files with 36 additions and 30 deletions
|
|
@ -236,7 +236,7 @@ fn create_session_tree(dir: &Path) -> Result<(), AppError> {
|
|||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
std::fs::create_dir_all(dir.join("trace"))?;
|
||||
crate::private_file_parent::ensure_private(&dir.join("trace"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ pub(crate) fn ensure_trace_dir(dir: &Path) -> Result<(), AppError> {
|
|||
.create(dir)?;
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
std::fs::create_dir_all(dir)?;
|
||||
crate::private_file_parent::ensure_private(dir)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ use common::{ad_adapter_create, ad_adapter_destroy, with_isolated_home};
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x400;
|
||||
|
||||
struct ProbeOps;
|
||||
|
||||
impl PrivateFileOps for ProbeOps {}
|
||||
|
|
@ -24,27 +26,34 @@ fn plant_junction(link: &Path, target: &Path) {
|
|||
"mklink /J must succeed without privilege: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
let attributes = {
|
||||
use std::os::windows::fs::MetadataExt;
|
||||
std::fs::symlink_metadata(link)
|
||||
.expect("junction link exists")
|
||||
.file_attributes()
|
||||
};
|
||||
assert!(
|
||||
attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0,
|
||||
"planted link must carry FILE_ATTRIBUTE_REPARSE_POINT"
|
||||
);
|
||||
}
|
||||
|
||||
fn regular_files_under(root: &Path) -> Vec<PathBuf> {
|
||||
let mut files = Vec::new();
|
||||
fn entries_under(root: &Path) -> Vec<PathBuf> {
|
||||
let mut entries = Vec::new();
|
||||
let mut pending = vec![root.to_path_buf()];
|
||||
while let Some(directory) = pending.pop() {
|
||||
let Ok(entries) = std::fs::read_dir(&directory) else {
|
||||
let Ok(read) = std::fs::read_dir(&directory) else {
|
||||
continue;
|
||||
};
|
||||
for entry in entries.flatten() {
|
||||
let Ok(file_type) = entry.file_type() else {
|
||||
continue;
|
||||
};
|
||||
if file_type.is_dir() {
|
||||
pending.push(entry.path());
|
||||
} else {
|
||||
files.push(entry.path());
|
||||
for entry in read.flatten() {
|
||||
let path = entry.path();
|
||||
if entry.file_type().is_ok_and(|file_type| file_type.is_dir()) {
|
||||
pending.push(path.clone());
|
||||
}
|
||||
entries.push(path);
|
||||
}
|
||||
}
|
||||
files
|
||||
entries
|
||||
}
|
||||
|
||||
/// Core's five private-file primitives are `pub(crate)`, so the behavioral arm
|
||||
|
|
@ -86,10 +95,10 @@ fn adapter_create_without_ad_init_installs_the_windows_private_file_ops() {
|
|||
"a manifest write through a junction component must be refused \
|
||||
by the installed WindowsPrivateFile"
|
||||
);
|
||||
let leaked = regular_files_under(&target);
|
||||
let leaked = entries_under(&target);
|
||||
assert!(
|
||||
leaked.is_empty(),
|
||||
"no session artifact may land under the junction target: {leaked:?}"
|
||||
"no session artifact — file or directory — may land under the junction target: {leaked:?}"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,25 +74,22 @@ fn parse_envelope(output: &Output) -> serde_json::Value {
|
|||
serde_json::from_slice(&output.stdout).expect("stdout is one JSON envelope")
|
||||
}
|
||||
|
||||
fn regular_files_under(root: &Path) -> Vec<PathBuf> {
|
||||
let mut files = Vec::new();
|
||||
fn entries_under(root: &Path) -> Vec<PathBuf> {
|
||||
let mut entries = Vec::new();
|
||||
let mut pending = vec![root.to_path_buf()];
|
||||
while let Some(directory) = pending.pop() {
|
||||
let Ok(entries) = std::fs::read_dir(&directory) else {
|
||||
let Ok(read) = std::fs::read_dir(&directory) else {
|
||||
continue;
|
||||
};
|
||||
for entry in entries.flatten() {
|
||||
let Ok(file_type) = entry.file_type() else {
|
||||
continue;
|
||||
};
|
||||
if file_type.is_dir() {
|
||||
pending.push(entry.path());
|
||||
} else {
|
||||
files.push(entry.path());
|
||||
for entry in read.flatten() {
|
||||
let path = entry.path();
|
||||
if entry.file_type().is_ok_and(|file_type| file_type.is_dir()) {
|
||||
pending.push(path.clone());
|
||||
}
|
||||
entries.push(path);
|
||||
}
|
||||
}
|
||||
files
|
||||
entries
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -112,10 +109,10 @@ fn session_start_through_a_junction_home_is_refused_by_the_installed_windows_ops
|
|||
success means the portable default wrote through the junction"
|
||||
);
|
||||
assert_eq!(envelope["ok"], false);
|
||||
let leaked = regular_files_under(&target);
|
||||
let leaked = entries_under(&target);
|
||||
assert!(
|
||||
leaked.is_empty(),
|
||||
"no session artifact may land under the junction target: {leaked:?}"
|
||||
"no session artifact — file or directory — may land under the junction target: {leaked:?}"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue