/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::{Pid, IO_TIMEOUT}; use std::{ ffi::CString, mem::{zeroed, MaybeUninit}, os::windows::io::{ AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, OwnedHandle, RawHandle, }, ptr::{null, null_mut}, rc::Rc, }; use thiserror::Error; use windows_sys::Win32::{ Foundation::{ GetLastError, ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_NOT_FOUND, ERROR_PIPE_CONNECTED, FALSE, HANDLE, WAIT_TIMEOUT, WIN32_ERROR, }, Storage::FileSystem::{ReadFile, WriteFile}, System::{ Pipes::ConnectNamedPipe, Threading::{CreateEventA, ResetEvent, SetEvent, INFINITE}, IO::{CancelIoEx, GetOverlappedResultEx, OVERLAPPED}, }, }; pub(crate) const CHILD_RENDEZVOUS_ANCILLARY_DATA_LEN: usize = 0; pub type ProcessHandle = OwnedHandle; #[derive(Error, Debug)] pub enum PlatformError { #[error("Could not accept incoming connection: {0}")] AcceptFailed(WIN32_ERROR), #[error("Broken pipe")] BrokenPipe, #[error("Failed to duplicate handle: {0}")] DuplicateHandleFailed(WIN32_ERROR), #[error("Could not create event: {0}")] CreateEventFailed(WIN32_ERROR), #[error("Could not create or add an I/O completion port: {0}")] CreateIoCompletionPortFailed(WIN32_ERROR), #[error("Could not create a pipe: {0}")] CreatePipeFailure(WIN32_ERROR), #[error("Malformed string cannot be converted")] InvalidString, #[error("I/O error: {0}")] IOError(WIN32_ERROR), #[error("No process handle specified")] MissingProcessHandle, #[error("Could not listen for incoming connections: {0}")] ListenFailed(WIN32_ERROR), #[error("Could not parse HANDLE from string")] ParseHandle, #[error("Receiving {expected} bytes failed, only {received} bytes received")] ReceiveTooShort { expected: usize, received: usize }, #[error("Could not reset event: {0}")] ResetEventFailed(WIN32_ERROR), #[error("Sending {expected} bytes failed, only {sent} bytes sent")] SendTooShort { expected: usize, sent: usize }, #[error("Could not set event: {0}")] SetEventFailed(WIN32_ERROR), #[error("Could not set pipe state in message mode: {0}")] SetNamedPipeHandleState(WIN32_ERROR), #[error("Value exceeds a 32-bit integer")] ValueTooLarge, #[error("Waiting for pipe failed: {0}")] WaitNamedPipeFailed(WIN32_ERROR), } pub(crate) fn get_last_error() -> WIN32_ERROR { // SAFETY: This is always safe to call unsafe { GetLastError() } } pub fn server_addr(pid: Pid) -> CString { // We'll be passing this to CreateNamedPipeA() so we nul-terminate it. CString::new(format!("\\\\.\\pipe\\gecko-crash-helper-pipe.{pid:}")).unwrap() } pub(crate) fn create_manual_reset_event() -> Result { // SAFETY: We pass null pointers for all the pointer arguments. let raw_handle = unsafe { CreateEventA( /* lpEventAttributes */ null(), /* bManualReset */ FALSE, /* bInitialState */ FALSE, /* lpName */ null(), ) } as RawHandle; if raw_handle.is_null() { return Err(PlatformError::CreateEventFailed(get_last_error())); } // SAFETY: We just verified that `raw_handle` is valid. Ok(unsafe { OwnedHandle::from_raw_handle(raw_handle) }) } fn set_event(handle: BorrowedHandle) -> Result<(), PlatformError> { // SAFETY: This is always safe, even when passing an invalid handle. if unsafe { SetEvent(handle.as_raw_handle() as HANDLE) } == FALSE { Err(PlatformError::SetEventFailed(get_last_error())) } else { Ok(()) } } fn reset_event(handle: BorrowedHandle) -> Result<(), PlatformError> { // SAFETY: This is always safe, even when passing an invalid handle. if unsafe { ResetEvent(handle.as_raw_handle() as HANDLE) } == FALSE { Err(PlatformError::ResetEventFailed(get_last_error())) } else { Ok(()) } } fn cancel_overlapped_io(handle: BorrowedHandle, overlapped: &OVERLAPPED) -> bool { // SAFETY: the pointer to the overlapped structure is always valid as the // structure is passed by reference. The handle should be valid but will // be handled properly in case it isn't. let res = unsafe { CancelIoEx(handle.as_raw_handle() as HANDLE, overlapped) }; if res == FALSE { if get_last_error() == ERROR_NOT_FOUND { // There was no pending operation return true; } return false; } if overlapped.hEvent == 0 { // No associated event, don't wait return true; } // Just wait for the operation to finish, we don't care about the result let mut number_of_bytes_transferred = MaybeUninit::::uninit(); // SAFETY: Same as above let res = unsafe { GetOverlappedResultEx( handle.as_raw_handle() as HANDLE, overlapped, number_of_bytes_transferred.as_mut_ptr(), INFINITE, /* bAlertable */ FALSE, ) }; res != FALSE } pub(crate) struct OverlappedOperation { handle: Rc, overlapped: Option>, buffer: Option>, } enum OverlappedOperationType { Read, Write, } impl OverlappedOperation { // Asynchronously listen for an incoming connection pub(crate) fn listen(handle: &Rc) -> Result { let mut overlapped = Self::overlapped(); // SAFETY: We guarantee that the handle and OVERLAPPED object are both // valid and remain so while used by this function. let res = unsafe { ConnectNamedPipe(handle.as_raw_handle() as HANDLE, overlapped.as_mut()) }; let error = get_last_error(); if res != FALSE { // According to Microsoft's documentation this should never happen, // we check out of an abundance of caution. return Err(PlatformError::ListenFailed(error)); } match error { ERROR_PIPE_CONNECTED | ERROR_IO_PENDING => { // The operations succeeded, we'll get a completion event } error => return Err(PlatformError::ListenFailed(error)), }; Ok(OverlappedOperation { handle: handle.clone(), overlapped: Some(overlapped), buffer: None, }) } // Synchronously accept an incoming connection, does not wait and fails if // no incoming connection is present. pub(crate) fn accept(mut self) -> Result<(), PlatformError> { let overlapped = self.overlapped.take().unwrap(); let mut number_of_bytes_transferred = MaybeUninit::::uninit(); // SAFETY: The pointer to the OVERLAPPED structure is under our // control and thus guaranteed to be valid. let res = unsafe { GetOverlappedResultEx( self.handle.as_raw_handle() as HANDLE, overlapped.as_ref(), number_of_bytes_transferred.as_mut_ptr(), 0, /* bAlertable */ FALSE, ) }; if res == FALSE { return Err(PlatformError::AcceptFailed(get_last_error())); } Ok(()) } fn await_io( mut self, optype: OverlappedOperationType, ) -> Result>, PlatformError> { let overlapped = self.overlapped.take().unwrap(); let buffer = self.buffer.take().unwrap(); let mut number_of_bytes_transferred = MaybeUninit::::uninit(); // SAFETY: All the pointers passed to this call are under our control // and thus guaranteed to be valid. let res = unsafe { GetOverlappedResultEx( self.handle.as_raw_handle() as HANDLE, overlapped.as_ref(), number_of_bytes_transferred.as_mut_ptr(), IO_TIMEOUT as u32, /* bAlertable */ FALSE, ) }; if res == FALSE { let error = get_last_error(); if error == WAIT_TIMEOUT { // The I/O operation did not complete yet self.cancel_or_leak(overlapped, Some(buffer)); } else if error == ERROR_BROKEN_PIPE { return Err(PlatformError::BrokenPipe); } return Err(PlatformError::IOError(error)); } // SAFETY: We've verified that `number_of_bytes_transferred` has been // populated by the `GetOverlappedResultEx()` call. let number_of_bytes_transferred = unsafe { number_of_bytes_transferred.assume_init() }; if number_of_bytes_transferred as usize != buffer.len() { return Err(match optype { OverlappedOperationType::Read => PlatformError::ReceiveTooShort { expected: buffer.len(), received: number_of_bytes_transferred as usize, }, OverlappedOperationType::Write => PlatformError::SendTooShort { expected: buffer.len(), sent: number_of_bytes_transferred as usize, }, }); } Ok(match optype { OverlappedOperationType::Read => Some(buffer), OverlappedOperationType::Write => None, }) } fn sched_recv_internal( handle: &Rc, event: Option, expected_size: usize, ) -> Result { let mut overlapped = if let Some(event) = event { OverlappedOperation::overlapped_with_event(event)? } else { OverlappedOperation::overlapped() }; let mut buffer = vec![0u8; expected_size]; let number_of_bytes_to_read: u32 = expected_size .try_into() .map_err(|_e| PlatformError::ValueTooLarge)?; // SAFETY: We control all the pointers going into this call, guarantee // that they're valid and that they will be alive for the entire // duration of the asynchronous operation. let res = unsafe { ReadFile( handle.as_raw_handle() as HANDLE, buffer.as_mut_ptr(), number_of_bytes_to_read, null_mut(), overlapped.as_mut(), ) }; let error = get_last_error(); if res != FALSE { if let Some(event) = event { // The operation completed synchronously, if we have an event // set it so that waiting on it will return immediately. set_event(event)?; } } else if error == ERROR_BROKEN_PIPE { return Err(PlatformError::BrokenPipe); } else if error != ERROR_IO_PENDING { return Err(PlatformError::IOError(error)); } Ok(OverlappedOperation { handle: handle.clone(), overlapped: Some(overlapped), buffer: Some(buffer), }) } pub(crate) fn recv( handle: &Rc, event: BorrowedHandle<'_>, expected_size: usize, ) -> Result, PlatformError> { let overlapped = Self::sched_recv_internal(handle, Some(event), expected_size)?; overlapped .await_io(OverlappedOperationType::Read) .map(|buffer| buffer.unwrap()) } pub(crate) fn sched_recv( handle: &Rc, expected_size: usize, ) -> Result { Self::sched_recv_internal(handle, None, expected_size) } pub(crate) fn collect_recv(mut self) -> Vec { self.buffer.take().expect("Missing receive buffer") } pub(crate) fn send( handle: &Rc, event: BorrowedHandle<'_>, mut buffer: Vec, ) -> Result<(), PlatformError> { let mut overlapped = Self::overlapped_with_event(event)?; let number_of_bytes_to_write: u32 = buffer .len() .try_into() .map_err(|_e| PlatformError::ValueTooLarge)?; // SAFETY: We control all the pointers going into this call, guarantee // that they're valid and that they will be alive for the entire // duration of the asynchronous operation. let res = unsafe { WriteFile( handle.as_raw_handle() as HANDLE, buffer.as_mut_ptr(), number_of_bytes_to_write, null_mut(), overlapped.as_mut(), ) }; let error = get_last_error(); if res != FALSE { // The operation completed synchronously, set the event so that // waiting on it will return immediately. set_event(event)?; } else if error == ERROR_BROKEN_PIPE { return Err(PlatformError::BrokenPipe); } else if error != ERROR_IO_PENDING { return Err(PlatformError::IOError(error)); } let overlapped = OverlappedOperation { handle: handle.clone(), overlapped: Some(overlapped), buffer: Some(buffer), }; overlapped .await_io(OverlappedOperationType::Write) .map(|buffer| { debug_assert!(buffer.is_none()); }) } fn overlapped_with_event(event: BorrowedHandle<'_>) -> Result, PlatformError> { reset_event(event)?; // We set the last bit of the `hEvent` field to prevent this overlapped // operation from generating completion events. The event handle will // be notified instead when it completes. Ok(Box::new(OVERLAPPED { hEvent: event.as_raw_handle() as HANDLE | 1, ..unsafe { zeroed() } })) } fn overlapped() -> Box { Box::new(unsafe { zeroed() }) } /// Cancel the pending operation but leave the buffers intact. It's the /// caller's responsibility to wait for the operation to complete and free /// the buffers. pub(crate) fn cancel(&self) -> bool { if let Some(overlapped) = self.overlapped.as_deref() { return cancel_overlapped_io(self.handle.as_handle(), overlapped); } true } /// Leak the buffers involved in the operation. pub(crate) fn leak(&mut self) { if let Some(overlapped) = self.overlapped.take() { Box::leak(overlapped); if let Some(buffer) = self.buffer.take() { buffer.leak(); } } } fn cancel_or_leak(&self, mut overlapped: Box, buffer: Option>) { if !cancel_overlapped_io(self.handle.as_handle(), overlapped.as_mut()) { // If we cannot cancel the operation we must leak the // associated buffers so that they're available in case it // ever completes. Box::leak(overlapped); if let Some(buffer) = buffer { buffer.leak(); } } } } impl Drop for OverlappedOperation { fn drop(&mut self) { let overlapped = self.overlapped.take(); let buffer = self.buffer.take(); if let Some(overlapped) = overlapped { if overlapped.hEvent == 0 { return; // This operation should have already been cancelled. } self.cancel_or_leak(overlapped, buffer); } } }