diff --git a/litebox_common_windows/src/nt_status.rs b/litebox_common_windows/src/nt_status.rs index 08260943c..96a2264f3 100644 --- a/litebox_common_windows/src/nt_status.rs +++ b/litebox_common_windows/src/nt_status.rs @@ -221,6 +221,9 @@ impl NtStatus { 0xC0000286 => "STATUS_MAGAZINE_NOT_PRESENT: Magazine not present", 0xC0000287 => "STATUS_REINITIALIZATION_NEEDED: Reinitialization needed", 0xC0000302 => "STATUS_WMI_ALREADY_DISABLED: WMI collection or events already disabled", + 0xC0000354 => { + "STATUS_DEBUGGER_INACTIVE: An attempt to do an operation on a debug object failed because the object is in the process of being deleted" + } 0xC0000718 => { "STATUS_WNF_EVENT_ALREADY_SUBSCRIBED: The WNF event is already subscribed" } @@ -637,6 +640,9 @@ impl NtStatus { /// STATUS_WMI_ALREADY_DISABLED pub const WMI_ALREADY_DISABLED: Self = Self::from_raw(0xC0000302); + /// STATUS_DEBUGGER_INACTIVE + pub const DEBUGGER_INACTIVE: Self = Self::from_raw(0xC0000354); + /// STATUS_WNF_EVENT_ALREADY_SUBSCRIBED pub const WNF_EVENT_ALREADY_SUBSCRIBED: Self = Self::from_raw(0xC0000718); } diff --git a/litebox_shim_windows/src/lib.rs b/litebox_shim_windows/src/lib.rs index 0c10218e0..8c7259849 100644 --- a/litebox_shim_windows/src/lib.rs +++ b/litebox_shim_windows/src/lib.rs @@ -598,6 +598,7 @@ pub struct Process { default_hard_error_mode: AtomicU32, wnf_notification_event: Mutex>>>, wnf_subscriptions: Mutex, + trace_notifications: Mutex>, cookie: u32, exit_code: AtomicI32, next_thread_id: AtomicUsize, @@ -693,6 +694,7 @@ impl Process { default_hard_error_mode: AtomicU32::new(0), wnf_notification_event: Mutex::new(None), wnf_subscriptions: Mutex::new(syscalls::wnf::WnfProcessSubscriptions::default()), + trace_notifications: Mutex::new(syscalls::trace::TraceNotifications::default()), cookie: syscalls::process::default_process_cookie(), exit_code: AtomicI32::new(DEFAULT_PROCESS_EXIT_CODE), next_thread_id: AtomicUsize::new(syscalls::process::INITIAL_THREAD_ID + 1), @@ -1394,6 +1396,17 @@ impl Task { byte_offset, key, ), + SyscallRequest::NtQueryDebugFilterState { + component_id, + level, + } => { + litebox_util_log::debug!( + component_id, + level; + "NtQueryDebugFilterState reports no attached debugger" + ); + NtStatus::DEBUGGER_INACTIVE + } SyscallRequest::NtQueryVolumeInformationFile { file_handle, io_status_block, @@ -2069,6 +2082,21 @@ impl Task { field_size, fields, } => self.sys_nt_trace_event(trace_handle, flags, field_size, fields), + SyscallRequest::NtTraceControl { + function_code, + input_buffer, + input_buffer_length, + output_buffer, + output_buffer_length, + return_length, + } => self.sys_nt_trace_control( + function_code, + input_buffer, + input_buffer_length, + output_buffer, + output_buffer_length, + return_length, + ), SyscallRequest::NtTerminateProcess { process_handle, exit_status, diff --git a/litebox_shim_windows/src/nt_types.rs b/litebox_shim_windows/src/nt_types.rs index 7ba729b00..f659d9abd 100644 --- a/litebox_shim_windows/src/nt_types.rs +++ b/litebox_shim_windows/src/nt_types.rs @@ -503,7 +503,7 @@ pub struct ListEntry { } #[repr(C)] -#[derive(Clone, Copy, Debug, FromBytes, IntoBytes, Immutable)] +#[derive(Clone, Copy, Debug, Default, FromBytes, IntoBytes, Immutable)] pub struct Guid { pub data: [u8; 16], } diff --git a/litebox_shim_windows/src/syscalls/mod.rs b/litebox_shim_windows/src/syscalls/mod.rs index e66fbcae5..3cd16c160 100644 --- a/litebox_shim_windows/src/syscalls/mod.rs +++ b/litebox_shim_windows/src/syscalls/mod.rs @@ -374,6 +374,10 @@ pub(crate) enum SyscallRequest { byte_offset: Option>, key: Option>, }, + NtQueryDebugFilterState { + component_id: u32, + level: u32, + }, NtQueryVolumeInformationFile { file_handle: Handle, io_status_block: Platform::RawMutPointer, @@ -689,6 +693,14 @@ pub(crate) enum SyscallRequest { field_size: u32, fields: Platform::RawConstPointer, }, + NtTraceControl { + function_code: u32, + input_buffer: Platform::RawConstPointer, + input_buffer_length: u32, + output_buffer: Option>, + output_buffer_length: u32, + return_length: Platform::RawMutPointer, + }, NtAllocateVirtualMemory { process_handle: ProcessHandle, base_address: Platform::RawMutPointer, @@ -1047,6 +1059,10 @@ impl SyscallRequest { byte_offset:*, key:*, })), + NtSysno::NtQueryDebugFilterState => Some(sys_req!(NtQueryDebugFilterState { + component_id, + level, + })), NtSysno::NtQueryVolumeInformationFile => Some(sys_req!(NtQueryVolumeInformationFile { file_handle:{Handle::from_raw}, io_status_block:*, @@ -1372,6 +1388,14 @@ impl SyscallRequest { field_size, fields:*, })), + NtSysno::NtTraceControl => Some(sys_req!(NtTraceControl { + function_code, + input_buffer:*, + input_buffer_length, + output_buffer:*, + output_buffer_length, + return_length:*, + })), NtSysno::NtAllocateVirtualMemory => Some(sys_req!(NtAllocateVirtualMemory { process_handle: { ProcessHandle::from_raw }, base_address:*, diff --git a/litebox_shim_windows/src/syscalls/trace.rs b/litebox_shim_windows/src/syscalls/trace.rs index 088def1e5..7ccecf31a 100644 --- a/litebox_shim_windows/src/syscalls/trace.rs +++ b/litebox_shim_windows/src/syscalls/trace.rs @@ -1,18 +1,23 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +use alloc::collections::VecDeque; +use alloc::sync::Arc; +use alloc::vec::Vec; use core::mem::size_of; use int_enum::IntEnum; -use litebox::platform::RawConstPointer as _; +use litebox::platform::{RawConstPointer as _, RawMutPointer as _}; use litebox_common_windows::nt_status::NtStatus; use zerocopy::{FromBytes, Immutable, IntoBytes}; use crate::nt_types::Guid; use crate::syscalls::Handle; -use crate::{ConstPtr, ShimFS, ShimPlatform, Task}; +use crate::syscalls::event::{EventAccess, EventObject, EventSubsystem}; +use crate::{ConstPtr, MutPtr, ShimFS, ShimPlatform, Task}; const ETW_NT_TRACE_TYPE_MASK: u32 = 0x0000_ff00; +const ETW_MAX_DATA_BLOCK_BUFFER_SIZE: u32 = 65_536; const USER_LOADER_PROVIDER_ID: Guid = Guid { // {b059b83f-d946-4b13-87ca-4292839dc2f2}, in native GUID byte order. data: [ @@ -34,6 +39,50 @@ enum EtwNtTraceType { Instance = 0x0000_0800, } +#[repr(u32)] +#[derive(Clone, Copy, Debug, Eq, IntEnum, PartialEq)] +enum EtwTraceControlCode { + ReceiveNotification = 16, + SendDataBlock = 17, + AddNotificationEvent = 27, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, FromBytes, Immutable, IntoBytes)] +struct EtwNotificationHeader { + notification_type: u32, + notification_size: u32, + offset: u32, + reply_requested: u8, + padding: [u8; 3], + timeout: u32, + reply_count: u32, + reserved2: u64, + target_pid: u32, + source_pid: u32, + destination_guid: Guid, + source_guid: Guid, +} + +struct TraceNotification { + header: EtwNotificationHeader, + payload: Vec, +} + +pub(crate) struct TraceNotifications { + event: Option>>, + pending: VecDeque, +} + +impl Default for TraceNotifications { + fn default() -> Self { + Self { + event: None, + pending: VecDeque::new(), + } + } +} + bitflags::bitflags! { #[derive(Clone, Copy, Debug, Eq, PartialEq)] struct EtwNtTraceModifiers: u32 { @@ -89,6 +138,197 @@ const _: () = assert!(size_of::() == 16); const _: () = assert!(size_of::() == 80); impl Task { + pub(crate) fn sys_nt_trace_control( + &self, + function_code: u32, + input_buffer: ConstPtr, + input_buffer_length: u32, + output_buffer: Option>, + output_buffer_length: u32, + return_length: MutPtr, + ) -> NtStatus { + let Ok(function_code) = EtwTraceControlCode::try_from(function_code) else { + // TODO(etw-trace-control): Model logger and provider control operations. + litebox_util_log::debug!(function_code; "Unsupported NtTraceControl operation"); + return NtStatus::NOT_SUPPORTED; + }; + match function_code { + EtwTraceControlCode::ReceiveNotification => self.receive_trace_notification( + input_buffer_length, + output_buffer, + output_buffer_length, + return_length, + ), + EtwTraceControlCode::SendDataBlock => self.send_trace_data_block( + input_buffer, + input_buffer_length, + output_buffer, + output_buffer_length, + ), + EtwTraceControlCode::AddNotificationEvent => self.add_trace_notification_event( + input_buffer, + input_buffer_length, + output_buffer, + output_buffer_length, + ), + } + } + + fn send_trace_data_block( + &self, + input_buffer: ConstPtr, + input_buffer_length: u32, + output_buffer: Option>, + output_buffer_length: u32, + ) -> NtStatus { + let header_length = u32::try_from(size_of::()).unwrap(); + if !(header_length..=ETW_MAX_DATA_BLOCK_BUFFER_SIZE).contains(&input_buffer_length) + || output_buffer_length < header_length + { + return NtStatus::INVALID_PARAMETER; + } + let input_address = input_buffer.as_usize(); + let input_buffer = Platform::RawConstPointer::::from_usize(input_address); + let Some(header_bytes) = input_buffer.to_owned_slice(size_of::()) + else { + return NtStatus::ACCESS_VIOLATION; + }; + let Ok(mut header) = EtwNotificationHeader::read_from_bytes(&header_bytes) else { + return NtStatus::INVALID_PARAMETER; + }; + if header.notification_size != input_buffer_length { + return NtStatus::INVALID_PARAMETER; + } + let payload_length = input_buffer_length as usize - size_of::(); + let Some(payload_address) = input_address.checked_add(size_of::()) + else { + return NtStatus::ACCESS_VIOLATION; + }; + let payload_buffer = Platform::RawConstPointer::::from_usize(payload_address); + let Some(payload) = payload_buffer.to_owned_slice(payload_length) else { + return NtStatus::ACCESS_VIOLATION; + }; + + header.reply_count = 1; + let Some(output_buffer) = output_buffer else { + return NtStatus::ACCESS_VIOLATION; + }; + if output_buffer + .write_slice_at_offset(0, header.as_bytes()) + .is_none() + { + return NtStatus::ACCESS_VIOLATION; + } + + let notification = TraceNotification { + header, + payload: payload.into_vec(), + }; + self.queue_trace_notification(notification); + NtStatus::SUCCESS + } + + fn add_trace_notification_event( + &self, + input_buffer: ConstPtr, + input_buffer_length: u32, + output_buffer: Option>, + output_buffer_length: u32, + ) -> NtStatus { + if input_buffer_length != u32::try_from(size_of::()).unwrap() + || output_buffer.is_some() + || output_buffer_length != 0 + { + return NtStatus::INVALID_PARAMETER; + } + let Some(event_handle) = input_buffer.read_at_offset(0) else { + return NtStatus::ACCESS_VIOLATION; + }; + let entry = match self.typed_handle_entry_with_access::>( + Handle::from_raw(event_handle as usize), + EventAccess::MODIFY_STATE.bits(), + ) { + Ok(entry) => entry, + Err(status) => return status, + }; + let event = entry.with_entry(|entry| entry.event.clone()); + let mut notifications = self.process.trace_notifications.lock(); + if notifications.event.is_some() { + return NtStatus::WNF_EVENT_ALREADY_SUBSCRIBED; + } + if !notifications.pending.is_empty() { + event.set(); + } + notifications.event = Some(event); + NtStatus::SUCCESS + } + + fn receive_trace_notification( + &self, + input_buffer_length: u32, + output_buffer: Option>, + output_buffer_length: u32, + return_length: MutPtr, + ) -> NtStatus { + if input_buffer_length != 0 { + return NtStatus::INVALID_PARAMETER; + } + + let mut notifications = self.process.trace_notifications.lock(); + let Some(notification) = notifications.pending.front() else { + if return_length.write_at_offset(0, 0).is_none() { + return NtStatus::ACCESS_VIOLATION; + } + if let Some(event) = notifications.event.as_ref() { + event.clear(); + } + return NtStatus::NO_MORE_ENTRIES; + }; + let Some(required_length) = size_of::() + .checked_add(notification.payload.len()) + .and_then(|length| u32::try_from(length).ok()) + else { + return NtStatus::BUFFER_TOO_SMALL; + }; + if return_length.write_at_offset(0, required_length).is_none() { + return NtStatus::ACCESS_VIOLATION; + } + if output_buffer_length < required_length { + return NtStatus::BUFFER_TOO_SMALL; + } + let Some(output_buffer) = output_buffer else { + return NtStatus::ACCESS_VIOLATION; + }; + if output_buffer + .write_slice_at_offset(0, notification.header.as_bytes()) + .is_none() + || output_buffer + .write_slice_at_offset( + size_of::().cast_signed(), + ¬ification.payload, + ) + .is_none() + { + return NtStatus::ACCESS_VIOLATION; + } + + notifications.pending.pop_front(); + if notifications.pending.is_empty() + && let Some(event) = notifications.event.as_ref() + { + event.clear(); + } + NtStatus::SUCCESS + } + + fn queue_trace_notification(&self, notification: TraceNotification) { + let mut notifications = self.process.trace_notifications.lock(); + notifications.pending.push_back(notification); + if let Some(event) = notifications.event.as_ref() { + event.set(); + } + } + pub(crate) fn sys_nt_trace_event( &self, trace_handle: Handle, @@ -176,7 +416,8 @@ mod tests { use litebox::platform::ThreadProvider; use super::*; - use crate::tests::{const_ptr, null_const_ptr}; + use crate::syscalls::event::EventType; + use crate::tests::{const_ptr, mut_byte_ptr, mut_ptr, null_const_ptr}; type TestPlatform = crate::tests::TestPlatform; @@ -269,6 +510,137 @@ mod tests { }); } + #[test] + fn nt_trace_control_registers_one_notification_event() { + let task = crate::tests::test_task(); + let mut event = Handle::default(); + assert_eq!( + task.sys_nt_create_event( + mut_ptr(&mut event), + EventAccess::MODIFY_STATE.bits(), + None, + EventType::Notification as u32, + 0, + ), + NtStatus::SUCCESS + ); + let event_handle: u32 = event.as_raw().try_into().unwrap(); + let mut return_length = u32::MAX; + assert_eq!( + task.sys_nt_trace_control( + EtwTraceControlCode::AddNotificationEvent as u32, + const_ptr(&event_handle), + 4, + None, + 0, + mut_ptr(&mut return_length), + ), + NtStatus::SUCCESS + ); + assert_eq!(return_length, u32::MAX); + assert_eq!( + task.sys_nt_trace_control( + EtwTraceControlCode::AddNotificationEvent as u32, + const_ptr(&event_handle), + 4, + None, + 0, + mut_ptr(&mut return_length), + ), + NtStatus::WNF_EVENT_ALREADY_SUBSCRIBED + ); + + let registered_event = task + .process + .trace_notifications + .lock() + .event + .clone() + .unwrap(); + assert!(!registered_event.is_signaled()); + + let payload = [3, 4, 5, 6]; + let mut header = EtwNotificationHeader { + notification_type: 1, + notification_size: (size_of::() + payload.len()) + .try_into() + .unwrap(), + destination_guid: Guid { data: [1; 16] }, + source_guid: Guid { data: [2; 16] }, + ..Default::default() + }; + let mut input_packet = header.as_bytes().to_vec(); + input_packet.extend_from_slice(&payload); + let input_buffer = + ConstPtr::::from_usize(input_packet.as_ptr() as usize); + let mut send_output = [0; size_of::()]; + return_length = u32::MAX; + assert_eq!( + task.sys_nt_trace_control( + EtwTraceControlCode::SendDataBlock as u32, + input_buffer, + header.notification_size, + Some(mut_byte_ptr(&mut send_output)), + send_output.len().try_into().unwrap(), + mut_ptr(&mut return_length), + ), + NtStatus::SUCCESS + ); + header.reply_count = 1; + let mut packet = header.as_bytes().to_vec(); + packet.extend_from_slice(&payload); + assert_eq!(send_output, header.as_bytes()); + assert_eq!(return_length, u32::MAX); + assert!(registered_event.is_signaled()); + + let mut short_output = [0; size_of::() + 3]; + return_length = u32::MAX; + assert_eq!( + task.sys_nt_trace_control( + EtwTraceControlCode::ReceiveNotification as u32, + null_const_ptr(), + 0, + Some(mut_byte_ptr(&mut short_output)), + short_output.len().try_into().unwrap(), + mut_ptr(&mut return_length), + ), + NtStatus::BUFFER_TOO_SMALL + ); + assert_eq!(return_length, packet.len().try_into().unwrap()); + assert!(registered_event.is_signaled()); + + let mut output = [0; size_of::() + 4]; + return_length = u32::MAX; + assert_eq!( + task.sys_nt_trace_control( + EtwTraceControlCode::ReceiveNotification as u32, + null_const_ptr(), + 0, + Some(mut_byte_ptr(&mut output)), + output.len().try_into().unwrap(), + mut_ptr(&mut return_length), + ), + NtStatus::SUCCESS + ); + assert_eq!(output, packet.as_slice()); + assert_eq!(return_length, packet.len().try_into().unwrap()); + assert!(!registered_event.is_signaled()); + + return_length = u32::MAX; + assert_eq!( + task.sys_nt_trace_control( + EtwTraceControlCode::ReceiveNotification as u32, + null_const_ptr(), + 0, + Some(mut_byte_ptr(&mut output)), + output.len().try_into().unwrap(), + mut_ptr(&mut return_length), + ), + NtStatus::NO_MORE_ENTRIES + ); + assert_eq!(return_length, 0); + } + #[cfg(all(target_os = "windows", target_arch = "x86_64"))] #[test] fn nt_trace_event_terminal_statuses_match_host() {