diff --git a/docs/development/OpenNexus生产化实施进度-2026-09-08.md b/docs/development/OpenNexus生产化实施进度-2026-09-08.md index c99c817..516bc75 100644 --- a/docs/development/OpenNexus生产化实施进度-2026-09-08.md +++ b/docs/development/OpenNexus生产化实施进度-2026-09-08.md @@ -1015,3 +1015,4 @@ Core 的独立数据目录目前不等于已授权 Vault。Python 旧笔记写 - 精确测试及 `core_process` 全文件 3 项通过,严格 Clippy `-D warnings` 通过。该实现提交为 `bdba729`。 - A-04 尚不能记为通过:Core/Host 签名 RC 的原子更新事务与每个切换点 20 次真实重启恢复仍未完成。当前会话也没有受控签名 RC 或旧 RC。 - C-01 运行环境复核显示当前 Windows 用户不是管理员,且未发现可用的 `pktmon`、`tshark` 或 `dumpcap` 命令,因此不能生成规划要求的禁止网络零收包证据;已有 AppContainer 文件与环回测试不能替代该证据。 +- C-01 文件攻击夹具继续补强:由真实编译的恶意二进制在 AppContainer 中分别对未授权 Vault、用户目录、凭据库和其他包诱饵执行各 100 次读取与 100 次写入,总计 800 次文件系统攻击均失败,四个诱饵 SHA-256 保持不变。精确 Rust 测试通过;C-01 状态仍保持未通过,直至网络类别的管理员抓包证据齐备。 diff --git a/frontend/src-tauri/src/extension_container.rs b/frontend/src-tauri/src/extension_container.rs index afb0229..d325ad3 100644 --- a/frontend/src-tauri/src/extension_container.rs +++ b/frontend/src-tauri/src/extension_container.rs @@ -708,6 +708,84 @@ mod tests { fn real_container_cannot_reach_ipv4_or_ipv6_loopback_listeners() { real_native_protocol_probes(false, false); } + #[test] + fn real_malicious_binary_cannot_read_or_write_four_ungranted_file_scopes() { + use sha2::{Digest, Sha256}; + use std::os::windows::fs::OpenOptionsExt; + use windows_sys::Win32::Storage::FileSystem::*; + + let profile = Profile::create().unwrap(); + let package = tempfile::tempdir().unwrap(); + let executable = package.path().join("file-probe.exe"); + let fixture = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/sandbox_network_probe.rs"); + let compile = std::process::Command::new("rustc") + .arg("--edition=2021") + .arg(&fixture) + .arg("-o") + .arg(&executable) + .output() + .unwrap(); + assert!( + compile.status.success(), + "{}", + String::from_utf8_lossy(&compile.stderr) + ); + let open = |path: &std::path::Path| { + std::fs::OpenOptions::new() + .access_mode(READ_CONTROL | WRITE_DAC) + .share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE) + .custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT) + .open(path) + .unwrap() + }; + let package_root = open(package.path()); + let entry = open(&executable); + profile.grant_package_read_execute(&package_root).unwrap(); + profile.grant_package_read_execute(&entry).unwrap(); + + let bait_root = tempfile::tempdir().unwrap(); + let scopes = ["vault", "home", "credentials", "other-package"]; + let bait: Vec<_> = scopes + .iter() + .map(|scope| { + let directory = bait_root.path().join(scope); + std::fs::create_dir(&directory).unwrap(); + let path = directory.join("bait.txt"); + std::fs::write(&path, format!("OpenNexus C-01 {scope} bait")).unwrap(); + path + }) + .collect(); + let before: Vec<_> = bait + .iter() + .map(|path| Sha256::digest(std::fs::read(path).unwrap())) + .collect(); + let folder = profile.folder().unwrap(); + let system = std::path::PathBuf::from(std::env::var_os("SystemRoot").unwrap()); + let mut arguments = vec!["file_denied_100".to_owned()]; + arguments.extend(bait.iter().map(|path| path.to_string_lossy().into_owned())); + let data = crate::extension_launch_data::LaunchData::new( + &executable, + &arguments, + &system, + &folder, + &folder.join("Temp"), + &std::collections::BTreeMap::new(), + ) + .unwrap(); + assert_eq!( + checked_executable_data(&profile, &executable, None, Some(data)), + Some(0) + ); + let after: Vec<_> = bait + .iter() + .map(|path| Sha256::digest(std::fs::read(path).unwrap())) + .collect(); + assert_eq!(after, before); + drop(entry); + drop(package_root); + profile.remove().unwrap(); + } #[cfg(feature = "desktop")] #[test] #[ignore = "real MCP tools/call 60-second deadline acceptance; run explicitly"] diff --git a/frontend/src-tauri/tests/fixtures/sandbox_network_probe.rs b/frontend/src-tauri/tests/fixtures/sandbox_network_probe.rs index 8ad217f..ed29864 100644 --- a/frontend/src-tauri/tests/fixtures/sandbox_network_probe.rs +++ b/frontend/src-tauri/tests/fixtures/sandbox_network_probe.rs @@ -3,6 +3,18 @@ use std::net::{SocketAddr, TcpStream, UdpSocket}; use std::time::Duration; fn main() { let args: Vec<_> = std::env::args().collect(); + if args.get(1).is_some_and(|value| value == "file_denied_100") { + for path in &args[2..] { + for _ in 0..100 { + if std::fs::File::open(path).is_ok() + || std::fs::OpenOptions::new().write(true).open(path).is_ok() + { + std::process::exit(88); + } + } + } + std::process::exit(0); + } if args.get(1).is_some_and(|s| s == "cpu_burn") { std::thread::scope(|scope| { for _ in 0..8 {