diff --git a/litebox_shim_windows/src/lib.rs b/litebox_shim_windows/src/lib.rs index f70dc7f48..055fbbf18 100644 --- a/litebox_shim_windows/src/lib.rs +++ b/litebox_shim_windows/src/lib.rs @@ -1744,6 +1744,15 @@ impl Task { performance_counter, performance_frequency, } => self.sys_nt_query_performance_counter(performance_counter, performance_frequency), + SyscallRequest::NtQueryTimerResolution { + minimum_resolution, + maximum_resolution, + current_resolution, + } => Self::sys_nt_query_timer_resolution( + minimum_resolution, + maximum_resolution, + current_resolution, + ), SyscallRequest::NtQueryLicenseValue { value_name, value_type, diff --git a/litebox_shim_windows/src/syscalls/mod.rs b/litebox_shim_windows/src/syscalls/mod.rs index 51b962964..478c453e3 100644 --- a/litebox_shim_windows/src/syscalls/mod.rs +++ b/litebox_shim_windows/src/syscalls/mod.rs @@ -569,6 +569,11 @@ pub(crate) enum SyscallRequest { performance_counter: Platform::RawMutPointer, performance_frequency: Option>, }, + NtQueryTimerResolution { + minimum_resolution: Platform::RawMutPointer, + maximum_resolution: Platform::RawMutPointer, + current_resolution: Platform::RawMutPointer, + }, NtQueryLicenseValue { value_name: Platform::RawConstPointer, value_type: Platform::RawMutPointer, @@ -1333,6 +1338,11 @@ impl SyscallRequest { performance_counter:*, performance_frequency:*, })), + NtSysno::NtQueryTimerResolution => Some(sys_req!(NtQueryTimerResolution { + minimum_resolution:*, + maximum_resolution:*, + current_resolution:*, + })), NtSysno::NtQueryLicenseValue => Some(sys_req!(NtQueryLicenseValue { value_name:*, value_type:*, diff --git a/litebox_shim_windows/src/syscalls/sysinfo.rs b/litebox_shim_windows/src/syscalls/sysinfo.rs index 1ca1a7ca9..dada8cf75 100644 --- a/litebox_shim_windows/src/syscalls/sysinfo.rs +++ b/litebox_shim_windows/src/syscalls/sysinfo.rs @@ -21,6 +21,7 @@ const QPC_FREQUENCY_HZ: i64 = 1_000_000_000; // They avoid leaking host topology while satisfying Windows CRT/environment // probes that require plausible system-information success outputs. const TIMER_RESOLUTION_100NS: u32 = 156_250; +const MINIMUM_TIMER_RESOLUTION_100NS: u32 = 5_000; const DEFAULT_PHYSICAL_PAGES: u32 = 1024 * 1024; const NUMBER_OF_PROCESSORS: u8 = 1; const PROCESSOR_AFFINITY_MASK: usize = (1usize << NUMBER_OF_PROCESSORS) - 1; @@ -34,6 +35,25 @@ const NUMA_NODE_COUNT: usize = NUMBER_OF_PROCESSORS as usize; const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9; const SYSTEM_VERIFIER_INFORMATION_LENGTH: u32 = 0x90; const SYSTEM_VERIFIER_INFORMATION_LENGTH_USIZE: usize = 0x90; +// Synthetic generation for the immutable feature table. Increment if the table changes. +const FEATURE_CONFIGURATION_CHANGE_STAMP: u64 = 1; +// Feature configurations observed from host ntdll on Windows 11 build 26200. +// This is the subset found in the scanned ID ranges, not an exhaustive feature list. +// Keep entries sorted by feature ID for binary search. +const OBSERVED_FEATURE_CONFIGURATIONS: &[(u32, u32)] = &[ + (58_599_532, 0x2f), + (58_599_550, 0x2f), + (58_599_553, 0x2f), + (58_599_559, 0x2f), + (58_599_563, 0x2f), + (58_599_570, 0x2f), + (58_988_972, 0x2f), + (58_989_002, 0x2f), + (58_989_021, 0x2f), + (58_989_070, 0x64), + (58_989_092, 0x64), + (58_989_177, 0x64), +]; const X64_SYSTEM_RANGE_START: usize = 0xffff_8000_0000_0000; #[repr(C)] @@ -67,6 +87,7 @@ enum SystemInformationClass { LogicalProcessorAndGroup = 107, Flush = 192, HypervisorSharedPage = 197, + FeatureConfiguration = 210, FeatureConfigurationSection = 211, ProcessorFeaturesBitMap = 250, } @@ -83,6 +104,13 @@ enum LogicalProcessorRelationship { All = 0xffff, } +#[repr(u32)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, IntEnum)] +enum FeatureConfigurationType { + Boot = 0, + Runtime = 1, +} + #[repr(C)] #[derive(Clone, Copy, Debug, FromBytes, Immutable, IntoBytes)] struct SystemBasicInformation { @@ -145,6 +173,22 @@ struct SystemProcessorFeaturesBitMapInformation { feature_bits: [u64; 2], } +#[repr(C)] +#[derive(Clone, Copy, Debug, FromBytes, Immutable, IntoBytes)] +struct SystemFeatureConfigurationQuery { + configuration_type: u32, + feature_id: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, FromBytes, Immutable, IntoBytes)] +struct SystemFeatureConfigurationInformation { + change_stamp: u64, + feature_id: u32, + configuration: u32, + variant_payload: u64, +} + #[repr(C)] #[derive(Clone, Copy, Debug, FromBytes, Immutable, IntoBytes)] struct ProcessorRelationship { @@ -324,6 +368,7 @@ impl Task { }, ), SystemInformationClass::LogicalProcessorAndGroup + | SystemInformationClass::FeatureConfiguration | SystemInformationClass::FeatureConfigurationSection => NtStatus::INVALID_INFO_CLASS, }; @@ -372,6 +417,13 @@ impl Task { return_length, ) } + SystemInformationClass::FeatureConfiguration => Self::query_feature_configuration( + input_buffer, + input_buffer_length, + system_information, + system_information_length, + return_length, + ), // TODO: Windows returns section handles for this class. LiteBox does not yet model those // NT section objects, so do not publish a fabricated success payload. SystemInformationClass::FeatureConfigurationSection => NtStatus::INVALID_INFO_CLASS, @@ -395,6 +447,64 @@ impl Task { status } + fn query_feature_configuration( + input_buffer: ConstPtr, + input_buffer_length: u32, + system_information: MutPtr, + system_information_length: u32, + return_length: Option>, + ) -> NtStatus { + if input_buffer_length != size_of::().trunc() { + return Self::write_return_length_for_short_buffer(return_length, 0); + } + + let input_buffer = ConstPtr::::from_usize( + input_buffer.as_usize(), + ); + let Some(query) = input_buffer.read_at_offset(0) else { + return NtStatus::ACCESS_VIOLATION; + }; + // Boot and Runtime returned identical values for every observed feature. Validate the + // selector to match Windows, but use the same deterministic table for both. + if FeatureConfigurationType::try_from(query.configuration_type).is_err() { + if Self::write_return_length(return_length, 0).is_err() { + return NtStatus::ACCESS_VIOLATION; + } + return NtStatus::INVALID_PARAMETER; + } + + let required_len = size_of::().trunc(); + if system_information_length != required_len { + return Self::write_return_length_for_short_buffer(return_length, required_len); + } + + let configuration = OBSERVED_FEATURE_CONFIGURATIONS + .binary_search_by_key(&query.feature_id, |&(feature_id, _)| feature_id) + .ok() + .map(|index| OBSERVED_FEATURE_CONFIGURATIONS[index].1); + if configuration.is_none() { + litebox_util_log::debug!( + feature_id = query.feature_id; + "No observed feature configuration" + ); + } + let information = SystemFeatureConfigurationInformation { + change_stamp: FEATURE_CONFIGURATION_CHANGE_STAMP, + feature_id: configuration.map_or(0, |_| query.feature_id), + configuration: configuration.unwrap_or(0), + variant_payload: 0, + }; + if system_information + .write_slice_at_offset(0, information.as_bytes()) + .is_none() + || Self::write_return_length(return_length, required_len).is_err() + { + return NtStatus::ACCESS_VIOLATION; + } + + configuration.map_or(NtStatus::NOT_FOUND, |_| NtStatus::SUCCESS) + } + fn write_logical_processor_and_group_information( input_buffer: ConstPtr, system_information: MutPtr, @@ -657,6 +767,33 @@ impl Task { NtStatus::SUCCESS } + pub(crate) fn sys_nt_query_timer_resolution( + minimum_resolution: MutPtr, + maximum_resolution: MutPtr, + current_resolution: MutPtr, + ) -> NtStatus { + if minimum_resolution + .write_at_offset(0, TIMER_RESOLUTION_100NS) + .is_none() + { + return NtStatus::ACCESS_VIOLATION; + } + if maximum_resolution + .write_at_offset(0, MINIMUM_TIMER_RESOLUTION_100NS) + .is_none() + { + return NtStatus::ACCESS_VIOLATION; + } + if current_resolution + .write_at_offset(0, TIMER_RESOLUTION_100NS) + .is_none() + { + return NtStatus::ACCESS_VIOLATION; + } + + NtStatus::SUCCESS + } + pub(crate) fn sys_nt_convert_between_auxiliary_counter_and_performance_counter( _flag: u32, source: ConstPtr, @@ -990,6 +1127,44 @@ mod tests { }); } + #[test] + fn nt_query_system_information_ex_reports_configured_features() { + run_with_test_platform_pointers(|| { + assert!( + OBSERVED_FEATURE_CONFIGURATIONS + .windows(2) + .all(|entries| entries[0].0 < entries[1].0) + ); + + for configuration_type in [ + FeatureConfigurationType::Boot, + FeatureConfigurationType::Runtime, + ] { + for &(feature_id, expected_configuration) in OBSERVED_FEATURE_CONFIGURATIONS { + let query = SystemFeatureConfigurationQuery { + configuration_type: configuration_type as u32, + feature_id, + }; + let mut output = SystemFeatureConfigurationInformation::default(); + + assert_eq!( + TestTask::sys_nt_query_system_information_ex( + SystemInformationClass::FeatureConfiguration as u32, + Some(const_byte_ptr(&query)), + size_of::().trunc(), + mut_byte_ptr(&mut output), + size_of::().trunc(), + None, + ), + NtStatus::SUCCESS + ); + assert_eq!(output.feature_id, feature_id); + assert_eq!(output.configuration, expected_configuration); + } + } + }); + } + #[test] fn nt_query_system_information_ex_reports_required_logical_processor_length() { run_with_test_platform_pointers(|| {