diff --git a/litebox_common_windows/src/loader.rs b/litebox_common_windows/src/loader.rs index 486b7ae735..0e3f18134d 100644 --- a/litebox_common_windows/src/loader.rs +++ b/litebox_common_windows/src/loader.rs @@ -489,6 +489,21 @@ impl PeParsedFile { self.image.image_base } + #[must_use] + pub fn machine(&self) -> u16 { + self.image.machine + } + + #[must_use] + pub fn characteristics(&self) -> u16 { + self.image.characteristics + } + + #[must_use] + pub fn dll_characteristics(&self) -> u16 { + self.image.dll_characteristics + } + /// Returns whether the image opts into dynamic-base loading. #[must_use] pub fn has_dynamic_base(&self) -> bool { diff --git a/litebox_common_windows/src/nt_status.rs b/litebox_common_windows/src/nt_status.rs index 439cd7fe63..bfeb2249ee 100644 --- a/litebox_common_windows/src/nt_status.rs +++ b/litebox_common_windows/src/nt_status.rs @@ -159,6 +159,8 @@ impl NtStatus { 0xC0000046 => "STATUS_MUTANT_NOT_OWNED: Mutant not owned", 0xC0000047 => "STATUS_SEMAPHORE_LIMIT_EXCEEDED: Semaphore limit exceeded", 0xC0000048 => "STATUS_PORT_ALREADY_SET: Port already set", + 0xC0000049 => "STATUS_SECTION_NOT_IMAGE: Section not image", + 0xC000004E => "STATUS_SECTION_PROTECTION: Section protection", 0xC000004F => "STATUS_EAS_NOT_SUPPORTED: EAS not supported", 0xC0000050 => "STATUS_EA_TOO_LARGE: EA too large", 0xC0000056 => "STATUS_DELETE_PENDING: Delete pending", @@ -420,6 +422,9 @@ impl NtStatus { /// STATUS_SECTION_TOO_BIG pub const SECTION_TOO_BIG: Self = Self::from_raw(0xC0000040); + /// STATUS_SECTION_NOT_IMAGE + pub const SECTION_NOT_IMAGE: Self = Self::from_raw(0xC0000049); + /// STATUS_PORT_CONNECTION_REFUSED pub const PORT_CONNECTION_REFUSED: Self = Self::from_raw(0xC0000041); @@ -435,6 +440,9 @@ impl NtStatus { /// STATUS_INVALID_PAGE_PROTECTION pub const INVALID_PAGE_PROTECTION: Self = Self::from_raw(0xC0000045); + /// STATUS_SECTION_PROTECTION + pub const SECTION_PROTECTION: Self = Self::from_raw(0xC000004E); + /// STATUS_MUTANT_NOT_OWNED pub const MUTANT_NOT_OWNED: Self = Self::from_raw(0xC0000046); diff --git a/litebox_shim_windows/src/lib.rs b/litebox_shim_windows/src/lib.rs index 3de8199688..a9a8d92476 100644 --- a/litebox_shim_windows/src/lib.rs +++ b/litebox_shim_windows/src/lib.rs @@ -38,6 +38,9 @@ use crate::syscalls::event::{EventHandleObject, EventObject, EventSubsystem}; use crate::syscalls::file::{FileObject, FileObjectSubsystem}; use crate::syscalls::iocp::{IoCompletionHandleObject, IoCompletionSubsystem}; use crate::syscalls::registry::{RegistryKeyObject, RegistryKeySubsystem}; +use crate::syscalls::section::{ + MapViewOfSectionParameters, SectionHandleObject, SectionObject, SectionSubsystem, +}; use crate::syscalls::symlink::{SymbolicLinkHandleObject, SymbolicLinkSubsystem}; use crate::syscalls::timer::{TimerCreateParameters, TimerHandleObject, TimerSubsystem}; use crate::syscalls::wait_completion_packet::{ @@ -90,6 +93,10 @@ pub(crate) type WindowsNlsSectionMappings = litebox::sync::RwLock>; pub(crate) type WindowsVirtualAllocations = litebox::sync::RwLock>; +pub(crate) type WindowsSectionNamespace = + litebox::sync::RwLock>>>; +pub(crate) type WindowsSectionViews = + litebox::sync::RwLock>>; pub(crate) type WindowsEventNamespace = litebox::sync::RwLock>>>; pub(crate) type WindowsDirectoryNamespace = DirectoryNamespace; @@ -103,6 +110,22 @@ pub(crate) struct WindowsVirtualAllocation { pub(crate) pages: rangemap::RangeMap, } +pub(crate) struct WindowsSectionView { + pub(crate) size: usize, + pub(crate) section_offset: usize, + pub(crate) section: Option>>, +} + +impl Clone for WindowsSectionView { + fn clone(&self) -> Self { + Self { + size: self.size, + section_offset: self.section_offset, + section: self.section.clone(), + } + } +} + pub type DefaultFS = WindowsFS; pub type WindowsFS = litebox::fs::layered::FileSystem< @@ -343,6 +366,8 @@ impl WindowsShim { handles: WindowsHandleStore::::new(litebox::fd::RawDescriptorStorage::new()), directory_namespace, event_namespace: WindowsEventNamespace::::new(BTreeMap::new()), + section_namespace: WindowsSectionNamespace::::new(BTreeMap::new()), + section_views: WindowsSectionViews::::new(BTreeMap::new()), nls_section_mappings: WindowsNlsSectionMappings::::new(BTreeMap::new()), virtual_allocations: load_info.virtual_allocations, system_lcid: AtomicU32::new(syscalls::nls::DEFAULT_LOCALE_ID), @@ -387,6 +412,8 @@ pub struct Process { handles: WindowsHandleStore, directory_namespace: WindowsDirectoryNamespace, event_namespace: WindowsEventNamespace, + section_namespace: WindowsSectionNamespace, + section_views: WindowsSectionViews, nls_section_mappings: WindowsNlsSectionMappings, virtual_allocations: WindowsVirtualAllocations, system_lcid: AtomicU32, @@ -588,6 +615,15 @@ impl Task { ); (status, ContinueOperation::Resume) } + SyscallRequest::NtOpenSection { + section_handle, + desired_access, + object_attributes, + } => { + let status = + self.sys_nt_open_section(section_handle, desired_access, object_attributes); + (status, ContinueOperation::Resume) + } SyscallRequest::NtQueryDirectoryObject { directory_handle, buffer, @@ -662,6 +698,50 @@ impl Task { ); (status, ContinueOperation::Resume) } + SyscallRequest::NtCreateSection { + section_handle, + desired_access, + object_attributes, + maximum_size, + section_page_protection, + allocation_attributes, + file_handle, + } => { + let status = self.sys_nt_create_section( + section_handle, + desired_access, + object_attributes, + maximum_size, + section_page_protection, + allocation_attributes, + file_handle, + ); + (status, ContinueOperation::Resume) + } + SyscallRequest::NtCreateSectionEx { + section_handle, + desired_access, + object_attributes, + maximum_size, + section_page_protection, + allocation_attributes, + file_handle, + extended_parameters, + extended_parameter_count, + } => { + let status = self.sys_nt_create_section_ex( + section_handle, + desired_access, + object_attributes, + maximum_size, + section_page_protection, + allocation_attributes, + file_handle, + extended_parameters, + extended_parameter_count, + ); + (status, ContinueOperation::Resume) + } SyscallRequest::NtCreateWaitCompletionPacket { wait_completion_packet_handle, desired_access, @@ -1023,6 +1103,22 @@ impl Task { ); (status, ContinueOperation::Resume) } + SyscallRequest::NtQuerySection { + section_handle, + section_information_class, + section_information, + section_information_length, + return_length, + } => { + let status = self.sys_nt_query_section( + section_handle, + section_information_class, + section_information, + section_information_length, + return_length, + ); + (status, ContinueOperation::Resume) + } SyscallRequest::NtQueryInformationProcess { process_handle, process_information_class, @@ -1199,6 +1295,80 @@ impl Task { ); (status, ContinueOperation::Resume) } + SyscallRequest::NtMapViewOfSection { + section_handle, + process_handle, + base_address, + zero_bits, + commit_size, + section_offset, + view_size, + inherit_disposition, + allocation_type, + page_protection, + } => { + let status = self.sys_nt_map_view_of_section(MapViewOfSectionParameters { + section_handle, + process_handle, + base_address, + zero_bits, + commit_size, + section_offset, + view_size, + inherit_disposition, + allocation_type, + page_protection, + }); + (status, ContinueOperation::Resume) + } + SyscallRequest::NtMapViewOfSectionEx { + section_handle, + process_handle, + base_address, + zero_bits, + commit_size, + section_offset, + view_size, + inherit_disposition, + allocation_type, + page_protection, + extended_parameters, + extended_parameter_count, + } => { + let status = self.sys_nt_map_view_of_section_ex( + MapViewOfSectionParameters { + section_handle, + process_handle, + base_address, + zero_bits, + commit_size, + section_offset, + view_size, + inherit_disposition, + allocation_type, + page_protection, + }, + extended_parameters, + extended_parameter_count, + ); + (status, ContinueOperation::Resume) + } + SyscallRequest::NtUnmapViewOfSection { + process_handle, + base_address, + } => { + let status = self.sys_nt_unmap_view_of_section(process_handle, base_address); + (status, ContinueOperation::Resume) + } + SyscallRequest::NtUnmapViewOfSectionEx { + process_handle, + base_address, + flags, + } => { + let status = + self.sys_nt_unmap_view_of_section_ex(process_handle, base_address, flags); + (status, ContinueOperation::Resume) + } SyscallRequest::NtTerminateProcess { process_handle, exit_status, @@ -1306,6 +1476,14 @@ impl Task { ) { return NtStatus::SUCCESS; } + if remove_raw_handle_by_raw_fd::>( + &self.global.litebox, + &self.process.handles, + raw_fd, + |section| visitor.section(section), + ) { + return NtStatus::SUCCESS; + } NtStatus::INVALID_HANDLE } @@ -1342,6 +1520,8 @@ trait RawHandleVisitor { ); fn worker_factory(&self, worker_factory: WorkerFactoryHandleObject); + + fn section(&self, section: SectionHandleObject); } struct CloseRawHandleVisitor<'task, Platform: ShimPlatform, FS: ShimFS> { @@ -1389,6 +1569,10 @@ impl RawHandleVisitor fn worker_factory(&self, worker_factory: WorkerFactoryHandleObject) { Task::::close_worker_factory(worker_factory); } + + fn section(&self, section: SectionHandleObject) { + Task::::close_section(section); + } } /// The shim entrypoint object passed to the platform. diff --git a/litebox_shim_windows/src/loader/mod.rs b/litebox_shim_windows/src/loader/mod.rs index 3d7b9a32e0..7922fdcdb8 100644 --- a/litebox_shim_windows/src/loader/mod.rs +++ b/litebox_shim_windows/src/loader/mod.rs @@ -4,3 +4,4 @@ mod pe; pub(super) use pe::{PeLoader, WindowsLoadError}; +pub(crate) use pe::{image_section_metadata, load_image_section}; diff --git a/litebox_shim_windows/src/loader/pe.rs b/litebox_shim_windows/src/loader/pe.rs index 05bcc2b536..4ba683cf64 100644 --- a/litebox_shim_windows/src/loader/pe.rs +++ b/litebox_shim_windows/src/loader/pe.rs @@ -1224,6 +1224,58 @@ fn load_image( load_image_with_writable_sections(fs, path, platform, page_manager, &[]) } +pub(crate) fn load_image_section( + platform: &'static Platform, + fs: Arc, + path: &str, + page_manager: &crate::WindowsPageManager, + virtual_allocations: &crate::WindowsVirtualAllocations, +) -> Result { + let image = load_image(platform, fs, path, page_manager)?; + let mapping = image.mapping; + register_image_virtual_allocation(virtual_allocations, mapping, image.pages); + Ok(mapping) +} + +pub(crate) struct ImageSectionMetadata { + pub(crate) transfer_address: usize, + pub(crate) file_size: u32, + pub(crate) subsystem: u32, + pub(crate) subsystem_major_version: u16, + pub(crate) subsystem_minor_version: u16, + pub(crate) image_characteristics: u16, + pub(crate) dll_characteristics: u16, + pub(crate) machine: u16, +} + +pub(crate) fn image_section_metadata( + fs: Arc, + path: &str, +) -> Result { + let file = PeImageFile::open(fs, path)?; + let parsed = PeParsedFile::parse(&mut &file).map_err(WindowsLoadError::Parse)?; + let file_size = file + .fs + .fd_file_status(&file.fd) + .map_err(PeImageAccessError::FileStatus)? + .size + .try_into() + .map_err(|_| PeImageAccessError::AddressOverflow)?; + Ok(ImageSectionMetadata { + transfer_address: parsed + .image_base() + .checked_add(parsed.entry_point_rva()) + .ok_or(PeImageAccessError::AddressOverflow)?, + file_size, + subsystem: u32::from(parsed.subsystem()), + subsystem_major_version: parsed.major_subsystem_version(), + subsystem_minor_version: parsed.minor_subsystem_version(), + image_characteristics: parsed.characteristics(), + dll_characteristics: parsed.dll_characteristics(), + machine: parsed.machine(), + }) +} + fn load_image_with_writable_sections( fs: Arc, path: &str, diff --git a/litebox_shim_windows/src/syscalls/directory.rs b/litebox_shim_windows/src/syscalls/directory.rs index 853056a618..f992c12319 100644 --- a/litebox_shim_windows/src/syscalls/directory.rs +++ b/litebox_shim_windows/src/syscalls/directory.rs @@ -314,7 +314,10 @@ impl DirectoryNamespace { } } - fn resolve_directory(&self, path: &str) -> Result>, NtStatus> { + pub(super) fn resolve_directory( + &self, + path: &str, + ) -> Result>, NtStatus> { let tail = absolute_path_tail(path)?; let node = self.resolve_tail(tail, NtStatus::OBJECT_NAME_NOT_FOUND, false)?; if node.is_directory() { @@ -324,6 +327,23 @@ impl DirectoryNamespace { } } + pub(super) fn resolve_object(&self, path: &str) -> Result>, NtStatus> { + let tail = absolute_path_tail(path)?; + self.resolve_tail(tail, NtStatus::OBJECT_NAME_NOT_FOUND, true) + } + + pub(super) fn parent_directory_exists(&self, path: &str) -> bool { + let path = trim_trailing_directory_path(path); + if path == r"\" { + return false; + } + let Some(index) = path.rfind('\\') else { + return false; + }; + let parent = if index == 0 { r"\" } else { &path[..index] }; + self.resolve_directory(parent).is_ok() + } + fn create_directory( &self, path: &str, @@ -1223,6 +1243,17 @@ mod tests { handle } + fn object_attributes_with_root( + name: &UnicodeString, + root_directory: Handle, + attributes: u32, + ) -> ObjectAttributes { + ObjectAttributes { + root_directory, + ..object_attributes(name, attributes) + } + } + fn expected_record_size(name: &str, type_name: &str) -> usize { size_of::() + name.encode_utf16().count() * size_of::() @@ -1326,6 +1357,104 @@ mod tests { }); } + #[test] + fn open_section_rejects_empty_known_dlls_with_zeroed_output() { + run_with_test_platform_pointers(|| { + let task = test_task(); + let known_dlls_units = utf16_units(r"\KnownDlls"); + let known_dlls_name = unicode_string(&known_dlls_units); + let known_dlls_attrs = object_attributes( + &known_dlls_name, + ObjectAttributesFlags::CASE_INSENSITIVE.bits(), + ); + let mut known_dlls = Handle::default(); + assert_eq!( + task.sys_nt_open_directory_object( + mut_ptr(&mut known_dlls), + DIRECTORY_QUERY | DIRECTORY_TRAVERSE, + Some(const_ptr(&known_dlls_attrs)), + ), + NtStatus::SUCCESS + ); + let kernel32_units = utf16_units("KERNEL32.DLL"); + let kernel32 = unicode_string(&kernel32_units); + let attrs = object_attributes_with_root( + &kernel32, + known_dlls, + ObjectAttributesFlags::CASE_INSENSITIVE.bits(), + ); + let mut handle = Handle::from_raw(0x5555_5555); + + assert_eq!( + task.sys_nt_open_section(mut_ptr(&mut handle), 0x0d, Some(const_ptr(&attrs))), + NtStatus::OBJECT_NAME_NOT_FOUND + ); + assert_eq!(handle, Handle::default()); + + let attrs = object_attributes_with_root( + &kernel32, + known_dlls, + (ObjectAttributesFlags::CASE_INSENSITIVE | ObjectAttributesFlags::OPENLINK).bits(), + ); + handle = Handle::from_raw(0x5555_5555); + assert_eq!( + task.sys_nt_open_section(mut_ptr(&mut handle), 0x0d, Some(const_ptr(&attrs))), + NtStatus::OBJECT_NAME_NOT_FOUND + ); + assert_eq!(handle, Handle::default()); + + let missing_parent_units = utf16_units(r"\MissingLiteBoxParent\KERNEL32.DLL"); + let missing_parent = unicode_string(&missing_parent_units); + let attrs = object_attributes( + &missing_parent, + ObjectAttributesFlags::CASE_INSENSITIVE.bits(), + ); + handle = Handle::from_raw(0x5555_5555); + assert_eq!( + task.sys_nt_open_section(mut_ptr(&mut handle), 0x0d, Some(const_ptr(&attrs))), + NtStatus::OBJECT_PATH_NOT_FOUND + ); + assert_eq!(handle, Handle::default()); + + let attrs = object_attributes( + &known_dlls_name, + ObjectAttributesFlags::CASE_INSENSITIVE.bits(), + ); + handle = Handle::from_raw(0x5555_5555); + assert_eq!( + task.sys_nt_open_section(mut_ptr(&mut handle), 0x0d, Some(const_ptr(&attrs))), + NtStatus::OBJECT_TYPE_MISMATCH + ); + assert_eq!(handle, Handle::default()); + + let attrs = object_attributes_with_root( + &kernel32, + Handle::from_raw(0x1234), + ObjectAttributesFlags::CASE_INSENSITIVE.bits(), + ); + handle = Handle::from_raw(0x5555_5555); + assert_eq!( + task.sys_nt_open_section(mut_ptr(&mut handle), 0x0d, Some(const_ptr(&attrs))), + NtStatus::INVALID_HANDLE + ); + assert_eq!(handle, Handle::default()); + + handle = Handle::from_raw(0x5555_5555); + assert_eq!( + task.sys_nt_open_section(mut_ptr(&mut handle), 0x0d, None), + NtStatus::INVALID_PARAMETER + ); + assert_eq!(handle, Handle::default()); + + assert_eq!( + task.sys_nt_open_section(null_mut_ptr::(), 0x0d, Some(const_ptr(&attrs))), + NtStatus::ACCESS_VIOLATION + ); + + assert_eq!(task.sys_nt_close(known_dlls), NtStatus::SUCCESS); + }); + } + #[test] fn create_directory_distinguishes_null_object_name_from_empty_name() { run_with_test_platform_pointers(|| { diff --git a/litebox_shim_windows/src/syscalls/mm.rs b/litebox_shim_windows/src/syscalls/mm.rs index f67931a198..000e2ea4f6 100644 --- a/litebox_shim_windows/src/syscalls/mm.rs +++ b/litebox_shim_windows/src/syscalls/mm.rs @@ -40,7 +40,7 @@ bitflags::bitflags! { } impl PageProtection { - const BASE_MASK: u32 = 0xff; + pub(super) const BASE_MASK: u32 = 0xff; fn base(self) -> u32 { self.bits() & Self::BASE_MASK @@ -1065,7 +1065,9 @@ fn mark_pages_decommitted( allocation.pages.remove(base..end); } -fn parse_page_protection(protect: u32) -> Option<(PageProtection, MemoryRegionPermissions)> { +pub(super) fn parse_page_protection( + protect: u32, +) -> Option<(PageProtection, MemoryRegionPermissions)> { let protect = PageProtection::from_bits(protect)?; let permissions = page_protect_to_permissions(protect)?; Some((protect, permissions)) @@ -1124,7 +1126,7 @@ fn permissions_to_page_protect(permissions: MemoryRegionPermissions) -> PageProt } } -fn create_pages( +pub(super) fn create_pages( page_manager: &WindowsPageManager, suggested_address: Option>, length: NonZeroPageSize, diff --git a/litebox_shim_windows/src/syscalls/mod.rs b/litebox_shim_windows/src/syscalls/mod.rs index ced9039d02..1e3bda7d5f 100644 --- a/litebox_shim_windows/src/syscalls/mod.rs +++ b/litebox_shim_windows/src/syscalls/mod.rs @@ -9,6 +9,7 @@ pub(crate) mod mm; pub(crate) mod nls; pub(crate) mod process; pub(crate) mod registry; +pub(crate) mod section; pub(crate) mod symlink; mod sysinfo; pub(crate) mod thread; @@ -143,6 +144,11 @@ pub(crate) enum SyscallRequest { desired_access: u32, object_attributes: Option>, }, + NtOpenSection { + section_handle: Platform::RawMutPointer, + desired_access: u32, + object_attributes: Option>, + }, NtQueryDirectoryObject { directory_handle: Handle, buffer: Platform::RawMutPointer, @@ -174,6 +180,26 @@ pub(crate) enum SyscallRequest { object_attributes: Option>, number_of_concurrent_threads: u32, }, + NtCreateSection { + section_handle: Platform::RawMutPointer, + desired_access: u32, + object_attributes: Option>, + maximum_size: Option>, + section_page_protection: u32, + allocation_attributes: u32, + file_handle: Handle, + }, + NtCreateSectionEx { + section_handle: Platform::RawMutPointer, + desired_access: u32, + object_attributes: Option>, + maximum_size: Option>, + section_page_protection: u32, + allocation_attributes: u32, + file_handle: Handle, + extended_parameters: Option>, + extended_parameter_count: u32, + }, NtCreateWaitCompletionPacket { wait_completion_packet_handle: Platform::RawMutPointer, desired_access: u32, @@ -346,6 +372,13 @@ pub(crate) enum SyscallRequest { system_information_length: u32, return_length: Option>, }, + NtQuerySection { + section_handle: Handle, + section_information_class: u32, + section_information: Platform::RawMutPointer, + section_information_length: usize, + return_length: Option>, + }, NtQueryInformationProcess { process_handle: ProcessHandle, process_information_class: u32, @@ -422,6 +455,41 @@ pub(crate) enum SyscallRequest { memory_information_length: usize, return_length: Option>, }, + NtMapViewOfSection { + section_handle: Handle, + process_handle: ProcessHandle, + base_address: Platform::RawMutPointer, + zero_bits: usize, + commit_size: usize, + section_offset: Option>, + view_size: Platform::RawMutPointer, + inherit_disposition: u32, + allocation_type: u32, + page_protection: u32, + }, + NtMapViewOfSectionEx { + section_handle: Handle, + process_handle: ProcessHandle, + base_address: Platform::RawMutPointer, + zero_bits: usize, + commit_size: usize, + section_offset: Option>, + view_size: Platform::RawMutPointer, + inherit_disposition: u32, + allocation_type: u32, + page_protection: u32, + extended_parameters: Option>, + extended_parameter_count: u32, + }, + NtUnmapViewOfSection { + process_handle: ProcessHandle, + base_address: usize, + }, + NtUnmapViewOfSectionEx { + process_handle: ProcessHandle, + base_address: usize, + flags: u32, + }, NtTerminateProcess { process_handle: ProcessHandle, exit_status: i32, @@ -434,7 +502,7 @@ impl SyscallRequest { pub(crate) fn try_from_raw(pt_regs: &litebox_common_linux::PtRegs) -> Option { macro_rules! sys_req { ($id:ident { $( $field:ident $(:$star:tt)? ),* $(,)? }) => { - sys_req!(@[$id] [ $( $field $(:$star)? ),* ] [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] [ ]) + sys_req!(@[$id] [ $( $field $(:$star)? ),* ] [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ] [ ]) }; (@[$id:ident] [ $f:ident $(,)? $($field:ident $(:$star:tt)?),* ] [ $n:literal $(,)? $($ns:literal),* ] [ $($tail:tt)* ]) => { sys_req!(@[$id] [ $( $field $(:$star)? ),* ] [ $($ns),* ] [ $($tail)* $f: win_sys_req_arg::(pt_regs, $n)?, ]) @@ -478,8 +546,13 @@ impl SyscallRequest { desired_access, object_attributes:*, })), + NtSysno::NtOpenSection => Some(sys_req!(NtOpenSection { + section_handle:*, + desired_access, + object_attributes:*, + })), NtSysno::NtQueryDirectoryObject => Some(sys_req!(NtQueryDirectoryObject { - directory_handle:{Handle::from_raw}, + directory_handle:{ Handle::from_raw }, buffer:*, buffer_length, return_single_entry, @@ -509,6 +582,26 @@ impl SyscallRequest { object_attributes:*, number_of_concurrent_threads, })), + NtSysno::NtCreateSection => Some(sys_req!(NtCreateSection { + section_handle:*, + desired_access, + object_attributes:*, + maximum_size:*, + section_page_protection, + allocation_attributes, + file_handle:{ Handle::from_raw }, + })), + NtSysno::NtCreateSectionEx => Some(sys_req!(NtCreateSectionEx { + section_handle:*, + desired_access, + object_attributes:*, + maximum_size:*, + section_page_protection, + allocation_attributes, + file_handle:{ Handle::from_raw }, + extended_parameters:*, + extended_parameter_count, + })), NtSysno::NtCreateWaitCompletionPacket => Some(sys_req!( NtCreateWaitCompletionPacket { wait_completion_packet_handle:*, @@ -687,6 +780,13 @@ impl SyscallRequest { system_information_length, return_length:*, })), + NtSysno::NtQuerySection => Some(sys_req!(NtQuerySection { + section_handle: { Handle::from_raw }, + section_information_class, + section_information:*, + section_information_length, + return_length:*, + })), NtSysno::NtQueryInformationProcess => Some(sys_req!(NtQueryInformationProcess { process_handle: { ProcessHandle::from_raw }, process_information_class, @@ -765,6 +865,41 @@ impl SyscallRequest { memory_information_length, return_length:*, })), + NtSysno::NtMapViewOfSection => Some(sys_req!(NtMapViewOfSection { + section_handle: { Handle::from_raw }, + process_handle: { ProcessHandle::from_raw }, + base_address:*, + zero_bits, + commit_size, + section_offset:*, + view_size:*, + inherit_disposition, + allocation_type, + page_protection, + })), + NtSysno::NtMapViewOfSectionEx => Some(sys_req!(NtMapViewOfSectionEx { + section_handle: { Handle::from_raw }, + process_handle: { ProcessHandle::from_raw }, + base_address:*, + zero_bits, + commit_size, + section_offset:*, + view_size:*, + inherit_disposition, + allocation_type, + page_protection, + extended_parameters:*, + extended_parameter_count, + })), + NtSysno::NtUnmapViewOfSection => Some(sys_req!(NtUnmapViewOfSection { + process_handle: { ProcessHandle::from_raw }, + base_address, + })), + NtSysno::NtUnmapViewOfSectionEx => Some(sys_req!(NtUnmapViewOfSectionEx { + process_handle: { ProcessHandle::from_raw }, + base_address, + flags, + })), NtSysno::NtTerminateProcess => Some(sys_req!(NtTerminateProcess { process_handle: { ProcessHandle::from_raw }, exit_status, diff --git a/litebox_shim_windows/src/syscalls/section.rs b/litebox_shim_windows/src/syscalls/section.rs new file mode 100644 index 0000000000..7d770e83cb --- /dev/null +++ b/litebox_shim_windows/src/syscalls/section.rs @@ -0,0 +1,1617 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +use alloc::string::String; +use alloc::sync::Arc; +use core::marker::PhantomData; +use core::mem::size_of; +use core::sync::atomic::{AtomicBool, Ordering}; + +use int_enum::IntEnum; +use litebox::fd::{FdEnabledSubsystem, FdEnabledSubsystemEntry}; +use litebox::mm::linux::{CreatePagesFlags, NonZeroPageSize}; +use litebox::platform::page_mgmt::MemoryRegionPermissions; +use litebox::platform::{RawConstPointer as _, RawMutPointer as _}; +use litebox_common_windows::nt_status::NtStatus; +use rangemap::RangeMap; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +use crate::nt_types::{AccessMask, ObjectAttributes}; +use crate::syscalls::mm::{MemoryType, PageProtection, create_pages, parse_page_protection}; +use crate::syscalls::{Handle, ProcessHandle}; +use crate::{ConstPtr, MutPtr, PAGE_SIZE, ShimFS, ShimPlatform, Task, WindowsSectionView}; + +const VIEW_SHARE: u32 = 1; +const VIEW_UNMAP: u32 = 2; +const MEM_TOP_DOWN: u32 = 0x0010_0000; +const MEM_PHYSICAL: u32 = 0x0040_0000; +const MEM_DIFFERENT_IMAGE_BASE_OK: u32 = 0x0080_0000; +const SUPPORTED_MAP_ALLOCATION_TYPES: u32 = + MEM_TOP_DOWN | MEM_PHYSICAL | MEM_DIFFERENT_IMAGE_BASE_OK; + +enum SectionBacking { + /// LiteBox lacks shared anonymous backing, so a pagefile section is + /// metadata-only until its single allowed view is mapped. Remap after unmap + /// is rejected instead of storing contents in shim memory or a file. + /// + /// Shared write-through backing across concurrent views is the deferred + /// capability (see `TODO(section-subsystem)`); until it lands, a single view + /// is the only observable-faithful case, which is why both the + /// second-concurrent-view and the remap-after-unmap rejects exist. They are + /// one missing feature, not two unrelated limitations. + Pagefile, + ImageFile, +} + +pub(crate) struct SectionSubsystem(PhantomData); + +impl FdEnabledSubsystem for SectionSubsystem { + type Entry = SectionHandleObject; +} + +impl FdEnabledSubsystemEntry for SectionHandleObject {} + +pub(crate) struct SectionHandleObject { + section: Arc>, + granted_access: SectionAccess, +} + +pub(crate) struct SectionObject { + fs_path: Option, + size: usize, + attributes: SectionAllocationAttributes, + protection: PageProtection, + backing: SectionBacking, + pagefile_view_active: AtomicBool, + _platform: PhantomData, +} + +pub(crate) struct MapViewOfSectionParameters { + pub(crate) section_handle: Handle, + pub(crate) process_handle: ProcessHandle, + pub(crate) base_address: MutPtr, + pub(crate) zero_bits: usize, + pub(crate) commit_size: usize, + pub(crate) section_offset: Option>, + pub(crate) view_size: MutPtr, + pub(crate) inherit_disposition: u32, + pub(crate) allocation_type: u32, + pub(crate) page_protection: u32, +} + +bitflags::bitflags! { + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + struct SectionAllocationAttributes: u32 { + const SEC_FILE = 0x0080_0000; + const SEC_IMAGE = 0x0100_0000; + const SEC_RESERVE = 0x0400_0000; + const SEC_COMMIT = 0x0800_0000; + } +} + +bitflags::bitflags! { + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + struct SectionAccess: u32 { + const QUERY = 0x0001; + const MAP_WRITE = 0x0002; + const MAP_READ = 0x0004; + const MAP_EXECUTE = 0x0008; + const EXTEND_SIZE = 0x0010; + const MAP_EXECUTE_EXPLICIT = 0x0020; + + const GENERIC_READ_EXPANSION = AccessMask::STANDARD_RIGHTS_READ.bits() + | Self::QUERY.bits() + | Self::MAP_READ.bits(); + const GENERIC_WRITE_EXPANSION = AccessMask::STANDARD_RIGHTS_WRITE.bits() + | Self::MAP_WRITE.bits() + | Self::EXTEND_SIZE.bits(); + const GENERIC_EXECUTE_EXPANSION = AccessMask::STANDARD_RIGHTS_EXECUTE.bits() + | Self::MAP_EXECUTE.bits(); + const ALL_ACCESS = AccessMask::STANDARD_RIGHTS_ALL.bits() + | Self::QUERY.bits() + | Self::MAP_WRITE.bits() + | Self::MAP_READ.bits() + | Self::MAP_EXECUTE.bits() + | Self::EXTEND_SIZE.bits(); + const GENERIC_ALL = AccessMask::GENERIC_ALL.bits(); + const GENERIC_EXECUTE = AccessMask::GENERIC_EXECUTE.bits(); + const GENERIC_WRITE = AccessMask::GENERIC_WRITE.bits(); + const GENERIC_READ = AccessMask::GENERIC_READ.bits(); + + const _ = !0; + } +} + +bitflags::bitflags! { + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + struct UnmapViewOfSectionFlags: u32 { + const _ = 0; + } +} + +impl SectionAccess { + fn from_desired_access(desired_access: u32) -> Self { + let mut access = Self::from_bits_retain(desired_access); + if access.contains(Self::GENERIC_READ) { + access.remove(Self::GENERIC_READ); + access.insert(Self::GENERIC_READ_EXPANSION); + } + if access.contains(Self::GENERIC_WRITE) { + access.remove(Self::GENERIC_WRITE); + access.insert(Self::GENERIC_WRITE_EXPANSION); + } + if access.contains(Self::GENERIC_EXECUTE) { + access.remove(Self::GENERIC_EXECUTE); + access.insert(Self::GENERIC_EXECUTE_EXPANSION); + } + if access.contains(Self::GENERIC_ALL) { + access.remove(Self::GENERIC_ALL); + access.insert(Self::ALL_ACCESS); + } + access + } + + fn require(self, required: Self) -> Result<(), NtStatus> { + if self.contains(required) { + Ok(()) + } else { + Err(NtStatus::ACCESS_DENIED) + } + } +} + +#[repr(u32)] +#[derive(Clone, Copy, Debug, Eq, IntEnum, PartialEq)] +enum SectionInformationClass { + Basic = 0, + Image = 1, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, FromBytes, Immutable, IntoBytes)] +struct SectionBasicInformation { + base_address: usize, + attributes: u32, + _padding: u32, + size: i64, +} + +const _: () = assert!(size_of::() == 24); + +#[repr(C)] +#[derive(Clone, Copy, Debug, FromBytes, Immutable, IntoBytes)] +struct SectionImageInformation { + transfer_address: usize, + zero_bits: u32, + _padding0: u32, + maximum_stack_size: usize, + committed_stack_size: usize, + subsystem_type: u32, + subsystem_minor_version: u16, + subsystem_major_version: u16, + gp_value: u32, + image_characteristics: u16, + dll_characteristics: u16, + machine: u16, + image_contains_code: u8, + image_flags: u8, + loader_flags: u32, + image_file_size: u32, + checksum: u32, +} + +const _: () = assert!(size_of::() == 64); + +impl Task { + fn section_entry( + &self, + handle: Handle, + ) -> Result>, NtStatus> { + self.typed_handle_entry::>(handle) + } + + fn insert_section_handle( + &self, + section: Arc>, + granted_access: SectionAccess, + ) -> Result { + self.insert_typed_handle::>( + SectionHandleObject { + section, + granted_access, + }, + drop, + ) + } + + pub(crate) fn close_section_handle(&self, handle: Handle) { + self.close_typed_handle::>(handle, drop); + } + + pub(crate) fn close_section(section: SectionHandleObject) { + drop(section); + } + + #[expect( + clippy::too_many_arguments, + reason = "NtCreateSection has seven ABI parameters; keeping ABI args explicit avoids reshuffling" + )] + pub(crate) fn sys_nt_create_section( + &self, + section_handle: MutPtr, + desired_access: u32, + object_attributes: Option>, + maximum_size: Option>, + section_page_protection: u32, + allocation_attributes: u32, + file_handle: Handle, + ) -> NtStatus { + // Host ntdll preserves the output handle for pre-creation validation failures such as a + // NULL MaximumSize pagefile section. + if let Err(status) = + crate::probe_guest_output_preserving_value::(section_handle) + { + return status; + } + let granted_access = SectionAccess::from_desired_access(desired_access); + if granted_access.is_empty() { + return NtStatus::ACCESS_DENIED; + } + let Some((protection, _)) = parse_page_protection(section_page_protection) else { + return NtStatus::INVALID_PAGE_PROTECTION; + }; + // NtCreateSection currently supports only pagefile-backed sections. File-backed image + // sections are synthesized by NtOpenSection for KnownDlls; accepting a file handle here + // requires section lifetime/sharing to be keyed by the underlying file object identity. + if !file_handle.is_null() { + litebox_util_log::debug!( + file_handle = file_handle.as_raw(), + allocation_attributes:% = format_args!("{allocation_attributes:#x}"), + section_page_protection:% = format_args!("{section_page_protection:#x}"), + desired_access:% = format_args!("{desired_access:#x}"); + "Unsupported file-backed NtCreateSection" + ); + return NtStatus::INVALID_HANDLE; + } + let allocation_attributes = + SectionAllocationAttributes::from_bits_retain(allocation_attributes); + let supported_create_attributes = + SectionAllocationAttributes::SEC_RESERVE | SectionAllocationAttributes::SEC_COMMIT; + if !allocation_attributes + .difference(supported_create_attributes) + .is_empty() + { + return NtStatus::INVALID_PARAMETER; + } + if !allocation_attributes.intersects(supported_create_attributes) { + return NtStatus::INVALID_PARAMETER; + } + + let Some(maximum_size) = maximum_size else { + return NtStatus::INVALID_PARAMETER_4; + }; + let maximum_size = match maximum_size.read_at_offset(0) { + Some(value) if value > 0 => value, + Some(_) => return NtStatus::INVALID_PARAMETER, + None => return NtStatus::ACCESS_VIOLATION, + }; + let Ok(size) = usize::try_from(maximum_size) else { + return NtStatus::SECTION_TOO_BIG; + }; + let Some(size) = size.checked_next_multiple_of(PAGE_SIZE) else { + return NtStatus::SECTION_TOO_BIG; + }; + if NonZeroPageSize::::new(size).is_none() { + return NtStatus::INVALID_PARAMETER; + } + + let name = match self.read_section_name(object_attributes) { + Ok(name) => name, + Err(status) => return status, + }; + let attributes = if allocation_attributes.contains(SectionAllocationAttributes::SEC_RESERVE) + { + SectionAllocationAttributes::SEC_RESERVE + } else { + SectionAllocationAttributes::SEC_COMMIT + }; + let section = Arc::new(SectionObject { + fs_path: None, + size, + attributes, + protection, + backing: SectionBacking::Pagefile, + pagefile_view_active: AtomicBool::new(false), + _platform: PhantomData, + }); + if let Some(name) = &name { + let status = self.insert_named_section(name, §ion); + if status != NtStatus::SUCCESS { + return status; + } + } + self.publish_section_handle(section_handle, section, granted_access) + } + + #[expect( + clippy::too_many_arguments, + reason = "NtCreateSectionEx extends NtCreateSection with two ABI parameters" + )] + pub(crate) fn sys_nt_create_section_ex( + &self, + section_handle: MutPtr, + desired_access: u32, + object_attributes: Option>, + maximum_size: Option>, + section_page_protection: u32, + allocation_attributes: u32, + file_handle: Handle, + extended_parameters: Option>, + extended_parameter_count: u32, + ) -> NtStatus { + if extended_parameters.is_some() || extended_parameter_count != 0 { + return NtStatus::INVALID_PARAMETER; + } + self.sys_nt_create_section( + section_handle, + desired_access, + object_attributes, + maximum_size, + section_page_protection, + allocation_attributes, + file_handle, + ) + } + + pub(crate) fn sys_nt_open_section( + &self, + section_handle: MutPtr, + desired_access: u32, + object_attributes: Option>, + ) -> NtStatus { + // Host ntdll zeroes the output handle before resolving a missing section name. + if section_handle + .write_at_offset(0, Handle::default()) + .is_none() + { + return NtStatus::ACCESS_VIOLATION; + } + let granted_access = SectionAccess::from_desired_access(desired_access); + if granted_access.is_empty() { + return NtStatus::ACCESS_DENIED; + } + if object_attributes.is_none() { + return NtStatus::INVALID_PARAMETER; + } + let name = match self.read_required_section_name(object_attributes) { + Ok(name) => name, + Err(status) => return status, + }; + if self + .process + .directory_namespace + .resolve_object(&name) + .is_ok() + { + return NtStatus::OBJECT_TYPE_MISMATCH; + } + if let Some(section) = self.named_section(&name) { + return self.publish_section_handle(section_handle, section, granted_access); + } + let Some(fs_path) = known_dll_section_fs_path(&name) else { + return section_missing_status( + self.process + .directory_namespace + .parent_directory_exists(&name), + ); + }; + let Ok(file_status) = self.fs.file_status(&fs_path) else { + return NtStatus::OBJECT_NAME_NOT_FOUND; + }; + let section = Arc::new(SectionObject { + fs_path: Some(fs_path), + size: file_status.size, + attributes: SectionAllocationAttributes::SEC_FILE + | SectionAllocationAttributes::SEC_IMAGE, + protection: PageProtection::PAGE_EXECUTE_WRITECOPY, + backing: SectionBacking::ImageFile, + pagefile_view_active: AtomicBool::new(false), + _platform: PhantomData, + }); + self.publish_section_handle(section_handle, section, granted_access) + } + + pub(crate) fn sys_nt_query_section( + &self, + section_handle: Handle, + section_information_class: u32, + section_information: MutPtr, + section_information_length: usize, + return_length: Option>, + ) -> NtStatus { + let Ok(information_class) = SectionInformationClass::try_from(section_information_class) + else { + return NtStatus::INVALID_INFO_CLASS; + }; + let entry = match self.section_entry(section_handle) { + Ok(entry) => entry, + Err(status) => return status, + }; + let result = entry.with_entry(|entry| { + entry + .granted_access + .require(SectionAccess::QUERY) + .map(|()| Arc::clone(&entry.section)) + }); + let section = match result { + Ok(section) => section, + Err(status) => return status, + }; + match information_class { + SectionInformationClass::Basic => write_section_basic_information::( + §ion, + section_information, + section_information_length, + return_length, + ), + SectionInformationClass::Image => write_section_image_information::( + §ion, + Arc::clone(&self.fs), + section_information, + section_information_length, + return_length, + ), + } + } + + pub(crate) fn sys_nt_map_view_of_section( + &self, + request: MapViewOfSectionParameters, + ) -> NtStatus { + if !request.process_handle.is_current() { + return NtStatus::INVALID_HANDLE; + } + let Some(base) = request.base_address.read_at_offset(0) else { + return NtStatus::ACCESS_VIOLATION; + }; + let Some(requested_view_size) = request.view_size.read_at_offset(0) else { + return NtStatus::ACCESS_VIOLATION; + }; + let section_offset = match request.section_offset { + Some(section_offset) => match section_offset.read_at_offset(0) { + Some(value) if value >= 0 => usize::try_from(value).unwrap_or(usize::MAX), + Some(_) => return NtStatus::INVALID_PARAMETER, + None => return NtStatus::ACCESS_VIOLATION, + }, + None => 0, + }; + if base != 0 + || request.zero_bits != 0 + || request.commit_size != 0 + || !section_offset.is_multiple_of(PAGE_SIZE) + || !matches!(request.inherit_disposition, VIEW_SHARE | VIEW_UNMAP) + || request.allocation_type & !SUPPORTED_MAP_ALLOCATION_TYPES != 0 + { + return NtStatus::INVALID_PARAMETER; + } + let Some((page_protection, permissions)) = parse_page_protection(request.page_protection) + else { + return NtStatus::INVALID_PAGE_PROTECTION; + }; + let entry = match self.section_entry(request.section_handle) { + Ok(entry) => entry, + Err(status) => return status, + }; + let result = entry.with_entry(|entry| { + entry + .granted_access + .require(required_map_access(page_protection)) + .map(|()| Arc::clone(&entry.section)) + }); + let section = match result { + Ok(section) => section, + Err(status) => return status, + }; + match section.backing { + SectionBacking::Pagefile => self.map_pagefile_section( + request, + §ion, + requested_view_size, + section_offset, + page_protection, + permissions, + ), + SectionBacking::ImageFile => self.map_image_section(request, §ion, page_protection), + } + } + + pub(crate) fn sys_nt_map_view_of_section_ex( + &self, + request: MapViewOfSectionParameters, + extended_parameters: Option>, + extended_parameter_count: u32, + ) -> NtStatus { + if extended_parameters.is_some() || extended_parameter_count != 0 { + // TODO(section-subsystem): model MEM_EXTENDED_PARAMETER address requirements. + return NtStatus::INVALID_PARAMETER; + } + self.sys_nt_map_view_of_section(request) + } + + pub(crate) fn sys_nt_unmap_view_of_section( + &self, + process_handle: ProcessHandle, + base_address: usize, + ) -> NtStatus { + if !process_handle.is_current() { + return NtStatus::INVALID_HANDLE; + } + let Some((view_base, view)) = self.remove_section_view_for_address(base_address) else { + return NtStatus::NOT_MAPPED_VIEW; + }; + let ptr = MutPtr::::from_usize(view_base); + // SAFETY: Section views are tracked only after this shim successfully creates the pages; + // unmapping consumes the tracked view and removes the exact owned range. + if unsafe { self.global.page_manager.remove_pages(ptr, view.size) }.is_err() { + self.process.section_views.write().insert(view_base, view); + return NtStatus::UNABLE_TO_FREE_VM; + } + self.process.virtual_allocations.write().remove(&view_base); + NtStatus::SUCCESS + } + + pub(crate) fn sys_nt_unmap_view_of_section_ex( + &self, + process_handle: ProcessHandle, + base_address: usize, + flags: u32, + ) -> NtStatus { + let flags = UnmapViewOfSectionFlags::from_bits_retain(flags); + if !flags.is_empty() { + return NtStatus::INVALID_PARAMETER; + } + self.sys_nt_unmap_view_of_section(process_handle, base_address) + } + + fn publish_section_handle( + &self, + section_handle: MutPtr, + section: Arc>, + granted_access: SectionAccess, + ) -> NtStatus { + let handle = match self.insert_section_handle(section, granted_access) { + Ok(handle) => handle, + Err(status) => return status, + }; + if section_handle.write_at_offset(0, handle).is_none() { + self.close_section_handle(handle); + return NtStatus::ACCESS_VIOLATION; + } + NtStatus::SUCCESS + } + + fn insert_named_section(&self, name: &str, section: &Arc>) -> NtStatus { + if self + .process + .directory_namespace + .resolve_object(name) + .is_ok() + { + return NtStatus::OBJECT_NAME_COLLISION; + } + if !self + .process + .directory_namespace + .parent_directory_exists(name) + { + return NtStatus::OBJECT_PATH_NOT_FOUND; + } + let key = section_key(name); + let mut namespace = self.process.section_namespace.write(); + if let Some(existing) = namespace.get(&key) + && existing.upgrade().is_some() + { + return NtStatus::OBJECT_NAME_EXISTS; + } + namespace.insert(key, Arc::downgrade(section)); + NtStatus::SUCCESS + } + + fn named_section(&self, name: &str) -> Option>> { + let key = section_key(name); + let mut namespace = self.process.section_namespace.write(); + let section = namespace.get(&key).and_then(alloc::sync::Weak::upgrade); + if section.is_none() { + namespace.remove(&key); + } + section + } + + fn map_pagefile_section( + &self, + request: MapViewOfSectionParameters, + section: &Arc>, + requested_view_size: usize, + section_offset: usize, + page_protection: PageProtection, + permissions: MemoryRegionPermissions, + ) -> NtStatus { + if section_offset > section.size { + return NtStatus::INVALID_VIEW_SIZE; + } + let remaining = section.size - section_offset; + let view_size = if requested_view_size == 0 { + remaining + } else { + requested_view_size + }; + if view_size == 0 || view_size > remaining { + return NtStatus::INVALID_VIEW_SIZE; + } + let Some(mapped_size) = view_size.checked_next_multiple_of(PAGE_SIZE) else { + return NtStatus::INVALID_VIEW_SIZE; + }; + let Some(length) = NonZeroPageSize::::new(mapped_size) else { + return NtStatus::INVALID_VIEW_SIZE; + }; + match section.backing { + SectionBacking::Pagefile => {} + SectionBacking::ImageFile => return NtStatus::INVALID_FILE_FOR_SECTION, + } + if !pagefile_view_protection_is_compatible(section.protection, page_protection) { + litebox_util_log::debug!( + section_protection:% = format_args!("{:#x}", section.protection.bits()), + page_protection:% = format_args!("{:#x}", page_protection.bits()); + "Rejected pagefile section view protection incompatible with section protection" + ); + return NtStatus::SECTION_PROTECTION; + } + if section.pagefile_view_active.swap(true, Ordering::AcqRel) { + litebox_util_log::debug!( + section_size = section.size, + requested_view_size, + section_offset; + "Rejected additional pagefile section view" + ); + // Host 25H2 allows repeated and simultaneous pagefile views. LiteBox + // returns NOT_SUPPORTED until PageManager has first-class shared + // anonymous backing that avoids kernel-side content storage. + return NtStatus::NOT_SUPPORTED; + } + let Ok(mapping) = create_pages( + &self.global.page_manager, + None, + length, + CreatePagesFlags::empty(), + permissions, + |_| Ok(0), + ) else { + section.pagefile_view_active.store(false, Ordering::Release); + return NtStatus::NO_MEMORY; + }; + let base = mapping.as_usize(); + if request.base_address.write_at_offset(0, base).is_none() + || request.view_size.write_at_offset(0, view_size).is_none() + { + let _ = remove_view_pages::(&self.global.page_manager, base, mapped_size); + section.pagefile_view_active.store(false, Ordering::Release); + return NtStatus::ACCESS_VIOLATION; + } + self.process.section_views.write().insert( + base, + WindowsSectionView { + size: mapped_size, + section_offset, + section: Some(Arc::clone(section)), + }, + ); + self.process.virtual_allocations.write().insert( + base, + crate::WindowsVirtualAllocation { + base, + size: mapped_size, + allocation_protect: section.protection, + type_: MemoryType::MEM_MAPPED, + pages: committed_pages(base, mapped_size, page_protection), + }, + ); + NtStatus::SUCCESS + } + + fn map_image_section( + &self, + request: MapViewOfSectionParameters, + section: &SectionObject, + page_protection: PageProtection, + ) -> NtStatus { + let Some(fs_path) = §ion.fs_path else { + return NtStatus::INVALID_FILE_FOR_SECTION; + }; + if required_map_access(page_protection).contains(SectionAccess::MAP_WRITE) { + litebox_util_log::debug!( + page_protection:% = format_args!("{:#x}", page_protection.bits()), + fs_path:% = fs_path; + "Rejected writable image section view" + ); + // Host 25H2 maps SEC_IMAGE with PAGE_READWRITE/PAGE_EXECUTE_READWRITE successfully + // (NtMapViewOfSection returns STATUS_IMAGE_NOT_AT_BASE in the probe). LiteBox rejects + // TODO(section-subsystem): allow this once image mappings support writable + // copy-on-write/shared image pages. + return NtStatus::SECTION_PROTECTION; + } + let mapping = match crate::loader::load_image_section( + self.global.platform, + Arc::clone(&self.fs), + fs_path, + &self.global.page_manager, + &self.process.virtual_allocations, + ) { + Ok(mapping) => mapping, + Err(crate::loader::WindowsLoadError::Access(_)) => { + return NtStatus::OBJECT_NAME_NOT_FOUND; + } + Err(crate::loader::WindowsLoadError::Load(_)) => return NtStatus::NO_MEMORY, + Err(_) => return NtStatus::INVALID_FILE_FOR_SECTION, + }; + if request + .base_address + .write_at_offset(0, mapping.base_addr) + .is_none() + || request + .view_size + .write_at_offset(0, mapping.image_size) + .is_none() + { + let _ = remove_view_pages::( + &self.global.page_manager, + mapping.base_addr, + mapping.mapping_size, + ); + self.process + .virtual_allocations + .write() + .remove(&mapping.base_addr); + return NtStatus::ACCESS_VIOLATION; + } + self.process.section_views.write().insert( + mapping.base_addr, + WindowsSectionView { + size: mapping.mapping_size, + section_offset: 0, + section: None, + }, + ); + NtStatus::SUCCESS + } + + fn remove_section_view_for_address( + &self, + base_address: usize, + ) -> Option<(usize, WindowsSectionView)> { + let mut views = self.process.section_views.write(); + let (&view_base, view) = views.range(..=base_address).next_back()?; + let view = view.clone(); + let view_end = view_base.checked_add(view.size)?; + if base_address < view_end { + views.remove(&view_base); + Some((view_base, view)) + } else { + None + } + } + + fn read_section_name( + &self, + object_attributes: Option>, + ) -> Result, NtStatus> { + let (_, directory_name) = + self.read_directory_object_attributes(object_attributes, false)?; + Ok(directory_name.map(|name| name.original_path)) + } + + fn read_required_section_name( + &self, + object_attributes: Option>, + ) -> Result { + let (_, Some(directory_name)) = + self.read_directory_object_attributes(object_attributes, true)? + else { + return Err(NtStatus::INVALID_PARAMETER); + }; + Ok(directory_name.original_path) + } +} + +fn section_key(path: &str) -> String { + path.to_ascii_lowercase() +} + +fn section_missing_status(parent_exists: bool) -> NtStatus { + if parent_exists { + NtStatus::OBJECT_NAME_NOT_FOUND + } else { + NtStatus::OBJECT_PATH_NOT_FOUND + } +} + +fn known_dll_section_fs_path(object_path: &str) -> Option { + let (dll_name, fs_directory) = + if let Some(rest) = strip_case_insensitive_prefix(object_path, r"\KnownDlls\") { + (rest, "/Windows/System32/") + } else if let Some(rest) = strip_case_insensitive_prefix(object_path, r"\KnownDlls32\") { + (rest, "/Windows/SysWOW64/") + } else { + return None; + }; + if dll_name.contains(['\\', '/']) || !ends_with_ignore_ascii_case(dll_name, ".dll") { + return None; + } + let mut fs_path = String::from(fs_directory); + fs_path.push_str(&dll_name.to_ascii_lowercase()); + Some(fs_path) +} + +fn strip_case_insensitive_prefix<'a>(value: &'a str, prefix: &str) -> Option<&'a str> { + value + .get(..prefix.len()) + .is_some_and(|head| head.eq_ignore_ascii_case(prefix)) + .then_some(&value[prefix.len()..]) +} + +fn ends_with_ignore_ascii_case(value: &str, suffix: &str) -> bool { + value + .get(value.len().saturating_sub(suffix.len())..) + .is_some_and(|tail| tail.eq_ignore_ascii_case(suffix)) +} + +fn required_map_access(protection: PageProtection) -> SectionAccess { + let base = protection.bits() & PageProtection::BASE_MASK; + if base == PageProtection::PAGE_NOACCESS.bits() { + SectionAccess::MAP_READ + } else if matches!( + base, + value if value == PageProtection::PAGE_READWRITE.bits() + || value == PageProtection::PAGE_EXECUTE_READWRITE.bits() + ) { + SectionAccess::MAP_WRITE + } else if matches!( + base, + value if value == PageProtection::PAGE_EXECUTE.bits() + || value == PageProtection::PAGE_EXECUTE_READ.bits() + || value == PageProtection::PAGE_EXECUTE_WRITECOPY.bits() + ) { + SectionAccess::MAP_EXECUTE + } else { + SectionAccess::MAP_READ + } +} + +fn pagefile_view_protection_is_compatible( + section_protection: PageProtection, + view_protection: PageProtection, +) -> bool { + let view_base = view_protection.bits() & PageProtection::BASE_MASK; + if view_base == PageProtection::PAGE_NOACCESS.bits() { + return true; + } + + if page_protection_has_read(view_protection) && !page_protection_has_read(section_protection) { + return false; + } + if page_protection_has_direct_write(view_protection) + && !page_protection_has_direct_write(section_protection) + { + return false; + } + if page_protection_has_execute(view_protection) + && !page_protection_has_execute(section_protection) + { + return false; + } + true +} + +fn page_protection_has_read(protection: PageProtection) -> bool { + matches!( + protection.bits() & PageProtection::BASE_MASK, + value if value == PageProtection::PAGE_READONLY.bits() + || value == PageProtection::PAGE_READWRITE.bits() + || value == PageProtection::PAGE_WRITECOPY.bits() + || value == PageProtection::PAGE_EXECUTE_READ.bits() + || value == PageProtection::PAGE_EXECUTE_READWRITE.bits() + || value == PageProtection::PAGE_EXECUTE_WRITECOPY.bits() + ) +} + +fn page_protection_has_direct_write(protection: PageProtection) -> bool { + matches!( + protection.bits() & PageProtection::BASE_MASK, + value if value == PageProtection::PAGE_READWRITE.bits() + || value == PageProtection::PAGE_EXECUTE_READWRITE.bits() + ) +} + +fn page_protection_has_execute(protection: PageProtection) -> bool { + matches!( + protection.bits() & PageProtection::BASE_MASK, + value if value == PageProtection::PAGE_EXECUTE.bits() + || value == PageProtection::PAGE_EXECUTE_READ.bits() + || value == PageProtection::PAGE_EXECUTE_READWRITE.bits() + || value == PageProtection::PAGE_EXECUTE_WRITECOPY.bits() + ) +} + +fn write_section_basic_information( + section: &SectionObject, + section_information: MutPtr, + section_information_length: usize, + return_length: Option>, +) -> NtStatus { + let required_len = size_of::(); + if section_information_length < required_len { + return NtStatus::INFO_LENGTH_MISMATCH; + } + let Ok(size) = i64::try_from(section.size) else { + return NtStatus::SECTION_TOO_BIG; + }; + let info = SectionBasicInformation { + base_address: 0, + attributes: section.attributes.bits(), + _padding: 0, + size, + }; + let output = + MutPtr::::from_usize(section_information.as_usize()); + if output.write_at_offset(0, info).is_none() { + return NtStatus::ACCESS_VIOLATION; + } + if let Some(return_length) = return_length + && return_length.write_at_offset(0, required_len).is_none() + { + return NtStatus::ACCESS_VIOLATION; + } + NtStatus::SUCCESS +} + +fn write_section_image_information( + section: &SectionObject, + fs: Arc, + section_information: MutPtr, + section_information_length: usize, + return_length: Option>, +) -> NtStatus { + if !matches!(section.backing, SectionBacking::ImageFile) { + return NtStatus::SECTION_NOT_IMAGE; + } + let required_len = size_of::(); + if section_information_length < required_len { + return NtStatus::INFO_LENGTH_MISMATCH; + } + let Some(fs_path) = §ion.fs_path else { + return NtStatus::INVALID_FILE_FOR_SECTION; + }; + let metadata = match crate::loader::image_section_metadata(fs, fs_path) { + Ok(metadata) => metadata, + Err(crate::loader::WindowsLoadError::Access(_)) => return NtStatus::OBJECT_NAME_NOT_FOUND, + Err(_) => return NtStatus::INVALID_FILE_FOR_SECTION, + }; + // Host ntdll reports ReturnLength=64 for SectionImageInformation on x64; the public + // winternl.h layout ends at CheckSum and has no trailing extension fields. + let info = SectionImageInformation { + transfer_address: metadata.transfer_address, + zero_bits: 0, + _padding0: 0, + maximum_stack_size: 0, + committed_stack_size: 0, + subsystem_type: metadata.subsystem, + subsystem_minor_version: metadata.subsystem_minor_version, + subsystem_major_version: metadata.subsystem_major_version, + gp_value: 0, + image_characteristics: metadata.image_characteristics, + dll_characteristics: metadata.dll_characteristics, + machine: metadata.machine, + image_contains_code: 1, + image_flags: 0, + loader_flags: 0, + image_file_size: metadata.file_size, + checksum: 0, + }; + let output = + MutPtr::::from_usize(section_information.as_usize()); + if output.write_at_offset(0, info).is_none() { + return NtStatus::ACCESS_VIOLATION; + } + if let Some(return_length) = return_length + && return_length.write_at_offset(0, required_len).is_none() + { + return NtStatus::ACCESS_VIOLATION; + } + NtStatus::SUCCESS +} + +fn committed_pages( + base: usize, + size: usize, + protect: PageProtection, +) -> RangeMap { + let mut pages = RangeMap::new(); + if let Some(end) = base.checked_add(size) { + pages.insert(base..end, protect); + } + pages +} + +fn remove_view_pages( + page_manager: &crate::WindowsPageManager, + base: usize, + size: usize, +) -> Result<(), ()> { + let ptr = MutPtr::::from_usize(base); + // SAFETY: The caller passes a section view range created by this module and not yet exposed, + // or a tracked view being rolled back after output write failure. + unsafe { page_manager.remove_pages(ptr, size) }.map_err(|_| ()) +} + +#[cfg(test)] +mod tests { + extern crate std; + + use core::mem::{size_of, size_of_val}; + + use litebox::platform::RawMutPointer as _; + use litebox_common_windows::nt_status::NtStatus; + + use super::*; + use crate::nt_types::{ObjectAttributes, UnicodeString}; + use crate::tests::{TestFS, TestPlatform, const_ptr, mut_byte_ptr, mut_ptr, test_task}; + + #[cfg(all(target_os = "windows", target_arch = "x86_64"))] + const IMAGE_FILE_MACHINE_AMD64: u16 = 0x8664; + #[cfg(all(target_os = "windows", target_arch = "x86_64"))] + const IMAGE_SUBSYSTEM_WINDOWS_CUI: u32 = 3; + + fn wide(value: &str) -> alloc::vec::Vec { + value.encode_utf16().collect() + } + + fn unicode(value: &[u16]) -> UnicodeString { + UnicodeString { + length: u16::try_from(size_of_val(value)).unwrap(), + maximum_length: u16::try_from(size_of_val(value)).unwrap(), + padding_0: [0; 4], + buffer: value.as_ptr() as usize, + } + } + + fn object_attributes(name: &UnicodeString) -> ObjectAttributes { + ObjectAttributes { + length: u32::try_from(size_of::()).unwrap(), + root_directory: Handle::from_raw(0), + object_name: core::ptr::from_ref(name) as usize, + attributes: 0, + security_descriptor: 0, + security_quality_of_service: 0, + } + } + + fn create_pagefile_section( + task: &Task, + access: u32, + size: i64, + protection: PageProtection, + ) -> Handle { + let mut handle = Handle::default(); + assert_eq!( + task.sys_nt_create_section( + mut_ptr(&mut handle), + access, + None, + Some(const_ptr(&size)), + protection.bits(), + SectionAllocationAttributes::SEC_COMMIT.bits(), + Handle::default(), + ), + NtStatus::SUCCESS + ); + handle + } + + fn map_pagefile_section(task: &Task, handle: Handle) -> (usize, usize) { + let mut base = 0usize; + let mut view_size = 0usize; + assert_eq!( + task.sys_nt_map_view_of_section(MapViewOfSectionParameters { + section_handle: handle, + process_handle: ProcessHandle::CURRENT, + base_address: mut_ptr(&mut base), + zero_bits: 0, + commit_size: 0, + section_offset: None, + view_size: mut_ptr(&mut view_size), + inherit_disposition: VIEW_SHARE, + allocation_type: 0, + page_protection: PageProtection::PAGE_READWRITE.bits(), + }), + NtStatus::SUCCESS + ); + (base, view_size) + } + + #[cfg(all(target_os = "windows", target_arch = "x86_64"))] + fn host_kernel32_image() -> std::vec::Vec { + let system_root = std::env::var_os("SystemRoot").expect("SystemRoot is set on Windows"); + std::fs::read( + std::path::PathBuf::from(system_root) + .join("System32") + .join("kernel32.dll"), + ) + .expect("host kernel32.dll is readable") + } + + #[test] + fn nt_create_section_creates_queryable_pagefile_section() { + let task = test_task(); + let handle = create_pagefile_section( + &task, + SectionAccess::ALL_ACCESS.bits(), + 0x2345, + PageProtection::PAGE_READWRITE, + ); + let mut info = SectionBasicInformation { + base_address: usize::MAX, + attributes: u32::MAX, + _padding: u32::MAX, + size: -1, + }; + let mut return_length = 0usize; + + assert_eq!( + task.sys_nt_query_section( + handle, + SectionInformationClass::Basic as u32, + mut_byte_ptr(&mut info), + size_of::(), + Some(mut_ptr(&mut return_length)), + ), + NtStatus::SUCCESS + ); + assert_eq!(return_length, size_of::()); + assert_eq!(info.base_address, 0); + assert_eq!( + info.attributes, + SectionAllocationAttributes::SEC_COMMIT.bits() + ); + assert_eq!(info.size, 0x3000); + + let mut too_small = [0xcc; size_of::() - 1]; + let too_small_len = too_small.len(); + return_length = 0x5555_5555; + // Host 25H2 leaves ReturnLength untouched on INFO_LENGTH_MISMATCH + // (Basic len=23 -> ret stays sentinel) and writes 0x18 only on success. + assert_eq!( + task.sys_nt_query_section( + handle, + SectionInformationClass::Basic as u32, + mut_byte_ptr(&mut too_small), + too_small_len, + Some(mut_ptr(&mut return_length)), + ), + NtStatus::INFO_LENGTH_MISMATCH + ); + assert_eq!(return_length, 0x5555_5555); + } + + #[cfg(all(target_os = "windows", target_arch = "x86_64"))] + #[test] + fn nt_query_section_image_information_uses_pe_headers() { + let image = host_kernel32_image(); + let task = + crate::tests::test_task_with_nls_files(&[("/Windows/System32/kernel32.dll", &image)]); + let name = wide(r"\KnownDlls\kernel32.dll"); + let unicode = unicode(&name); + let attrs = object_attributes(&unicode); + let mut handle = Handle::default(); + assert_eq!( + task.sys_nt_open_section( + mut_ptr(&mut handle), + SectionAccess::QUERY.bits(), + Some(const_ptr(&attrs)), + ), + NtStatus::SUCCESS + ); + + let mut info = ::new_zeroed(); + let mut return_length = 0usize; + assert_eq!( + task.sys_nt_query_section( + handle, + SectionInformationClass::Image as u32, + mut_byte_ptr(&mut info), + size_of::(), + Some(mut_ptr(&mut return_length)), + ), + NtStatus::SUCCESS + ); + + assert_eq!(return_length, size_of::()); + assert_eq!(info.machine, IMAGE_FILE_MACHINE_AMD64); + assert_eq!(info.subsystem_type, IMAGE_SUBSYSTEM_WINDOWS_CUI); + assert_eq!(info.image_contains_code, 1); + assert_eq!(info.image_file_size, u32::try_from(image.len()).unwrap()); + + let mut too_small = [0xcc; size_of::() - 1]; + let too_small_len = too_small.len(); + return_length = 0x5555_5555; + // Host 25H2 leaves ReturnLength untouched on INFO_LENGTH_MISMATCH + // (Image len=63 -> ret stays sentinel) and writes 0x40 only on success. + assert_eq!( + task.sys_nt_query_section( + handle, + SectionInformationClass::Image as u32, + mut_byte_ptr(&mut too_small), + too_small_len, + Some(mut_ptr(&mut return_length)), + ), + NtStatus::INFO_LENGTH_MISMATCH + ); + assert_eq!(return_length, 0x5555_5555); + } + + #[cfg(all(target_os = "windows", target_arch = "x86_64"))] + #[test] + fn image_section_rejects_writable_view_protection() { + let image = host_kernel32_image(); + let task = + crate::tests::test_task_with_nls_files(&[("/Windows/System32/kernel32.dll", &image)]); + let name = wide(r"\KnownDlls\kernel32.dll"); + let unicode = unicode(&name); + let attrs = object_attributes(&unicode); + let mut handle = Handle::default(); + assert_eq!( + task.sys_nt_open_section( + mut_ptr(&mut handle), + SectionAccess::ALL_ACCESS.bits(), + Some(const_ptr(&attrs)), + ), + NtStatus::SUCCESS + ); + + let mut base = 0usize; + let mut view_size = 0usize; + // Host 25H2 maps SEC_IMAGE with PAGE_READWRITE successfully + // (NtMapViewOfSection returns STATUS_IMAGE_NOT_AT_BASE). LiteBox rejects writable image + // views until image mappings are backed by real shared image pages. + assert_eq!( + task.sys_nt_map_view_of_section(MapViewOfSectionParameters { + section_handle: handle, + process_handle: ProcessHandle::CURRENT, + base_address: mut_ptr(&mut base), + zero_bits: 0, + commit_size: 0, + section_offset: None, + view_size: mut_ptr(&mut view_size), + inherit_disposition: VIEW_SHARE, + allocation_type: 0, + page_protection: PageProtection::PAGE_READWRITE.bits(), + }), + NtStatus::SECTION_PROTECTION + ); + assert_eq!(base, 0); + assert_eq!(view_size, 0); + + assert_eq!( + task.sys_nt_map_view_of_section(MapViewOfSectionParameters { + section_handle: handle, + process_handle: ProcessHandle::CURRENT, + base_address: mut_ptr(&mut base), + zero_bits: 0, + commit_size: 0, + section_offset: None, + view_size: mut_ptr(&mut view_size), + inherit_disposition: VIEW_SHARE, + allocation_type: 0, + page_protection: PageProtection::PAGE_EXECUTE_READ.bits(), + }), + NtStatus::SUCCESS + ); + assert_ne!(base, 0); + assert_ne!(view_size, 0); + } + + #[test] + fn section_output_handles_follow_host_probe_contracts() { + let task = test_task(); + let name = wide(r"\KnownDlls\DefinitelyMissingLiteBoxProbe.dll"); + let unicode = unicode(&name); + let attrs = object_attributes(&unicode); + let mut open_handle = Handle::from_raw(0x1111_2222); + assert_eq!( + task.sys_nt_open_section( + mut_ptr(&mut open_handle), + SectionAccess::QUERY.bits(), + Some(const_ptr(&attrs)), + ), + NtStatus::OBJECT_NAME_NOT_FOUND + ); + assert_eq!(open_handle, Handle::default()); + + let mut create_handle = Handle::from_raw(0x3333_4444); + assert_eq!( + task.sys_nt_create_section( + mut_ptr(&mut create_handle), + SectionAccess::ALL_ACCESS.bits(), + None, + None, + PageProtection::PAGE_READWRITE.bits(), + SectionAllocationAttributes::SEC_COMMIT.bits(), + Handle::default(), + ), + NtStatus::INVALID_PARAMETER_4 + ); + assert_eq!(create_handle, Handle::from_raw(0x3333_4444)); + } + + #[test] + fn nt_map_view_of_section_maps_writable_pagefile_section() { + let task = test_task(); + let handle = create_pagefile_section( + &task, + SectionAccess::ALL_ACCESS.bits(), + 0x2000, + PageProtection::PAGE_READWRITE, + ); + let (base, view_size) = map_pagefile_section(&task, handle); + assert_ne!(base, 0); + assert_eq!(view_size, 0x2000); + + let mapped = MutPtr::::from_usize(base); + assert_eq!(mapped.read_at_offset(0), Some(0)); + assert!(mapped.write_at_offset(0, 0xfeed_cafe).is_some()); + assert_eq!(mapped.read_at_offset(0), Some(0xfeed_cafe)); + + assert_eq!( + task.sys_nt_unmap_view_of_section(ProcessHandle::CURRENT, base + 0x100), + NtStatus::SUCCESS + ); + assert_eq!( + task.sys_nt_unmap_view_of_section(ProcessHandle::CURRENT, base), + NtStatus::NOT_MAPPED_VIEW + ); + } + + #[test] + fn pagefile_map_rejects_protection_incompatible_with_section_protection() { + let task = test_task(); + let readonly = create_pagefile_section( + &task, + SectionAccess::ALL_ACCESS.bits(), + 0x2000, + PageProtection::PAGE_READONLY, + ); + let execute = create_pagefile_section( + &task, + SectionAccess::ALL_ACCESS.bits(), + 0x2000, + PageProtection::PAGE_EXECUTE, + ); + let readwrite = create_pagefile_section( + &task, + SectionAccess::ALL_ACCESS.bits(), + 0x2000, + PageProtection::PAGE_READWRITE, + ); + + for (handle, page_protection) in [ + (readonly, PageProtection::PAGE_READWRITE), + (execute, PageProtection::PAGE_READONLY), + (readwrite, PageProtection::PAGE_EXECUTE_READ), + ] { + let mut base = 0usize; + let mut view_size = 0usize; + assert_eq!( + task.sys_nt_map_view_of_section(MapViewOfSectionParameters { + section_handle: handle, + process_handle: ProcessHandle::CURRENT, + base_address: mut_ptr(&mut base), + zero_bits: 0, + commit_size: 0, + section_offset: None, + view_size: mut_ptr(&mut view_size), + inherit_disposition: VIEW_SHARE, + allocation_type: 0, + page_protection: page_protection.bits(), + }), + NtStatus::SECTION_PROTECTION + ); + assert_eq!(base, 0); + assert_eq!(view_size, 0); + } + } + + #[test] + fn pagefile_map_accepts_compatible_noaccess_and_copy_protections() { + let task = test_task(); + // Host 25H2 and ReactOS allow PAGE_WRITECOPY and PAGE_NOACCESS views of + // a PAGE_READONLY pagefile section. + for page_protection in [ + PageProtection::PAGE_WRITECOPY, + PageProtection::PAGE_NOACCESS, + ] { + let readonly = create_pagefile_section( + &task, + SectionAccess::ALL_ACCESS.bits(), + 0x2000, + PageProtection::PAGE_READONLY, + ); + let mut base = 0usize; + let mut view_size = 0usize; + assert_eq!( + task.sys_nt_map_view_of_section(MapViewOfSectionParameters { + section_handle: readonly, + process_handle: ProcessHandle::CURRENT, + base_address: mut_ptr(&mut base), + zero_bits: 0, + commit_size: 0, + section_offset: None, + view_size: mut_ptr(&mut view_size), + inherit_disposition: VIEW_SHARE, + allocation_type: 0, + page_protection: page_protection.bits(), + }), + NtStatus::SUCCESS + ); + assert_ne!(base, 0); + assert_eq!(view_size, 0x2000); + assert_eq!( + task.sys_nt_unmap_view_of_section(ProcessHandle::CURRENT, base), + NtStatus::SUCCESS + ); + } + } + + #[test] + fn pagefile_noaccess_view_requires_map_read_access() { + let task = test_task(); + let handle = create_pagefile_section( + &task, + SectionAccess::QUERY.bits(), + 0x2000, + PageProtection::PAGE_READWRITE, + ); + let mut base = 0usize; + let mut view_size = 0usize; + + // Host 25H2 returns STATUS_ACCESS_DENIED for PAGE_NOACCESS maps unless + // the section handle has SECTION_MAP_READ. + assert_eq!( + task.sys_nt_map_view_of_section(MapViewOfSectionParameters { + section_handle: handle, + process_handle: ProcessHandle::CURRENT, + base_address: mut_ptr(&mut base), + zero_bits: 0, + commit_size: 0, + section_offset: None, + view_size: mut_ptr(&mut view_size), + inherit_disposition: VIEW_SHARE, + allocation_type: 0, + page_protection: PageProtection::PAGE_NOACCESS.bits(), + }), + NtStatus::ACCESS_DENIED + ); + assert_eq!(base, 0); + assert_eq!(view_size, 0); + } + + #[test] + fn pagefile_section_rejects_additional_views_across_handles_and_unmap() { + let task = test_task(); + let name = wide(r"\BaseNamedObjects\LiteBoxSingleViewSection"); + let unicode = unicode(&name); + let attrs = object_attributes(&unicode); + let size = 0x2000i64; + let mut handle = Handle::default(); + assert_eq!( + task.sys_nt_create_section( + mut_ptr(&mut handle), + SectionAccess::ALL_ACCESS.bits(), + Some(const_ptr(&attrs)), + Some(const_ptr(&size)), + PageProtection::PAGE_READWRITE.bits(), + SectionAllocationAttributes::SEC_COMMIT.bits(), + Handle::default(), + ), + NtStatus::SUCCESS + ); + let mut opened = Handle::default(); + assert_eq!( + task.sys_nt_open_section( + mut_ptr(&mut opened), + SectionAccess::ALL_ACCESS.bits(), + Some(const_ptr(&attrs)), + ), + NtStatus::SUCCESS + ); + let (first_base, first_size) = map_pagefile_section(&task, handle); + assert_eq!(first_size, 0x2000); + + let mut second_base = 0usize; + let mut second_size = 0usize; + // Host 25H2 permits this second simultaneous pagefile view (STATUS_SUCCESS). LiteBox + // deliberately returns STATUS_NOT_SUPPORTED until shared anonymous backing exists. + assert_eq!( + task.sys_nt_map_view_of_section(MapViewOfSectionParameters { + section_handle: opened, + process_handle: ProcessHandle::CURRENT, + base_address: mut_ptr(&mut second_base), + zero_bits: 0, + commit_size: 0, + section_offset: None, + view_size: mut_ptr(&mut second_size), + inherit_disposition: VIEW_SHARE, + allocation_type: 0, + page_protection: PageProtection::PAGE_READWRITE.bits(), + }), + NtStatus::NOT_SUPPORTED + ); + assert_eq!(second_base, 0); + assert_eq!(second_size, 0); + + assert_eq!( + task.sys_nt_unmap_view_of_section(ProcessHandle::CURRENT, first_base), + NtStatus::SUCCESS + ); + second_base = 0; + second_size = 0; + assert_eq!( + task.sys_nt_map_view_of_section(MapViewOfSectionParameters { + section_handle: opened, + process_handle: ProcessHandle::CURRENT, + base_address: mut_ptr(&mut second_base), + zero_bits: 0, + commit_size: 0, + section_offset: None, + view_size: mut_ptr(&mut second_size), + inherit_disposition: VIEW_SHARE, + allocation_type: 0, + page_protection: PageProtection::PAGE_READWRITE.bits(), + }), + NtStatus::NOT_SUPPORTED + ); + assert_eq!(second_base, 0); + assert_eq!(second_size, 0); + } + + #[test] + fn nt_open_section_opens_existing_named_pagefile_section() { + let task = test_task(); + let name = wide(r"\BaseNamedObjects\LiteBoxNamedSection"); + let unicode = unicode(&name); + let attrs = object_attributes(&unicode); + let size = 0x1000i64; + let mut created = Handle::default(); + assert_eq!( + task.sys_nt_create_section( + mut_ptr(&mut created), + SectionAccess::ALL_ACCESS.bits(), + Some(const_ptr(&attrs)), + Some(const_ptr(&size)), + PageProtection::PAGE_READWRITE.bits(), + SectionAllocationAttributes::SEC_COMMIT.bits(), + Handle::default(), + ), + NtStatus::SUCCESS + ); + + let mut opened = Handle::default(); + assert_eq!( + task.sys_nt_open_section( + mut_ptr(&mut opened), + SectionAccess::QUERY.bits(), + Some(const_ptr(&attrs)), + ), + NtStatus::SUCCESS + ); + assert_ne!(opened, Handle::default()); + assert_ne!(opened, created); + } +} diff --git a/litebox_shim_windows/src/tests.rs b/litebox_shim_windows/src/tests.rs index c76ad3e2af..2583ab0396 100644 --- a/litebox_shim_windows/src/tests.rs +++ b/litebox_shim_windows/src/tests.rs @@ -155,6 +155,8 @@ pub(crate) fn test_task_with_nls_files(nls_files: &[(&str, &[u8])]) -> Task::new(RawDescriptorStorage::new()), directory_namespace, event_namespace: crate::WindowsEventNamespace::::new(BTreeMap::new()), + section_namespace: crate::WindowsSectionNamespace::::new(BTreeMap::new()), + section_views: crate::WindowsSectionViews::::new(BTreeMap::new()), nls_section_mappings: WindowsNlsSectionMappings::::new(BTreeMap::new()), virtual_allocations: crate::WindowsVirtualAllocations::::new( BTreeMap::new(),