From 58447f72f688b048b658bc5600d329e4085b0334 Mon Sep 17 00:00:00 2001 From: George Pollard Date: Wed, 9 Aug 2023 09:13:42 +1200 Subject: [PATCH] Migrate from winapi to windows-rs (#3050) `windows-rs` is the newer, Microsoft-supported version of the API bindings; `winapi` hasn't been updated in some time. This allows us to remove some code, as `windows-rs` includes the `Sym*` functions that we had to previously defined ourselves in `dbghelp`. Rather than port the `jobs` and `com` code I removed it, as it was unused. The `check_hr` and `check_winapi` macros have been replaced by use of `.ok()?` on `BOOL` and `?` on `HRESULT` which now support standard error handling facilities. --- .gitattributes | 5 +- src/agent/Cargo.lock | 13 +- src/agent/debugger/Cargo.toml | 20 +- src/agent/debugger/src/breakpoint.rs | 2 +- src/agent/debugger/src/dbghelp.rs | 343 ++++---- src/agent/debugger/src/debug_event.rs | 50 +- src/agent/debugger/src/debugger.rs | 66 +- src/agent/debugger/src/module.rs | 10 +- src/agent/debugger/src/stack.rs | 4 +- src/agent/debugger/src/target.rs | 51 +- src/agent/dynamic-library/Cargo.toml | 16 +- src/agent/input-tester/Cargo.toml | 11 +- src/agent/input-tester/src/crash_detector.rs | 15 +- .../input-tester/src/test_result/asan.rs | 88 ++- .../input-tester/src/test_result/fast_fail.rs | 48 +- src/agent/input-tester/src/test_result/mod.rs | 21 +- .../src/test_result/vcpp_debugger.rs | 103 +-- .../src/test_result/verifier_stop.rs | 51 +- src/agent/input-tester/src/tester.rs | 2 +- src/agent/onefuzz-agent/Cargo.toml | 2 +- src/agent/onefuzz-agent/src/worker.rs | 8 +- src/agent/onefuzz/Cargo.toml | 2 +- src/agent/onefuzz/src/memory.rs | 26 +- src/agent/onefuzz/src/memory/tests_windows.rs | 8 + src/agent/win-util/Cargo.toml | 36 +- src/agent/win-util/src/aedebug.rs | 43 - src/agent/win-util/src/com.rs | 110 --- src/agent/win-util/src/file.rs | 42 +- src/agent/win-util/src/handle.rs | 45 +- src/agent/win-util/src/hr.rs | 30 - src/agent/win-util/src/jobs.rs | 736 ------------------ src/agent/win-util/src/lib.rs | 63 -- src/agent/win-util/src/macros.rs | 108 --- src/agent/win-util/src/memory.rs | 42 +- src/agent/win-util/src/pipe_handle.rs | 88 +-- src/agent/win-util/src/process.rs | 139 ++-- src/agent/win-util/src/string.rs | 17 +- src/agent/win-util/src/wer.rs | 78 -- src/ci/build_cli.ps1 | 92 +-- src/ci/check-dependencies.sh | 5 + 40 files changed, 694 insertions(+), 1945 deletions(-) create mode 100644 src/agent/onefuzz/src/memory/tests_windows.rs delete mode 100644 src/agent/win-util/src/aedebug.rs delete mode 100644 src/agent/win-util/src/com.rs delete mode 100644 src/agent/win-util/src/hr.rs delete mode 100644 src/agent/win-util/src/jobs.rs delete mode 100644 src/agent/win-util/src/macros.rs delete mode 100644 src/agent/win-util/src/wer.rs diff --git a/.gitattributes b/.gitattributes index 050765b992..0991d20b4c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ -* text=auto -*.ps1 text=crlf +* text=auto +*.ps1 text eol=crlf +*.sh text eol=lf diff --git a/src/agent/Cargo.lock b/src/agent/Cargo.lock index 9161837430..1d856f278d 100644 --- a/src/agent/Cargo.lock +++ b/src/agent/Cargo.lock @@ -829,7 +829,7 @@ dependencies = [ "rand 0.8.4", "serde", "win-util", - "winapi 0.3.9", + "windows", ] [[package]] @@ -909,7 +909,7 @@ dependencies = [ "lazy_static", "regex", "thiserror", - "winapi 0.3.9", + "windows", "winreg 0.50.0", ] @@ -1632,7 +1632,7 @@ dependencies = [ "rayon", "sha2", "win-util", - "winapi 0.3.9", + "windows", ] [[package]] @@ -2236,7 +2236,7 @@ dependencies = [ "url-escape", "urlparse", "uuid", - "winapi 0.3.9", + "windows", "winreg 0.50.0", ] @@ -2268,7 +2268,7 @@ dependencies = [ "tokio", "url", "uuid", - "winapi 0.3.9", + "windows", ] [[package]] @@ -4065,7 +4065,8 @@ dependencies = [ "atexit", "log", "os_pipe", - "winapi 0.3.9", + "tempfile", + "windows", "winreg 0.50.0", ] diff --git a/src/agent/debugger/Cargo.toml b/src/agent/debugger/Cargo.toml index 296dc03254..d52d429c12 100644 --- a/src/agent/debugger/Cargo.toml +++ b/src/agent/debugger/Cargo.toml @@ -16,20 +16,6 @@ rand = "0.8" serde = { version = "1.0", features = ["derive"] } win-util = { path = "../win-util" } -[dependencies.winapi] -version = "0.3" -features = [ - "dbghelp", - "debugapi", - "handleapi", - "impl-default", - "memoryapi", - "namedpipeapi", - "processthreadsapi", - "securitybaseapi", - "shellapi", - "synchapi", - "werapi", - "winbase", - "winerror", -] +[dependencies.windows] +version = "0.48" + diff --git a/src/agent/debugger/src/breakpoint.rs b/src/agent/debugger/src/breakpoint.rs index 3182b72909..ff36749731 100644 --- a/src/agent/debugger/src/breakpoint.rs +++ b/src/agent/debugger/src/breakpoint.rs @@ -8,7 +8,7 @@ use std::{ use anyhow::Result; use win_util::process; -use winapi::um::winnt::HANDLE; +use windows::Win32::Foundation::HANDLE; use crate::debugger::{BreakpointId, BreakpointType}; diff --git a/src/agent/debugger/src/dbghelp.rs b/src/agent/debugger/src/dbghelp.rs index ea756d36bf..dbae18d251 100644 --- a/src/agent/debugger/src/dbghelp.rs +++ b/src/agent/debugger/src/dbghelp.rs @@ -1,20 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -// This is only needed because of the types defined here that are missing from winapi. -// Once they get added to winapi, this should be removed. -#![allow(bad_style)] -#![allow(clippy::unreadable_literal)] -#![allow(clippy::collapsible_if)] -#![allow(clippy::needless_return)] -#![allow(clippy::upper_case_acronyms)] - /// This module defines a wrapper around dbghelp apis so they can be used in a thread safe manner /// as well as providing a more Rust like api. use std::{ cmp, - ffi::{OsStr, OsString}, - mem::{size_of, MaybeUninit}, + ffi::{c_void, OsStr, OsString}, + mem::size_of, num::NonZeroU64, path::{Path, PathBuf}, sync::Once, @@ -22,37 +14,32 @@ use std::{ use anyhow::{Context, Result}; use log::warn; -use win_util::{check_winapi, last_os_error, process}; -use winapi::{ - shared::{ - basetsd::{DWORD64, PDWORD64}, - guiddef::GUID, - minwindef::{BOOL, DWORD, FALSE, LPVOID, MAX_PATH, PDWORD, TRUE, ULONG, WORD}, - ntdef::{PCWSTR, PWSTR}, - winerror::{ERROR_ALREADY_EXISTS, ERROR_SUCCESS}, - }, - um::{ - dbghelp::{ - AddrModeFlat, StackWalkEx, SymCleanup, SymFindFileInPathW, SymFromNameW, - SymFunctionTableAccess64, SymGetModuleBase64, SymInitializeW, SymLoadModuleExW, - IMAGEHLP_LINEW64, INLINE_FRAME_CONTEXT_IGNORE, INLINE_FRAME_CONTEXT_INIT, - PIMAGEHLP_LINEW64, PSYMBOL_INFOW, STACKFRAME_EX, SYMBOL_INFOW, SYMOPT_DEBUG, - SYMOPT_DEFERRED_LOADS, SYMOPT_FAIL_CRITICAL_ERRORS, SYMOPT_NO_PROMPTS, - SYM_STKWALK_DEFAULT, - }, - errhandlingapi::GetLastError, - handleapi::CloseHandle, - processthreadsapi::{GetThreadContext, SetThreadContext}, - synchapi::{CreateMutexA, ReleaseMutex, WaitForSingleObjectEx}, - winbase::{ - Wow64GetThreadContext, Wow64SetThreadContext, INFINITE, WAIT_ABANDONED, WAIT_FAILED, +use win_util::{last_os_error, process}; +use windows::{ + core::{PCSTR, PCWSTR, PWSTR}, + Win32::{ + Foundation::{ + CloseHandle, GetLastError, ERROR_ALREADY_EXISTS, ERROR_SUCCESS, FALSE, HANDLE, + MAX_PATH, TRUE, WAIT_ABANDONED, WAIT_FAILED, }, - winnt::{ - CONTEXT, CONTEXT_ALL, HANDLE, IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_I386, WCHAR, - WOW64_CONTEXT, WOW64_CONTEXT_ALL, + System::{ + Diagnostics::Debug::{ + AddrModeFlat, GetThreadContext, SetThreadContext, StackWalkEx, SymCleanup, + SymFindFileInPathW, SymFromInlineContextW, SymFromNameW, SymFunctionTableAccess64, + SymGetLineFromInlineContextW, SymGetModuleBase64, SymGetModuleInfoW64, + SymGetOptions, SymGetSearchPathW, SymInitializeW, SymLoadModuleExW, SymSetOptions, + SymSetSearchPathW, Wow64GetThreadContext, Wow64SetThreadContext, CONTEXT, + IMAGEHLP_LINEW64, IMAGEHLP_MODULEW64, INLINE_FRAME_CONTEXT_IGNORE, + INLINE_FRAME_CONTEXT_INIT, SSRVOPT_DWORD, STACKFRAME_EX, SYMBOL_INFOW, + SYMOPT_DEBUG, SYMOPT_DEFERRED_LOADS, SYMOPT_FAIL_CRITICAL_ERRORS, + SYMOPT_NO_PROMPTS, SYM_LOAD_FLAGS, SYM_STKWALK_DEFAULT, WOW64_CONTEXT, + }, + SystemInformation::{ + IMAGE_FILE_MACHINE, IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_I386, + }, + Threading::{CreateMutexA, ReleaseMutex, WaitForSingleObjectEx, INFINITE}, }, }, - ENUM, STRUCT, }; // We use 4096 based on C4503 - the documented VC++ warning that a name is truncated. @@ -61,11 +48,6 @@ const MAX_SYM_NAME: usize = 4096; // Arbitrary practical choice, but must not exceed `u32::MAX`. const MAX_SYM_SEARCH_PATH_LEN: usize = 8192; -/// For `flags` parameter of `SymFindFileInPath`. -/// -/// Missing from `winapi-rs`. -const SSRVOPT_DWORD: DWORD = 0x0002; - // Ideally this would be a function, but it would require returning a large stack // allocated object **and** an interior pointer to the object, so we use a macro instead. macro_rules! init_sym_info { @@ -83,10 +65,10 @@ macro_rules! init_sym_info { // Clippy isn't smart enough to know the first field of our aligned struct is also aligned. #[allow(clippy::cast_ptr_alignment)] let symbol_info_ptr = unsafe { &mut *(aligned_sym_info.as_mut_ptr() as *mut SYMBOL_INFOW) }; - symbol_info_ptr.MaxNameLen = MAX_SYM_NAME as ULONG; + symbol_info_ptr.MaxNameLen = MAX_SYM_NAME as u32; // the struct size not counting the variable length name. - symbol_info_ptr.SizeOfStruct = size_of::() as DWORD; + symbol_info_ptr.SizeOfStruct = size_of::() as u32; symbol_info_ptr }}; } @@ -101,34 +83,25 @@ macro_rules! init_sym_info { /// we use the same named local mutex to hopefully avoid any unsynchronized uses of dbghlp /// in std. pub fn lock() -> Result { - use core::sync::atomic::{AtomicUsize, Ordering}; + use core::sync::atomic::{AtomicIsize, Ordering}; - static LOCK: AtomicUsize = AtomicUsize::new(0); + static LOCK: AtomicIsize = AtomicIsize::new(0); let mut lock = LOCK.load(Ordering::SeqCst); if lock == 0 { - lock = unsafe { - CreateMutexA( - std::ptr::null_mut(), - 0, - "Local\\RustBacktraceMutex\0".as_ptr() as _, - ) as usize - }; - - if lock == 0 { - return Err(last_os_error()); - } + lock = + unsafe { CreateMutexA(None, FALSE, PCSTR("Local\\RustBacktraceMutex\0".as_ptr()))?.0 }; // Handle the race between threads creating our mutex by closing ours if another // thread created the mutex first. if let Err(other) = LOCK.compare_exchange(0, lock, Ordering::SeqCst, Ordering::SeqCst) { debug_assert_ne!(other, 0); debug_assert_eq!(unsafe { GetLastError() }, ERROR_ALREADY_EXISTS); - unsafe { CloseHandle(lock as HANDLE) }; + unsafe { CloseHandle(HANDLE(lock)) }; lock = other; } } debug_assert_ne!(lock, 0); - let lock = lock as HANDLE; + let lock = HANDLE(lock); match unsafe { WaitForSingleObjectEx(lock, INFINITE, FALSE) } { WAIT_FAILED => return Err(last_os_error()), WAIT_ABANDONED => { @@ -155,76 +128,6 @@ pub fn lock() -> Result { Ok(dbghlp) } -// Not defined in winapi yet -ENUM! {enum SYM_TYPE { - SymNone = 0, - SymCoff, - SymCv, - SymPdb, - SymExport, - SymDeferred, - SymSym, - SymDia, - SymVirtual, - NumSymTypes, -}} -STRUCT! {struct IMAGEHLP_MODULEW64 { - SizeOfStruct: DWORD, - BaseOfImage: DWORD64, - ImageSize: DWORD, - TimeDateStamp: DWORD, - CheckSum: DWORD, - NumSyms: DWORD, - SymType: SYM_TYPE, - ModuleName: [WCHAR; 32], - ImageName: [WCHAR; 256], - LoadedImageName: [WCHAR; 256], - LoadedPdbName: [WCHAR; 256], - CVSig: DWORD, - CVData: [WCHAR; MAX_PATH * 3], - PdbSig: DWORD, - PdbSig70: GUID, - PdbAge: DWORD, - PdbUnmatched: BOOL, - DbgUnmatched: BOOL, - LineNumbers: BOOL, - GlobalSymbols: BOOL, - TypeInfo: BOOL, - SourceIndexed: BOOL, - Publics: BOOL, - MachineType: DWORD, - Reserved: DWORD, -}} -pub type PIMAGEHLP_MODULEW64 = *mut IMAGEHLP_MODULEW64; - -// Not defined in winapi yet -extern "system" { - pub fn SymGetOptions() -> DWORD; - pub fn SymSetOptions(_: DWORD) -> DWORD; - pub fn SymFromInlineContextW( - hProcess: HANDLE, - Address: DWORD64, - InlineContext: ULONG, - Displacement: PDWORD64, - Symbol: PSYMBOL_INFOW, - ) -> BOOL; - pub fn SymGetLineFromInlineContextW( - hProcess: HANDLE, - dwAddr: DWORD64, - InlineContext: ULONG, - qwModuleBaseAddress: DWORD64, - pdwDisplacement: PDWORD, - Line: PIMAGEHLP_LINEW64, - ) -> BOOL; - pub fn SymGetModuleInfoW64( - hProcess: HANDLE, - qwAddr: DWORD64, - ModuleInfo: PIMAGEHLP_MODULEW64, - ) -> BOOL; - pub fn SymGetSearchPathW(hProcess: HANDLE, SearchPath: PWSTR, SearchPathLength: DWORD) -> BOOL; - pub fn SymSetSearchPathW(hProcess: HANDLE, SearchPath: PCWSTR) -> BOOL; -} - #[repr(C, align(8))] struct Aligned8(T); @@ -285,14 +188,14 @@ impl FrameContext { } } - pub fn as_mut_ptr(&mut self) -> LPVOID { + pub fn as_mut_ptr(&mut self) -> *mut c_void { match self { - FrameContext::X64(ctx) => &mut ctx.0 as *mut CONTEXT as LPVOID, - FrameContext::X86(ctx) => ctx as *mut WOW64_CONTEXT as LPVOID, + FrameContext::X64(ctx) => &mut ctx.0 as *mut CONTEXT as _, + FrameContext::X86(ctx) => ctx as *mut WOW64_CONTEXT as _, } } - pub fn machine_type(&self) -> WORD { + pub fn machine_type(&self) -> IMAGE_FILE_MACHINE { match self { FrameContext::X64(_) => IMAGE_FILE_MACHINE_AMD64, FrameContext::X86(_) => IMAGE_FILE_MACHINE_I386, @@ -301,17 +204,11 @@ impl FrameContext { pub fn set_thread_context(&self, thread_handle: HANDLE) -> Result<()> { match self { - FrameContext::X86(ctx) => { - check_winapi(|| unsafe { Wow64SetThreadContext(thread_handle, ctx) }) - .context("SetThreadContext")? - } - FrameContext::X64(ctx) => { - check_winapi(|| unsafe { SetThreadContext(thread_handle, &ctx.0) }) - .context("SetThreadContext")? - } + FrameContext::X86(ctx) => unsafe { Wow64SetThreadContext(thread_handle, ctx) }, + FrameContext::X64(ctx) => unsafe { SetThreadContext(thread_handle, &ctx.0) }, } - - Ok(()) + .ok() + .context("SetThreadContext") } pub fn get_register_u64>(&self, reg: R) -> u64 { @@ -385,20 +282,32 @@ impl FrameContext { } pub fn get_thread_frame(process_handle: HANDLE, thread_handle: HANDLE) -> Result { + // not yet defined in windows-rs + const WOW64_CONTEXT_ALL: u32 = 0x1003f; + const CONTEXT_ALL: u32 = 0x10001f; + if process::is_wow64_process(process_handle) { - let mut ctx: WOW64_CONTEXT = unsafe { MaybeUninit::zeroed().assume_init() }; - ctx.ContextFlags = WOW64_CONTEXT_ALL; + let mut ctx = WOW64_CONTEXT { + ContextFlags: WOW64_CONTEXT_ALL, + ..Default::default() + }; - check_winapi(|| unsafe { Wow64GetThreadContext(thread_handle, &mut ctx) }) + unsafe { Wow64GetThreadContext(thread_handle, &mut ctx) } + .ok() .context("Wow64GetThreadContext")?; + Ok(FrameContext::X86(ctx)) } else { - // required by `CONTEXT`, is a FIXME in winapi right now - let mut ctx: Aligned16 = unsafe { MaybeUninit::zeroed().assume_init() }; + // alignment is required by `CONTEXT`, not enforced by windows-rs + let mut ctx: Aligned16 = Aligned16(CONTEXT { + ContextFlags: CONTEXT_ALL, + ..Default::default() + }); - ctx.0.ContextFlags = CONTEXT_ALL; - check_winapi(|| unsafe { GetThreadContext(thread_handle, &mut ctx.0) }) + unsafe { GetThreadContext(thread_handle, &mut ctx.0) } + .ok() .context("GetThreadContext")?; + Ok(FrameContext::X64(ctx)) } } @@ -470,25 +379,40 @@ pub struct DebugHelpGuard { lock: HANDLE, } +// have to wrap this to get extern "system" +#[allow(non_snake_case)] +unsafe extern "system" fn WrappedSymFunctionTableAccess64( + process_handle: HANDLE, + address: u64, +) -> *mut c_void { + SymFunctionTableAccess64(process_handle, address) +} + +// have to wrap this to get extern "system" +#[allow(non_snake_case)] +unsafe extern "system" fn WrappedSymGetModuleBase64(process_handle: HANDLE, address: u64) -> u64 { + SymGetModuleBase64(process_handle, address) +} + impl DebugHelpGuard { pub fn new(lock: HANDLE) -> Self { DebugHelpGuard { lock } } - pub fn sym_get_options(&self) -> DWORD { + pub fn sym_get_options(&self) -> u32 { unsafe { SymGetOptions() } } - pub fn sym_set_options(&self, options: DWORD) -> DWORD { + pub fn sym_set_options(&self, options: u32) -> u32 { unsafe { SymSetOptions(options) } } pub fn sym_initialize(&self, process_handle: HANDLE) -> Result<()> { - check_winapi(|| unsafe { SymInitializeW(process_handle, std::ptr::null(), FALSE) }) + Ok(unsafe { SymInitializeW(process_handle, None, FALSE) }.ok()?) } pub fn sym_cleanup(&self, process_handle: HANDLE) -> Result<()> { - check_winapi(|| unsafe { SymCleanup(process_handle) }) + Ok(unsafe { SymCleanup(process_handle) }.ok()?) } pub fn sym_load_module( @@ -496,19 +420,19 @@ impl DebugHelpGuard { process_handle: HANDLE, file_handle: HANDLE, image_name: &Path, - base_of_dll: DWORD64, + base_of_dll: u64, image_size: u32, - ) -> Result { + ) -> Result { let load_address = unsafe { SymLoadModuleExW( process_handle, file_handle, - win_util::string::to_wstring(image_name).as_ptr(), - std::ptr::null_mut(), + PCWSTR(win_util::string::to_wstring(image_name).as_ptr()), + None, base_of_dll, image_size, - std::ptr::null_mut(), - 0, + None, + SYM_LOAD_FLAGS::default(), ) }; @@ -518,7 +442,7 @@ impl DebugHelpGuard { // when we have multiple debuggers - each tracks loading symbols separately. let last_error = std::io::Error::last_os_error(); match last_error.raw_os_error() { - Some(code) if code == ERROR_SUCCESS as i32 => Ok(0), + Some(code) if code == ERROR_SUCCESS.0 as i32 => Ok(0), _ => Err(last_error.into()), } } @@ -526,7 +450,7 @@ impl DebugHelpGuard { } } - pub fn get_module_base(&self, process_handle: HANDLE, addr: DWORD64) -> Result { + pub fn get_module_base(&self, process_handle: HANDLE, addr: u64) -> Result { if let Some(base) = NonZeroU64::new(unsafe { SymGetModuleBase64(process_handle, addr) }) { Ok(base) } else { @@ -535,16 +459,16 @@ impl DebugHelpGuard { } } - pub fn stackwalk_ex bool>( + pub fn stackwalk_ex( &self, process_handle: HANDLE, thread_handle: HANDLE, walk_inline_frames: bool, - mut f: F, + mut f: impl FnMut(&STACKFRAME_EX) -> bool, ) -> Result<()> { let mut frame_context = get_thread_frame(process_handle, thread_handle)?; - let mut frame: STACKFRAME_EX = unsafe { MaybeUninit::zeroed().assume_init() }; + let mut frame = STACKFRAME_EX::default(); frame.AddrPC.Offset = frame_context.program_counter(); frame.AddrPC.Mode = AddrModeFlat; frame.AddrStack.Offset = frame_context.stack_pointer(); @@ -560,14 +484,14 @@ impl DebugHelpGuard { loop { let success = unsafe { StackWalkEx( - frame_context.machine_type().into(), + frame_context.machine_type().0 as _, process_handle, thread_handle, &mut frame, frame_context.as_mut_ptr(), None, - Some(SymFunctionTableAccess64), - Some(SymGetModuleBase64), + Some(WrappedSymFunctionTableAccess64), + Some(WrappedSymGetModuleBase64), None, SYM_STKWALK_DEFAULT, ) @@ -589,21 +513,22 @@ impl DebugHelpGuard { &self, process_handle: HANDLE, program_counter: u64, - inline_context: DWORD, + inline_context: u32, ) -> Result { let mut sym_info; let sym_info_ptr = init_sym_info!(sym_info); let mut displacement = 0; - check_winapi(|| unsafe { + unsafe { SymFromInlineContextW( process_handle, program_counter, inline_context, - &mut displacement, + Some(&mut displacement), sym_info_ptr, ) - })?; + .ok()? + }; let address = sym_info_ptr.Address; let name_len = cmp::min( @@ -625,12 +550,14 @@ impl DebugHelpGuard { &self, process_handle: HANDLE, program_counter: u64, - inline_context: DWORD, + inline_context: u32, ) -> Result { - let mut line_info: IMAGEHLP_LINEW64 = unsafe { MaybeUninit::zeroed().assume_init() }; - line_info.SizeOfStruct = size_of::() as DWORD; - let mut displacement: DWORD = 0; - check_winapi(|| unsafe { + let mut line_info = IMAGEHLP_LINEW64 { + SizeOfStruct: size_of::() as u32, + ..Default::default() + }; + let mut displacement: u32 = 0; + unsafe { SymGetLineFromInlineContextW( process_handle, program_counter, @@ -639,9 +566,10 @@ impl DebugHelpGuard { &mut displacement, &mut line_info, ) - })?; + .ok()? + }; - let filename = unsafe { win_util::string::os_string_from_wide_ptr(line_info.FileName) }; + let filename = unsafe { win_util::string::os_string_from_wide_ptr(line_info.FileName.0) }; Ok(SymLineInfo { filename: filename.into(), line_number: line_info.LineNumber, @@ -653,11 +581,11 @@ impl DebugHelpGuard { process_handle: HANDLE, program_counter: u64, ) -> Result { - let mut module_info: IMAGEHLP_MODULEW64 = unsafe { MaybeUninit::zeroed().assume_init() }; - module_info.SizeOfStruct = size_of::() as DWORD; - check_winapi(|| unsafe { - SymGetModuleInfoW64(process_handle, program_counter, &mut module_info) - })?; + let mut module_info = IMAGEHLP_MODULEW64 { + SizeOfStruct: size_of::() as u32, + ..Default::default() + }; + unsafe { SymGetModuleInfoW64(process_handle, program_counter, &mut module_info).ok()? }; let module_name = unsafe { win_util::string::os_string_from_wide_ptr(module_info.ModuleName.as_ptr()) }; @@ -681,13 +609,14 @@ impl DebugHelpGuard { let mut qualified_sym = OsString::from(modname.as_ref()); qualified_sym.push("!"); qualified_sym.push(sym); - check_winapi(|| unsafe { + unsafe { SymFromNameW( process_handle, - win_util::string::to_wstring(qualified_sym).as_ptr(), + PCWSTR(win_util::string::to_wstring(qualified_sym).as_ptr()), sym_info_ptr, ) - })?; + .ok()? + }; Ok(SymInfo { symbol: sym.to_string(), @@ -712,11 +641,7 @@ impl DebugHelpGuard { let file_name = win_util::string::to_wstring(file_name); // Must be at least `MAX_PATH` characters in length. - let mut found_file_data = Vec::::with_capacity(MAX_PATH); - - // Inherit search path used in `SymInitializeW()`. When that is also set - // to `NULL`, the default search path is used. - let search_path = std::ptr::null_mut(); + let mut found_file_data = vec![0; MAX_PATH as usize]; // See: https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symfindfileinpathw#remarks let id = pdb_signature as *mut _; @@ -726,20 +651,21 @@ impl DebugHelpGuard { // Assert that we are passing a DWORD signature in `id`. let flags = SSRVOPT_DWORD; - let result = check_winapi(|| unsafe { + let result = unsafe { SymFindFileInPathW( process_handle, - search_path, - file_name.as_ptr(), - id, + None, // use default search path + PCWSTR(file_name.as_ptr()), + Some(id), two, three, flags, - found_file_data.as_mut_ptr(), + PWSTR(found_file_data.as_mut_ptr() as _), + None, None, - std::ptr::null_mut(), ) - }); + .ok() + }; if result.is_ok() { // Safety: `found_file_data` must contain at least one NUL byte. @@ -756,15 +682,8 @@ impl DebugHelpGuard { } pub fn sym_get_search_path(&self, process_handle: HANDLE) -> Result { - let mut search_path_data = Vec::::with_capacity(MAX_SYM_SEARCH_PATH_LEN); - let search_path_len = MAX_SYM_SEARCH_PATH_LEN as u32; - check_winapi(|| unsafe { - SymGetSearchPathW( - process_handle, - search_path_data.as_mut_ptr(), - search_path_len, - ) - })?; + let mut search_path_data = vec![0; MAX_SYM_SEARCH_PATH_LEN]; + unsafe { SymGetSearchPathW(process_handle, &mut search_path_data).ok()? }; // Safety: `search_path_data` must contain at least one NUL byte. // @@ -781,9 +700,9 @@ impl DebugHelpGuard { process_handle: HANDLE, search_path: impl AsRef, ) -> Result<()> { - let mut search_path = win_util::string::to_wstring(search_path.as_ref()); + let search_path = win_util::string::to_wstring(search_path.as_ref()); - check_winapi(|| unsafe { SymSetSearchPathW(process_handle, search_path.as_mut_ptr()) })?; + unsafe { SymSetSearchPathW(process_handle, PCWSTR(search_path.as_ptr())).ok()? }; Ok(()) } @@ -792,6 +711,6 @@ impl DebugHelpGuard { impl Drop for DebugHelpGuard { fn drop(&mut self) { let r = unsafe { ReleaseMutex(self.lock) }; - debug_assert!(r != 0); + debug_assert!(r.as_bool()); } } diff --git a/src/agent/debugger/src/debug_event.rs b/src/agent/debugger/src/debug_event.rs index 9665404899..ee4a31225f 100644 --- a/src/agent/debugger/src/debug_event.rs +++ b/src/agent/debugger/src/debug_event.rs @@ -9,16 +9,12 @@ use std::{ }; use win_util::file::get_path_from_handle; -use winapi::{ - shared::minwindef::DWORD, - um::minwinbase::{ - CREATE_PROCESS_DEBUG_EVENT, CREATE_PROCESS_DEBUG_INFO, CREATE_THREAD_DEBUG_EVENT, - CREATE_THREAD_DEBUG_INFO, DEBUG_EVENT, EXCEPTION_DEBUG_EVENT, EXCEPTION_DEBUG_INFO, - EXIT_PROCESS_DEBUG_EVENT, EXIT_PROCESS_DEBUG_INFO, EXIT_THREAD_DEBUG_EVENT, - EXIT_THREAD_DEBUG_INFO, LOAD_DLL_DEBUG_EVENT, LOAD_DLL_DEBUG_INFO, - OUTPUT_DEBUG_STRING_EVENT, OUTPUT_DEBUG_STRING_INFO, RIP_EVENT, RIP_INFO, - UNLOAD_DLL_DEBUG_EVENT, UNLOAD_DLL_DEBUG_INFO, - }, +use windows::Win32::System::Diagnostics::Debug::{ + CREATE_PROCESS_DEBUG_EVENT, CREATE_PROCESS_DEBUG_INFO, CREATE_THREAD_DEBUG_EVENT, + CREATE_THREAD_DEBUG_INFO, DEBUG_EVENT, EXCEPTION_DEBUG_EVENT, EXCEPTION_DEBUG_INFO, + EXIT_PROCESS_DEBUG_EVENT, EXIT_PROCESS_DEBUG_INFO, EXIT_THREAD_DEBUG_EVENT, + EXIT_THREAD_DEBUG_INFO, LOAD_DLL_DEBUG_EVENT, LOAD_DLL_DEBUG_INFO, OUTPUT_DEBUG_STRING_EVENT, + OUTPUT_DEBUG_STRING_INFO, RIP_EVENT, RIP_INFO, UNLOAD_DLL_DEBUG_EVENT, UNLOAD_DLL_DEBUG_INFO, }; pub enum DebugEventInfo<'a> { @@ -42,7 +38,7 @@ impl<'a> Display for DebugEventInfo<'a> { write!( formatter, "Exception code=0x{:08x} address=0x{:08x} {}", - info.ExceptionRecord.ExceptionCode, + info.ExceptionRecord.ExceptionCode.0, info.ExceptionRecord.ExceptionAddress as u64, if info.dwFirstChance == 0 { "second_chance" @@ -52,7 +48,7 @@ impl<'a> Display for DebugEventInfo<'a> { )?; } CreateThread(info) => { - write!(formatter, "CreateThread handle={:x}", info.hThread as u64)?; + write!(formatter, "CreateThread handle={:x}", info.hThread.0 as u64)?; } CreateProcess(info) => { let image_name = get_path_from_handle(info.hFile).unwrap_or_else(|_| "???".into()); @@ -89,14 +85,14 @@ impl<'a> Display for DebugEventInfo<'a> { write!( formatter, "OutputDebugString unicode={} address=0x{:016x} length={}", - info.fUnicode, info.lpDebugStringData as u64, info.nDebugStringLength, + info.fUnicode, info.lpDebugStringData.0 as u64, info.nDebugStringLength, )?; } Rip(info) => { write!( formatter, "Rip error=0x{:x} type={}", - info.dwError, info.dwType + info.dwError, info.dwType.0 )?; } Unknown => { @@ -109,8 +105,8 @@ impl<'a> Display for DebugEventInfo<'a> { } pub struct DebugEvent<'a> { - process_id: DWORD, - thread_id: DWORD, + process_id: u32, + thread_id: u32, info: DebugEventInfo<'a>, } @@ -118,17 +114,17 @@ impl<'a> DebugEvent<'a> { pub fn new(de: &'a DEBUG_EVENT) -> Self { let info = unsafe { match de.dwDebugEventCode { - EXCEPTION_DEBUG_EVENT => DebugEventInfo::Exception(de.u.Exception()), + EXCEPTION_DEBUG_EVENT => DebugEventInfo::Exception(&de.u.Exception), CREATE_PROCESS_DEBUG_EVENT => { - DebugEventInfo::CreateProcess(de.u.CreateProcessInfo()) + DebugEventInfo::CreateProcess(&de.u.CreateProcessInfo) } - CREATE_THREAD_DEBUG_EVENT => DebugEventInfo::CreateThread(de.u.CreateThread()), - EXIT_PROCESS_DEBUG_EVENT => DebugEventInfo::ExitProcess(de.u.ExitProcess()), - EXIT_THREAD_DEBUG_EVENT => DebugEventInfo::ExitThread(de.u.ExitThread()), - LOAD_DLL_DEBUG_EVENT => DebugEventInfo::LoadDll(de.u.LoadDll()), - UNLOAD_DLL_DEBUG_EVENT => DebugEventInfo::UnloadDll(de.u.UnloadDll()), - OUTPUT_DEBUG_STRING_EVENT => DebugEventInfo::OutputDebugString(de.u.DebugString()), - RIP_EVENT => DebugEventInfo::Rip(de.u.RipInfo()), + CREATE_THREAD_DEBUG_EVENT => DebugEventInfo::CreateThread(&de.u.CreateThread), + EXIT_PROCESS_DEBUG_EVENT => DebugEventInfo::ExitProcess(&de.u.ExitProcess), + EXIT_THREAD_DEBUG_EVENT => DebugEventInfo::ExitThread(&de.u.ExitThread), + LOAD_DLL_DEBUG_EVENT => DebugEventInfo::LoadDll(&de.u.LoadDll), + UNLOAD_DLL_DEBUG_EVENT => DebugEventInfo::UnloadDll(&de.u.UnloadDll), + OUTPUT_DEBUG_STRING_EVENT => DebugEventInfo::OutputDebugString(&de.u.DebugString), + RIP_EVENT => DebugEventInfo::Rip(&de.u.RipInfo), _ => DebugEventInfo::Unknown, } }; @@ -140,11 +136,11 @@ impl<'a> DebugEvent<'a> { } } - pub fn process_id(&self) -> DWORD { + pub fn process_id(&self) -> u32 { self.process_id } - pub fn thread_id(&self) -> DWORD { + pub fn thread_id(&self) -> u32 { self.thread_id } diff --git a/src/agent/debugger/src/debugger.rs b/src/agent/debugger/src/debugger.rs index 2a61995414..d7c0f1ba5e 100644 --- a/src/agent/debugger/src/debugger.rs +++ b/src/agent/debugger/src/debugger.rs @@ -9,7 +9,7 @@ #![allow(clippy::redundant_closure)] #![allow(clippy::redundant_clone)] use std::{ - ffi::OsString, + ffi::{c_void, OsString}, mem::MaybeUninit, os::windows::process::CommandExt, path::{Path, PathBuf}, @@ -18,19 +18,19 @@ use std::{ use anyhow::{Context, Result}; use log::{error, trace}; -use win_util::{check_winapi, last_os_error, process}; -use winapi::{ - shared::{ - minwindef::{DWORD, FALSE, LPCVOID, TRUE}, - winerror::ERROR_SEM_TIMEOUT, +use win_util::{last_os_error, process}; +use windows::Win32::{ + Foundation::{ + GetLastError, DBG_CONTINUE, DBG_EXCEPTION_NOT_HANDLED, ERROR_SEM_TIMEOUT, + EXCEPTION_BREAKPOINT, EXCEPTION_SINGLE_STEP, FALSE, HANDLE, NTSTATUS, + STATUS_WX86_BREAKPOINT, TRUE, }, - um::{ - dbghelp::ADDRESS64, - debugapi::{ContinueDebugEvent, WaitForDebugEvent}, - errhandlingapi::GetLastError, - minwinbase::{EXCEPTION_BREAKPOINT, EXCEPTION_DEBUG_INFO, EXCEPTION_SINGLE_STEP}, - winbase::{DebugSetProcessKillOnExit, DEBUG_ONLY_THIS_PROCESS, INFINITE}, - winnt::{DBG_CONTINUE, DBG_EXCEPTION_NOT_HANDLED, HANDLE}, + System::{ + Diagnostics::Debug::{ + ContinueDebugEvent, DebugSetProcessKillOnExit, WaitForDebugEvent, ADDRESS64, + EXCEPTION_DEBUG_INFO, RIP_INFO_TYPE, + }, + Threading::{DEBUG_ONLY_THIS_PROCESS, INFINITE}, }, }; @@ -41,9 +41,6 @@ use crate::{ target::Target, }; -// When debugging a WoW64 process, we see STATUS_WX86_BREAKPOINT in addition to EXCEPTION_BREAKPOINT -const STATUS_WX86_BREAKPOINT: u32 = ::winapi::shared::ntstatus::STATUS_WX86_BREAKPOINT as u32; - /// Uniquely identify a breakpoint. #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct BreakpointId(pub u64); @@ -106,7 +103,7 @@ impl ModuleLoadInfo { #[rustfmt::skip] #[allow(clippy::trivially_copy_pass_by_ref)] pub trait DebugEventHandler { - fn on_exception(&mut self, _debugger: &mut Debugger, _info: &EXCEPTION_DEBUG_INFO, _process_handle: HANDLE) -> DWORD { + fn on_exception(&mut self, _debugger: &mut Debugger, _info: &EXCEPTION_DEBUG_INFO, _process_handle: HANDLE) -> NTSTATUS { // Continue normal exception handling processing DBG_EXCEPTION_NOT_HANDLED } @@ -118,7 +115,7 @@ pub trait DebugEventHandler { fn on_unload_dll(&mut self, _debugger: &mut Debugger, _base_address: u64) {} fn on_output_debug_string(&mut self, _debugger: &mut Debugger, _message: String) {} fn on_output_debug_os_string(&mut self, _debugger: &mut Debugger, _message: OsString) {} - fn on_rip(&mut self, _debugger: &mut Debugger, _error: u32, _type: u32) {} + fn on_rip(&mut self, _debugger: &mut Debugger, _error: u32, _type: RIP_INFO_TYPE) {} fn on_poll(&mut self, _debugger: &mut Debugger) {} fn on_breakpoint(&mut self, _debugger: &mut Debugger, _id: BreakpointId) {} } @@ -127,7 +124,7 @@ pub trait DebugEventHandler { struct ContinueDebugEventArguments { process_id: u32, thread_id: u32, - continue_status: u32, + continue_status: NTSTATUS, } pub struct Debugger { @@ -142,11 +139,12 @@ impl Debugger { callbacks: &mut impl DebugEventHandler, ) -> Result<(Self, Child)> { let child = command - .creation_flags(DEBUG_ONLY_THIS_PROCESS) + .creation_flags(DEBUG_ONLY_THIS_PROCESS.0) .spawn() .context("debugee failed to start")?; - check_winapi(|| unsafe { DebugSetProcessKillOnExit(TRUE) }) + unsafe { DebugSetProcessKillOnExit(TRUE) } + .ok() .context("Setting DebugSetProcessKillOnExit to TRUE")?; // Call once to get our initial CreateProcess event. @@ -242,7 +240,7 @@ impl Debugger { pub fn process_event( &mut self, callbacks: &mut impl DebugEventHandler, - timeout_ms: DWORD, + timeout_ms: u32, ) -> Result { let mut de = MaybeUninit::uninit(); if unsafe { WaitForDebugEvent(de.as_mut_ptr(), timeout_ms) } == TRUE { @@ -312,7 +310,11 @@ impl Debugger { } } - fn dispatch_event(&mut self, de: &DebugEvent, callbacks: &mut impl DebugEventHandler) -> u32 { + fn dispatch_event( + &mut self, + de: &DebugEvent, + callbacks: &mut impl DebugEventHandler, + ) -> NTSTATUS { let mut continue_status = DBG_CONTINUE; if let DebugEventInfo::CreateThread(info) = de.info() { @@ -378,7 +380,7 @@ impl Debugger { if info.fUnicode != 0 { if let Ok(message) = process::read_wide_string( self.target.process_handle(), - info.lpDebugStringData as LPCVOID, + info.lpDebugStringData.0.cast(), length, ) { callbacks.on_output_debug_os_string(self, message); @@ -386,7 +388,7 @@ impl Debugger { } else { if let Ok(message) = process::read_narrow_string( self.target.process_handle(), - info.lpDebugStringData as LPCVOID, + info.lpDebugStringData.0.cast(), length, ) { callbacks.on_output_debug_string(self, message); @@ -408,7 +410,7 @@ impl Debugger { &mut self, info: &EXCEPTION_DEBUG_INFO, callbacks: &mut impl DebugEventHandler, - ) -> Result { + ) -> Result { match is_debugger_notification( info.ExceptionRecord.ExceptionCode, info.ExceptionRecord.ExceptionAddress as u64, @@ -486,7 +488,11 @@ impl Debugger { self.target.read_flags_register() } - pub fn read_memory(&mut self, remote_address: LPCVOID, buf: &mut [impl Copy]) -> Result<()> { + pub fn read_memory( + &mut self, + remote_address: *const c_void, + buf: &mut [impl Copy], + ) -> Result<()> { self.target.read_memory(remote_address, buf) } @@ -523,17 +529,17 @@ enum DebuggerNotification { InitialBreak, InitialWow64Break, Breakpoint { pc: u64 }, - SingleStep { thread_id: DWORD }, + SingleStep { thread_id: u32 }, } fn is_debugger_notification( - exception_code: u32, + exception_code: NTSTATUS, exception_address: u64, target: &mut Target, ) -> Option { // The CLR debugger notification exception is not a crash: // https://github.com/dotnet/coreclr/blob/9ee6b8a33741cc5f3eb82e990646dd3a81de996a/src/debug/inc/dbgipcevents.h#L37 - const CLRDBG_NOTIFICATION_EXCEPTION_CODE: DWORD = 0x04242420; + const CLRDBG_NOTIFICATION_EXCEPTION_CODE: NTSTATUS = NTSTATUS(0x04242420); match exception_code { // Not a breakpoint, but sent to debuggers as a notification that the process has diff --git a/src/agent/debugger/src/module.rs b/src/agent/debugger/src/module.rs index 271a99da78..acea7ace7f 100644 --- a/src/agent/debugger/src/module.rs +++ b/src/agent/debugger/src/module.rs @@ -11,9 +11,11 @@ use std::{ use anyhow::Result; use log::error; use win_util::{file, handle::Handle}; -use winapi::um::{ - handleapi::INVALID_HANDLE_VALUE, - winnt::{HANDLE, IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_I386}, +use windows::Win32::{ + Foundation::{HANDLE, INVALID_HANDLE_VALUE}, + System::SystemInformation::{ + IMAGE_FILE_MACHINE, IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_I386, + }, }; use crate::{ @@ -190,7 +192,7 @@ fn get_image_details(path: &Path) -> Result { .map(|h| h.windows_fields.size_of_image) .ok_or_else(|| anyhow::anyhow!("Missing optional header in PE image"))?; - let machine = match header.coff_header.machine { + let machine = match IMAGE_FILE_MACHINE(header.coff_header.machine) { IMAGE_FILE_MACHINE_AMD64 => Machine::X64, IMAGE_FILE_MACHINE_I386 => Machine::X86, _ => Machine::Unknown, diff --git a/src/agent/debugger/src/stack.rs b/src/agent/debugger/src/stack.rs index 95c4ef2088..8bd16f0a65 100644 --- a/src/agent/debugger/src/stack.rs +++ b/src/agent/debugger/src/stack.rs @@ -11,7 +11,7 @@ use fnv::FnvHasher; use log::trace; use serde::{Serialize, Serializer}; use win_util::memory; -use winapi::{shared::minwindef::DWORD, um::winnt::HANDLE}; +use windows::Win32::Foundation::HANDLE; use crate::dbghelp::{self, DebugHelpGuard, ModuleInfo, SymInfo, SymLineInfo}; @@ -167,7 +167,7 @@ fn get_function_location_in_module( module_info: &ModuleInfo, process_handle: HANDLE, program_counter: u64, - inline_context: DWORD, + inline_context: u32, ) -> DebugStackFrame { let module_name = module_info.name().to_string_lossy().to_string(); let module_offset = program_counter - module_info.base_address(); diff --git a/src/agent/debugger/src/target.rs b/src/agent/debugger/src/target.rs index 5291b4fead..d9bf8a4cc3 100644 --- a/src/agent/debugger/src/target.rs +++ b/src/agent/debugger/src/target.rs @@ -3,22 +3,15 @@ #![allow(clippy::single_match)] -use std::{io, num::NonZeroU64, path::Path}; +use std::{ffi::c_void, io, num::NonZeroU64, path::Path}; use anyhow::{format_err, Result}; use log::{debug, error, trace}; use rand::{thread_rng, Rng}; use win_util::process; -use winapi::{ - shared::{ - minwindef::{DWORD, LPCVOID}, - winerror::ERROR_ACCESS_DENIED, - }, - um::{ - processthreadsapi::{ResumeThread, SuspendThread}, - winbase::Wow64SuspendThread, - winnt::HANDLE, - }, +use windows::Win32::{ + Foundation::{ERROR_ACCESS_DENIED, HANDLE}, + System::Threading::{ResumeThread, SuspendThread, Wow64SuspendThread}, }; use crate::{ @@ -49,7 +42,7 @@ struct ThreadInfo { wow64: bool, } -const SUSPEND_RESUME_ERROR_CODE: DWORD = -1i32 as DWORD; +const SUSPEND_RESUME_ERROR_CODE: u32 = -1i32 as u32; impl ThreadInfo { fn new(id: u32, handle: HANDLE, wow64: bool) -> Self { @@ -137,7 +130,7 @@ impl ThreadInfo { // // This means we are observing a race between OS-level thread exit and // the (pending) debug event. - let exited = matches!(raw_os_error as DWORD, ERROR_ACCESS_DENIED); + let exited = raw_os_error as u32 == ERROR_ACCESS_DENIED.0; Ok(exited) } @@ -151,10 +144,10 @@ enum SymInitalizeState { } pub struct Target { - process_id: DWORD, + process_id: u32, process_handle: HANDLE, current_thread_handle: HANDLE, - current_thread_id: DWORD, + current_thread_id: u32, saw_initial_bp: bool, saw_initial_wow64_bp: bool, wow64: bool, @@ -165,7 +158,7 @@ pub struct Target { exited: bool, // Map of thread ID to thread info. - thread_info: fnv::FnvHashMap, + thread_info: fnv::FnvHashMap, // We cache the current thread context for possible repeated queries and modifications. // We want to call GetThreadContext once, then call SetThreadContext (if necessary) before @@ -180,7 +173,7 @@ pub struct Target { unresolved_breakpoints: Vec, // Map of thread ID to stepping state (e.g. breakpoint address to restore breakpoints) - single_step: fnv::FnvHashMap, + single_step: fnv::FnvHashMap, // When stepping after hitting a breakpoint, we need to restore the breakpoint. // We track the address of the breakpoint to restore. 1 is sufficient because we @@ -190,8 +183,8 @@ pub struct Target { impl Target { pub fn new( - process_id: DWORD, - thread_id: DWORD, + process_id: u32, + thread_id: u32, process_handle: HANDLE, thread_handle: HANDLE, ) -> Self { @@ -222,11 +215,11 @@ impl Target { self.current_thread_handle } - pub fn current_thread_id(&self) -> DWORD { + pub fn current_thread_id(&self) -> u32 { self.current_thread_id } - pub fn create_new_thread(&mut self, thread_handle: HANDLE, thread_id: DWORD) { + pub fn create_new_thread(&mut self, thread_handle: HANDLE, thread_id: u32) { self.current_thread_handle = thread_handle; self.thread_info.insert( thread_id, @@ -234,12 +227,12 @@ impl Target { ); } - pub fn set_current_thread(&mut self, thread_id: DWORD) { + pub fn set_current_thread(&mut self, thread_id: u32) { self.current_thread_id = thread_id; self.current_thread_handle = self.thread_info.get(&thread_id).unwrap().handle; } - pub fn exit_thread(&mut self, thread_id: DWORD) { + pub fn exit_thread(&mut self, thread_id: u32) { self.thread_info.remove(&thread_id); } @@ -247,7 +240,7 @@ impl Target { self.process_handle } - pub fn process_id(&self) -> DWORD { + pub fn process_id(&self) -> u32 { self.process_id } @@ -423,11 +416,11 @@ impl Target { .contains_breakpoint(address) } - pub(crate) fn expecting_single_step(&self, thread_id: DWORD) -> bool { + pub(crate) fn expecting_single_step(&self, thread_id: u32) -> bool { self.single_step.contains_key(&thread_id) } - pub(crate) fn complete_single_step(&mut self, thread_id: DWORD) -> Result<()> { + pub(crate) fn complete_single_step(&mut self, thread_id: u32) -> Result<()> { // We now re-enable the breakpoint so that the next time we step, the breakpoint // will be restored. if let Some(restore_breakpoint_pc) = self.restore_breakpoint_pc.take() { @@ -626,7 +619,11 @@ impl Target { Ok(current_context.get_flags()) } - pub fn read_memory(&mut self, remote_address: LPCVOID, buf: &mut [impl Copy]) -> Result<()> { + pub fn read_memory( + &mut self, + remote_address: *const c_void, + buf: &mut [impl Copy], + ) -> Result<()> { process::read_memory_array(self.process_handle, remote_address, buf)?; // We don't remove breakpoints when processing an event, so it's possible that the diff --git a/src/agent/dynamic-library/Cargo.toml b/src/agent/dynamic-library/Cargo.toml index f440b477c8..604d221700 100644 --- a/src/agent/dynamic-library/Cargo.toml +++ b/src/agent/dynamic-library/Cargo.toml @@ -15,20 +15,8 @@ thiserror = "1.0" debugger = { path = "../debugger" } winreg = "0.50" -[dependencies.winapi] -version = "0.3" -features = [ - "dbghelp", - "debugapi", - "handleapi", - "memoryapi", - "processthreadsapi", - "securitybaseapi", - "shellapi", - "werapi", - "winbase", - "winerror", -] +[dependencies.windows] +version = "0.48" [[bin]] name = "dynamic-library" diff --git a/src/agent/input-tester/Cargo.toml b/src/agent/input-tester/Cargo.toml index 58473c6d2d..fe5ac6032c 100644 --- a/src/agent/input-tester/Cargo.toml +++ b/src/agent/input-tester/Cargo.toml @@ -17,13 +17,8 @@ rayon = "1.7" sha2 = "0.10.2" win-util = { path = "../win-util" } -[dependencies.winapi] -version = "0.3" +[dependencies.windows] +version = "0.48" features = [ - "debugapi", - "handleapi", - "memoryapi", - "processthreadsapi", - "werapi", - "winbase", + "Win32_System_SystemServices" ] diff --git a/src/agent/input-tester/src/crash_detector.rs b/src/agent/input-tester/src/crash_detector.rs index 5009a7fde2..f788981166 100644 --- a/src/agent/input-tester/src/crash_detector.rs +++ b/src/agent/input-tester/src/crash_detector.rs @@ -20,12 +20,9 @@ use win_util::{ pipe_handle::{pipe, PipeReaderNonBlocking}, process, }; -use winapi::{ - shared::minwindef::DWORD, - um::{ - minwinbase::EXCEPTION_DEBUG_INFO, - winnt::{DBG_EXCEPTION_NOT_HANDLED, HANDLE}, - }, +use windows::Win32::{ + Foundation::{DBG_EXCEPTION_NOT_HANDLED, HANDLE, NTSTATUS}, + System::Diagnostics::Debug::EXCEPTION_DEBUG_INFO, }; use crate::{ @@ -187,7 +184,7 @@ impl DebugEventHandler for CrashDetectorEventHandler { debugger: &mut Debugger, info: &EXCEPTION_DEBUG_INFO, process_handle: HANDLE, - ) -> DWORD { + ) -> NTSTATUS { if !is_vcpp_notification(info, process_handle) { // An exception might be handled, or other cleanup might occur between // the first chance and the second chance, so we continue execution. @@ -354,8 +351,8 @@ mod tests { use super::*; use crate::test_result::{ExceptionCode, ExceptionDescription}; - const READ_AV: u32 = 0xc0000005; - const EXCEPTION_CPP: u32 = 0xE06D7363; + const READ_AV: NTSTATUS = NTSTATUS(0xc0000005_u32 as i32); + const EXCEPTION_CPP: NTSTATUS = NTSTATUS(0xE06D7363_u32 as i32); macro_rules! runps { ($timeout: expr, $script: expr) => {{ diff --git a/src/agent/input-tester/src/test_result/asan.rs b/src/agent/input-tester/src/test_result/asan.rs index 7082ee28cd..99067acaad 100644 --- a/src/agent/input-tester/src/test_result/asan.rs +++ b/src/agent/input-tester/src/test_result/asan.rs @@ -3,19 +3,15 @@ #![allow(clippy::unreadable_literal)] +use std::ffi::c_void; use std::mem::size_of; use anyhow::{bail, Result}; use win_util::process; -use win_util::UNION; // Ideally this would be exported from winapi. -use winapi::{ - shared::{ - basetsd::UINT64, - minwindef::{DWORD, LPCVOID}, - ntdef::LPWSTR, - }, - um::winnt::{EXCEPTION_RECORD, HANDLE}, - STRUCT, +use windows::core::PWSTR; +use windows::Win32::{ + Foundation::{HANDLE, NTSTATUS}, + System::Diagnostics::Debug::EXCEPTION_RECORD, }; /// An error detected by ASAN. @@ -54,52 +50,55 @@ pub enum AsanError { } // Types defined in vcasan.h -STRUCT! { #[allow(non_snake_case)] +#[repr(C)] +#[derive(Copy, Debug, Clone)] struct EXCEPTION_ASAN_ERROR { // The description string from asan, such as heap-use-after-free - uiRuntimeDescriptionLength: UINT64, - pwRuntimeDescription: LPWSTR, + uiRuntimeDescriptionLength: u64, + pwRuntimeDescription: PWSTR, // A translation of the description string to something more user friendly done by this lib // not localized - uiRuntimeShortMessageLength: UINT64, - pwRuntimeShortMessage: LPWSTR, + uiRuntimeShortMessageLength: u64, + pwRuntimeShortMessage: PWSTR, // the full report from asan, not localized - uiRuntimeFullMessageLength: UINT64, - pwRuntimeFullMessage: LPWSTR, /* pointer to Unicode message (or NULL) */ + uiRuntimeFullMessageLength: u64, + pwRuntimeFullMessage: PWSTR, /* pointer to Unicode message (or NULL) */ // azure payload, WIP - uiCustomDataLength: UINT64, - pwCustomData: LPWSTR, -}} + uiCustomDataLength: u64, + pwCustomData: PWSTR, +} -UNION! { -union EXCEPTION_SANITIZER_ERROR_u { - [u64; 8], - asan asan_mut: EXCEPTION_ASAN_ERROR, -}} +#[repr(C)] +#[derive(Copy, Clone)] +union EXCEPTION_SANITIZER_ERROR_UNION { + _padding: [u64; 8], + asan: EXCEPTION_ASAN_ERROR, +} -STRUCT! { #[allow(non_snake_case)] +#[repr(C)] +#[derive(Copy, Clone)] struct EXCEPTION_SANITIZER_ERROR { // the size of this structure, set by the caller - cbSize: DWORD, + cbSize: u32, // the specific type of sanitizer error this is. Set by the caller, determines which member of the union is valid - dwSanitizerKind: DWORD, - u: EXCEPTION_SANITIZER_ERROR_u, -}} + dwSanitizerKind: NTSTATUS, + u: EXCEPTION_SANITIZER_ERROR_UNION, +} // #define EH_SANITIZER ('san' | 0xE0000000) // #define EH_SANITIZER_ASAN (EH_SANITIZER + 1) -pub const EH_SANITIZER: u32 = - 0xe0000000 | ((b's' as u32) << 16) | ((b'a' as u32) << 8) | b'n' as u32; // 0xe073616e; -pub const EH_SANITIZER_ASAN: u32 = EH_SANITIZER + 1; +pub const EH_SANITIZER: NTSTATUS = + NTSTATUS((0xe0000000 | ((b's' as u32) << 16) | ((b'a' as u32) << 8) | b'n' as u32) as i32); // 0xe073616e; +pub const EH_SANITIZER_ASAN: NTSTATUS = NTSTATUS(EH_SANITIZER.0 + 1); fn get_exception_sanitizer_error( process_handle: HANDLE, - remote_asan_error: LPCVOID, + remote_asan_error: *const c_void, ) -> Result { let record = process::read_memory::(process_handle, remote_asan_error)?; @@ -112,25 +111,28 @@ fn get_exception_sanitizer_error( Ok(record) } -fn get_runtime_description(process_handle: HANDLE, remote_asan_error: LPCVOID) -> Result { +fn get_runtime_description( + process_handle: HANDLE, + remote_asan_error: *const c_void, +) -> Result { let record = get_exception_sanitizer_error(process_handle, remote_asan_error)?; - let asan_error = unsafe { record.u.asan() }; + let asan_error = unsafe { record.u.asan }; let size = asan_error.uiRuntimeDescriptionLength as usize; - let remote_message_address = asan_error.pwRuntimeDescription as LPCVOID; - let message = process::read_wide_string(process_handle, remote_message_address, size)?; + let remote_message_address = asan_error.pwRuntimeDescription.0; + let message = process::read_wide_string(process_handle, remote_message_address as _, size)?; Ok(message.to_string_lossy().to_string()) } -fn get_full_message(process_handle: HANDLE, remote_asan_error: LPCVOID) -> Result { +fn get_full_message(process_handle: HANDLE, remote_asan_error: *const c_void) -> Result { let record = get_exception_sanitizer_error(process_handle, remote_asan_error)?; - let asan_error = unsafe { record.u.asan() }; + let asan_error = unsafe { record.u.asan }; let size = asan_error.uiRuntimeFullMessageLength as usize; - let remote_message_address = asan_error.pwRuntimeFullMessage as LPCVOID; + let remote_message_address = asan_error.pwRuntimeFullMessage.0; if size == 0 || remote_message_address.is_null() { bail!("Empty full message"); } - let message = process::read_wide_string(process_handle, remote_message_address, size)?; + let message = process::read_wide_string(process_handle, remote_message_address as _, size)?; Ok(message.to_string_lossy().to_string()) } @@ -172,7 +174,7 @@ pub fn asan_error_from_exception_record( if exception_record.NumberParameters >= 1 { let message = get_runtime_description( process_handle, - exception_record.ExceptionInformation[0] as LPCVOID, + exception_record.ExceptionInformation[0] as _, ) .ok(); @@ -192,7 +194,7 @@ pub fn get_asan_report( if exception_record.NumberParameters >= 1 { let message = get_full_message( process_handle, - exception_record.ExceptionInformation[0] as LPCVOID, + exception_record.ExceptionInformation[0] as _, ) .ok(); diff --git a/src/agent/input-tester/src/test_result/fast_fail.rs b/src/agent/input-tester/src/test_result/fast_fail.rs index 6cf1d16d86..81944eaa13 100644 --- a/src/agent/input-tester/src/test_result/fast_fail.rs +++ b/src/agent/input-tester/src/test_result/fast_fail.rs @@ -3,26 +3,32 @@ #![allow(clippy::unreadable_literal)] -use winapi::um::winnt::{ - EXCEPTION_RECORD, FAST_FAIL_APCS_DISABLED, FAST_FAIL_CERTIFICATION_FAILURE, - FAST_FAIL_CORRUPT_LIST_ENTRY, FAST_FAIL_CRYPTO_LIBRARY, FAST_FAIL_DEPRECATED_SERVICE_INVOKED, - FAST_FAIL_DLOAD_PROTECTION_FAILURE, FAST_FAIL_FATAL_APP_EXIT, FAST_FAIL_GS_COOKIE_INIT, - FAST_FAIL_GUARD_EXPORT_SUPPRESSION_FAILURE, FAST_FAIL_GUARD_ICALL_CHECK_FAILURE, - FAST_FAIL_GUARD_ICALL_CHECK_SUPPRESSED, FAST_FAIL_GUARD_JUMPTABLE, FAST_FAIL_GUARD_SS_FAILURE, - FAST_FAIL_GUARD_WRITE_CHECK_FAILURE, FAST_FAIL_INCORRECT_STACK, FAST_FAIL_INVALID_ARG, - FAST_FAIL_INVALID_BALANCED_TREE, FAST_FAIL_INVALID_BUFFER_ACCESS, - FAST_FAIL_INVALID_CALL_IN_DLL_CALLOUT, FAST_FAIL_INVALID_CONTROL_STACK, - FAST_FAIL_INVALID_DISPATCH_CONTEXT, FAST_FAIL_INVALID_EXCEPTION_CHAIN, - FAST_FAIL_INVALID_FIBER_SWITCH, FAST_FAIL_INVALID_FILE_OPERATION, FAST_FAIL_INVALID_IDLE_STATE, - FAST_FAIL_INVALID_IMAGE_BASE, FAST_FAIL_INVALID_JUMP_BUFFER, FAST_FAIL_INVALID_LOCK_STATE, - FAST_FAIL_INVALID_LONGJUMP_TARGET, FAST_FAIL_INVALID_NEXT_THREAD, - FAST_FAIL_INVALID_REFERENCE_COUNT, FAST_FAIL_INVALID_SET_OF_CONTEXT, - FAST_FAIL_INVALID_SYSCALL_NUMBER, FAST_FAIL_INVALID_THREAD, FAST_FAIL_LEGACY_GS_VIOLATION, - FAST_FAIL_LOADER_CONTINUITY_FAILURE, FAST_FAIL_LPAC_ACCESS_DENIED, FAST_FAIL_MRDATA_MODIFIED, - FAST_FAIL_MRDATA_PROTECTION_FAILURE, FAST_FAIL_RANGE_CHECK_FAILURE, - FAST_FAIL_SET_CONTEXT_DENIED, FAST_FAIL_STACK_COOKIE_CHECK_FAILURE, - FAST_FAIL_UNEXPECTED_HEAP_EXCEPTION, FAST_FAIL_UNSAFE_EXTENSION_CALL, - FAST_FAIL_UNSAFE_REGISTRY_ACCESS, FAST_FAIL_VTGUARD_CHECK_FAILURE, +use windows::Win32::{ + Foundation::NTSTATUS, + System::Diagnostics::Debug::EXCEPTION_RECORD, + System::SystemServices::{ + FAST_FAIL_APCS_DISABLED, FAST_FAIL_CERTIFICATION_FAILURE, FAST_FAIL_CORRUPT_LIST_ENTRY, + FAST_FAIL_CRYPTO_LIBRARY, FAST_FAIL_DEPRECATED_SERVICE_INVOKED, + FAST_FAIL_DLOAD_PROTECTION_FAILURE, FAST_FAIL_FATAL_APP_EXIT, FAST_FAIL_GS_COOKIE_INIT, + FAST_FAIL_GUARD_EXPORT_SUPPRESSION_FAILURE, FAST_FAIL_GUARD_ICALL_CHECK_FAILURE, + FAST_FAIL_GUARD_ICALL_CHECK_SUPPRESSED, FAST_FAIL_GUARD_JUMPTABLE, + FAST_FAIL_GUARD_SS_FAILURE, FAST_FAIL_GUARD_WRITE_CHECK_FAILURE, FAST_FAIL_INCORRECT_STACK, + FAST_FAIL_INVALID_ARG, FAST_FAIL_INVALID_BALANCED_TREE, FAST_FAIL_INVALID_BUFFER_ACCESS, + FAST_FAIL_INVALID_CALL_IN_DLL_CALLOUT, FAST_FAIL_INVALID_CONTROL_STACK, + FAST_FAIL_INVALID_DISPATCH_CONTEXT, FAST_FAIL_INVALID_EXCEPTION_CHAIN, + FAST_FAIL_INVALID_FIBER_SWITCH, FAST_FAIL_INVALID_FILE_OPERATION, + FAST_FAIL_INVALID_IDLE_STATE, FAST_FAIL_INVALID_IMAGE_BASE, FAST_FAIL_INVALID_JUMP_BUFFER, + FAST_FAIL_INVALID_LOCK_STATE, FAST_FAIL_INVALID_LONGJUMP_TARGET, + FAST_FAIL_INVALID_NEXT_THREAD, FAST_FAIL_INVALID_REFERENCE_COUNT, + FAST_FAIL_INVALID_SET_OF_CONTEXT, FAST_FAIL_INVALID_SYSCALL_NUMBER, + FAST_FAIL_INVALID_THREAD, FAST_FAIL_LEGACY_GS_VIOLATION, + FAST_FAIL_LOADER_CONTINUITY_FAILURE, FAST_FAIL_LPAC_ACCESS_DENIED, + FAST_FAIL_MRDATA_MODIFIED, FAST_FAIL_MRDATA_PROTECTION_FAILURE, + FAST_FAIL_RANGE_CHECK_FAILURE, FAST_FAIL_SET_CONTEXT_DENIED, + FAST_FAIL_STACK_COOKIE_CHECK_FAILURE, FAST_FAIL_UNEXPECTED_HEAP_EXCEPTION, + FAST_FAIL_UNSAFE_EXTENSION_CALL, FAST_FAIL_UNSAFE_REGISTRY_ACCESS, + FAST_FAIL_VTGUARD_CHECK_FAILURE, + }, }; /// The C compiler intrinsic __fastfail was called with one of these values - we use UnknownFastFailCode for values @@ -81,7 +87,7 @@ pub enum FastFail { } // See https://docs.microsoft.com/en-us/cpp/intrinsics/fastfail?view=vs-2017 for __fastfail details. -pub const EXCEPTION_FAIL_FAST: u32 = 0xC0000409; +pub const EXCEPTION_FAIL_FAST: NTSTATUS = NTSTATUS(0xC0000409_u32 as i32); fn fast_fail_from_u32(code: u32) -> FastFail { match code { diff --git a/src/agent/input-tester/src/test_result/mod.rs b/src/agent/input-tester/src/test_result/mod.rs index 32b5d1bd46..5ea36e2a18 100644 --- a/src/agent/input-tester/src/test_result/mod.rs +++ b/src/agent/input-tester/src/test_result/mod.rs @@ -13,18 +13,18 @@ use std::{fmt, path::Path}; use debugger::stack::{DebugStack, DebugStackFrame}; use log::error; use win_util::process; -use winapi::um::{ - minwinbase::{ +use windows::Win32::{ + Foundation::{ EXCEPTION_ACCESS_VIOLATION, EXCEPTION_ARRAY_BOUNDS_EXCEEDED, EXCEPTION_BREAKPOINT, - EXCEPTION_DATATYPE_MISALIGNMENT, EXCEPTION_DEBUG_INFO, EXCEPTION_FLT_DENORMAL_OPERAND, + EXCEPTION_DATATYPE_MISALIGNMENT, EXCEPTION_FLT_DENORMAL_OPERAND, EXCEPTION_FLT_DIVIDE_BY_ZERO, EXCEPTION_FLT_INEXACT_RESULT, EXCEPTION_FLT_INVALID_OPERATION, EXCEPTION_FLT_OVERFLOW, EXCEPTION_FLT_STACK_CHECK, EXCEPTION_FLT_UNDERFLOW, EXCEPTION_ILLEGAL_INSTRUCTION, EXCEPTION_INT_DIVIDE_BY_ZERO, EXCEPTION_INT_OVERFLOW, EXCEPTION_INVALID_DISPOSITION, EXCEPTION_IN_PAGE_ERROR, EXCEPTION_NONCONTINUABLE_EXCEPTION, EXCEPTION_PRIV_INSTRUCTION, EXCEPTION_SINGLE_STEP, - EXCEPTION_STACK_OVERFLOW, + EXCEPTION_STACK_OVERFLOW, HANDLE, NTSTATUS, STATUS_WX86_BREAKPOINT, }, - winnt::{EXCEPTION_RECORD, HANDLE}, + System::Diagnostics::Debug::{EXCEPTION_DEBUG_INFO, EXCEPTION_RECORD}, }; use crate::{ @@ -38,15 +38,12 @@ use crate::{ }; // See https://github.com/dotnet/coreclr/blob/030a3ea9b8dbeae89c90d34441d4d9a1cf4a7de6/src/inc/corexcep.h#L21 -const EXCEPTION_CLR: u32 = 0xE0434352; +const EXCEPTION_CLR: NTSTATUS = NTSTATUS(0xE0434352_u32 as i32); // From vc crt source file ehdata_values.h // #define EH_EXCEPTION_NUMBER ('msc' | 0xE0000000) // The NT Exception # that we use // Also defined here: https://github.com/dotnet/coreclr/blob/030a3ea9b8dbeae89c90d34441d4d9a1cf4a7de6/src/inc/corexcep.h#L19 -const EXCEPTION_CPP: u32 = 0xE06D7363; - -// When debugging a WoW64 process, we see STATUS_WX86_BREAKPOINT in addition to EXCEPTION_BREAKPOINT -const STATUS_WX86_BREAKPOINT: u32 = ::winapi::shared::ntstatus::STATUS_WX86_BREAKPOINT as u32; +const EXCEPTION_CPP: NTSTATUS = NTSTATUS(0xE06D7363_u32 as i32); fn get_av_description(exception_record: &EXCEPTION_RECORD) -> ExceptionCode { if exception_record.NumberParameters >= 2 { @@ -201,7 +198,7 @@ pub fn new_test_result( #[derive(Clone)] pub struct Exception { /// The win32 exception code. - pub exception_code: u32, + pub exception_code: NTSTATUS, /// A friendly description of the exception based on the exception code and other /// parameters available to the debugger when the exception was raised. @@ -221,7 +218,7 @@ pub struct Exception { impl fmt::Display for Exception { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - writeln!(formatter, "Exception: 0x{:8x}", self.exception_code)?; + writeln!(formatter, "Exception: 0x{:8x}", self.exception_code.0)?; writeln!(formatter, " Description: {}", self.description)?; writeln!(formatter, " FirstChance: {}", self.first_chance)?; writeln!(formatter, " StackHash: {}", self.stack_hash)?; diff --git a/src/agent/input-tester/src/test_result/vcpp_debugger.rs b/src/agent/input-tester/src/test_result/vcpp_debugger.rs index 13ae09b974..bb1b3a2534 100644 --- a/src/agent/input-tester/src/test_result/vcpp_debugger.rs +++ b/src/agent/input-tester/src/test_result/vcpp_debugger.rs @@ -3,6 +3,8 @@ #![allow(clippy::unreadable_literal)] +use std::ffi::c_void; + /// This module wraps an exception raised by the VC++ runtime or by user code implementing /// https://docs.microsoft.com/en-us/visualstudio/debugger/how-to-set-a-thread-name-in-native-code?view=vs-2019 /// @@ -12,9 +14,12 @@ /// layouts that differ between x86 and x64 (our target process could be either). use anyhow::Result; use win_util::process; -use winapi::{ - shared::minwindef::{BOOL, DWORD, PBYTE}, - um::winnt::{EXCEPTION_RECORD, HANDLE, LPCSTR, LPCWSTR, PVOID}, +use windows::{ + core::{PCSTR, PCWSTR}, + Win32::{ + Foundation::{BOOL, HANDLE, NTSTATUS}, + System::Diagnostics::Debug::EXCEPTION_RECORD, + }, }; /// Errors reported via the VC++ /RTC compiler flag, as defined in vctruntime\inc\rtcapi.h @@ -37,72 +42,72 @@ pub enum VcppRtcError { // * name a thread // * report rtc errors // we should not report any bugs if the exception was raised to name a thread. -pub const EXCEPTION_VISUALCPP_DEBUGGER: DWORD = 0x406d1388; +pub const EXCEPTION_VISUALCPP_DEBUGGER: NTSTATUS = NTSTATUS(0x406d1388); // The exception is a simple notification to the debugger which can be ignored. -const EXCEPTION_DEBUGGER_NAME_THREAD: DWORD = 0x1000; +const EXCEPTION_DEBUGGER_NAME_THREAD: u32 = 0x1000; // The exception is asking the debugger if it is aware of RTC errors. If the debugger // is aware, it will modify the memory of the target which will then raise a RUNTIMECHECK // exception. We should do this eventually (PBI #6530). -const EXCEPTION_DEBUGGER_PROBE: DWORD = 0x1001; +const EXCEPTION_DEBUGGER_PROBE: u32 = 0x1001; // This exception is raised only after the PROBE exception and if the debugger set the // target memory to raise this exception. -const EXCEPTION_DEBUGGER_RUNTIMECHECK: DWORD = 0x1002; +const EXCEPTION_DEBUGGER_RUNTIMECHECK: u32 = 0x1002; // Unsure if this is used at all, but there is info to extract -const EXCEPTION_DEBUGGER_FIBER: DWORD = 0x1003; +const EXCEPTION_DEBUGGER_FIBER: u32 = 0x1003; // Defined in the vc headers, but no info to extract and no uses. //const EXCEPTION_DEBUGGER_HANDLECHECK: DWORD = 0x1004; #[allow(non_snake_case)] pub struct ThreadNameInfo { /// pointer to name (in user addr space) - pub szName: LPCSTR, + pub szName: PCSTR, /// thread id (-1=caller thread) - pub dwThreadId: DWORD, + pub dwThreadId: u32, /// reserved for future use (eg user thread, system thread) - pub dwFlags: DWORD, + pub dwFlags: u32, } #[allow(non_snake_case)] pub struct DebuggerProbeInfo { /// 0 = do you understand this private exception, else max value of enum - pub dwLevelRequired: DWORD, + pub dwLevelRequired: u32, /// debugger puts a non-zero value in this address to tell runtime debugger is aware of RTC - pub pbDebuggerPresent: PBYTE, + pub pbDebuggerPresent: *mut c_void, } impl DebuggerProbeInfo { pub fn notify_target(&self, process_handle: HANDLE) -> Result<()> { // This will tell the VC++ runtime to raise another exception to report the error. - process::write_memory(process_handle, self.pbDebuggerPresent as PVOID, &1u8) + process::write_memory(process_handle, self.pbDebuggerPresent, &1u8) } } // Based on _RTC_ErrorNumber, used in the dwRuntimeNumber field -const RTC_CHKSTK: DWORD = 0; -const RTC_CVRT_LOSS_INFO: DWORD = 1; -const RTC_CORRUPT_STACK: DWORD = 2; -const RTC_UNINIT_LOCAL_USE: DWORD = 3; -const RTC_CORRUPTED_ALLOCA: DWORD = 4; +const RTC_CHKSTK: u32 = 0; +const RTC_CVRT_LOSS_INFO: u32 = 1; +const RTC_CORRUPT_STACK: u32 = 2; +const RTC_UNINIT_LOCAL_USE: u32 = 3; +const RTC_CORRUPTED_ALLOCA: u32 = 4; #[allow(non_snake_case)] pub struct RuntimeErrorInfo { /// the type of the runtime check - pub dwRuntimeNumber: DWORD, + pub dwRuntimeNumber: u32, /// true if never a false-positive pub bRealBug: BOOL, /// caller puts a return address in here - pub pvReturnAddress: PVOID, + pub pvReturnAddress: *mut c_void, /// debugger puts a non-zero value in this address if handled it - pub pbDebuggerPresent: PBYTE, + pub pbDebuggerPresent: *mut c_void, /// pointer to unicode message (or null) - pub pwRuntimeMessage: LPCWSTR, + pub pwRuntimeMessage: PCWSTR, } impl RuntimeErrorInfo { pub fn notify_target(&self, process_handle: HANDLE) -> Result<()> { // This will tell the VC++ runtime to **not** use __debugbreak() to report the error. - process::write_memory(process_handle, self.pbDebuggerPresent as PVOID, &1u8) + process::write_memory(process_handle, self.pbDebuggerPresent, &1u8) } pub fn get_rtc_error(&self) -> VcppRtcError { @@ -120,11 +125,11 @@ impl RuntimeErrorInfo { #[allow(non_snake_case)] pub struct FiberInfo { /// 0=ConvertThreadToFiber, 1=CreateFiber, 2=DeleteFiber - pub dwType: DWORD, + pub dwType: u32, /// pointer to fiber - pub pvFiber: PVOID, + pub pvFiber: *mut c_void, /// pointer to FIBER_START_ROUTINE (CreateFiber only) - pub pvStartRoutine: PVOID, + pub pvStartRoutine: *mut c_void, } pub enum VcppDebuggerExceptionInfo { @@ -143,14 +148,14 @@ impl VcppDebuggerExceptionInfo { return VcppDebuggerExceptionInfo::UnknownException; } - match exception_record.ExceptionInformation[0] as DWORD { + match exception_record.ExceptionInformation[0] as u32 { EXCEPTION_DEBUGGER_NAME_THREAD if target_x64 && exception_record.NumberParameters >= 3 => { VcppDebuggerExceptionInfo::ThreadName(ThreadNameInfo { - szName: exception_record.ExceptionInformation[1] as LPCSTR, - dwThreadId: exception_record.ExceptionInformation[2] as DWORD, - dwFlags: (exception_record.ExceptionInformation[2] >> 32) as DWORD, + szName: PCSTR(exception_record.ExceptionInformation[1] as _), + dwThreadId: exception_record.ExceptionInformation[2] as u32, + dwFlags: (exception_record.ExceptionInformation[2] >> 32) as u32, }) } @@ -158,16 +163,16 @@ impl VcppDebuggerExceptionInfo { if !target_x64 && exception_record.NumberParameters >= 4 => { VcppDebuggerExceptionInfo::ThreadName(ThreadNameInfo { - szName: exception_record.ExceptionInformation[1] as LPCSTR, - dwThreadId: exception_record.ExceptionInformation[2] as DWORD, - dwFlags: exception_record.ExceptionInformation[3] as DWORD, + szName: PCSTR(exception_record.ExceptionInformation[1] as _), + dwThreadId: exception_record.ExceptionInformation[2] as u32, + dwFlags: exception_record.ExceptionInformation[3] as u32, }) } EXCEPTION_DEBUGGER_PROBE if exception_record.NumberParameters >= 3 => { VcppDebuggerExceptionInfo::Probe(DebuggerProbeInfo { - dwLevelRequired: exception_record.ExceptionInformation[1] as DWORD, - pbDebuggerPresent: exception_record.ExceptionInformation[2] as PBYTE, + dwLevelRequired: exception_record.ExceptionInformation[1] as u32, + pbDebuggerPresent: exception_record.ExceptionInformation[2] as *mut c_void, }) } @@ -175,11 +180,11 @@ impl VcppDebuggerExceptionInfo { if target_x64 && exception_record.NumberParameters >= 6 => { VcppDebuggerExceptionInfo::RuntimeError(RuntimeErrorInfo { - dwRuntimeNumber: exception_record.ExceptionInformation[1] as DWORD, - bRealBug: exception_record.ExceptionInformation[2] as BOOL, - pvReturnAddress: exception_record.ExceptionInformation[3] as PVOID, - pbDebuggerPresent: exception_record.ExceptionInformation[4] as PBYTE, - pwRuntimeMessage: exception_record.ExceptionInformation[5] as LPCWSTR, + dwRuntimeNumber: exception_record.ExceptionInformation[1] as u32, + bRealBug: BOOL(exception_record.ExceptionInformation[2] as _), + pvReturnAddress: exception_record.ExceptionInformation[3] as *mut c_void, + pbDebuggerPresent: exception_record.ExceptionInformation[4] as *mut c_void, + pwRuntimeMessage: PCWSTR(exception_record.ExceptionInformation[5] as _), }) } @@ -187,19 +192,19 @@ impl VcppDebuggerExceptionInfo { if !target_x64 && exception_record.NumberParameters >= 5 => { VcppDebuggerExceptionInfo::RuntimeError(RuntimeErrorInfo { - dwRuntimeNumber: exception_record.ExceptionInformation[1] as DWORD, - bRealBug: (exception_record.ExceptionInformation[1] >> 32) as BOOL, - pvReturnAddress: exception_record.ExceptionInformation[2] as PVOID, - pbDebuggerPresent: exception_record.ExceptionInformation[3] as PBYTE, - pwRuntimeMessage: exception_record.ExceptionInformation[4] as LPCWSTR, + dwRuntimeNumber: exception_record.ExceptionInformation[1] as u32, + bRealBug: BOOL((exception_record.ExceptionInformation[1] >> 32) as _), + pvReturnAddress: exception_record.ExceptionInformation[2] as *mut c_void, + pbDebuggerPresent: exception_record.ExceptionInformation[3] as *mut c_void, + pwRuntimeMessage: PCWSTR(exception_record.ExceptionInformation[4] as _), }) } EXCEPTION_DEBUGGER_FIBER if exception_record.NumberParameters >= 4 => { VcppDebuggerExceptionInfo::Fiber(FiberInfo { - dwType: exception_record.ExceptionInformation[1] as DWORD, - pvFiber: exception_record.ExceptionInformation[2] as PVOID, - pvStartRoutine: exception_record.ExceptionInformation[3] as PVOID, + dwType: exception_record.ExceptionInformation[1] as u32, + pvFiber: exception_record.ExceptionInformation[2] as *mut c_void, + pvStartRoutine: exception_record.ExceptionInformation[3] as *mut c_void, }) } diff --git a/src/agent/input-tester/src/test_result/verifier_stop.rs b/src/agent/input-tester/src/test_result/verifier_stop.rs index 23902b7d20..049ed46827 100644 --- a/src/agent/input-tester/src/test_result/verifier_stop.rs +++ b/src/agent/input-tester/src/test_result/verifier_stop.rs @@ -4,48 +4,43 @@ use std::fmt; use win_util::process; -use winapi::{ - shared::{ - basetsd::ULONG64, - minwindef::{LPCVOID, ULONG}, - }, - um::winnt::{EXCEPTION_RECORD, HANDLE}, - STRUCT, -}; +use windows::Win32::{Foundation::HANDLE, System::Diagnostics::Debug::EXCEPTION_RECORD}; use crate::appverifier::stop_codes; -pub const STATUS_VERIFIER_STOP: u32 = ::winapi::shared::ntstatus::STATUS_VERIFIER_STOP as u32; +pub use windows::Win32::Foundation::STATUS_VERIFIER_STOP; // VERIFIER_STOP_HEADER and VERIFIER_STOP_PARAMS are not public apis (but probably could be). // They are defined in os/src/onecore/base/avrf/verifier/logging.h const MAX_STACK_DEPTH: usize = 32; -STRUCT! { #[allow(non_snake_case)] +#[repr(C)] +#[derive(Copy, Clone)] struct VERIFIER_STOP_HEADER { - StopCode: ULONG64, - StopFlags: ULONG, - StackTraceDepth: ULONG, - BackTrace: [ULONG64; MAX_STACK_DEPTH], -}} + StopCode: u64, + StopFlags: u32, + StackTraceDepth: u32, + BackTrace: [u64; MAX_STACK_DEPTH], +} // For our use here, pointers in this struct point to memory in another process, so if you // want to read those strings, you must use ReadProcessMemory. -STRUCT! { #[allow(non_snake_case)] +#[repr(C)] +#[derive(Copy, Clone)] struct VERIFIER_STOP_PARAMS { - Header: VERIFIER_STOP_HEADER, - Message: ULONG64, - Parameter1: ULONG64, - StringPtr1: ULONG64, - Parameter2: ULONG64, - StringPtr2: ULONG64, - Parameter3: ULONG64, - StringPtr3: ULONG64, - Parameter4: ULONG64, - StringPtr4: ULONG64, -}} + Header: VERIFIER_STOP_HEADER, + Message: u64, + Parameter1: u64, + StringPtr1: u64, + Parameter2: u64, + StringPtr2: u64, + Parameter3: u64, + StringPtr3: u64, + Parameter4: u64, + StringPtr4: u64, +} fn handles_stop_from_u32(code: u32) -> HandlesStop { match code { @@ -208,7 +203,7 @@ pub fn new(process_handle: HANDLE, exception_record: &EXCEPTION_RECORD) -> Verif if exception_record.NumberParameters >= 3 { match process::read_memory::( process_handle, - exception_record.ExceptionInformation[2] as LPCVOID, + exception_record.ExceptionInformation[2] as _, ) { Ok(stop_params) => { let code = stop_params.Header.StopCode as u32; diff --git a/src/agent/input-tester/src/tester.rs b/src/agent/input-tester/src/tester.rs index 8550a65594..2c5e2aa3ef 100644 --- a/src/agent/input-tester/src/tester.rs +++ b/src/agent/input-tester/src/tester.rs @@ -481,7 +481,7 @@ fn log_input_test_result(result: &InputTestResult) { info!( "Exception found testing {} ExceptionCode=0x{:08x} Description={} FirstChance={} StackHash={}", input_path.display(), - exception.exception_code, + exception.exception_code.0, exception.description, exception.first_chance, exception.stack_hash, diff --git a/src/agent/onefuzz-agent/Cargo.toml b/src/agent/onefuzz-agent/Cargo.toml index 28c158b89c..5ce8669766 100644 --- a/src/agent/onefuzz-agent/Cargo.toml +++ b/src/agent/onefuzz-agent/Cargo.toml @@ -46,4 +46,4 @@ azure_storage_blobs = { version = "0.13", default-features = false, features = [ nix = "0.26" [target.'cfg(target_family = "windows")'.dependencies] -winapi = "0.3" +windows = "0.48" diff --git a/src/agent/onefuzz-agent/src/worker.rs b/src/agent/onefuzz-agent/src/worker.rs index 4bd333d7bf..d05a95dacb 100644 --- a/src/agent/onefuzz-agent/src/worker.rs +++ b/src/agent/onefuzz-agent/src/worker.rs @@ -525,10 +525,10 @@ impl SuspendableChild for Child { fn suspend(&self) -> Result<()> { // DebugActiveProcess suspends all threads in the process. // https://docs.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-debugactiveprocess#remarks - let result = unsafe { winapi::um::debugapi::DebugActiveProcess(self.id()) }; - if result == 0 { - bail!("unable to suspend child process"); - } + unsafe { windows::Win32::System::Diagnostics::Debug::DebugActiveProcess(self.id()) } + .ok() + .context("unable to suspend child process")?; + Ok(()) } } diff --git a/src/agent/onefuzz/Cargo.toml b/src/agent/onefuzz/Cargo.toml index 631fc120ab..506cfb84ae 100644 --- a/src/agent/onefuzz/Cargo.toml +++ b/src/agent/onefuzz/Cargo.toml @@ -51,7 +51,7 @@ backoff = { version = "0.4", features = ["tokio"] } winreg = "0.50" input-tester = { path = "../input-tester" } debugger = { path = "../debugger" } -winapi = { version = "0.3", features = ["impl-default", "psapi"] } +windows = { version = "0.48", features=["Win32_System_ProcessStatus", "Win32_Foundation"] } [target.'cfg(target_family = "unix")'.dependencies] cpp_demangle = "0.4" diff --git a/src/agent/onefuzz/src/memory.rs b/src/agent/onefuzz/src/memory.rs index 673b88b38a..958279fc52 100644 --- a/src/agent/onefuzz/src/memory.rs +++ b/src/agent/onefuzz/src/memory.rs @@ -7,7 +7,7 @@ use anyhow::Result; use regex::Regex; #[cfg(target_os = "windows")] -use winapi::um::psapi::PERFORMANCE_INFORMATION; +use windows::Win32::System::ProcessStatus::PERFORMANCE_INFORMATION; #[cfg(target_os = "windows")] pub fn available_bytes() -> Result { @@ -21,25 +21,19 @@ pub fn available_bytes() -> Result { #[cfg(target_os = "windows")] fn get_performance_info() -> Result { - use winapi::shared::minwindef::FALSE; - use winapi::um::errhandlingapi::GetLastError; - use winapi::um::psapi::GetPerformanceInfo; + use anyhow::Context; + use windows::Win32::System::ProcessStatus::GetPerformanceInfo; let mut info = PERFORMANCE_INFORMATION::default(); - let success = unsafe { - // Will always fit in a `u32`. - // + unsafe { + // Will always fit in a `u32` (size is 104). // https://docs.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-performance_information - let size = std::mem::size_of::(); - let size = u32::try_from(size)?; + let size = std::mem::size_of::() as u32; GetPerformanceInfo(&mut info, size) - }; - - if success == FALSE { - let code = unsafe { GetLastError() }; - bail!("error querying performance information: {:x}", code); } + .ok() + .context("error querying performance information")?; Ok(info) } @@ -78,3 +72,7 @@ lazy_static::lazy_static! { #[cfg(test)] #[cfg(target_os = "linux")] mod tests_linux; + +#[cfg(test)] +#[cfg(target_os = "windows")] +mod tests_windows; diff --git a/src/agent/onefuzz/src/memory/tests_windows.rs b/src/agent/onefuzz/src/memory/tests_windows.rs new file mode 100644 index 0000000000..2217ff41fd --- /dev/null +++ b/src/agent/onefuzz/src/memory/tests_windows.rs @@ -0,0 +1,8 @@ +use super::available_bytes; + +#[test] +fn can_read_available_memory() -> anyhow::Result<()> { + let available_bytes = available_bytes(); + assert!(available_bytes? > 0); + Ok(()) +} diff --git a/src/agent/win-util/Cargo.toml b/src/agent/win-util/Cargo.toml index 1b5b66a009..1edaa3fc58 100644 --- a/src/agent/win-util/Cargo.toml +++ b/src/agent/win-util/Cargo.toml @@ -14,23 +14,23 @@ os_pipe = "1.1" [target.'cfg(windows)'.dependencies] winreg = "0.50" -[dependencies.winapi] -version = "0.3" +[dependencies.windows] +version = "0.48" features = [ - "debugapi", - "errhandlingapi", - "handleapi", - "jobapi2", - "memoryapi", - "ioapiset", - "processthreadsapi", - "psapi", - "securitybaseapi", - "shellapi", - "std", - "unknwnbase", - "werapi", - "winbase", - "winerror", - "wow64apiset", + "Win32_Foundation", + "Win32_Security", + "Win32_Storage_FileSystem", + "Win32_System_Diagnostics_Debug", + "Win32_System_ErrorReporting", + "Win32_System_IO", + "Win32_System_Kernel", + "Win32_System_Memory", + "Win32_System_Pipes", + "Win32_System_ProcessStatus", + "Win32_System_SystemInformation", + "Win32_System_Threading", + "Win32_UI_Shell", ] + +[dev-dependencies] +tempfile = "3.7.0" diff --git a/src/agent/win-util/src/aedebug.rs b/src/agent/win-util/src/aedebug.rs deleted file mode 100644 index 9c80709268..0000000000 --- a/src/agent/win-util/src/aedebug.rs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use std::ffi::{OsStr, OsString}; - -use anyhow::{Context, Result}; -use log::error; -use winreg::{ - enums::{HKEY_LOCAL_MACHINE, KEY_SET_VALUE, KEY_WOW64_32KEY}, - RegKey, -}; - -const AEDEBUG_EXCLUSION_LIST: &str = - r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug\AutoExclusionList"; - -pub fn add_exclusion(exe_name: &OsStr) -> Result<()> { - edit_exclusion_list(|key| key.set_value(exe_name, &1u32))?; - let exe_name = exe_name.to_owned(); - atexit::register(move || remove_exclusion(&exe_name)); - Ok(()) -} - -fn edit_exclusion_list ::core::result::Result<(), std::io::Error>>( - f: F, -) -> Result<()> { - let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); - - // We want to set both the 32 and 64 bit registries. - for flags in [0, KEY_WOW64_32KEY] { - let exclusion_list = hklm - .open_subkey_with_flags(AEDEBUG_EXCLUSION_LIST, KEY_SET_VALUE | flags) - .context("Opening AeDebug\\AutoExclusionList")?; - f(exclusion_list)?; - } - - Ok(()) -} - -fn remove_exclusion(exe_name: &OsString) { - if let Err(err) = edit_exclusion_list(|key| key.delete_value(exe_name)) { - error!("Error removing aedebug exclusions: {}", err); - } -} diff --git a/src/agent/win-util/src/com.rs b/src/agent/win-util/src/com.rs deleted file mode 100644 index a19442361b..0000000000 --- a/src/agent/win-util/src/com.rs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -//! This module implements a simple smart pointer wrapping a COM pointer. -use std::{ops::Deref, ptr}; - -use winapi::{ - ctypes::c_void, - shared::{guiddef::GUID, ntdef::HRESULT, winerror::SUCCEEDED}, - um::unknwnbase::IUnknown, - Interface, -}; - -use crate::hr::HRESULTError; - -/// A wrapper for a COM pointer that implements AddRef via the Clone trait -/// and Release via the Drop trait. -#[repr(C)] -pub struct ComPtr(*mut T) -where - T: Interface; - -impl ComPtr -where - T: Interface, -{ - fn from_raw(ptr: *mut T) -> ComPtr { - ComPtr(ptr) - } - - /// # Safety - /// - /// For use with C/C++ APIs that use IID_PPV_ARGS - they take the GUID and - /// an out parameter to return the COM object. - pub unsafe fn new_with_uuid(f: F) -> Result - where - F: FnOnce(&GUID, *mut *mut c_void) -> HRESULT, - { - Self::new_with(|ptr| f(&T::uuidof(), ptr as *mut *mut c_void)) - } - - /// # Safety - /// - /// For use with APIs that return a new COM object through an out parameter. - pub unsafe fn new_with(f: F) -> Result - where - F: FnOnce(*mut *mut T) -> HRESULT, - { - let mut ptr = ptr::null_mut(); - let hresult = f(&mut ptr); - if SUCCEEDED(hresult) { - Ok(ComPtr::from_raw(ptr)) - } else { - if !ptr.is_null() { - let ptr = ptr as *mut IUnknown; - (*ptr).Release(); - } - Err(HRESULTError(hresult)) - } - } - - /// # Safety - fn as_iunknown(&self) -> &IUnknown { - unsafe { &*(self.0 as *mut IUnknown) } - } - - /// # Safety - /// - /// Perform a "cast" via QueryInterface. - pub fn query_to(&self) -> Result, HRESULTError> - where - U: Interface, - { - let mut ptr = ptr::null_mut(); - check_hr!(unsafe { self.as_iunknown().QueryInterface(&U::uuidof(), &mut ptr) }); - Ok(ComPtr::from_raw(ptr as *mut U)) - } -} - -impl Clone for ComPtr -where - T: Interface, -{ - fn clone(&self) -> Self { - unsafe { self.as_iunknown().AddRef() }; - ComPtr::from_raw(self.0) - } -} - -impl Deref for ComPtr -where - T: Interface, -{ - type Target = T; - - /// # Safety - fn deref(&self) -> &T { - unsafe { &*self.0 } - } -} - -impl Drop for ComPtr -where - T: Interface, -{ - /// # Safety - fn drop(&mut self) { - unsafe { self.as_iunknown().Release() }; - } -} diff --git a/src/agent/win-util/src/file.rs b/src/agent/win-util/src/file.rs index 579e7b81ba..b8b9eb9046 100644 --- a/src/agent/win-util/src/file.rs +++ b/src/agent/win-util/src/file.rs @@ -4,24 +4,23 @@ use std::{ffi::OsString, os::windows::ffi::OsStringExt, path::PathBuf}; use anyhow::Result; -use winapi::{ - shared::minwindef::{DWORD, MAX_PATH}, - um::{fileapi::GetFinalPathNameByHandleW, winnt::HANDLE}, +use windows::Win32::{ + Foundation::{HANDLE, MAX_PATH}, + Storage::FileSystem::{GetFinalPathNameByHandleW, FILE_NAME_NORMALIZED}, }; use crate::last_os_error; pub fn get_path_from_handle(handle: HANDLE) -> Result { let mut actual_len: usize; - let mut buf: Vec = Vec::with_capacity(MAX_PATH); + let mut buf: Vec = vec![0; MAX_PATH as usize]; loop { actual_len = unsafe { GetFinalPathNameByHandleW( handle, - buf.as_mut_ptr(), - buf.capacity() as DWORD, - 0, // default options - normalized with drive letter + &mut buf, + FILE_NAME_NORMALIZED, // default options - normalized with drive letter ) as usize }; @@ -29,16 +28,35 @@ pub fn get_path_from_handle(handle: HANDLE) -> Result { return Err(last_os_error()); } - if actual_len > buf.capacity() { - buf.reserve(actual_len); + if actual_len > buf.len() { + buf.resize(actual_len, 0); } else { break; } } - unsafe { - buf.set_len(actual_len); - } + debug_assert!(actual_len <= buf.len()); + buf.truncate(actual_len); Ok(PathBuf::from(OsString::from_wide(&buf))) } + +#[cfg(test)] +mod test { + use std::os::windows::prelude::AsRawHandle; + use windows::Win32::Foundation::HANDLE; + + #[test] + pub fn very_long_filename() { + // makes sure the looping portion of get_path_from_handle works + let tempdir = tempfile::tempdir().unwrap(); + let canon_path = tempdir.path().canonicalize().unwrap(); // ensure we have \\?\ prefix + + // NTFS max component length is 255, but this should push us over MAX_PATH + let path = canon_path.join("a".repeat(255)); + let file = std::fs::File::create(&path).unwrap(); + + let found_path = super::get_path_from_handle(HANDLE(file.as_raw_handle() as _)).unwrap(); + assert_eq!(path, found_path); + } +} diff --git a/src/agent/win-util/src/handle.rs b/src/agent/win-util/src/handle.rs index 8d92c48c44..7a2a7a034b 100644 --- a/src/agent/win-util/src/handle.rs +++ b/src/agent/win-util/src/handle.rs @@ -1,17 +1,23 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use winapi::{ - shared::minwindef::{FALSE, LPHANDLE}, - um::{ - handleapi::{CloseHandle, DuplicateHandle, INVALID_HANDLE_VALUE}, - processthreadsapi::GetCurrentProcess, - winnt::{DUPLICATE_SAME_ACCESS, HANDLE}, +use windows::Win32::{ + Foundation::{ + CloseHandle, DuplicateHandle, DUPLICATE_SAME_ACCESS, FALSE, HANDLE, INVALID_HANDLE_VALUE, }, + System::Threading::GetCurrentProcess, }; pub struct Handle(pub HANDLE); +impl Handle { + fn try_drop(&mut self) -> anyhow::Result<()> { + unsafe { CloseHandle(self.0) }.ok()?; + self.0 = INVALID_HANDLE_VALUE; + Ok(()) + } +} + impl Clone for Handle { fn clone(&self) -> Self { let mut duplicate = INVALID_HANDLE_VALUE; @@ -21,7 +27,7 @@ impl Clone for Handle { current_process, self.0, current_process, - &mut duplicate as LPHANDLE, + &mut duplicate, 0, FALSE, DUPLICATE_SAME_ACCESS, @@ -34,10 +40,33 @@ impl Clone for Handle { impl Drop for Handle { fn drop(&mut self) { - unsafe { CloseHandle(self.0) }; + // ignore any error + _ = self.try_drop(); } } unsafe impl Send for Handle {} unsafe impl Sync for Handle {} + +#[cfg(test)] +mod test { + use windows::Win32::System::Threading::{GetCurrentProcessId, OpenProcess, PROCESS_ALL_ACCESS}; + + use super::*; + + #[test] + fn handle_clone() { + // get a real (not pseudo) handle to play with + let mut handle1 = Handle( + unsafe { OpenProcess(PROCESS_ALL_ACCESS, false, GetCurrentProcessId()) }.unwrap(), + ); + + let mut handle2 = handle1.clone(); + assert_ne!(handle1.0, handle2.0); + handle1.try_drop().unwrap(); + assert_eq!(handle1.0, INVALID_HANDLE_VALUE); + handle2.try_drop().unwrap(); + assert_eq!(handle2.0, INVALID_HANDLE_VALUE); + } +} diff --git a/src/agent/win-util/src/hr.rs b/src/agent/win-util/src/hr.rs deleted file mode 100644 index a9f04ac918..0000000000 --- a/src/agent/win-util/src/hr.rs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#![allow(clippy::upper_case_acronyms)] - -//! A module used to wrap an HRESULT for use as an Error. -use std::fmt::{self, Display, Formatter}; - -use winapi::shared::winerror::HRESULT; - -#[derive(Copy, Clone, Debug)] -pub struct HRESULTError(pub HRESULT); - -impl Display for HRESULTError { - fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> { - write!(formatter, "HRESULT: {:x}", self.0) - } -} - -impl std::error::Error for HRESULTError {} - -#[macro_export] -macro_rules! check_hr { - ($x:expr) => { - let hr = $x; - if !winapi::shared::winerror::SUCCEEDED(hr) { - return Err(From::from($crate::hr::HRESULTError(hr))); - } - }; -} diff --git a/src/agent/win-util/src/jobs.rs b/src/agent/win-util/src/jobs.rs deleted file mode 100644 index e8fbc49fdb..0000000000 --- a/src/agent/win-util/src/jobs.rs +++ /dev/null @@ -1,736 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#![allow(clippy::new_without_default)] - -/// This module provides a Rust friendly wrapper around a Win32 JobObject. -use std::{ - convert::TryFrom, mem::MaybeUninit, os::windows::io::AsRawHandle, process::Child, ptr, - time::Duration, -}; - -use anyhow::Result; -use winapi::{ - shared::minwindef::{FALSE, LPVOID, TRUE}, - um::{ - handleapi::{CloseHandle, INVALID_HANDLE_VALUE}, - ioapiset::{CreateIoCompletionPort, GetQueuedCompletionStatus}, - jobapi2::{ - AssignProcessToJobObject, CreateJobObjectW, QueryInformationJobObject, - SetInformationJobObject, - }, - minwinbase::LPSECURITY_ATTRIBUTES, - processthreadsapi::GetCurrentProcess, - winbase::INFINITE, - winnt::{ - JobObjectAssociateCompletionPortInformation, JobObjectBasicAndIoAccountingInformation, - JobObjectExtendedLimitInformation, JobObjectNotificationLimitInformation, HANDLE, - IO_COUNTERS, JOBOBJECT_ASSOCIATE_COMPLETION_PORT, - JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION, JOBOBJECT_BASIC_LIMIT_INFORMATION, - JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION, - JOB_OBJECT_LIMIT_ACTIVE_PROCESS, JOB_OBJECT_LIMIT_AFFINITY, - JOB_OBJECT_LIMIT_BREAKAWAY_OK, JOB_OBJECT_LIMIT_JOB_MEMORY, - JOB_OBJECT_LIMIT_JOB_READ_BYTES, JOB_OBJECT_LIMIT_JOB_TIME, - JOB_OBJECT_LIMIT_JOB_WRITE_BYTES, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, - JOB_OBJECT_LIMIT_PRIORITY_CLASS, JOB_OBJECT_LIMIT_PROCESS_MEMORY, - JOB_OBJECT_LIMIT_PROCESS_TIME, JOB_OBJECT_LIMIT_SCHEDULING_CLASS, - JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK, JOB_OBJECT_LIMIT_WORKINGSET, - JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS, JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT, - JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO, JOB_OBJECT_MSG_END_OF_JOB_TIME, - JOB_OBJECT_MSG_END_OF_PROCESS_TIME, JOB_OBJECT_MSG_EXIT_PROCESS, - JOB_OBJECT_MSG_JOB_MEMORY_LIMIT, JOB_OBJECT_MSG_NEW_PROCESS, - JOB_OBJECT_MSG_NOTIFICATION_LIMIT, JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT, LARGE_INTEGER, - }, - }, -}; - -use crate::{ - handle::Handle, - {last_os_error, string}, -}; - -#[derive(Clone)] -pub struct JobObject { - handle: Handle, - name: String, -} - -impl JobObject { - pub fn new(name: &str) -> Result { - let handle = unsafe { - CreateJobObjectW( - ptr::null_mut() as LPSECURITY_ATTRIBUTES, - string::to_wstring(name).as_ptr(), - ) - }; - - if handle == ptr::null_mut() as HANDLE { - return Err(std::io::Error::last_os_error().into()); - } - - let handle = Handle(handle); - Ok(JobObject { - handle, - name: name.into(), - }) - } - - fn attach_process(&self, handle: HANDLE) -> Result<()> { - let res = unsafe { AssignProcessToJobObject(self.handle.0, handle) }; - if res == FALSE { - Err(std::io::Error::last_os_error().into()) - } else { - Ok(()) - } - } - - pub fn attach_current_process(&self) -> Result<()> { - self.attach_process(unsafe { GetCurrentProcess() }) - } - - pub fn attach(&self, child: &Child) -> Result<()> { - self.attach_process(child.as_raw_handle()) - } - - pub fn release(&mut self) -> Result<()> { - let handle = self.handle.0; - self.handle.0 = INVALID_HANDLE_VALUE; - let res = unsafe { CloseHandle(handle) }; - if res == FALSE { - Err(std::io::Error::last_os_error().into()) - } else { - Ok(()) - } - } - - pub fn set_information(&self, info: &mut JobInformation) -> Result<()> { - if let Some(mut extended_limit_information) = info.extended_limit_information { - let res = unsafe { - SetInformationJobObject( - self.handle.0, - JobObjectExtendedLimitInformation, - &mut extended_limit_information as *mut JOBOBJECT_EXTENDED_LIMIT_INFORMATION - as LPVOID, - std::mem::size_of::() as u32, - ) - }; - - if res == FALSE { - return Err(last_os_error()); - } - } - - if let Some(mut notification_information) = info.notification_information { - let res = unsafe { - SetInformationJobObject( - self.handle.0, - JobObjectNotificationLimitInformation, - &mut notification_information as *mut JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION - as LPVOID, - std::mem::size_of::() as u32, - ) - }; - - if res == FALSE { - return Err(last_os_error()); - } - } - - Ok(()) - } - - pub fn set_notification_routine( - &mut self, - f: F, - ) -> Result<()> { - const NUM_CONCURRENT_THREADS: u32 = 0; - const COMPLETION_KEY: LPVOID = ptr::null_mut(); - let completion_port = unsafe { - CreateIoCompletionPort( - INVALID_HANDLE_VALUE, - ptr::null_mut(), - COMPLETION_KEY as usize, - NUM_CONCURRENT_THREADS, - ) - }; - - let completion_port = Handle(completion_port); - - let completion_port_clone = completion_port.clone(); - let job_handle_clone = Handle(self.handle.0); - - // We specify a stack size we hope is sufficient to not require any additional allocation - // when we're running out of memory to give us a chance to at least report being out of - // memory. The size is based on similar code in EDGE. - std::thread::Builder::new() - .stack_size(32 * 1024) - .name(format!("{} notification routine", self.name)) - .spawn(move || { - jobobject_notification_threadproc(completion_port_clone, job_handle_clone, f) - })?; - - let mut port_info = JOBOBJECT_ASSOCIATE_COMPLETION_PORT { - CompletionKey: COMPLETION_KEY, - CompletionPort: completion_port.0, - }; - - let res = unsafe { - SetInformationJobObject( - self.handle.0, - JobObjectAssociateCompletionPortInformation, - &mut port_info as *mut JOBOBJECT_ASSOCIATE_COMPLETION_PORT as LPVOID, - std::mem::size_of::() as u32, - ) - }; - - if res == FALSE { - return Err(last_os_error()); - } - - Ok(()) - } - - pub fn query_job_stats(&self) -> Result { - let mut accounting_info = - MaybeUninit::::zeroed(); - - let res = unsafe { - QueryInformationJobObject( - self.handle.0, - JobObjectBasicAndIoAccountingInformation, - accounting_info.as_mut_ptr() as LPVOID, - std::mem::size_of::() as u32, - ptr::null_mut(), - ) - }; - - if res == FALSE { - return Err(last_os_error()); - } - - let mut limit_info = MaybeUninit::::zeroed(); - - let res = unsafe { - QueryInformationJobObject( - self.handle.0, - JobObjectExtendedLimitInformation, - limit_info.as_mut_ptr() as LPVOID, - std::mem::size_of::() as u32, - ptr::null_mut(), - ) - }; - - if res == FALSE { - return Err(last_os_error()); - } - - let job_stats = - (unsafe { (accounting_info.assume_init(), limit_info.assume_init()) }).into(); - - Ok(job_stats) - } -} - -#[derive(Debug, Default)] -pub struct JobStats { - pub total_user_time: Duration, - pub total_kernel_time: Duration, - pub total_page_fault_count: u32, - pub total_processes: u32, - pub active_processes: u32, - pub total_terminated_processes: u32, - pub peak_job_memory_used: usize, - pub peak_process_memory_used: usize, -} - -impl - From<( - JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION, - JOBOBJECT_EXTENDED_LIMIT_INFORMATION, - )> for JobStats -{ - fn from( - (accounting_info, limit_info): ( - JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION, - JOBOBJECT_EXTENDED_LIMIT_INFORMATION, - ), - ) -> Self { - let basic_info = &accounting_info.BasicInfo; - Self { - total_kernel_time: duration_from_100ns_ticks(basic_info.TotalKernelTime), - total_user_time: duration_from_100ns_ticks(basic_info.TotalUserTime), - total_page_fault_count: basic_info.TotalPageFaultCount, - total_processes: basic_info.TotalProcesses, - active_processes: basic_info.ActiveProcesses, - total_terminated_processes: basic_info.TotalTerminatedProcesses, - peak_job_memory_used: limit_info.PeakJobMemoryUsed, - peak_process_memory_used: limit_info.PeakProcessMemoryUsed, - } - } -} - -pub struct JobInformation { - extended_limit_information: Option, - notification_information: Option, -} - -impl JobInformation { - pub fn new() -> Self { - Self { - extended_limit_information: None, - notification_information: None, - } - } - - fn extended_limit_information_ref(&mut self) -> &mut JOBOBJECT_EXTENDED_LIMIT_INFORMATION { - self.extended_limit_information.get_or_insert_with(|| { - JOBOBJECT_EXTENDED_LIMIT_INFORMATION { - BasicLimitInformation: JOBOBJECT_BASIC_LIMIT_INFORMATION { - PerProcessUserTimeLimit: large_integer(0), - PerJobUserTimeLimit: large_integer(0), - LimitFlags: 0, - MinimumWorkingSetSize: 0, - MaximumWorkingSetSize: 0, - ActiveProcessLimit: 0, - Affinity: 0, - PriorityClass: 0, - SchedulingClass: 0, - }, - IoInfo: IO_COUNTERS { - ReadOperationCount: 0, - WriteOperationCount: 0, - OtherOperationCount: 0, - ReadTransferCount: 0, - WriteTransferCount: 0, - OtherTransferCount: 0, - }, - ProcessMemoryLimit: 0, - JobMemoryLimit: 0, - PeakProcessMemoryUsed: 0, - PeakJobMemoryUsed: 0, - } - }) - } - - pub fn per_process_user_time_limit(&mut self, limit: Duration) -> &mut Self { - let extended_info = self.extended_limit_information_ref(); - extended_info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_PROCESS_TIME; - extended_info.BasicLimitInformation.PerProcessUserTimeLimit = - large_integer(duration_as_100ns_ticks(&limit)); - self - } - - pub fn per_job_user_time_limit(&mut self, limit: Duration) -> &mut Self { - let extended_info = self.extended_limit_information_ref(); - extended_info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_JOB_TIME; - extended_info.BasicLimitInformation.PerJobUserTimeLimit = - large_integer(duration_as_100ns_ticks(&limit)); - self - } - - pub fn minimum_working_set_size(&mut self, limit: usize) -> &mut Self { - let extended_info = self.extended_limit_information_ref(); - extended_info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_WORKINGSET; - extended_info.BasicLimitInformation.MinimumWorkingSetSize = limit; - self - } - - pub fn maximum_working_set_size(&mut self, limit: usize) -> &mut Self { - let extended_info = self.extended_limit_information_ref(); - extended_info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_WORKINGSET; - extended_info.BasicLimitInformation.MaximumWorkingSetSize = limit; - self - } - - pub fn active_process_limit(&mut self, limit: u32) -> &mut Self { - let extended_info = self.extended_limit_information_ref(); - extended_info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_ACTIVE_PROCESS; - extended_info.BasicLimitInformation.ActiveProcessLimit = limit; - self - } - - pub fn affinity(&mut self, limit: usize) -> &mut Self { - let extended_info = self.extended_limit_information_ref(); - extended_info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_AFFINITY; - extended_info.BasicLimitInformation.Affinity = limit; - self - } - - pub fn priority_class(&mut self, limit: u32) -> &mut Self { - let extended_info = self.extended_limit_information_ref(); - extended_info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_PRIORITY_CLASS; - extended_info.BasicLimitInformation.PriorityClass = limit; - self - } - - pub fn scheduling_class(&mut self, limit: u32) -> &mut Self { - let extended_info = self.extended_limit_information_ref(); - extended_info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_SCHEDULING_CLASS; - extended_info.BasicLimitInformation.SchedulingClass = limit; - self - } - - pub fn process_memory_limit(&mut self, limit: usize) -> &mut Self { - let extended_info = self.extended_limit_information_ref(); - extended_info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_PROCESS_MEMORY; - extended_info.ProcessMemoryLimit = limit; - self - } - - pub fn job_memory_limit(&mut self, limit: usize) -> &mut Self { - let extended_info = self.extended_limit_information_ref(); - extended_info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_JOB_MEMORY; - extended_info.JobMemoryLimit = limit; - self - } - - pub fn breakaway_ok(&mut self) -> &mut Self { - let extended_info = self.extended_limit_information_ref(); - extended_info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_BREAKAWAY_OK; - self - } - - pub fn silent_breakaway_ok(&mut self) -> &mut Self { - let extended_info = self.extended_limit_information_ref(); - extended_info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK; - self - } - - pub fn kill_on_job_close(&mut self) -> &mut Self { - let extended_info = self.extended_limit_information_ref(); - extended_info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; - self - } - - fn notify_info_ref(&mut self) -> &mut JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION { - self.notification_information.get_or_insert_with(|| { - JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION { - IoReadBytesLimit: 0, - IoWriteBytesLimit: 0, - PerJobUserTimeLimit: large_integer(0), - JobMemoryLimit: 0, - RateControlTolerance: 0, - RateControlToleranceInterval: 0, - LimitFlags: 0, - } - }) - } - - pub fn notify_io_read_bytes_limit(&mut self, limit: u64) -> &mut Self { - let notify_info = self.notify_info_ref(); - notify_info.IoReadBytesLimit = limit; - notify_info.LimitFlags |= JOB_OBJECT_LIMIT_JOB_READ_BYTES; - self - } - - pub fn notify_io_write_bytes_limit(&mut self, limit: u64) -> &mut Self { - let notify_info = self.notify_info_ref(); - notify_info.IoWriteBytesLimit = limit; - notify_info.LimitFlags |= JOB_OBJECT_LIMIT_JOB_WRITE_BYTES; - self - } - - pub fn notify_per_job_user_time_limit(&mut self, limit: Duration) -> &mut Self { - let notify_info = self.notify_info_ref(); - notify_info.PerJobUserTimeLimit = large_integer(duration_as_100ns_ticks(&limit)); - notify_info.LimitFlags |= JOB_OBJECT_LIMIT_JOB_TIME; - self - } - - pub fn notify_job_memory_limit(&mut self, limit: u64) -> &mut Self { - let notify_info = self.notify_info_ref(); - notify_info.JobMemoryLimit = limit; - notify_info.LimitFlags |= JOB_OBJECT_LIMIT_JOB_MEMORY; - self - } -} - -#[derive(Debug)] -#[allow(dead_code)] -pub struct JobObjectLimitNotification { - io_read_bytes_limit: Option, - io_write_bytes_limit: Option, - per_job_user_time_limit: Option, - job_memory_limit: Option, -} - -impl From for JobObjectLimitNotification { - fn from(info: JOBOBJECT_NOTIFICATION_LIMIT_INFORMATION) -> Self { - let io_read_bytes_limit = if info.LimitFlags & JOB_OBJECT_LIMIT_JOB_READ_BYTES != 0 { - Some(info.IoReadBytesLimit) - } else { - None - }; - let io_write_bytes_limit = if info.LimitFlags & JOB_OBJECT_LIMIT_JOB_WRITE_BYTES != 0 { - Some(info.IoWriteBytesLimit) - } else { - None - }; - let per_job_user_time_limit = if info.LimitFlags & JOB_OBJECT_LIMIT_JOB_TIME != 0 { - Some(duration_from_100ns_ticks(info.PerJobUserTimeLimit)) - } else { - None - }; - let job_memory_limit = if info.LimitFlags & JOB_OBJECT_LIMIT_JOB_MEMORY != 0 { - Some(info.JobMemoryLimit) - } else { - None - }; - - JobObjectLimitNotification { - io_read_bytes_limit, - io_write_bytes_limit, - per_job_user_time_limit, - job_memory_limit, - } - } -} - -#[derive(Debug)] -pub enum JobObjectNotification { - AbnormalExitProcess(u32), - ActiveProcessLimit, - ActiveProcessZero, - EndOfJobTime, - EndOfProcessTime(u32), - ExitProcess(u32), - JobMemoryLimit(u32), - NewProcess(u32), - NotificationLimit(JobObjectLimitNotification), - ProcessMemoryLimit(u32), - UnknownMessage(u32), -} - -impl JobObjectNotification { - pub fn new(msg: u32, pid: u32, job_handle: HANDLE) -> Self { - match msg { - JOB_OBJECT_MSG_END_OF_JOB_TIME => JobObjectNotification::EndOfJobTime, - JOB_OBJECT_MSG_END_OF_PROCESS_TIME => JobObjectNotification::EndOfProcessTime(pid), - JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT => JobObjectNotification::ActiveProcessLimit, - JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO => JobObjectNotification::ActiveProcessZero, - JOB_OBJECT_MSG_NEW_PROCESS => JobObjectNotification::NewProcess(pid), - JOB_OBJECT_MSG_EXIT_PROCESS => JobObjectNotification::ExitProcess(pid), - JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS => JobObjectNotification::AbnormalExitProcess(pid), - JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT => JobObjectNotification::ProcessMemoryLimit(pid), - JOB_OBJECT_MSG_JOB_MEMORY_LIMIT => JobObjectNotification::JobMemoryLimit(pid), - JOB_OBJECT_MSG_NOTIFICATION_LIMIT => { - let mut notification_limit_info = - MaybeUninit::::zeroed(); - let res = unsafe { - QueryInformationJobObject( - job_handle, - JobObjectNotificationLimitInformation, - notification_limit_info.as_mut_ptr() as LPVOID, - std::mem::size_of::() as u32, - ptr::null_mut(), - ) - }; - - if res == FALSE { - JobObjectNotification::UnknownMessage(msg) - } else { - let notification_limit_info = unsafe { notification_limit_info.assume_init() }; - JobObjectNotification::NotificationLimit(notification_limit_info.into()) - } - } - _ => JobObjectNotification::UnknownMessage(msg), - } - } -} - -fn jobobject_notification_threadproc( - completion_port: Handle, - job_handle: Handle, - f: F, -) -> ! { - loop { - let mut message_id = MaybeUninit::zeroed(); - let mut completion_key = MaybeUninit::zeroed(); - let mut process_id = MaybeUninit::zeroed(); - let res = unsafe { - GetQueuedCompletionStatus( - completion_port.0, - message_id.as_mut_ptr(), - completion_key.as_mut_ptr(), - process_id.as_mut_ptr(), - INFINITE, - ) - }; - if res == TRUE { - let msg = JobObjectNotification::new( - unsafe { message_id.assume_init() }, - unsafe { process_id.assume_init() } as u32, - job_handle.0, - ); - f(msg); - } - } -} - -fn duration_from_100ns_ticks(duration: LARGE_INTEGER) -> Duration { - let duration = unsafe { *duration.QuadPart() } as u64; - Duration::from_nanos(duration.saturating_mul(100)) -} - -/// Return the duration as a count of 100ns ticks. -/// Defaults to i64::MAX if the duration is too long which is sufficient for job object purposes. -fn duration_as_100ns_ticks(duration: &Duration) -> i64 { - duration - .as_nanos() - .checked_div(100) - .and_then(|v: u128| i64::try_from(v).ok()) - .unwrap_or(std::i64::MAX) -} - -fn large_integer(val: i64) -> LARGE_INTEGER { - let mut result: LARGE_INTEGER; - unsafe { - result = std::mem::zeroed(); - *result.QuadPart_mut() = val; - }; - result -} - -#[cfg(test)] -mod tests { - use std::process::{Command, Stdio}; - - use super::*; - - #[allow(unused)] - fn debug_job_notifications(job_name: &str, msg: &JobObjectNotification) { - // Uncomment to debug job notifications. - // If left active, it can mess up normal test output. - //println!("job({}) msg: {:?}", job_name, msg); - } - - fn create_job_common(name: &'static str, f: F) -> JobObject - where - F: Fn(&mut JobInformation) -> &mut JobInformation, - { - let job = JobObject::new(name).expect("Job creation failed"); - - let mut job_info = JobInformation::new(); - f(&mut job_info); - job.set_information(&mut job_info) - .expect("Setting job information failed"); - - job - } - - fn create_job_with_notification(name: &'static str, f: F, notify_callback: G) -> JobObject - where - F: Fn(&mut JobInformation) -> &mut JobInformation, - G: Fn(JobObjectNotification) + Send + 'static, - { - let mut job = create_job_common(name, f); - - job.set_notification_routine(move |msg| { - debug_job_notifications(name, &msg); - notify_callback(msg); - }) - .expect("setting notification routine"); - - job - } - - fn create_job(name: &'static str, f: F) -> JobObject - where - F: Fn(&mut JobInformation) -> &mut JobInformation, - { - let mut job = create_job_common(name, f); - - job.set_notification_routine(move |msg| { - debug_job_notifications(name, &msg); - }) - .expect("setting notification routine"); - - job - } - - #[test] - fn limit_job_user_time() { - let job = create_job("limit_job_user_time", |builder| { - builder.per_job_user_time_limit(Duration::from_millis(100)) - }); - - let child = Command::new("cmd.exe") - .stdout(Stdio::piped()) - .args(["/c", "@echo off && for /L %x in (0,1,100000000) DO echo %x"]) - .spawn() - .expect("launching child process for job test failed"); - - job.attach(&child).expect("attaching process to job failed"); - let output = child.wait_with_output().unwrap(); - - assert!(!output.status.success()); - } - - #[test] - fn limit_process_user_time() { - let job = create_job("limit_process_user_time", |builder| { - builder.per_process_user_time_limit(Duration::from_millis(100)) - }); - - let child = Command::new("cmd.exe") - .stdout(Stdio::piped()) - .args(["/c", "@echo off && for /L %x in (0,1,100000000) DO echo %x"]) - .spawn() - .expect("launching child process for job test failed"); - - job.attach(&child).expect("attaching process to job failed"); - let output = child.wait_with_output().unwrap(); - - assert!(!output.status.success()); - } - - #[test] - fn job_memory_limit() { - let job = create_job("job_memory_limit", |builder| { - builder.job_memory_limit(10 * 1024 * 1024) - }); - - // Attaching should terminate PowerShell as it requires much more than 10MB, but - // we create a very large array of integers to be extra sure we'll exceed 10MB. - let child = Command::new("powershell.exe") - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .args(["/c", "$x=1..10mb"]) - .spawn() - .expect("launching child process for job test failed"); - - job.attach(&child).expect("attaching process to job failed"); - let output = child.wait_with_output().unwrap(); - assert!(!output.status.success()); - } - - #[test] - fn job_notification_limit_process_time() { - let limit = Duration::from_millis(100); - let (tx, rx) = std::sync::mpsc::channel(); - let job = create_job_with_notification( - "notification_limit_process_time", - |builder| builder.notify_per_job_user_time_limit(limit), - move |msg| { - if let JobObjectNotification::NotificationLimit(info) = msg { - tx.send(info).ok(); - } - }, - ); - - let child = Command::new("powershell.exe") - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .args(["/c", "$x=1..10000|%{$_}|%{$_}"]) - .spawn() - .expect("launching child process for job test failed"); - - job.attach(&child).expect("attaching process to job failed"); - let output = child.wait_with_output().unwrap(); - assert!(output.status.success()); - - let info = rx.recv().expect("should have received notification"); - assert!(info.per_job_user_time_limit.is_some()); - assert_eq!(info.per_job_user_time_limit.unwrap(), limit); - } -} diff --git a/src/agent/win-util/src/lib.rs b/src/agent/win-util/src/lib.rs index 66425c0a20..edcc048bae 100644 --- a/src/agent/win-util/src/lib.rs +++ b/src/agent/win-util/src/lib.rs @@ -2,77 +2,14 @@ // Licensed under the MIT License. #![cfg(windows)] -// Allow safe functions that take `HANDLE` arguments. -// -// Though they type alias raw pointers, they are opaque. In the future, we will -// wrap them in a newtype. This will witness that they were obtained via win32 -// API calls or documented pseudohandle construction. -#![allow(clippy::not_unsafe_ptr_arg_deref)] -#[macro_use] -pub mod macros; - -#[macro_use] -pub mod hr; - -mod aedebug; -pub mod com; pub mod file; pub mod handle; -pub mod jobs; pub mod memory; pub mod pipe_handle; pub mod process; pub mod string; -mod wer; - -use std::path::Path; - -use anyhow::Result; -use winapi::{ - shared::minwindef::{BOOL, FALSE}, - um::{ - errhandlingapi::SetErrorMode, - winbase::{SEM_FAILCRITICALERRORS, SEM_NOGPFAULTERRORBOX}, - }, -}; pub fn last_os_error() -> anyhow::Error { std::io::Error::last_os_error().into() } - -/// Call a windows api that returns BOOL, and if it fails, returns the os error -pub fn check_winapi BOOL>(f: T) -> Result<()> { - if f() == FALSE { - Err(last_os_error()) - } else { - Ok(()) - } -} - -pub fn configure_machine_wide_app_debug_settings(application_path: impl AsRef) -> Result<()> { - anyhow::ensure!( - process::is_elevated(), - "Changing registry requires elevation" - ); - - let exe_name = application_path.as_ref().file_name().ok_or_else(|| { - anyhow::anyhow!( - "Missing executable name in path {}", - application_path.as_ref().display() - ) - })?; - - // This should avoid some popups, e.g. if a dll can't be found. - // I'm not sure SEM_NOGPFAULTERRORBOX is useful anymore (because of Watson), - // but it is another source of popups that could block automation. - unsafe { SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX) }; - - // This is a machine-wide setting, not process specific. - wer::disable_wer_ui()?; - - wer::add_exclusion(exe_name)?; - aedebug::add_exclusion(exe_name)?; - - Ok(()) -} diff --git a/src/agent/win-util/src/macros.rs b/src/agent/win-util/src/macros.rs deleted file mode 100644 index 71b06771fb..0000000000 --- a/src/agent/win-util/src/macros.rs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -// This repo relies on some macros not exported from winapi-rs, so we copy the definitions here. -// See https://github.com/retep998/winapi-rs/blob/4d2a34011891ea047859e0376a5fcc5efd58ec0d/src/macros.rs - -#[macro_export] -macro_rules! UNION { - ($(#[$attrs:meta])* union $name:ident { - [$stype:ty; $ssize:expr], - $($variant:ident $variant_mut:ident: $ftype:ty,)+ - }) => ( - #[repr(C)] $(#[$attrs])* - pub struct $name([$stype; $ssize]); - impl Copy for $name {} - impl Clone for $name { - #[inline] - fn clone(&self) -> $name { *self } - } - #[cfg(feature = "impl-default")] - impl Default for $name { - #[inline] - fn default() -> $name { unsafe { $crate::_core::mem::zeroed() } } - } - impl $name {$( - #[inline] - pub unsafe fn $variant(&self) -> &$ftype { - &*(self as *const _ as *const $ftype) - } - #[inline] - pub unsafe fn $variant_mut(&mut self) -> &mut $ftype { - &mut *(self as *mut _ as *mut $ftype) - } - )+} - ); - ($(#[$attrs:meta])* union $name:ident { - [$stype32:ty; $ssize32:expr] [$stype64:ty; $ssize64:expr], - $($variant:ident $variant_mut:ident: $ftype:ty,)+ - }) => ( - #[repr(C)] $(#[$attrs])* #[cfg(target_arch = "x86")] - pub struct $name([$stype32; $ssize32]); - #[repr(C)] $(#[$attrs])* #[cfg(target_arch = "x86_64")] - pub struct $name([$stype64; $ssize64]); - impl Copy for $name {} - impl Clone for $name { - #[inline] - fn clone(&self) -> $name { *self } - } - #[cfg(feature = "impl-default")] - impl Default for $name { - #[inline] - fn default() -> $name { unsafe { $crate::_core::mem::zeroed() } } - } - impl $name {$( - #[inline] - pub unsafe fn $variant(&self) -> &$ftype { - &*(self as *const _ as *const $ftype) - } - #[inline] - pub unsafe fn $variant_mut(&mut self) -> &mut $ftype { - &mut *(self as *mut _ as *mut $ftype) - } - )+} - ); -} - -#[macro_export] -macro_rules! BITFIELD { - ($base:ident $field:ident: $fieldtype:ty [ - $($thing:ident $set_thing:ident[$r:expr],)+ - ]) => { - impl $base {$( - #[inline] - pub fn $thing(&self) -> $fieldtype { - let size = ::std::mem::size_of::<$fieldtype>() * 8; - self.$field << (size - $r.end) >> (size - $r.end + $r.start) - } - #[inline] - pub fn $set_thing(&mut self, val: $fieldtype) { - let mask = ((1 << ($r.end - $r.start)) - 1) << $r.start; - self.$field &= !mask; - self.$field |= (val << $r.start) & mask; - } - )+} - } -} - -#[macro_export] -macro_rules! FN { - (stdcall $func:ident($($t:ty,)*) -> $ret:ty) => ( - pub type $func = Option $ret>; - ); - (stdcall $func:ident($($p:ident: $t:ty,)*) -> $ret:ty) => ( - pub type $func = Option $ret>; - ); - (cdecl $func:ident($($t:ty,)*) -> $ret:ty) => ( - pub type $func = Option $ret>; - ); - (cdecl_variadic $func:ident($($t:ty,)*) -> $ret:ty) => ( - pub type $func = Option $ret>; - ); - (cdecl $func:ident($($p:ident: $t:ty,)*) -> $ret:ty) => ( - pub type $func = Option $ret>; - ); - (cdecl_variadic $func:ident($($p:ident: $t:ty,)*) -> $ret:ty) => ( - pub type $func = Option $ret>; - ); -} diff --git a/src/agent/win-util/src/memory.rs b/src/agent/win-util/src/memory.rs index 635d37b2fa..591cf9e8a5 100644 --- a/src/agent/win-util/src/memory.rs +++ b/src/agent/win-util/src/memory.rs @@ -3,31 +3,32 @@ #![allow(clippy::fn_to_numeric_cast_with_truncation)] -use std::mem::{size_of, MaybeUninit}; +use std::{ + ffi::c_void, + mem::{size_of, MaybeUninit}, +}; +use crate::last_os_error; use anyhow::Result; -use winapi::{ - shared::minwindef::{DWORD, FALSE, LPVOID}, - um::{ - memoryapi::VirtualQueryEx, - psapi::{K32GetPerformanceInfo, PERFORMANCE_INFORMATION}, - winnt::{ - HANDLE, MEMORY_BASIC_INFORMATION, PAGE_EXECUTE, PAGE_EXECUTE_READ, - PAGE_EXECUTE_READWRITE, PAGE_EXECUTE_WRITECOPY, +use windows::Win32::{ + Foundation::HANDLE, + System::{ + Memory::{ + VirtualQueryEx, MEMORY_BASIC_INFORMATION, PAGE_EXECUTE, PAGE_EXECUTE_READ, + PAGE_EXECUTE_READWRITE, PAGE_EXECUTE_WRITECOPY, PAGE_PROTECTION_FLAGS, }, + ProcessStatus::{GetPerformanceInfo, PERFORMANCE_INFORMATION}, }, }; -use crate::last_os_error; - pub struct MemoryInfo { base_address: u64, region_size: u64, - protection: DWORD, + protection: PAGE_PROTECTION_FLAGS, } impl MemoryInfo { - pub fn new(base_address: u64, region_size: u64, protection: DWORD) -> Self { + pub fn new(base_address: u64, region_size: u64, protection: PAGE_PROTECTION_FLAGS) -> Self { Self { base_address, region_size, @@ -44,8 +45,9 @@ impl MemoryInfo { } pub fn is_executable(&self) -> bool { - 0 != (self.protection + (self.protection & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)) + != PAGE_PROTECTION_FLAGS::default() } } @@ -54,11 +56,12 @@ pub fn get_memory_info(process_handle: HANDLE, address: u64) -> Result(), ) }; + if size != size_of::() { return Err(last_os_error()); } @@ -107,14 +110,9 @@ impl From for SystemMemoryInfo { pub fn get_system_memory_info() -> Result { let mut info = MaybeUninit::zeroed(); - if unsafe { - K32GetPerformanceInfo(info.as_mut_ptr(), size_of:: as u32) - } == FALSE - { - return Err(last_os_error()); - } + const SIZE: u32 = size_of::() as u32; + unsafe { GetPerformanceInfo(info.as_mut_ptr(), SIZE).ok()? }; let info = unsafe { info.assume_init() }; - Ok(info.into()) } diff --git a/src/agent/win-util/src/pipe_handle.rs b/src/agent/win-util/src/pipe_handle.rs index 48899c4d81..d6548928a2 100644 --- a/src/agent/win-util/src/pipe_handle.rs +++ b/src/agent/win-util/src/pipe_handle.rs @@ -9,35 +9,12 @@ use std::os::windows::io::{AsRawHandle, RawHandle}; use anyhow::{Context, Result}; use log::trace; -use winapi::{ - shared::{ - minwindef::{DWORD, FALSE, LPDWORD, LPVOID}, - ntdef::NULL, - winerror::{ERROR_BROKEN_PIPE, ERROR_NO_DATA}, - }, - um::{ - errhandlingapi::GetLastError, fileapi::ReadFile, minwinbase::LPOVERLAPPED, - namedpipeapi::SetNamedPipeHandleState, winbase::PIPE_NOWAIT, - }, +use windows::Win32::{ + Foundation::{GetLastError, ERROR_BROKEN_PIPE, ERROR_NO_DATA, FALSE, HANDLE}, + Storage::FileSystem::ReadFile, + System::Pipes::{SetNamedPipeHandleState, PIPE_NOWAIT}, }; -use crate::check_winapi; - -// A wrapper over a Vec that uses RAII to set the length correctly after uses of the -// Vec internal buffer in unsafe Win32 apis that bypass safe Vec apis when writing data. -struct Guard<'a> { - buf: &'a mut Vec, - len: usize, -} - -impl Drop for Guard<'_> { - fn drop(&mut self) { - unsafe { - self.buf.set_len(self.len); - } - } -} - // A wrapper around a Win32 named pipe that does not block on read. pub struct PipeReaderNonBlocking { reader: os_pipe::PipeReader, @@ -54,35 +31,22 @@ impl PipeReaderNonBlocking { let start_len = buf.len(); - // When the guard is dropped, the length of the buffer will be set to the correct length. - let mut g = Guard { - len: buf.len(), - buf, - }; - loop { - // Reserve more buffer space if we need it. The check isn't strictly necessary, - // but it does avoid reallocating the buffer too often if we had many small reads. - if g.len == g.buf.len() { - g.buf.reserve(reservation_size as usize); - unsafe { - // We set the length so our slice below doesn't panic. - // We track the real length in the guard - setting it correctly via drop. - g.buf.set_len(g.buf.capacity()); - // newly reserved memory is not initialized. - } + // expand capacity if needed + if buf.capacity() == buf.len() { + buf.reserve(reservation_size as usize); } - let unused_space = &mut g.buf[g.len..]; + let unused_space = buf.spare_capacity_mut(); let unused_len = unused_space.len(); let mut bytes_read = 0u32; let success = unsafe { ReadFile( - self.reader.as_raw_handle(), - unused_space.as_mut_ptr() as LPVOID, - unused_len as DWORD, - &mut bytes_read as LPDWORD, - NULL as LPOVERLAPPED, + HANDLE(self.reader.as_raw_handle() as _), + Some(unused_space.as_mut_ptr().cast()), + unused_len as u32, + Some(&mut bytes_read), + None, ) }; @@ -105,18 +69,23 @@ impl PipeReaderNonBlocking { } } } else { - g.len += bytes_read as usize; + // commit that the bytes have been read + debug_assert!(bytes_read as usize <= unused_len); + unsafe { + buf.set_len(buf.len() + bytes_read as usize); + } } } - let total_bytes_read = g.len - start_len; + let total_bytes_read = buf.len() - start_len; if total_bytes_read > 0 { trace!( "Read {} bytes from pipe, `{}`", total_bytes_read, - String::from_utf8_lossy(&g.buf[start_len..(start_len + total_bytes_read)]) + String::from_utf8_lossy(&buf[start_len..]) ); } + Ok(total_bytes_read) } @@ -133,18 +102,9 @@ impl PipeReaderNonBlocking { } fn set_nonblocking_mode(handle: RawHandle) -> Result<()> { - let mut mode = PIPE_NOWAIT as DWORD; - check_winapi(|| unsafe { - SetNamedPipeHandleState( - handle, - &mut mode as LPDWORD, - NULL as LPDWORD, - NULL as LPDWORD, - ) - }) - .context("Setting pipe to non-blocking mode")?; - - Ok(()) + unsafe { SetNamedPipeHandleState(HANDLE(handle as _), Some(&PIPE_NOWAIT), None, None) } + .ok() + .context("Setting pipe to non-blocking mode") } // Return a pair a reader and writer handle wrapping a Win32 named pipe. diff --git a/src/agent/win-util/src/process.rs b/src/agent/win-util/src/process.rs index cca059f50b..3ee490d0fd 100644 --- a/src/agent/win-util/src/process.rs +++ b/src/agent/win-util/src/process.rs @@ -2,60 +2,53 @@ // Licensed under the MIT License. use std::{ - ffi::OsString, + ffi::{c_void, OsString}, mem::{size_of, size_of_val, MaybeUninit}, os::windows::ffi::OsStringExt, - ptr, }; use anyhow::{Context, Result}; use log::{error, warn}; -use winapi::{ - ctypes::c_void, - shared::{ - basetsd::SIZE_T, - minwindef::{BOOL, DWORD, FALSE, LPCVOID, LPVOID, TRUE}, - }, - um::{ - handleapi::{CloseHandle, INVALID_HANDLE_VALUE}, - memoryapi::{ReadProcessMemory, WriteProcessMemory}, - processthreadsapi::{GetCurrentProcess, GetProcessId, OpenProcessToken, TerminateProcess}, - securitybaseapi::GetTokenInformation, - winnt::{TokenElevation, HANDLE, TOKEN_ELEVATION, TOKEN_QUERY}, - wow64apiset::IsWow64Process, +use windows::Win32::{ + Foundation::{CloseHandle, FALSE, HANDLE, INVALID_HANDLE_VALUE}, + Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION, TOKEN_QUERY}, + System::{ + Diagnostics::Debug::{FlushInstructionCache, ReadProcessMemory, WriteProcessMemory}, + Threading::{ + GetCurrentProcess, GetProcessId, IsWow64Process, OpenProcessToken, TerminateProcess, + }, }, }; -use crate::check_winapi; -use winapi::um::processthreadsapi::FlushInstructionCache; - pub fn is_elevated() -> bool { fn is_elevated_impl() -> Result { let mut process_token = INVALID_HANDLE_VALUE; - check_winapi(|| unsafe { - OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut process_token) - }) - .context("Opening process token")?; + unsafe { OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut process_token) } + .ok() + .context("Opening process token")?; let mut token_elevation: MaybeUninit = MaybeUninit::uninit(); - let mut size = size_of::() as DWORD; + let mut size = size_of::() as u32; - check_winapi(|| unsafe { + unsafe { GetTokenInformation( process_token, TokenElevation, - token_elevation.as_mut_ptr() as *mut c_void, + Some(token_elevation.as_mut_ptr().cast()), size, &mut size, ) - }) + } + .ok() .context("Getting process token information")?; if process_token != INVALID_HANDLE_VALUE { unsafe { CloseHandle(process_token) }; } + debug_assert!(size == size_of::() as u32); + let token_elevation = unsafe { token_elevation.assume_init() }; Ok(token_elevation.TokenIsElevated != 0) } @@ -69,44 +62,50 @@ pub fn is_elevated() -> bool { } } -pub fn read_memory(process_handle: HANDLE, remote_address: LPCVOID) -> Result { +#[allow(clippy::not_unsafe_ptr_arg_deref)] // pointer is not in this process +pub fn read_memory(process_handle: HANDLE, remote_address: *const c_void) -> Result { let mut buf: MaybeUninit = MaybeUninit::uninit(); - check_winapi(|| unsafe { + unsafe { ReadProcessMemory( process_handle, remote_address, - buf.as_mut_ptr() as LPVOID, + buf.as_mut_ptr().cast(), size_of::(), - ptr::null_mut(), + None, ) - }) + } + .ok() .context("Reading process memory")?; let buf = unsafe { buf.assume_init() }; Ok(buf) } +#[allow(clippy::not_unsafe_ptr_arg_deref)] // pointer is not in this process pub fn read_memory_array( process_handle: HANDLE, - remote_address: LPCVOID, + remote_address: *const c_void, buf: &mut [T], ) -> Result<()> { - check_winapi(|| unsafe { + unsafe { ReadProcessMemory( process_handle, remote_address, - buf.as_mut_ptr() as LPVOID, + buf.as_mut_ptr().cast(), size_of_val(buf), - ptr::null_mut(), + None, ) - }) + } + .ok() .context("Reading process memory")?; + Ok(()) } +#[allow(clippy::not_unsafe_ptr_arg_deref)] // pointer is not in this process pub fn read_narrow_string( process_handle: HANDLE, - remote_address: LPCVOID, + remote_address: *const c_void, len: usize, ) -> Result { let mut buf: Vec = Vec::with_capacity(len); @@ -117,9 +116,10 @@ pub fn read_narrow_string( Ok(String::from_utf8_lossy(&buf).into()) } +#[allow(clippy::not_unsafe_ptr_arg_deref)] // pointer is not in this process pub fn read_wide_string( process_handle: HANDLE, - remote_address: LPCVOID, + remote_address: *const c_void, len: usize, ) -> Result { let mut buf: Vec = Vec::with_capacity(len); @@ -130,60 +130,66 @@ pub fn read_wide_string( Ok(OsString::from_wide(&buf)) } +#[allow(clippy::not_unsafe_ptr_arg_deref)] // pointer is not in this process pub fn write_memory_slice( process_handle: HANDLE, - remote_address: LPVOID, + remote_address: *mut c_void, buffer: &[u8], ) -> Result<()> { - let mut bytes_written: SIZE_T = 0; - check_winapi(|| unsafe { + let mut bytes_written: usize = 0; + unsafe { WriteProcessMemory( process_handle, remote_address, - buffer.as_ptr() as LPCVOID, + buffer.as_ptr().cast(), buffer.len(), - &mut bytes_written, + Some(&mut bytes_written), ) - }) + } + .ok() .context("writing process memory")?; Ok(()) } +#[allow(clippy::not_unsafe_ptr_arg_deref)] // pointer is not in this process pub fn write_memory( process_handle: HANDLE, - remote_address: LPVOID, + remote_address: *mut c_void, value: &T, ) -> Result<()> { - let mut bytes_written: SIZE_T = 0; - check_winapi(|| unsafe { + let mut bytes_written: usize = 0; + unsafe { WriteProcessMemory( process_handle, remote_address, - value as *const T as LPCVOID, + (value as *const T).cast(), size_of::(), - &mut bytes_written, + Some(&mut bytes_written), ) - }) + } + .ok() .context("writing process memory")?; Ok(()) } -pub fn id(process_handle: HANDLE) -> DWORD { +pub fn id(process_handle: HANDLE) -> u32 { unsafe { GetProcessId(process_handle) } } pub fn is_wow64_process(process_handle: HANDLE) -> bool { + #[cfg(target_arch = "x86_64")] // break build on ARM64 fn is_wow64_process_impl(process_handle: HANDLE) -> Result { let mut is_wow64 = FALSE; - check_winapi(|| - // If we ever run as a 32 bit process, or if we run on ARM64, this code is wrong, - // we should be using IsWow64Process2. We don't because it's not supported by - // every OS we'd like to run on, e.g. the vs2017-win2016 vm we use in CI. - unsafe { IsWow64Process(process_handle, &mut is_wow64 as *mut BOOL) }) - .context("IsWow64Process")?; - Ok(is_wow64 == TRUE) + // If we ever run as a 32 bit process, or if we run on ARM64, this code is wrong, + // we should be using IsWow64Process2. We don't because it's not supported by + // every OS we'd like to run on, e.g. the vs2017-win2016 vm we use in CI. + unsafe { IsWow64Process(process_handle, &mut is_wow64) } + .ok() + .context("IsWow64Process")?; + + Ok(is_wow64.as_bool()) } match is_wow64_process_impl(process_handle) { @@ -197,12 +203,12 @@ pub fn is_wow64_process(process_handle: HANDLE) -> bool { pub fn terminate(process_handle: HANDLE) { fn terminate_impl(process_handle: HANDLE) -> Result<()> { - check_winapi(|| unsafe { TerminateProcess(process_handle, 0) }) - .context("TerminateProcess")?; - Ok(()) + unsafe { TerminateProcess(process_handle, 0) } + .ok() + .context("TerminateProcess") } - if process_handle != INVALID_HANDLE_VALUE && !process_handle.is_null() { + if process_handle != INVALID_HANDLE_VALUE && process_handle != HANDLE::default() { if let Err(err) = terminate_impl(process_handle) { error!("Error terminating process: {}", err); } @@ -211,12 +217,9 @@ pub fn terminate(process_handle: HANDLE) { pub fn flush_instruction_cache( process_handle: HANDLE, - remote_address: LPCVOID, + remote_address: *const c_void, len: usize, ) -> Result<()> { - check_winapi(|| unsafe { FlushInstructionCache(process_handle, remote_address, len) }) -} - -pub fn current_process_handle() -> HANDLE { - unsafe { GetCurrentProcess() } + unsafe { FlushInstructionCache(process_handle, Some(remote_address), len) }.ok()?; + Ok(()) } diff --git a/src/agent/win-util/src/string.rs b/src/agent/win-util/src/string.rs index e111498fd3..4d262fa576 100644 --- a/src/agent/win-util/src/string.rs +++ b/src/agent/win-util/src/string.rs @@ -9,9 +9,9 @@ use std::{ slice, }; -use winapi::{ - shared::minwindef::HLOCAL, - um::{shellapi, winbase}, +use windows::{ + core::{Error, PCWSTR}, + Win32::{Foundation::HLOCAL, System::Memory::LocalFree, UI::Shell::CommandLineToArgvW}, }; pub fn to_wstring(str: impl AsRef) -> Vec { @@ -24,14 +24,19 @@ pub fn to_wstring(str: impl AsRef) -> Vec { pub fn to_argv(command_line: &str) -> Vec { let mut argv: Vec = Vec::new(); let mut argc = 0; + let wide_command_line = to_wstring(command_line); unsafe { - let args = shellapi::CommandLineToArgvW(to_wstring(command_line).as_ptr(), &mut argc); + let args = CommandLineToArgvW(PCWSTR(wide_command_line.as_ptr()), &mut argc); + assert!(!args.is_null()); for i in 0..argc { - argv.push(os_string_from_wide_ptr(*args.offset(i as isize))); + argv.push(os_string_from_wide_ptr((*args.offset(i as isize)).0)); } - winbase::LocalFree(args as HLOCAL); + let free_result = LocalFree(HLOCAL(args as _)); + // the return value here is backwards from what you'd expect + // "The operation completed successfully." + debug_assert!(free_result == Err(Error::OK)); } argv } diff --git a/src/agent/win-util/src/wer.rs b/src/agent/win-util/src/wer.rs deleted file mode 100644 index 7cf5ec5e0a..0000000000 --- a/src/agent/win-util/src/wer.rs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#![allow(clippy::upper_case_acronyms)] - -use std::ffi::OsStr; - -use anyhow::Result; -use log::error; -use winapi::{ - shared::minwindef::{DWORD, TRUE}, - um::werapi::{WerAddExcludedApplication, WerRemoveExcludedApplication}, -}; -use winreg::{enums::HKEY_LOCAL_MACHINE, RegKey}; - -use crate::{check_hr, string}; - -pub fn add_exclusion(exe_name: &OsStr) -> Result<()> { - let wexe_name = string::to_wstring(exe_name); - check_hr!(unsafe { - WerAddExcludedApplication(wexe_name.as_ptr(), /*AllUsers*/ TRUE) - }); - atexit::register(move || remove_exclusion(&wexe_name)); - - Ok(()) -} - -fn remove_exclusion(application_path: &[u16]) { - unsafe { - // TODO: Minor bug - we shouldn't remove from WER if we weren't the tool to add it. - WerRemoveExcludedApplication(application_path.as_ptr(), /*AllUsers*/ TRUE); - } -} - -const WINDOWS_ERROR_REPORTING_KEY: &str = r"SOFTWARE\Microsoft\Windows\Windows Error Reporting"; -const DONTSHOWUI_PROP: &str = "DontShowUI"; - -#[derive(Copy, Clone)] -enum RestoreWerUI { - DeleteKey, - Value(u32), -} - -pub fn disable_wer_ui() -> Result<()> { - let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); - let (wer, _) = hklm.create_subkey(WINDOWS_ERROR_REPORTING_KEY)?; - - let restore = match wer.get_value::(DONTSHOWUI_PROP) { - Err(_) => RestoreWerUI::DeleteKey, - Ok(v) => RestoreWerUI::Value(v), - }; - - wer.set_value(DONTSHOWUI_PROP, &1u32)?; - atexit::register(move || restore_wer_ui(restore)); - - Ok(()) -} - -fn restore_wer_ui(restore: RestoreWerUI) { - if let Err(err) = do_the_work(restore) { - error!( - r"Error restoring HKLM:{}\{}: {}", - WINDOWS_ERROR_REPORTING_KEY, DONTSHOWUI_PROP, err - ); - } - - fn do_the_work(restore: RestoreWerUI) -> Result<()> { - let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); - let (wer, _) = hklm.create_subkey(WINDOWS_ERROR_REPORTING_KEY)?; - - match restore { - RestoreWerUI::DeleteKey => wer.delete_value(DONTSHOWUI_PROP)?, - RestoreWerUI::Value(v) => wer.set_value(DONTSHOWUI_PROP, &v)?, - }; - - Ok(()) - } -} diff --git a/src/ci/build_cli.ps1 b/src/ci/build_cli.ps1 index d3ffc32d95..6a255e977a 100644 --- a/src/ci/build_cli.ps1 +++ b/src/ci/build_cli.ps1 @@ -1,46 +1,46 @@ -param ( - [Parameter()] - [string] $app_dir = "$PSScriptRoot", - [Parameter()] - [string] $version = "$null" -) - -try { - Push-Location - - pip uninstall onefuzztypes -y - - # Get Version and Replace versions - if ($version -eq "$null") { - $version = bash .\get-version.sh - } - bash .\set-versions.sh $version - - # Create wheel for onefuzztypes - Set-Location "$app_dir/../pytypes" - python setup.py sdist bdist_wheel - Copy-Item dist/*.whl "$app_dir/../cli" - - # setup creates whl file which replace - to _ - Set-Location "$app_dir/../cli" - $_version = $version -replace "-", "_" - Write-Host $version - - # Replace onefuzztypes requirement to whl file - (Get-Content -path "requirements.txt") -replace "onefuzztypes==$version", "./onefuzztypes-$_version-py3-none-any.whl" | Out-File -FilePath "requirements.txt" -Encoding "ascii" - pip install -r .\requirements.txt -r .\requirements-dev.txt - - # Build exe - pyinstaller onefuzz/__main__.py --onefile --name "onefuzz" --additional-hooks-dir extra/pyinstaller --hidden-import='pkg_resources.py2_warn' --hidden-import='opentelemetry.baggage' --hidden-import='opentelemetry.baggage.propagation' --hidden-import='opentelemetry.context.contextvars_context' --copy-metadata opentelemetry-sdk --copy-metadata opentelemetry-api --exclude-module tkinter --exclude-module PySide2 --exclude-module PIL.ImageDraw --exclude-module Pillow --clean - - # Cleanup - (Get-Content -path "requirements.txt") -replace "./onefuzztypes-$_version-py3-none-any.whl", "onefuzztypes==$version" | Out-File -FilePath "requirements.txt" -Encoding "ascii" - Remove-Item "*.whl" - Set-Location "$app_dir" - bash .\unset-versions.sh - - Write-Host "OneFuzz exe is available at src\cli\dist\onefuzz.exe" -} -finally { - Pop-Location -} +param ( + [Parameter()] + [string] $app_dir = "$PSScriptRoot", + [Parameter()] + [string] $version = "$null" +) + +try { + Push-Location + + pip uninstall onefuzztypes -y + + # Get Version and Replace versions + if ($version -eq "$null") { + $version = bash .\get-version.sh + } + bash .\set-versions.sh $version + + # Create wheel for onefuzztypes + Set-Location "$app_dir/../pytypes" + python setup.py sdist bdist_wheel + Copy-Item dist/*.whl "$app_dir/../cli" + + # setup creates whl file which replace - to _ + Set-Location "$app_dir/../cli" + $_version = $version -replace "-", "_" + Write-Host $version + + # Replace onefuzztypes requirement to whl file + (Get-Content -path "requirements.txt") -replace "onefuzztypes==$version", "./onefuzztypes-$_version-py3-none-any.whl" | Out-File -FilePath "requirements.txt" -Encoding "ascii" + pip install -r .\requirements.txt -r .\requirements-dev.txt + + # Build exe + pyinstaller onefuzz/__main__.py --onefile --name "onefuzz" --additional-hooks-dir extra/pyinstaller --hidden-import='pkg_resources.py2_warn' --hidden-import='opentelemetry.baggage' --hidden-import='opentelemetry.baggage.propagation' --hidden-import='opentelemetry.context.contextvars_context' --copy-metadata opentelemetry-sdk --copy-metadata opentelemetry-api --exclude-module tkinter --exclude-module PySide2 --exclude-module PIL.ImageDraw --exclude-module Pillow --clean + + # Cleanup + (Get-Content -path "requirements.txt") -replace "./onefuzztypes-$_version-py3-none-any.whl", "onefuzztypes==$version" | Out-File -FilePath "requirements.txt" -Encoding "ascii" + Remove-Item "*.whl" + Set-Location "$app_dir" + bash .\unset-versions.sh + + Write-Host "OneFuzz exe is available at src\cli\dist\onefuzz.exe" +} +finally { + Pop-Location +} diff --git a/src/ci/check-dependencies.sh b/src/ci/check-dependencies.sh index 79f21845c1..b6cb5ab87d 100755 --- a/src/ci/check-dependencies.sh +++ b/src/ci/check-dependencies.sh @@ -69,6 +69,7 @@ linux-vdso.so.1" \ apphelp.dll bcrypt.dll bcryptprimitives.dll +combase.dll crypt32.dll cryptbase.dll dbghelp.dll @@ -80,6 +81,7 @@ msasn1.dll msvcp_win.dll msvcrt.dll ntdll.dll +oleaut32.dll psapi.dll rpcrt4.dll sechost.dll @@ -113,14 +115,17 @@ linux-vdso.so.1" \ apphelp.dll bcrypt.dll bcryptprimitives.dll +combase.dll crypt32.dll cryptbase.dll dbghelp.dll kernel32.dll kernelbase.dll msasn1.dll +msvcp_win.dll msvcrt.dll ntdll.dll +oleaut32.dll rpcrt4.dll sechost.dll secur32.dll