1use std::ffi::{OsStr, OsString};
4use std::fmt;
5use std::mem::{size_of, zeroed};
6use std::os::windows::ffi::OsStrExt;
7use std::path::Path;
8use std::ptr::NonNull;
9use std::time::{Duration, Instant};
10
11use native_ipc_core::layout::{RegionSetLayout, ValidatedRegionLayout, ValidationExpectations};
12use native_ipc_core::mapping::{
13 BindingError, ReadOnlyMapping, ReaderRegion, SoleWriterMapping, WriterRegion,
14};
15use windows_sys::Win32::Foundation::{
16 CloseHandle, DuplicateHandle, ERROR_NO_DATA, ERROR_PIPE_CONNECTED, ERROR_PIPE_LISTENING,
17 GENERIC_READ, GENERIC_WRITE, GetLastError, HANDLE, INVALID_HANDLE_VALUE, WAIT_OBJECT_0,
18 WAIT_TIMEOUT,
19};
20use windows_sys::Win32::Security::Cryptography::{
21 BCRYPT_USE_SYSTEM_PREFERRED_RNG, BCryptGenRandom,
22};
23use windows_sys::Win32::Storage::FileSystem::{
24 CreateFileW, FILE_FLAG_FIRST_PIPE_INSTANCE, OPEN_EXISTING, PIPE_ACCESS_DUPLEX, ReadFile,
25 WriteFile,
26};
27use windows_sys::Win32::System::JobObjects::{
28 AssignProcessToJobObject, CreateJobObjectW, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
29 JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobObjectExtendedLimitInformation,
30 SetInformationJobObject,
31};
32use windows_sys::Win32::System::Memory::{
33 CreateFileMappingW, FILE_MAP_READ, FILE_MAP_WRITE, MEMORY_MAPPED_VIEW_ADDRESS, MapViewOfFile,
34 PAGE_READWRITE, SEC_COMMIT, UnmapViewOfFile,
35};
36use windows_sys::Win32::System::Pipes::{
37 ConnectNamedPipe, CreateNamedPipeW, GetNamedPipeClientProcessId, GetNamedPipeServerProcessId,
38 PIPE_NOWAIT, PIPE_READMODE_MESSAGE, PIPE_REJECT_REMOTE_CLIENTS, PIPE_TYPE_MESSAGE,
39 SetNamedPipeHandleState,
40};
41use windows_sys::Win32::System::SystemInformation::{GetSystemInfo, SYSTEM_INFO};
42use windows_sys::Win32::System::Threading::{
43 CREATE_SUSPENDED, CREATE_UNICODE_ENVIRONMENT, CreateProcessW, GetCurrentProcess,
44 GetCurrentProcessId, GetExitCodeProcess, PROCESS_INFORMATION, ResumeThread, STARTUPINFOW,
45 TerminateProcess, WaitForSingleObject,
46};
47
48use crate::BackendStatus;
49
50#[derive(Debug)]
52pub enum WindowsError {
53 Os {
55 operation: &'static str,
57 code: u32,
59 },
60 InvalidSize(usize),
62 WrongPeer,
64 InvalidHandle,
66 Layout(native_ipc_core::layout::LayoutError),
68 Binding(BindingError),
70 InvalidBootstrap,
72 CapabilityAlreadyTransferred,
74 TimedOut(&'static str),
76 ChildExit(u32),
78}
79
80impl fmt::Display for WindowsError {
81 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
82 write!(formatter, "Windows transport failed: {self:?}")
83 }
84}
85impl std::error::Error for WindowsError {}
86impl From<native_ipc_core::layout::LayoutError> for WindowsError {
87 fn from(value: native_ipc_core::layout::LayoutError) -> Self {
88 Self::Layout(value)
89 }
90}
91impl From<BindingError> for WindowsError {
92 fn from(value: BindingError) -> Self {
93 Self::Binding(value)
94 }
95}
96
97pub fn session_nonce() -> Result<[u8; 32], WindowsError> {
99 let mut nonce = [0_u8; 32];
100 let status = unsafe {
102 BCryptGenRandom(
103 std::ptr::null_mut(),
104 nonce.as_mut_ptr(),
105 nonce.len() as u32,
106 BCRYPT_USE_SYSTEM_PREFERRED_RNG,
107 )
108 };
109 if status < 0 || nonce == [0; 32] {
110 Err(last_os("BCryptGenRandom"))
111 } else {
112 Ok(nonce)
113 }
114}
115
116pub unsafe fn authenticate_pipe_client(
122 pipe: HANDLE,
123 expected_pid: u32,
124) -> Result<(), WindowsError> {
125 let mut actual = 0;
126 if unsafe { GetNamedPipeClientProcessId(pipe, &mut actual) } == 0 {
128 return Err(last_os("GetNamedPipeClientProcessId"));
129 }
130 if actual == expected_pid {
131 Ok(())
132 } else {
133 Err(WindowsError::WrongPeer)
134 }
135}
136
137pub unsafe fn authenticate_pipe_server(
143 pipe: HANDLE,
144 expected_pid: u32,
145) -> Result<(), WindowsError> {
146 let mut actual = 0;
147 if unsafe { GetNamedPipeServerProcessId(pipe, &mut actual) } == 0 {
149 return Err(last_os("GetNamedPipeServerProcessId"));
150 }
151 if actual == expected_pid {
152 Ok(())
153 } else {
154 Err(WindowsError::WrongPeer)
155 }
156}
157
158pub struct QuiescentRegion {
160 section: OwnedHandle,
161 view: View,
162 logical_len: usize,
163}
164
165impl QuiescentRegion {
166 pub fn new(logical_len: usize) -> Result<Self, WindowsError> {
168 let len = page_align(logical_len)?;
169 let size = len as u64;
170 let section = unsafe {
172 CreateFileMappingW(
173 INVALID_HANDLE_VALUE,
174 std::ptr::null(),
175 PAGE_READWRITE | SEC_COMMIT,
176 (size >> 32) as u32,
177 size as u32,
178 std::ptr::null(),
179 )
180 };
181 let section = OwnedHandle::new(section)?;
182 let view = View::map(section.0, len, FILE_MAP_WRITE)?;
183 unsafe { std::slice::from_raw_parts_mut(view.base.as_ptr(), len) }.fill(0);
185 Ok(Self {
186 section,
187 view,
188 logical_len,
189 })
190 }
191 pub const fn len(&self) -> usize {
193 self.view.len
194 }
195 pub const fn is_empty(&self) -> bool {
197 false
198 }
199 pub const fn logical_len(&self) -> usize {
201 self.logical_len
202 }
203 pub fn as_bytes(&self) -> &[u8] {
205 unsafe { std::slice::from_raw_parts(self.view.base.as_ptr(), self.view.len) }
207 }
208 pub fn as_bytes_mut(&mut self) -> &mut [u8] {
210 unsafe { std::slice::from_raw_parts_mut(self.view.base.as_ptr(), self.view.len) }
212 }
213
214 pub fn prepare_local_writer(
216 self,
217 expected: ValidationExpectations,
218 topology: RegionSetLayout,
219 ) -> Result<PreparedLocalWriter, WindowsError> {
220 let layout =
222 unsafe { ValidatedRegionLayout::validate(self.as_bytes(), expected, &topology) }?;
223 Ok(PreparedLocalWriter {
224 section: self.section,
225 view: self.view,
226 layout,
227 topology,
228 })
229 }
230
231 pub fn prepare_remote_writer(
233 self,
234 expected: ValidationExpectations,
235 topology: RegionSetLayout,
236 ) -> Result<PreparedRemoteWriter, WindowsError> {
237 let layout =
239 unsafe { ValidatedRegionLayout::validate(self.as_bytes(), expected, &topology) }?;
240 let len = self.view.len;
241 drop(self.view);
242 let view = View::map(self.section.0, len, FILE_MAP_READ)?;
243 Ok(PreparedRemoteWriter {
244 section: self.section,
245 view,
246 layout,
247 topology,
248 writer_duplicated: false,
249 })
250 }
251}
252
253#[derive(Clone, Copy, Debug, Eq, PartialEq)]
255pub struct RemoteHandle(pub usize);
256
257pub struct PreparedLocalWriter {
259 section: OwnedHandle,
260 view: View,
261 layout: ValidatedRegionLayout,
262 topology: RegionSetLayout,
263}
264impl PreparedLocalWriter {
265 pub unsafe fn duplicate_reader_to(
271 &self,
272 target_process: HANDLE,
273 ) -> Result<RemoteHandle, WindowsError> {
274 duplicate_to(self.section.0, target_process, FILE_MAP_READ)
275 }
276 pub fn bind(self) -> Result<WriterRegion<WindowsWriterMapping>, WindowsError> {
278 drop(self.section);
279 Ok(WriterRegion::new(
280 WindowsWriterMapping { view: self.view },
281 self.layout,
282 self.topology,
283 )?)
284 }
285}
286
287pub struct PreparedRemoteWriter {
289 section: OwnedHandle,
290 view: View,
291 layout: ValidatedRegionLayout,
292 topology: RegionSetLayout,
293 writer_duplicated: bool,
294}
295impl PreparedRemoteWriter {
296 pub unsafe fn duplicate_writer_to(
302 &mut self,
303 target_process: HANDLE,
304 ) -> Result<RemoteHandle, WindowsError> {
305 if self.writer_duplicated {
306 return Err(WindowsError::CapabilityAlreadyTransferred);
307 }
308 let handle = duplicate_to(self.section.0, target_process, FILE_MAP_WRITE)?;
309 self.writer_duplicated = true;
310 Ok(handle)
311 }
312 pub fn bind_reader(self) -> Result<ReaderRegion<WindowsReaderMapping>, WindowsError> {
314 drop(self.section);
315 Ok(ReaderRegion::new(
316 WindowsReaderMapping { view: self.view },
317 self.layout,
318 self.topology,
319 )?)
320 }
321}
322
323pub unsafe fn import_reader(
330 handle: usize,
331 len: usize,
332 expected: ValidationExpectations,
333 topology: RegionSetLayout,
334) -> Result<ReaderRegion<WindowsReaderMapping>, WindowsError> {
335 let section = OwnedHandle::new(handle as HANDLE)?;
336 let view = View::map(section.0, len, FILE_MAP_READ)?;
337 let bytes = unsafe { std::slice::from_raw_parts(view.base.as_ptr(), len) };
339 let layout = unsafe { ValidatedRegionLayout::validate(bytes, expected, &topology) }?;
340 drop(section);
341 Ok(ReaderRegion::new(
342 WindowsReaderMapping { view },
343 layout,
344 topology,
345 )?)
346}
347
348pub unsafe fn import_writer(
355 handle: usize,
356 len: usize,
357 expected: ValidationExpectations,
358 topology: RegionSetLayout,
359) -> Result<WriterRegion<WindowsWriterMapping>, WindowsError> {
360 let section = OwnedHandle::new(handle as HANDLE)?;
361 let view = View::map(section.0, len, FILE_MAP_WRITE)?;
362 let bytes = unsafe { std::slice::from_raw_parts(view.base.as_ptr(), len) };
364 let layout = unsafe { ValidatedRegionLayout::validate(bytes, expected, &topology) }?;
365 drop(section);
366 Ok(WriterRegion::new(
367 WindowsWriterMapping { view },
368 layout,
369 topology,
370 )?)
371}
372
373pub struct ChildJob(OwnedHandle);
375impl ChildJob {
376 pub fn new() -> Result<Self, WindowsError> {
378 let handle = unsafe { CreateJobObjectW(std::ptr::null(), std::ptr::null()) };
380 let handle = OwnedHandle::new(handle)?;
381 let mut information: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { zeroed() };
382 information.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
383 if unsafe {
385 SetInformationJobObject(
386 handle.0,
387 JobObjectExtendedLimitInformation,
388 (&information as *const JOBOBJECT_EXTENDED_LIMIT_INFORMATION).cast(),
389 size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
390 )
391 } == 0
392 {
393 return Err(last_os("SetInformationJobObject"));
394 }
395 Ok(Self(handle))
396 }
397 pub unsafe fn assign_suspended(&self, process: HANDLE) -> Result<(), WindowsError> {
403 if unsafe { AssignProcessToJobObject(self.0.0, process) } == 0 {
405 Err(last_os("AssignProcessToJobObject"))
406 } else {
407 Ok(())
408 }
409 }
410}
411
412const PIPE_ENV: &str = "NATIVE_IPC_WINDOWS_PIPE";
413const NONCE_ENV: &str = "NATIVE_IPC_WINDOWS_NONCE";
414const PARENT_ENV: &str = "NATIVE_IPC_PARENT_PID";
415const BOOTSTRAP_MAGIC: [u8; 8] = *b"NIPCWIN1";
416const AUTH_MAGIC: [u8; 8] = *b"NIPCAUT1";
417const READY_MAGIC: [u8; 8] = *b"NIPCRDY1";
418const CAPABILITY_MAGIC: [u8; 8] = *b"NIPCCAP1";
419#[cfg(not(test))]
420const WAIT_MS: u32 = 10_000;
421#[cfg(test)]
422const WAIT_MS: u32 = 1_000;
423
424#[repr(C)]
425#[derive(Clone, Copy)]
426struct BootstrapFrame {
427 magic: [u8; 8],
428 nonce: [u8; 32],
429 parent_pid: u32,
430 child_pid: u32,
431}
432
433#[repr(C)]
434#[derive(Clone, Copy)]
435struct CapabilityFrame {
436 magic: [u8; 8],
437 reader_handle: usize,
438 writer_handle: usize,
439 reader_len: usize,
440 writer_len: usize,
441}
442
443pub struct ChildSession {
445 pipe: OwnedHandle,
446 process: OwnedHandle,
447 _job: ChildJob,
448 pid: u32,
449 nonce: [u8; 32],
450 reaped: bool,
451}
452
453impl ChildSession {
454 pub fn spawn(path: &Path, arguments: &[OsString]) -> Result<Self, WindowsError> {
456 let nonce = session_nonce()?;
457 let name = format!(r"\\.\pipe\native-ipc-{}", hex(&nonce));
458 let pipe_name = wide_null(OsStr::new(&name));
459 let pipe = unsafe {
461 CreateNamedPipeW(
462 pipe_name.as_ptr(),
463 PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE,
464 PIPE_TYPE_MESSAGE
465 | PIPE_READMODE_MESSAGE
466 | PIPE_NOWAIT
467 | PIPE_REJECT_REMOTE_CLIENTS,
468 1,
469 4096,
470 4096,
471 WAIT_MS,
472 std::ptr::null(),
473 )
474 };
475 let pipe = OwnedHandle::new(pipe)?;
476 let job = ChildJob::new()?;
477
478 let application = wide_null(path.as_os_str());
479 let mut command = command_line(path.as_os_str(), arguments);
480 let parent_pid = unsafe { GetCurrentProcessId() };
481 let environment = environment_block(&[
482 (PIPE_ENV, name),
483 (NONCE_ENV, hex(&nonce)),
484 (PARENT_ENV, parent_pid.to_string()),
485 ]);
486 let mut startup: STARTUPINFOW = unsafe { zeroed() };
487 startup.cb = size_of::<STARTUPINFOW>() as u32;
488 let mut information: PROCESS_INFORMATION = unsafe { zeroed() };
489 if unsafe {
491 CreateProcessW(
492 application.as_ptr(),
493 command.as_mut_ptr(),
494 std::ptr::null(),
495 std::ptr::null(),
496 0,
497 CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT,
498 environment.as_ptr().cast(),
499 std::ptr::null(),
500 &startup,
501 &mut information,
502 )
503 } == 0
504 {
505 return Err(last_os("CreateProcessW"));
506 }
507 let process = OwnedHandle::new(information.hProcess)?;
508 let thread = OwnedHandle::new(information.hThread)?;
509 if let Err(error) = unsafe { job.assign_suspended(process.0) } {
511 let _ = unsafe { TerminateProcess(process.0, 127) };
513 return Err(error);
514 }
515 if unsafe { ResumeThread(thread.0) } == u32::MAX {
517 let error = last_os("ResumeThread");
518 let _ = unsafe { TerminateProcess(process.0, 127) };
519 return Err(error);
520 }
521 drop(thread);
522 connect_pipe(pipe.0, process.0)?;
523 unsafe { authenticate_pipe_client(pipe.0, information.dwProcessId)? };
525 let hello = BootstrapFrame {
526 magic: BOOTSTRAP_MAGIC,
527 nonce,
528 parent_pid,
529 child_pid: information.dwProcessId,
530 };
531 write_frame(pipe.0, &hello)?;
532 let ready = read_frame(pipe.0)?;
533 if ready.magic != AUTH_MAGIC
534 || ready.nonce != nonce
535 || ready.parent_pid != parent_pid
536 || ready.child_pid != information.dwProcessId
537 {
538 let _ = unsafe { TerminateProcess(process.0, 127) };
539 return Err(WindowsError::InvalidBootstrap);
540 }
541 Ok(Self {
542 pipe,
543 process,
544 _job: job,
545 pid: information.dwProcessId,
546 nonce,
547 reaped: false,
548 })
549 }
550
551 pub const fn process_handle(&self) -> HANDLE {
553 self.process.0
554 }
555 pub const fn pid(&self) -> u32 {
557 self.pid
558 }
559 pub fn send_capabilities(
561 &self,
562 reader: RemoteHandle,
563 reader_len: usize,
564 writer: RemoteHandle,
565 writer_len: usize,
566 ) -> Result<(), WindowsError> {
567 write_pod(
568 self.pipe.0,
569 &CapabilityFrame {
570 magic: CAPABILITY_MAGIC,
571 reader_handle: reader.0,
572 writer_handle: writer.0,
573 reader_len,
574 writer_len,
575 },
576 )
577 }
578 pub fn wait_ready(&self) -> Result<(), WindowsError> {
580 let ready: BootstrapFrame = read_pod(self.pipe.0)?;
581 if ready.magic == READY_MAGIC
582 && ready.nonce == self.nonce
583 && ready.parent_pid == unsafe { GetCurrentProcessId() }
584 && ready.child_pid == self.pid
585 {
586 Ok(())
587 } else {
588 Err(WindowsError::InvalidBootstrap)
589 }
590 }
591 pub fn wait(mut self) -> Result<(), WindowsError> {
593 match unsafe { WaitForSingleObject(self.process.0, WAIT_MS) } {
595 WAIT_OBJECT_0 => {
596 let mut code = 0;
597 if unsafe { GetExitCodeProcess(self.process.0, &mut code) } == 0 {
599 return Err(last_os("GetExitCodeProcess"));
600 }
601 if code != 0 {
602 return Err(WindowsError::ChildExit(code));
603 }
604 }
605 WAIT_TIMEOUT => return Err(WindowsError::TimedOut("helper exit")),
606 _ => return Err(last_os("WaitForSingleObject")),
607 }
608 self.reaped = true;
609 Ok(())
610 }
611}
612
613impl Drop for ChildSession {
614 fn drop(&mut self) {
615 if !self.reaped {
616 let _ = unsafe { TerminateProcess(self.process.0, 127) };
618 let _ = unsafe { WaitForSingleObject(self.process.0, WAIT_MS) };
619 }
620 }
621}
622
623pub fn connect_spawned_helper() -> Result<ChildChannel, WindowsError> {
625 let name = std::env::var_os(PIPE_ENV).ok_or(WindowsError::InvalidBootstrap)?;
626 let nonce =
627 parse_nonce(&std::env::var(NONCE_ENV).map_err(|_| WindowsError::InvalidBootstrap)?)?;
628 let parent_pid = std::env::var(PARENT_ENV)
629 .map_err(|_| WindowsError::InvalidBootstrap)?
630 .parse::<u32>()
631 .map_err(|_| WindowsError::InvalidBootstrap)?;
632 let name = wide_null(&name);
633 let pipe = unsafe {
635 CreateFileW(
636 name.as_ptr(),
637 GENERIC_READ | GENERIC_WRITE,
638 0,
639 std::ptr::null(),
640 OPEN_EXISTING,
641 0,
642 std::ptr::null_mut(),
643 )
644 };
645 let pipe = OwnedHandle::new(pipe)?;
646 let mode = PIPE_READMODE_MESSAGE | PIPE_NOWAIT;
647 if unsafe { SetNamedPipeHandleState(pipe.0, &mode, std::ptr::null(), std::ptr::null()) } == 0 {
649 return Err(last_os("SetNamedPipeHandleState"));
650 }
651 unsafe { authenticate_pipe_server(pipe.0, parent_pid)? };
653 let hello = read_frame(pipe.0)?;
654 let child_pid = unsafe { GetCurrentProcessId() };
655 if hello.magic != BOOTSTRAP_MAGIC
656 || hello.nonce != nonce
657 || hello.parent_pid != parent_pid
658 || hello.child_pid != child_pid
659 {
660 return Err(WindowsError::InvalidBootstrap);
661 }
662 write_frame(
663 pipe.0,
664 &BootstrapFrame {
665 magic: AUTH_MAGIC,
666 ..hello
667 },
668 )?;
669 Ok(ChildChannel {
670 pipe,
671 parent_pid,
672 nonce,
673 })
674}
675
676pub struct ChildChannel {
678 pipe: OwnedHandle,
679 parent_pid: u32,
680 nonce: [u8; 32],
681}
682impl ChildChannel {
683 pub const fn parent_pid(&self) -> u32 {
685 self.parent_pid
686 }
687 pub const fn pipe_handle(&self) -> HANDLE {
689 self.pipe.0
690 }
691 pub fn receive_capabilities(
693 &self,
694 ) -> Result<(RemoteHandle, usize, RemoteHandle, usize), WindowsError> {
695 let frame: CapabilityFrame = read_pod(self.pipe.0)?;
696 if frame.magic != CAPABILITY_MAGIC
697 || frame.reader_handle == 0
698 || frame.writer_handle == 0
699 || frame.reader_len == 0
700 || frame.writer_len == 0
701 {
702 return Err(WindowsError::InvalidBootstrap);
703 }
704 Ok((
705 RemoteHandle(frame.reader_handle),
706 frame.reader_len,
707 RemoteHandle(frame.writer_handle),
708 frame.writer_len,
709 ))
710 }
711 pub fn signal_ready(&self) -> Result<(), WindowsError> {
713 write_pod(
714 self.pipe.0,
715 &BootstrapFrame {
716 magic: READY_MAGIC,
717 nonce: self.nonce,
718 parent_pid: self.parent_pid,
719 child_pid: unsafe { GetCurrentProcessId() },
720 },
721 )
722 }
723}
724
725pub struct WindowsWriterMapping {
727 view: View,
728}
729unsafe impl SoleWriterMapping for WindowsWriterMapping {
731 fn base(&self) -> NonNull<u8> {
732 self.view.base
733 }
734 fn len(&self) -> usize {
735 self.view.len
736 }
737}
738pub struct WindowsReaderMapping {
740 view: View,
741}
742unsafe impl ReadOnlyMapping for WindowsReaderMapping {
744 fn base(&self) -> NonNull<u8> {
745 self.view.base
746 }
747 fn len(&self) -> usize {
748 self.view.len
749 }
750}
751
752struct OwnedHandle(HANDLE);
753impl OwnedHandle {
754 fn new(handle: HANDLE) -> Result<Self, WindowsError> {
755 if handle.is_null() || handle == INVALID_HANDLE_VALUE {
756 Err(WindowsError::InvalidHandle)
757 } else {
758 Ok(Self(handle))
759 }
760 }
761}
762impl Drop for OwnedHandle {
763 fn drop(&mut self) {
764 let _ = unsafe { CloseHandle(self.0) };
766 }
767}
768
769struct View {
770 base: NonNull<u8>,
771 len: usize,
772}
773impl View {
774 fn map(section: HANDLE, len: usize, access: u32) -> Result<Self, WindowsError> {
775 let address = unsafe { MapViewOfFile(section, access, 0, 0, len) };
777 let base = NonNull::new(address.Value.cast()).ok_or_else(|| last_os("MapViewOfFile"))?;
778 Ok(Self { base, len })
779 }
780}
781impl Drop for View {
782 fn drop(&mut self) {
783 let _ = unsafe {
785 UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS {
786 Value: self.base.as_ptr().cast(),
787 })
788 };
789 }
790}
791
792fn duplicate_to(source: HANDLE, target: HANDLE, access: u32) -> Result<RemoteHandle, WindowsError> {
793 let mut remote: HANDLE = std::ptr::null_mut();
794 if unsafe {
796 DuplicateHandle(
797 GetCurrentProcess(),
798 source,
799 target,
800 &mut remote,
801 access,
802 0,
803 0,
804 )
805 } == 0
806 {
807 return Err(last_os("DuplicateHandle"));
808 }
809 if remote.is_null() {
810 Err(WindowsError::InvalidHandle)
811 } else {
812 Ok(RemoteHandle(remote as usize))
813 }
814}
815
816fn page_align(size: usize) -> Result<usize, WindowsError> {
817 if size == 0 {
818 return Err(WindowsError::InvalidSize(size));
819 }
820 let mut information: SYSTEM_INFO = unsafe { zeroed() };
821 unsafe { GetSystemInfo(&mut information) };
823 let page = information.dwPageSize as usize;
824 if page == 0 || !page.is_power_of_two() {
825 return Err(WindowsError::InvalidSize(size));
826 }
827 size.checked_add(page - 1)
828 .map(|value| value & !(page - 1))
829 .filter(|value| *value <= isize::MAX as usize)
830 .ok_or(WindowsError::InvalidSize(size))
831}
832
833fn wide_null(value: &OsStr) -> Vec<u16> {
834 value.encode_wide().chain(std::iter::once(0)).collect()
835}
836
837fn quote_argument(value: &OsStr, output: &mut Vec<u16>) {
838 let units: Vec<u16> = value.encode_wide().collect();
839 let needs_quotes = units.is_empty()
840 || units
841 .iter()
842 .any(|unit| *unit == b' ' as u16 || *unit == b'\t' as u16);
843 if !needs_quotes {
844 output.extend(units);
845 return;
846 }
847 output.push(b'"' as u16);
848 let mut slashes = 0;
849 for unit in units {
850 if unit == b'\\' as u16 {
851 slashes += 1;
852 } else if unit == b'"' as u16 {
853 output.extend(std::iter::repeat_n(b'\\' as u16, slashes * 2 + 1));
854 output.push(unit);
855 slashes = 0;
856 } else {
857 output.extend(std::iter::repeat_n(b'\\' as u16, slashes));
858 output.push(unit);
859 slashes = 0;
860 }
861 }
862 output.extend(std::iter::repeat_n(b'\\' as u16, slashes * 2));
863 output.push(b'"' as u16);
864}
865
866fn command_line(path: &OsStr, arguments: &[OsString]) -> Vec<u16> {
867 let mut result = Vec::new();
868 quote_argument(path, &mut result);
869 for argument in arguments {
870 result.push(b' ' as u16);
871 quote_argument(argument, &mut result);
872 }
873 result.push(0);
874 result
875}
876
877fn environment_block(overrides: &[(&str, String)]) -> Vec<u16> {
878 let mut values: Vec<(OsString, OsString)> = std::env::vars_os()
879 .filter(|(key, _)| {
880 !overrides
881 .iter()
882 .any(|(name, _)| key.to_string_lossy().eq_ignore_ascii_case(name))
883 })
884 .collect();
885 values.extend(
886 overrides
887 .iter()
888 .map(|(key, value)| (OsString::from(key), OsString::from(value))),
889 );
890 values.sort_by(|left, right| {
891 left.0
892 .to_string_lossy()
893 .to_ascii_lowercase()
894 .cmp(&right.0.to_string_lossy().to_ascii_lowercase())
895 });
896 let mut block = Vec::new();
897 for (key, value) in values {
898 block.extend(key.encode_wide());
899 block.push(b'=' as u16);
900 block.extend(value.encode_wide());
901 block.push(0);
902 }
903 block.push(0);
904 block
905}
906
907fn pod_bytes<T>(value: &T) -> &[u8] {
908 unsafe { std::slice::from_raw_parts((value as *const T).cast(), size_of::<T>()) }
910}
911
912fn write_frame(pipe: HANDLE, frame: &BootstrapFrame) -> Result<(), WindowsError> {
913 write_pod(pipe, frame)
914}
915
916fn write_pod<T>(pipe: HANDLE, value: &T) -> Result<(), WindowsError> {
917 let bytes = pod_bytes(value);
918 let deadline = Instant::now() + Duration::from_millis(WAIT_MS.into());
919 loop {
920 let mut written = 0;
921 if unsafe {
923 WriteFile(
924 pipe,
925 bytes.as_ptr(),
926 bytes.len() as u32,
927 &mut written,
928 std::ptr::null_mut(),
929 )
930 } != 0
931 {
932 return if written as usize == bytes.len() {
933 Ok(())
934 } else {
935 Err(WindowsError::InvalidBootstrap)
936 };
937 }
938 let code = unsafe { GetLastError() };
939 if code != ERROR_NO_DATA && code != ERROR_PIPE_LISTENING {
940 return Err(WindowsError::Os {
941 operation: "WriteFile",
942 code,
943 });
944 }
945 wait_retry(deadline, "pipe write")?;
946 }
947}
948
949fn read_frame(pipe: HANDLE) -> Result<BootstrapFrame, WindowsError> {
950 read_pod(pipe)
951}
952
953fn read_pod<T>(pipe: HANDLE) -> Result<T, WindowsError> {
954 let deadline = Instant::now() + Duration::from_millis(WAIT_MS.into());
955 loop {
956 let mut value: T = unsafe { zeroed() };
957 let mut read = 0;
958 if unsafe {
960 ReadFile(
961 pipe,
962 (&mut value as *mut T).cast(),
963 size_of::<T>() as u32,
964 &mut read,
965 std::ptr::null_mut(),
966 )
967 } != 0
968 {
969 return if read as usize == size_of::<T>() {
970 Ok(value)
971 } else {
972 Err(WindowsError::InvalidBootstrap)
973 };
974 }
975 let code = unsafe { GetLastError() };
976 if code != ERROR_NO_DATA && code != ERROR_PIPE_LISTENING {
977 return Err(WindowsError::Os {
978 operation: "ReadFile",
979 code,
980 });
981 }
982 wait_retry(deadline, "pipe read")?;
983 }
984}
985
986fn connect_pipe(pipe: HANDLE, process: HANDLE) -> Result<(), WindowsError> {
987 let deadline = Instant::now() + Duration::from_millis(WAIT_MS.into());
988 loop {
989 if unsafe { ConnectNamedPipe(pipe, std::ptr::null_mut()) } != 0 {
991 return Ok(());
992 }
993 let code = unsafe { GetLastError() };
994 if code == ERROR_PIPE_CONNECTED {
995 return Ok(());
996 }
997 if code != ERROR_PIPE_LISTENING && code != ERROR_NO_DATA {
998 return Err(WindowsError::Os {
999 operation: "ConnectNamedPipe",
1000 code,
1001 });
1002 }
1003 if unsafe { WaitForSingleObject(process, 0) } == WAIT_OBJECT_0 {
1005 let mut exit = 0;
1006 if unsafe { GetExitCodeProcess(process, &mut exit) } != 0 {
1008 return Err(WindowsError::ChildExit(exit));
1009 }
1010 return Err(last_os("GetExitCodeProcess"));
1011 }
1012 wait_retry(deadline, "pipe connect")?;
1013 }
1014}
1015
1016fn wait_retry(deadline: Instant, operation: &'static str) -> Result<(), WindowsError> {
1017 if Instant::now() >= deadline {
1018 Err(WindowsError::TimedOut(operation))
1019 } else {
1020 std::thread::sleep(Duration::from_millis(1));
1021 Ok(())
1022 }
1023}
1024
1025fn hex(bytes: &[u8; 32]) -> String {
1026 let mut output = String::with_capacity(64);
1027 for byte in bytes {
1028 use std::fmt::Write as _;
1029 write!(&mut output, "{byte:02x}").expect("String writes are infallible");
1030 }
1031 output
1032}
1033
1034fn parse_nonce(value: &str) -> Result<[u8; 32], WindowsError> {
1035 if value.len() != 64 {
1036 return Err(WindowsError::InvalidBootstrap);
1037 }
1038 let mut nonce = [0; 32];
1039 for (index, output) in nonce.iter_mut().enumerate() {
1040 *output = u8::from_str_radix(&value[index * 2..index * 2 + 2], 16)
1041 .map_err(|_| WindowsError::InvalidBootstrap)?;
1042 }
1043 if nonce == [0; 32] {
1044 return Err(WindowsError::InvalidBootstrap);
1045 }
1046 Ok(nonce)
1047}
1048
1049fn last_os(operation: &'static str) -> WindowsError {
1050 WindowsError::Os {
1052 operation,
1053 code: unsafe { GetLastError() },
1054 }
1055}
1056
1057pub const fn status() -> BackendStatus {
1059 BackendStatus::Available
1060}
1061
1062#[cfg(test)]
1063mod tests {
1064 use super::*;
1065 use native_ipc_core::layout::{
1066 AcknowledgementRouteSpec, Endpoint, LayoutLimits, RegionSpec, RoleId,
1067 };
1068 use std::time::Duration;
1069 use windows_sys::Win32::System::Memory::{PAGE_EXECUTE_READWRITE, VirtualProtect};
1070
1071 fn topology() -> (RegionSetLayout, RoleId, RoleId) {
1072 let producer = RoleId::new(1).unwrap();
1073 let peer = RoleId::new(2).unwrap();
1074 let specs = [
1075 RegionSpec {
1076 role: producer,
1077 writer: Endpoint::Initiator,
1078 slot_count: 1,
1079 payload_bytes: 32,
1080 acknowledgement_count: 1,
1081 },
1082 RegionSpec {
1083 role: peer,
1084 writer: Endpoint::Responder,
1085 slot_count: 1,
1086 payload_bytes: 32,
1087 acknowledgement_count: 1,
1088 },
1089 ];
1090 let routes = [
1091 AcknowledgementRouteSpec {
1092 owner: peer,
1093 target: producer,
1094 slot_index: 0,
1095 cell_index: 0,
1096 },
1097 AcknowledgementRouteSpec {
1098 owner: producer,
1099 target: peer,
1100 slot_index: 0,
1101 cell_index: 0,
1102 },
1103 ];
1104 let topology = RegionSetLayout::calculate(
1105 [7; 32],
1106 23,
1107 &specs,
1108 &routes,
1109 LayoutLimits {
1110 maximum_mapping_size: 1 << 20,
1111 maximum_slot_count: 2,
1112 maximum_acknowledgement_count: 2,
1113 maximum_payload_bytes: 64,
1114 },
1115 )
1116 .unwrap();
1117 (topology, producer, peer)
1118 }
1119
1120 fn expected(topology: &RegionSetLayout, role: RoleId, len: usize) -> ValidationExpectations {
1121 let region = topology.region(role).unwrap();
1122 ValidationExpectations {
1123 schema_id: [7; 32],
1124 generation: 23,
1125 role,
1126 writer: region.writer(),
1127 maximum_mapping_size: len as u64,
1128 }
1129 }
1130
1131 #[test]
1132 fn nonce_is_nonzero_and_job_is_constructible() {
1133 assert_ne!(session_nonce().unwrap(), [0; 32]);
1134 let _job = ChildJob::new().unwrap();
1135 }
1136
1137 #[test]
1138 fn unnamed_section_is_page_rounded_and_zeroed() {
1139 let region = QuiescentRegion::new(37).unwrap();
1140 assert!(region.len() >= 37);
1141 assert!(region.as_bytes().iter().all(|byte| *byte == 0));
1142 let mut prior = 0;
1143 assert_eq!(
1145 unsafe {
1146 VirtualProtect(
1147 region.view.base.as_ptr().cast(),
1148 region.len(),
1149 PAGE_EXECUTE_READWRITE,
1150 &mut prior,
1151 )
1152 },
1153 0
1154 );
1155 }
1156
1157 #[test]
1158 fn read_only_duplicate_rejects_writable_mapping() {
1159 let region = QuiescentRegion::new(4096).unwrap();
1160 let duplicate = duplicate_to(
1161 region.section.0,
1162 unsafe { GetCurrentProcess() },
1163 FILE_MAP_READ,
1164 )
1165 .unwrap();
1166 let duplicate = OwnedHandle::new(duplicate.0 as HANDLE).unwrap();
1167 let denied = unsafe { MapViewOfFile(duplicate.0, FILE_MAP_WRITE, 0, 0, region.len()) };
1169 assert!(denied.Value.is_null());
1170 }
1171
1172 #[test]
1173 fn spawned_helper_is_pid_authenticated_and_job_owned() {
1174 let (topology, producer, peer) = topology();
1175 let producer_layout = topology.region(producer).unwrap();
1176 let mut producer_region =
1177 QuiescentRegion::new(producer_layout.total_size() as usize).unwrap();
1178 producer_layout
1179 .encode_into(producer_region.as_bytes_mut())
1180 .unwrap();
1181 let producer_expected = expected(&topology, producer, producer_region.len());
1182 let prepared_producer = producer_region
1183 .prepare_local_writer(producer_expected, topology.clone())
1184 .unwrap();
1185 let peer_layout = topology.region(peer).unwrap();
1186 let mut peer_region = QuiescentRegion::new(peer_layout.total_size() as usize).unwrap();
1187 peer_layout.encode_into(peer_region.as_bytes_mut()).unwrap();
1188 let peer_expected = expected(&topology, peer, peer_region.len());
1189 let mut prepared_peer = peer_region
1190 .prepare_remote_writer(peer_expected, topology.clone())
1191 .unwrap();
1192 let executable = std::env::current_exe().unwrap();
1193 let arguments = [
1194 OsString::from("--exact"),
1195 OsString::from("windows::tests::spawned_helper_entry"),
1196 OsString::from("--ignored"),
1197 OsString::from("--nocapture"),
1198 ];
1199 let child = ChildSession::spawn(&executable, &arguments).unwrap();
1200 assert_ne!(child.pid(), unsafe { GetCurrentProcessId() });
1201 let reader_handle = unsafe {
1203 prepared_producer
1204 .duplicate_reader_to(child.process_handle())
1205 .unwrap()
1206 };
1207 let writer_handle = unsafe {
1209 prepared_peer
1210 .duplicate_writer_to(child.process_handle())
1211 .unwrap()
1212 };
1213 assert!(matches!(
1214 unsafe { prepared_peer.duplicate_writer_to(child.process_handle()) },
1217 Err(WindowsError::CapabilityAlreadyTransferred)
1218 ));
1219 child
1220 .send_capabilities(
1221 reader_handle,
1222 prepared_producer.view.len,
1223 writer_handle,
1224 prepared_peer.view.len,
1225 )
1226 .unwrap();
1227 let mut writer = prepared_producer.bind().unwrap();
1228 let reader = prepared_peer.bind_reader().unwrap();
1229 child.wait_ready().unwrap();
1230 writer
1231 .publish(0, 1, None, b"cross-process-windows")
1232 .unwrap();
1233 for _ in 0..10_000 {
1234 if let Ok(payload) = reader.copy_payload(0, 1) {
1235 assert_eq!(payload, b"child-windows-writer");
1236 child.wait().unwrap();
1237 return;
1238 }
1239 std::thread::sleep(Duration::from_millis(1));
1240 }
1241 panic!("child never published payload");
1242 }
1243
1244 #[test]
1245 fn helper_exit_before_connect_is_bounded() {
1246 let executable = std::env::current_exe().unwrap();
1247 let arguments = [
1248 OsString::from("--exact"),
1249 OsString::from("windows::tests::exit_before_connect_entry"),
1250 OsString::from("--ignored"),
1251 ];
1252 let result = ChildSession::spawn(&executable, &arguments);
1253 assert!(matches!(
1254 result,
1255 Err(WindowsError::ChildExit(0) | WindowsError::Os { .. })
1256 ));
1257 }
1258
1259 #[test]
1260 fn helper_stall_before_auth_is_bounded() {
1261 let executable = std::env::current_exe().unwrap();
1262 let arguments = [
1263 OsString::from("--exact"),
1264 OsString::from("windows::tests::stall_before_auth_entry"),
1265 OsString::from("--ignored"),
1266 ];
1267 let result = ChildSession::spawn(&executable, &arguments);
1268 assert!(matches!(result, Err(WindowsError::TimedOut("pipe read"))));
1269 }
1270
1271 #[test]
1272 #[ignore = "spawned only by the exact lifecycle test"]
1273 fn spawned_helper_entry() {
1274 let (topology, producer, peer) = topology();
1275 let channel = connect_spawned_helper().unwrap();
1276 assert_ne!(channel.parent_pid(), unsafe { GetCurrentProcessId() });
1277 let (reader_handle, reader_len, writer_handle, writer_len) =
1278 channel.receive_capabilities().unwrap();
1279 let reader = unsafe {
1281 import_reader(
1282 reader_handle.0,
1283 reader_len,
1284 expected(&topology, producer, reader_len),
1285 topology.clone(),
1286 )
1287 .unwrap()
1288 };
1289 let mut writer = unsafe {
1291 import_writer(
1292 writer_handle.0,
1293 writer_len,
1294 expected(&topology, peer, writer_len),
1295 topology,
1296 )
1297 .unwrap()
1298 };
1299 channel.signal_ready().unwrap();
1300 for _ in 0..10_000 {
1301 if let Ok(payload) = reader.copy_payload(0, 1) {
1302 assert_eq!(payload, b"cross-process-windows");
1303 writer.publish(0, 1, None, b"child-windows-writer").unwrap();
1304 return;
1305 }
1306 std::thread::sleep(Duration::from_millis(1));
1307 }
1308 panic!("parent never published payload");
1309 }
1310
1311 #[test]
1312 #[ignore = "spawned only by the exit-before-connect lifecycle test"]
1313 fn exit_before_connect_entry() {}
1314
1315 #[test]
1316 #[ignore = "spawned only by the stalled-auth lifecycle test"]
1317 fn stall_before_auth_entry() {
1318 let name = wide_null(&std::env::var_os(PIPE_ENV).unwrap());
1319 let pipe = unsafe {
1321 CreateFileW(
1322 name.as_ptr(),
1323 GENERIC_READ | GENERIC_WRITE,
1324 0,
1325 std::ptr::null(),
1326 OPEN_EXISTING,
1327 0,
1328 std::ptr::null_mut(),
1329 )
1330 };
1331 let _pipe = OwnedHandle::new(pipe).unwrap();
1332 std::thread::sleep(Duration::from_secs(5));
1333 }
1334}