From 78ee4ecada768d95d85db81909d2934a2a199df4 Mon Sep 17 00:00:00 2001 From: Weidong Cui Date: Sat, 12 Sep 2026 17:44:17 -0700 Subject: [PATCH 1/3] Add Windows filesystem namespace facade Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 239ec5f7-870a-4259-bcae-4ca85fb913a0 --- litebox_shim_windows/Cargo.toml | 1 + litebox_shim_windows/src/fs.rs | 209 ++++++++++++++++++++++++++++++++ litebox_shim_windows/src/lib.rs | 3 + 3 files changed, 213 insertions(+) create mode 100644 litebox_shim_windows/src/fs.rs diff --git a/litebox_shim_windows/Cargo.toml b/litebox_shim_windows/Cargo.toml index 7075fb888..106c693c4 100644 --- a/litebox_shim_windows/Cargo.toml +++ b/litebox_shim_windows/Cargo.toml @@ -10,6 +10,7 @@ litebox_common_windows = { path = "../litebox_common_windows/", version = "0.1.0 bitflags = { version = "2.9.0", default-features = false } int-enum = "1.2.0" litebox = { path = "../litebox/", version = "0.1.0" } +litebox_broker_protocol = { path = "../litebox_broker_protocol/", version = "0.1.0" } litebox_common_linux = { path = "../litebox_common_linux/", version = "0.1.0" } litebox_common_windows = { path = "../litebox_common_windows/", version = "0.1.0" } litebox_platform = { path = "../litebox_platform", version = "0.1.0" } diff --git a/litebox_shim_windows/src/fs.rs b/litebox_shim_windows/src/fs.rs new file mode 100644 index 000000000..2f19e8da6 --- /dev/null +++ b/litebox_shim_windows/src/fs.rs @@ -0,0 +1,209 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +//! Windows-shim file access with registry namespace isolation. + +use alloc::string::{String, ToString}; +use alloc::sync::Arc; +use alloc::vec::Vec; + +use litebox::LiteBox; +#[cfg(test)] +use litebox::fs::errors::ChmodError; +use litebox::fs::errors::{ + CloseError, FileStatusError, MkdirError, OpenError, PathError, ReadDirError, ReadError, + RmdirError, SeekError, UnlinkError, WriteError, +}; +use litebox::fs::{Context, FileFd}; +use litebox_broker_protocol::fs::{ + FileAccessMode, FileDirectoryEntry, FileMode, FileOpenFlags, FileSeekWhence, FileStatus, +}; + +/// Broker filesystem subtree reserved for registry storage. +pub(crate) const REGISTRY_ROOT: &str = "/registry"; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum Namespace { + Regular, + Registry, +} + +impl Namespace { + fn resolve_path(self, context: &Context, path: &str) -> Result { + let path = context.resolve(path)?.to_string(); + match (self, is_registry_path(&path)) { + (Self::Regular, false) | (Self::Registry, true) => Ok(path), + (Self::Regular, true) => Err(PathError::MissingComponent), + (Self::Registry, false) => Err(PathError::InvalidPathname), + } + } +} + +/// File facade that confines operations to one Windows shim namespace. +pub(crate) struct Fs { + litebox: Arc>, + namespace: Namespace, +} + +impl Fs { + pub(crate) fn regular(litebox: Arc>) -> Self { + Self { + litebox, + namespace: Namespace::Regular, + } + } + + pub(crate) fn registry(litebox: Arc>) -> Self { + Self { + litebox, + namespace: Namespace::Registry, + } + } + + fn resolve_path(&self, context: &Context, path: &str) -> Result { + self.namespace.resolve_path(context, path) + } + + pub(crate) fn open_file( + &self, + context: &Context, + path: &str, + access: FileAccessMode, + flags: FileOpenFlags, + mode: FileMode, + ) -> Result { + let path = self.resolve_path(context, path)?; + self.litebox + .open_file(context, path.as_str(), access, flags, mode) + } + + pub(crate) fn close_file(&self, fd: &FileFd) -> Result<(), CloseError> { + self.litebox.close_file(fd) + } + + pub(crate) fn read_file( + &self, + fd: &FileFd, + buf: &mut [u8], + offset: Option, + ) -> Result { + self.litebox.read_file(fd, buf, offset) + } + + pub(crate) fn write_file( + &self, + fd: &FileFd, + buf: &[u8], + offset: Option, + ) -> Result { + self.litebox.write_file(fd, buf, offset) + } + + pub(crate) fn seek_file( + &self, + fd: &FileFd, + offset: isize, + whence: FileSeekWhence, + ) -> Result { + self.litebox.seek_file(fd, offset, whence) + } + + pub(crate) fn read_file_directory( + &self, + directory_path: &str, + fd: &FileFd, + ) -> Result, ReadDirError> { + let mut entries = self.litebox.read_file_directory(fd)?; + if self.namespace == Namespace::Regular && directory_path == "/" { + entries.retain(|entry| { + !entry + .name + .eq_ignore_ascii_case(REGISTRY_ROOT.trim_start_matches('/')) + }); + } + Ok(entries) + } + + pub(crate) fn path_file_status( + &self, + context: &Context, + path: &str, + ) -> Result { + let path = self.resolve_path(context, path)?; + self.litebox.path_file_status(context, path.as_str()) + } + + pub(crate) fn file_status(&self, fd: &FileFd) -> Result { + self.litebox.file_status(fd) + } + + #[cfg(test)] + pub(crate) fn chmod_file( + &self, + context: &Context, + path: &str, + mode: FileMode, + ) -> Result<(), ChmodError> { + let path = self.resolve_path(context, path)?; + self.litebox.chmod_file(context, path.as_str(), mode) + } + + pub(crate) fn unlink_file(&self, context: &Context, path: &str) -> Result<(), UnlinkError> { + let path = self.resolve_path(context, path)?; + self.litebox.unlink_file(context, path.as_str()) + } + + pub(crate) fn mkdir_file( + &self, + context: &Context, + path: &str, + mode: FileMode, + ) -> Result<(), MkdirError> { + let path = self.resolve_path(context, path)?; + self.litebox.mkdir_file(context, path.as_str(), mode) + } + + pub(crate) fn rmdir_file(&self, context: &Context, path: &str) -> Result<(), RmdirError> { + let path = self.resolve_path(context, path)?; + self.litebox.rmdir_file(context, path.as_str()) + } +} + +fn is_registry_path(path: &str) -> bool { + path.split('/') + .find(|component| !component.is_empty()) + .is_some_and(|component| { + component.eq_ignore_ascii_case(REGISTRY_ROOT.trim_start_matches('/')) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn namespace_path_resolution_is_symmetric() { + let context = Context::new(); + + assert_eq!( + Namespace::Regular + .resolve_path(&context, "/tmp/../file") + .unwrap(), + "/file" + ); + assert!(matches!( + Namespace::Regular.resolve_path(&context, "/tmp/../Registry/machine"), + Err(PathError::MissingComponent) + )); + assert_eq!( + Namespace::Registry + .resolve_path(&context, "/tmp/../Registry/machine") + .unwrap(), + "/Registry/machine" + ); + assert!(matches!( + Namespace::Registry.resolve_path(&context, "/tmp"), + Err(PathError::InvalidPathname) + )); + } +} diff --git a/litebox_shim_windows/src/lib.rs b/litebox_shim_windows/src/lib.rs index d03096081..ed6bb3ea2 100644 --- a/litebox_shim_windows/src/lib.rs +++ b/litebox_shim_windows/src/lib.rs @@ -65,6 +65,9 @@ mod nt_types; mod syscalls; mod wait; +#[allow(dead_code)] +mod fs; + #[cfg(test)] mod tests; From 27ca543c000d8aded2bb88cc6fd416fc5bc18d1d Mon Sep 17 00:00:00 2001 From: Weidong Cui Date: Sat, 12 Sep 2026 17:44:26 -0700 Subject: [PATCH 2/3] Update Windows shim dependency lockfile Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 239ec5f7-870a-4259-bcae-4ca85fb913a0 --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index fd1ed7ab0..5972a625a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2002,6 +2002,7 @@ dependencies = [ "bitflags", "int-enum", "litebox", + "litebox_broker_protocol", "litebox_common_linux", "litebox_common_windows", "litebox_platform", From 777a999a5486b102248c700b7ac7795aa2505649 Mon Sep 17 00:00:00 2001 From: Weidong Cui Date: Mon, 14 Sep 2026 14:36:41 -0700 Subject: [PATCH 3/3] Normalize Windows root directory filtering Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 239ec5f7-870a-4259-bcae-4ca85fb913a0 --- litebox_shim_windows/src/fs.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/litebox_shim_windows/src/fs.rs b/litebox_shim_windows/src/fs.rs index 2f19e8da6..d3681298d 100644 --- a/litebox_shim_windows/src/fs.rs +++ b/litebox_shim_windows/src/fs.rs @@ -37,6 +37,15 @@ impl Namespace { (Self::Registry, false) => Err(PathError::InvalidPathname), } } + + fn hides_registry_entry(self, context: &Context, directory_path: &str) -> bool { + self == Self::Regular + && context + .resolve(directory_path) + .expect("resolving a Rust string path cannot fail") + .to_string() + == "/" + } } /// File facade that confines operations to one Windows shim namespace. @@ -110,11 +119,12 @@ impl Fs { pub(crate) fn read_file_directory( &self, + context: &Context, directory_path: &str, fd: &FileFd, ) -> Result, ReadDirError> { let mut entries = self.litebox.read_file_directory(fd)?; - if self.namespace == Namespace::Regular && directory_path == "/" { + if self.namespace.hides_registry_entry(context, directory_path) { entries.retain(|entry| { !entry .name @@ -206,4 +216,15 @@ mod tests { Err(PathError::InvalidPathname) )); } + + #[test] + fn regular_namespace_hides_registry_for_root_aliases() { + let context = Context::new(); + + for path in ["/", "/.", "//", "/tmp/..", "."] { + assert!(Namespace::Regular.hides_registry_entry(&context, path)); + } + assert!(!Namespace::Regular.hides_registry_entry(&context, "/tmp")); + assert!(!Namespace::Registry.hides_registry_entry(&context, "/registry")); + } }