//! Windows named-pipe transport for the IDE context IPC client. use std::io; use std::io::Read; use std::io::Write; use std::os::windows::ffi::OsStrExt; use std::path::PathBuf; use std::ptr; use std::time::Instant; use windows_sys::Win32::Foundation::BOOL; use windows_sys::Win32::Foundation::CloseHandle; use windows_sys::Win32::Foundation::ERROR_IO_PENDING; use windows_sys::Win32::Foundation::ERROR_NOT_FOUND; use windows_sys::Win32::Foundation::GENERIC_READ; use windows_sys::Win32::Foundation::GENERIC_WRITE; use windows_sys::Win32::Foundation::HANDLE; use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE; use windows_sys::Win32::Foundation::WAIT_FAILED; use windows_sys::Win32::Foundation::WAIT_OBJECT_0; use windows_sys::Win32::Foundation::WAIT_TIMEOUT; use windows_sys::Win32::Security::EqualSid; use windows_sys::Win32::Security::GetTokenInformation; use windows_sys::Win32::Security::TOKEN_QUERY; use windows_sys::Win32::Security::TOKEN_USER; use windows_sys::Win32::Security::TokenUser; use windows_sys::Win32::Storage::FileSystem::CreateFileW; use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_NORMAL; use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OVERLAPPED; use windows_sys::Win32::Storage::FileSystem::FILE_SHARE_READ; use windows_sys::Win32::Storage::FileSystem::FILE_SHARE_WRITE; use windows_sys::Win32::Storage::FileSystem::OPEN_EXISTING; use windows_sys::Win32::Storage::FileSystem::ReadFile; use windows_sys::Win32::Storage::FileSystem::WriteFile; use windows_sys::Win32::System::IO::CancelIoEx; use windows_sys::Win32::System::IO::GetOverlappedResult; use windows_sys::Win32::System::IO::OVERLAPPED; use windows_sys::Win32::System::Pipes::GetNamedPipeServerProcessId; use windows_sys::Win32::System::Threading::CreateEventW; use windows_sys::Win32::System::Threading::GetCurrentProcess; use windows_sys::Win32::System::Threading::OpenProcess; use windows_sys::Win32::System::Threading::OpenProcessToken; use windows_sys::Win32::System::Threading::PROCESS_QUERY_LIMITED_INFORMATION; use windows_sys::Win32::System::Threading::WaitForSingleObject; const TRUE: BOOL = 1; const FALSE: BOOL = 0; const NULL_HANDLE: HANDLE = 0; pub(super) struct WindowsPipeStream { handle: OwnedHandle, deadline: Instant, } impl WindowsPipeStream { pub(super) fn connect(pipe_path: PathBuf, deadline: Instant) -> io::Result { let wide_path = pipe_path .as_os_str() .encode_wide() .chain(std::iter::once(0)) .collect::>(); let handle = unsafe { CreateFileW( wide_path.as_ptr(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, ptr::null(), OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL_HANDLE, ) }; if handle == INVALID_HANDLE_VALUE { return Err(io::Error::last_os_error()); } let handle = OwnedHandle(handle); validate_pipe_server_owner(handle.raw())?; Ok(Self { handle, deadline }) } pub(super) fn set_deadline(&mut self, deadline: Instant) { self.deadline = deadline; } } impl Read for WindowsPipeStream { fn read(&mut self, buf: &mut [u8]) -> io::Result { if buf.is_empty() { return Ok(0); } let bytes_to_read = u32::try_from(buf.len()).unwrap_or(u32::MAX); let mut operation = OverlappedOperation::new()?; let result = unsafe { ReadFile( self.handle.raw(), buf.as_mut_ptr(), bytes_to_read, ptr::null_mut(), operation.as_mut_ptr(), ) }; operation.complete(self.handle.raw(), result, self.deadline) } } impl Write for WindowsPipeStream { fn write(&mut self, buf: &[u8]) -> io::Result { if buf.is_empty() { return Ok(0); } let bytes_to_write = u32::try_from(buf.len()).unwrap_or(u32::MAX); let mut operation = OverlappedOperation::new()?; let result = unsafe { WriteFile( self.handle.raw(), buf.as_ptr(), bytes_to_write, ptr::null_mut(), operation.as_mut_ptr(), ) }; operation.complete(self.handle.raw(), result, self.deadline) } fn flush(&mut self) -> io::Result<()> { Ok(()) } } struct OverlappedOperation { event: OwnedHandle, overlapped: OVERLAPPED, } impl OverlappedOperation { fn new() -> io::Result { let event = unsafe { CreateEventW(ptr::null(), TRUE, FALSE, ptr::null()) }; if event == 0 { return Err(io::Error::last_os_error()); } let mut overlapped = unsafe { std::mem::zeroed::() }; overlapped.hEvent = event; Ok(Self { event: OwnedHandle(event), overlapped, }) } fn as_mut_ptr(&mut self) -> *mut OVERLAPPED { &mut self.overlapped } fn complete( &mut self, handle: HANDLE, initial_result: BOOL, deadline: Instant, ) -> io::Result { if initial_result == 0 { let error = io::Error::last_os_error(); if error.raw_os_error() != Some(ERROR_IO_PENDING as i32) { return Err(error); } // Use a zero wait after the deadline so pending overlapped I/O still flows through // cancel_and_timeout instead of returning while the OS operation owns this OVERLAPPED. match unsafe { WaitForSingleObject(self.event.raw(), remaining_timeout_ms(deadline)) } { WAIT_OBJECT_0 => {} WAIT_TIMEOUT => return Err(self.cancel_and_timeout(handle)), WAIT_FAILED => return Err(io::Error::last_os_error()), other => { return Err(io::Error::other(format!( "unexpected WaitForSingleObject result: {other}" ))); } } } let mut bytes_transferred = 0; let result = unsafe { GetOverlappedResult(handle, self.as_mut_ptr(), &mut bytes_transferred, FALSE) }; if result == 0 { return Err(io::Error::last_os_error()); } Ok(bytes_transferred as usize) } fn cancel_and_timeout(&mut self, handle: HANDLE) -> io::Error { let cancel_result = unsafe { CancelIoEx(handle, self.as_mut_ptr()) }; if cancel_result == 0 { let cancel_error = io::Error::last_os_error(); if cancel_error.raw_os_error() != Some(ERROR_NOT_FOUND as i32) { return cancel_error; } // ERROR_NOT_FOUND means the operation completed before cancellation was issued. Drain // it without waiting so the timeout path cannot block past the caller's deadline. let mut bytes_transferred = 0; unsafe { GetOverlappedResult(handle, self.as_mut_ptr(), &mut bytes_transferred, FALSE) }; return timeout_io_error(); } let mut bytes_transferred = 0; unsafe { GetOverlappedResult(handle, self.as_mut_ptr(), &mut bytes_transferred, TRUE); } timeout_io_error() } } struct OwnedHandle(HANDLE); impl OwnedHandle { fn raw(&self) -> HANDLE { self.0 } } impl Drop for OwnedHandle { fn drop(&mut self) { if self.0 != 0 && self.0 != INVALID_HANDLE_VALUE { unsafe { CloseHandle(self.0); } } } } struct TokenUserBuffer { buffer: Vec, } impl TokenUserBuffer { fn sid(&self) -> io::Result { if self.buffer.len() < std::mem::size_of::() { return Err(io::Error::new( io::ErrorKind::InvalidData, "token user buffer is too small", )); } // GetTokenInformation writes TOKEN_USER into a byte buffer. Vec has // no TOKEN_USER alignment guarantee, so copy the fixed header out with // an unaligned read before using its SID pointer. let token_user = unsafe { std::ptr::read_unaligned(self.buffer.as_ptr() as *const TOKEN_USER) }; Ok(token_user.User.Sid) } } fn validate_pipe_server_owner(pipe_handle: HANDLE) -> io::Result<()> { let mut server_process_id = 0; let result = unsafe { GetNamedPipeServerProcessId(pipe_handle, &mut server_process_id) }; if result == 0 { return Err(io::Error::last_os_error()); } let server_process = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, server_process_id) }; if server_process == 0 { return Err(io::Error::last_os_error()); } let server_process = OwnedHandle(server_process); let server_token = open_process_token(server_process.raw())?; let current_token = open_process_token(unsafe { GetCurrentProcess() })?; let server_user = token_user(server_token.raw())?; let current_user = token_user(current_token.raw())?; if unsafe { EqualSid(server_user.sid()?, current_user.sid()?) } == 0 { return Err(io::Error::new( io::ErrorKind::PermissionDenied, "IDE context provider is not owned by the current user", )); } Ok(()) } fn open_process_token(process: HANDLE) -> io::Result { let mut token = 0; let result = unsafe { OpenProcessToken(process, TOKEN_QUERY, &mut token) }; if result == 0 { return Err(io::Error::last_os_error()); } Ok(OwnedHandle(token)) } fn token_user(token: HANDLE) -> io::Result { let mut return_length = 0; unsafe { GetTokenInformation(token, TokenUser, ptr::null_mut(), 0, &mut return_length); } if return_length == 0 { return Err(io::Error::last_os_error()); } let mut buffer = vec![0_u8; return_length as usize]; let result = unsafe { GetTokenInformation( token, TokenUser, buffer.as_mut_ptr() as *mut _, return_length, &mut return_length, ) }; if result == 0 { return Err(io::Error::last_os_error()); } Ok(TokenUserBuffer { buffer }) } fn remaining_timeout_ms(deadline: Instant) -> u32 { let now = Instant::now(); if now >= deadline { return 0; } let millis = deadline.duration_since(now).as_millis().max(1); u32::try_from(millis).unwrap_or(u32::MAX) } fn timeout_io_error() -> io::Error { io::Error::new(io::ErrorKind::TimedOut, "timed out waiting for IDE context") }