detcore/syscalls/io.rs
1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! System calls dealing with IO and networking.
10//!
11//! Of course this overlaps somewhat with "files.rs".
12
13use std::net::Ipv4Addr;
14use std::net::Ipv6Addr;
15use std::os::unix::io::RawFd;
16use std::time::Duration;
17// Only the kernel-run `poll` restart reads the host clock, and only x86_64
18// has `poll`.
19#[cfg(target_arch = "x86_64")]
20use std::time::Instant;
21
22use nix::fcntl::OFlag;
23use reverie::Errno;
24use reverie::Error;
25use reverie::Guest;
26use reverie::Stack;
27use reverie::syscalls;
28use reverie::syscalls::Addr;
29use reverie::syscalls::AddrMut;
30use reverie::syscalls::Displayable;
31use reverie::syscalls::MemoryAccess;
32use reverie::syscalls::Syscall;
33use reverie::syscalls::SyscallArgs;
34use reverie::syscalls::SyscallInfo;
35use reverie::syscalls::Sysno;
36use reverie::syscalls::Timespec;
37use reverie::syscalls::linux_abi::Cmsghdr;
38use reverie::syscalls::linux_abi::Mmsghdr;
39use reverie::syscalls::linux_abi::Msghdr;
40#[cfg(target_arch = "x86_64")]
41use tracing::debug;
42use tracing::trace;
43
44use crate::config::SchedHeuristic;
45use crate::fd::FdType;
46use crate::record_or_replay::RecordOrReplay;
47use crate::resources::LOOPBACK_POLL_YIELD_FYI;
48use crate::resources::Permission;
49use crate::resources::ResourceID;
50use crate::resources::Resources;
51use crate::scheduler::runqueue::FIRST_PRIORITY;
52use crate::syscalls::files::SocketRole;
53use crate::syscalls::helpers::GuestHeldPollProof;
54#[cfg(target_arch = "x86_64")]
55use crate::syscalls::helpers::KernelPollDeadline;
56use crate::syscalls::helpers::KernelSignalWait;
57use crate::syscalls::helpers::NonblockableSyscall;
58use crate::syscalls::helpers::PublishedWaitMask;
59use crate::syscalls::helpers::REAL_TIME_SIGNALS;
60#[cfg(target_arch = "x86_64")]
61use crate::syscalls::helpers::RestartCall;
62use crate::syscalls::helpers::act_on_pending_real_time;
63use crate::syscalls::helpers::guest_signal_state_task;
64use crate::syscalls::helpers::is_accepted_connection;
65#[cfg(target_arch = "x86_64")]
66use crate::syscalls::helpers::keep_restart_block;
67use crate::syscalls::helpers::millis_duration_to_absolute_timeout;
68use crate::syscalls::helpers::observe_under_mask;
69use crate::syscalls::helpers::pending_real_time_among;
70use crate::syscalls::helpers::poll_deadline_for;
71use crate::syscalls::helpers::poll_targets_guest_held;
72use crate::syscalls::helpers::publish_wait_mask;
73use crate::syscalls::helpers::record_retry_event;
74use crate::syscalls::helpers::refuse_held_signal_loss;
75use crate::syscalls::helpers::result_after_restore;
76use crate::syscalls::helpers::retry_blocking_wait_under_mask;
77use crate::syscalls::helpers::retry_nonblocking_syscall_with_timeout;
78use crate::syscalls::helpers::unblocked_by;
79use crate::syscalls::scm_rights::ReceiveGeometry;
80use crate::syscalls::scm_rights::ReceivedFrom;
81use crate::syscalls::scm_rights::SentRights;
82use crate::syscalls::scm_rights::received_descriptors;
83use crate::syscalls::signal::STACK_RED_ZONE;
84use crate::syscalls::signal::kernel_installed_signal_mask;
85use crate::syscalls::signal::read_kernel_sigset;
86use crate::syscalls::threads::KernelSigset;
87use crate::syscalls::threads::read_thread_signal_state;
88use crate::syscalls::threads::read_thread_wait_signal_state;
89use crate::tool_global::*;
90use crate::tool_local::Detcore;
91use crate::types::DetTid;
92use crate::types::LogicalTime;
93
94mod temporary_mask_forms;
95
96// Printing helper
97// TODO: this should be subsumed by better syscall printing.
98#[cfg(target_arch = "x86_64")]
99fn print_poll(call: &syscalls::Poll) {
100 let len = call.nfds();
101 debug!("POLL: on {} fds, timeout {}", len, call.timeout());
102 // TODO: nicer API for reading arrays from the guest:
103 unsafe {
104 for i in 0..len {
105 debug!(
106 "POLL: fd {} = {}",
107 i,
108 call.fds().unwrap().offset(i as isize)
109 );
110 }
111 }
112}
113
114/// Build the scheduler request for a zero-timeout poll.
115///
116/// An empty request only rotates the caller within its current priority band. That is not a
117/// yield when a busy poller has a higher priority than the producer whose readiness it is
118/// probing. `SchedYield` excludes the caller from the next selection, allowing exactly one
119/// other runnable guest to make progress before the nonblocking poll executes. Limit that strong
120/// yield to SaBRe tasks with a loopback peer: libcurl alternates its loopback socket with an
121/// internal wakeup fd, and either zero-timeout probe can otherwise starve the peer. General
122/// build-tool polling retains the existing empty-turn behavior.
123fn zero_timeout_poll_request(dettid: DetTid, yield_to_peer: bool) -> Resources {
124 let mut request = Resources::new(dettid);
125 if yield_to_peer {
126 request.insert(ResourceID::SchedYield, Permission::W);
127 request.fyi(LOOPBACK_POLL_YIELD_FYI);
128 }
129 request
130}
131
132fn connect_result_allows_peer_classification(result: &Result<i64, Error>) -> bool {
133 match result {
134 Ok(_) => true,
135 Err(Error::Errno(errno)) => *errno == Errno::EINPROGRESS,
136 Err(_) => false,
137 }
138}
139
140const KERNEL_SIGSET_SIZE: usize = std::mem::size_of::<u64>();
141pub(super) const PSELECT6_INTERNAL_MAX_NFDS: i32 =
142 (std::mem::size_of::<libc::c_ulong>() * 8) as i32;
143
144#[derive(Clone, Copy)]
145#[repr(C)]
146pub(super) struct Pselect6SigmaskArg {
147 pub(super) sigmask: usize,
148 pub(super) sigsetsize: usize,
149}
150
151/// Where a call's private cells (`PrivateCells`) live while the kernel reads
152/// them (`stage_private_cells`).
153enum StagedCellsStorage<Guard> {
154 /// The guest stack scratch below the red zone, checked out by the guard.
155 Stack(Guard),
156 /// Part of the guest's red zone, whose first `len` bytes `saved` holds
157 /// until `StagedCells::release` writes them back.
158 RedZone {
159 at: usize,
160 len: usize,
161 saved: [u8; MAX_PRIVATE_CELLS_LEN],
162 },
163}
164
165/// What a call hands the kernel in place of the guest's own memory: a
166/// temporary signal mask, which a sibling could rewrite while the call waits
167/// and which Hermit sanitizes, and the cell that goes with it. The mask comes
168/// first.
169///
170/// Every layout with a mask takes 24 bytes, the size of replay's signal
171/// delivery for an interrupted call (`end_replayed_pselect6_under_its_temporary_mask`),
172/// so replay can deliver wherever recording found room for the call (Claude
173/// re-review of https://github.com/rrnewton/hermit/pull/4087).
174#[derive(Clone, Copy)]
175enum PrivateCells {
176 /// A pselect6 temporary mask and the `{ sigmask, sigsetsize }` wrapper
177 /// that names it.
178 Pselect6Mask(KernelSigset),
179 /// A temporary mask for a call that takes the mask without a wrapper
180 /// (`epoll_pwait`, a ppoll the kernel waits in), and 16 unused bytes.
181 Mask(KernelSigset),
182 /// A temporary mask and a zero timeout (a zero-timeout ppoll probe, and
183 /// the replayed signal delivery).
184 MaskAndZeroTimeout(KernelSigset),
185 /// A zero timeout alone (a zero-timeout ppoll probe with no mask).
186 ZeroTimeout,
187}
188
189const MASK_LEN: usize = std::mem::size_of::<KernelSigset>();
190
191/// The most any `PrivateCells` takes: a mask and a 16-byte cell.
192const MAX_PRIVATE_CELLS_LEN: usize = MASK_LEN + std::mem::size_of::<Timespec>();
193
194// The wrapper and the timeout share the cell after the mask.
195const _: () = assert!(std::mem::size_of::<Pselect6SigmaskArg>() == std::mem::size_of::<Timespec>());
196
197impl PrivateCells {
198 fn len(self) -> usize {
199 match self {
200 Self::Pselect6Mask(_) | Self::Mask(_) | Self::MaskAndZeroTimeout(_) => {
201 MAX_PRIVATE_CELLS_LEN
202 }
203 Self::ZeroTimeout => std::mem::size_of::<Timespec>(),
204 }
205 }
206
207 /// The cells' bytes when they start at `at`. Only the first `len` count,
208 /// and a zero timeout is all zeroes.
209 fn bytes(self, at: usize) -> [u8; MAX_PRIVATE_CELLS_LEN] {
210 let mut cells = [0u8; MAX_PRIVATE_CELLS_LEN];
211 match self {
212 Self::Pselect6Mask(mask) => {
213 cells[..MASK_LEN].copy_from_slice(&mask.to_ne_bytes());
214 cells[MASK_LEN..MASK_LEN + 8].copy_from_slice(&at.to_ne_bytes());
215 cells[MASK_LEN + 8..].copy_from_slice(&KERNEL_SIGSET_SIZE.to_ne_bytes());
216 }
217 Self::Mask(mask) | Self::MaskAndZeroTimeout(mask) => {
218 cells[..MASK_LEN].copy_from_slice(&mask.to_ne_bytes());
219 }
220 Self::ZeroTimeout => {}
221 }
222 cells
223 }
224}
225
226/// A call's private cells in guest memory (`stage_private_cells`).
227struct StagedCells<Guard> {
228 /// The first cell: the mask, or the zero timeout of
229 /// `PrivateCells::ZeroTimeout`.
230 at: usize,
231 storage: StagedCellsStorage<Guard>,
232 /// Memory for making an expired timerfd readable while these cells hold
233 /// the scratch stack (`crate::syscalls::timerfd::publish_expired_in`);
234 /// `None` for red-zone cells, which hold no stack.
235 timerfd_scratch: Option<crate::syscalls::timerfd::Scratch>,
236}
237
238impl<Guard> StagedCells<Guard> {
239 /// The mask copy.
240 fn mask(&self) -> usize {
241 self.at
242 }
243
244 /// The cell after the mask: a pselect6 wrapper, or a zero timeout.
245 fn after_mask(&self) -> usize {
246 self.at + MASK_LEN
247 }
248
249 /// Gives the storage back once the call has returned: drops the stack
250 /// guard, or writes the guest's red-zone bytes back. That is a write to
251 /// guest memory, not an injected syscall, so a call that ended with a
252 /// restart code goes back to the kernel untouched.
253 fn release<G, T>(self, guest: &mut G)
254 where
255 G: Guest<Detcore<T>>,
256 T: RecordOrReplay,
257 {
258 if let StagedCellsStorage::RedZone { at, len, saved } = self.storage
259 && let Err(errno) = AddrMut::<u8>::from_raw(at)
260 .ok_or(Errno::EFAULT)
261 .and_then(|bytes| guest.memory().write_exact(bytes, &saved[..len]))
262 {
263 // Not expected: the same bytes were written before the call.
264 tracing::warn!("[detcore] could not restore the red zone after a syscall: {errno}");
265 }
266 }
267}
268
269/// Stages `cells` where the kernel can read them in place of the guest's own
270/// memory.
271///
272/// The cells go in the guest stack scratch below the red zone when that is
273/// writable. A thread may run with no writable memory there, on a stack it
274/// placed near the start of a page, and a call on such a stack is valid
275/// (Codex re-check of https://github.com/rrnewton/hermit/pull/4053). Then the
276/// cells go in the red zone, whose bytes are saved here and written back by
277/// `StagedCells::release` when the call returns. The red zone may hold the
278/// call's own buffers, which Linux reads and writes, so the cells take the
279/// highest 8-aligned window that overlaps none of `live` (Codex re-check #3).
280/// The guest's own copies of the cells (its mask, wrapper or timeout) may be
281/// overwritten: Linux reads none of them, as the call is redirected to the
282/// private cells, and all are restored. When no window is free and writable,
283/// this returns `None`, and the caller refuses the call
284/// (`refuse_unstageable_pselect6_mask`). The guest runs no code in between:
285/// the call holds the thread in the kernel, a signal frame is built below the
286/// red zone, and a call the kernel restarts comes back as a new syscall with
287/// the guest's own arguments, which is staged again. Nothing is mapped, so the
288/// guest's address space is unchanged (Codex re-check of the earlier
289/// per-thread page).
290///
291/// Whether the scratch faults is a function of the guest's own stack, so the
292/// choice is deterministic.
293async fn stage_private_cells<G, T>(
294 guest: &mut G,
295 cells: PrivateCells,
296 live: &[std::ops::Range<usize>],
297) -> Result<Option<StagedCells<<G::Stack as Stack>::StackGuard>>, Errno>
298where
299 G: Guest<Detcore<T>>,
300 T: RecordOrReplay,
301{
302 let len = cells.len();
303 let mut stack = guest.stack().await;
304 let at = match cells {
305 PrivateCells::ZeroTimeout => stack.reserve::<Timespec>().as_raw(),
306 _ => stack.reserve::<[u64; 3]>().as_raw(),
307 };
308 // Only a process with an armed virtual timerfd can need the publication
309 // scratch; any other keeps the whole stack for its cells, as before
310 // timerfds were virtualized.
311 let timerfd_scratch = crate::syscalls::timerfd::holds_armed_virtual_timerfd(guest)
312 .then(|| crate::syscalls::timerfd::reserve_scratch(&mut stack));
313 match stack.commit() {
314 Ok(guard) => {
315 let bytes = AddrMut::<u8>::from_raw(at).ok_or(Errno::EFAULT)?;
316 // The commit wrote zeroes, which the cells' address fills in.
317 guest.memory().write_exact(bytes, &cells.bytes(at)[..len])?;
318 return Ok(Some(StagedCells {
319 at,
320 storage: StagedCellsStorage::Stack(guard),
321 timerfd_scratch,
322 }));
323 }
324 Err(Errno::EFAULT) => {}
325 Err(errno) => return Err(errno),
326 }
327 let rsp = crate::arch::stack_pointer(&guest.regs().await) as usize;
328 let Some(top) = rsp.checked_sub(len).map(|top| top & !7) else {
329 return Ok(None);
330 };
331 let bottom = rsp.saturating_sub(STACK_RED_ZONE);
332 let mut saved = [0u8; MAX_PRIVATE_CELLS_LEN];
333 let mut found = None;
334 for at in (bottom..=top).rev().step_by(8) {
335 let window = at..at + len;
336 if live
337 .iter()
338 .any(|range| range.start < window.end && window.start < range.end)
339 {
340 continue;
341 }
342 let Some(bytes) = AddrMut::<u8>::from_raw(at) else {
343 continue;
344 };
345 if guest.memory().read_exact(bytes, &mut saved[..len]).is_ok() {
346 found = Some((at, bytes));
347 break;
348 }
349 }
350 let Some((at, bytes)) = found else {
351 return Ok(None);
352 };
353 let staged = StagedCells {
354 at,
355 storage: StagedCellsStorage::RedZone { at, len, saved },
356 timerfd_scratch: None,
357 };
358 if guest
359 .memory()
360 .write_exact(bytes, &cells.bytes(at)[..len])
361 .is_err()
362 {
363 // A read-only stack page. A write that failed partway may have changed
364 // some bytes, so put them back.
365 staged.release(guest);
366 return Ok(None);
367 }
368 Ok(Some(staged))
369}
370
371/// The temporary mask a record/replay pselect6 that may wait sleeps under,
372/// read at the call's entry, where Linux copies it (recording waits on a
373/// private copy of it, `handle_pselect6`). The mask is read through the kernel
374/// (`read_kernel_sigset`), so a mask the kernel's copy faults on, such as one
375/// on a `PROT_NONE` page that a tracer read would still return, gives `None`:
376/// the call then keeps the guest's wrapper and the kernel fails it with EFAULT
377/// in Linux's order (Claude review of
378/// https://github.com/rrnewton/hermit/pull/4166). `None` too when the call
379/// has no mask, its wrapper cannot be read as the kernel copies it
380/// (`read_pselect6_argument`), or the size is not the kernel's.
381///
382/// The read is the call's first injection: a signal that stops it restarts
383/// the call (`restart_if_first_probe_not_run`), the only error returned.
384async fn entry_pselect6_temporary_mask<G, T>(
385 guest: &mut G,
386 call: syscalls::Pselect6,
387) -> Result<Option<KernelSigset>, Error>
388where
389 G: Guest<Detcore<T>>,
390 T: RecordOrReplay,
391{
392 let Some(wrapper) = call.sigmask() else {
393 return Ok(None);
394 };
395 let Some(argument) = read_pselect6_argument(guest, wrapper) else {
396 return Ok(None);
397 };
398 if argument.sigsetsize != KERNEL_SIGSET_SIZE {
399 return Ok(None);
400 }
401 let Some(mask_addr) = Addr::<libc::sigset_t>::from_raw(argument.sigmask) else {
402 return Ok(None);
403 };
404 let mask = match read_kernel_sigset(guest, mask_addr).await {
405 Ok(mask) => mask,
406 Err(error @ Error::Errno(Errno::ERESTARTSYS)) => {
407 return crate::syscalls::signal::restart_if_first_probe_not_run(Err(error));
408 }
409 Err(_) => return Ok(None),
410 };
411 super::signal_shadow::note_wait_mask(guest, mask);
412 Ok(Some(mask))
413}
414
415/// Whether a pselect6 names no mask: it passes no `{ sigmask, sigsetsize }`
416/// argument, or one whose mask pointer is NULL, as glibc's `pselect` passes
417/// it. Linux then leaves the thread's mask in place without reading the size
418/// (`set_user_sigmask`). The argument is read whole, once, with the guest's
419/// own access (`read_pselect6_argument`), so one the kernel would fail to copy
420/// counts as naming a mask: the call keeps it, and its EFAULT.
421fn pselect6_names_no_mask<G, T>(guest: &mut G, call: syscalls::Pselect6) -> bool
422where
423 G: Guest<Detcore<T>>,
424 T: RecordOrReplay,
425{
426 let Some(wrapper) = call.sigmask() else {
427 return true;
428 };
429 read_pselect6_argument(guest, wrapper).is_some_and(|argument| argument.sigmask == 0)
430}
431
432/// pselect6's `{ sigmask, sigsetsize }` argument at `wrapper`, read as Linux
433/// copies it (`get_sigset_argpack`): whole, in one read that honours the
434/// guest's page protections, or `None` where the kernel's copy would fault,
435/// or where the tracer cannot read it although the kernel can (a write-only
436/// page on x86). Either way the call keeps the guest's argument. A tracer read
437/// retried word by word reaches a `PROT_NONE` tail on ptrace
438/// (`PTRACE_PEEKDATA`), which the kernel's copy does not.
439fn read_pselect6_argument<G, T>(
440 guest: &mut G,
441 wrapper: Addr<'_, libc::sigset_t>,
442) -> Option<Pselect6SigmaskArg>
443where
444 G: Guest<Detcore<T>>,
445 T: RecordOrReplay,
446{
447 let mut bytes = [0u8; std::mem::size_of::<Pselect6SigmaskArg>()];
448 guest
449 .memory()
450 .read_exact_with_user_access(wrapper.cast::<u8>(), &mut bytes)
451 .ok()?;
452 let word = |at: usize| {
453 usize::from_ne_bytes(
454 bytes[at..at + std::mem::size_of::<usize>()]
455 .try_into()
456 .expect("a word"),
457 )
458 };
459 Some(Pselect6SigmaskArg {
460 sigmask: word(0),
461 sigsetsize: word(std::mem::size_of::<usize>()),
462 })
463}
464
465/// The temporary mask a replayed zero-timeout pselect6 sleeps under, read
466/// once the scheduler grants the call, which keeps its turn until it returns;
467/// `None` when the call has no mask or the wrapper or the mask cannot be read
468/// (`end_replayed_pselect6_under_its_temporary_mask`).
469fn replayed_pselect6_temporary_mask<G, T>(
470 guest: &mut G,
471 call: syscalls::Pselect6,
472) -> Option<KernelSigset>
473where
474 G: Guest<Detcore<T>>,
475 T: RecordOrReplay,
476{
477 let wrapper = call.sigmask()?;
478 let argument: Pselect6SigmaskArg = guest.memory().read_value(wrapper.cast()).ok()?;
479 if argument.sigsetsize != KERNEL_SIGSET_SIZE {
480 return None;
481 }
482 let mask_addr = Addr::<KernelSigset>::from_raw(argument.sigmask)?;
483 let mask = guest.memory().read_value(mask_addr).ok()?;
484 super::signal_shadow::note_wait_mask(guest, mask);
485 Some(mask)
486}
487
488/// Whether replay could deliver a signal that interrupts this record/replay
489/// pselect6 (`end_replayed_pselect6_under_its_temporary_mask`), whose 24 bytes
490/// go in the stack scratch or at the top of the red zone. Recording a
491/// zero-timeout call passes the guest's own wrapper to the kernel, so it
492/// accepts the call on any stack, and a call whose stack pointer is less than
493/// 24 bytes above unwritable memory left replay nowhere to deliver: the kernel
494/// restarted the served call and replay stopped at the next syscall
495/// (https://github.com/rrnewton/hermit/issues/4112). The top 24 bytes of the
496/// red zone are readable exactly when the stack pointer is at least 24 bytes
497/// above unwritable memory, and the stack scratch lies below them, so this
498/// reads them. `true` for a call with no mask to deliver under, and for one
499/// whose wrapper Linux cannot read, which it fails before it waits. A function
500/// of the guest's stack and memory, which replay reproduces.
501async fn replayed_pselect6_delivery_fits<G, T>(guest: &mut G, call: syscalls::Pselect6) -> bool
502where
503 G: Guest<Detcore<T>>,
504 T: RecordOrReplay,
505{
506 let Some(wrapper) = call.sigmask() else {
507 return true;
508 };
509 let argument: Result<Pselect6SigmaskArg, _> = guest.memory().read_value(wrapper.cast());
510 match argument {
511 Ok(argument) if argument.sigmask != 0 => {}
512 _ => return true,
513 }
514 let rsp = crate::arch::stack_pointer(&guest.regs().await) as usize;
515 let mut cells = [0u8; MAX_PRIVATE_CELLS_LEN];
516 rsp.checked_sub(MAX_PRIVATE_CELLS_LEN)
517 .and_then(|top| AddrMut::<u8>::from_raw(top & !7))
518 .is_some_and(|top| guest.memory().read_exact(top, &mut cells).is_ok())
519}
520
521/// Ends a replayed pselect6 that the recording says a signal interrupted
522/// (`ERESTARTNOHAND`), when the signal is one that only the call's temporary
523/// mask unblocks.
524///
525/// Replay serves pselect6 from the recording, so the kernel never installs the
526/// call's temporary mask. Under the thread's own mask, which blocks the signal
527/// the scheduler sent at the recorded point, the kernel finds nothing to
528/// deliver, restarts the call, and the guest's next system call no longer
529/// matches the recording (https://github.com/rrnewton/hermit/issues/3992).
530/// Linux delivers such a signal before it puts the thread's own mask back, and
531/// the handler's frame restores that mask when it returns. Only the kernel can
532/// do both, so replay injects a ppoll with no descriptors, a zero timeout
533/// and a scratch copy of the mask the call read at entry (`temporary_mask`,
534/// from `replayed_pselect6_temporary_mask`), never the guest's buffer, which a
535/// sibling may have rewritten while the call was served (review of
536/// https://github.com/rrnewton/hermit/pull/4052): the kernel installs
537/// the mask, finds the signal pending, returns `ERESTARTNOHAND` and delivers
538/// the signal, as it did when the recorded call returned. A zero timeout never
539/// sleeps, and Linux writes no remaining time back for it, so neither host
540/// timing nor host time reaches the guest.
541///
542/// A zero-timeout masked pselect6 and a zero-timeout masked ppoll end the same
543/// way when a signal their mask unblocks is already pending, and replay serves
544/// them the same way, so both end through this too
545/// (https://github.com/rrnewton/hermit/issues/4054).
546///
547/// Every value this reads (guest memory, and the kernel's pending set, mask
548/// and dispositions in this thread's turn) is a function of the replayed
549/// schedule. When no such signal is pending, the call returns as before.
550async fn end_replayed_pselect6_under_its_temporary_mask<G, T>(
551 guest: &mut G,
552 temporary_mask: KernelSigset,
553) where
554 G: Guest<Detcore<T>>,
555 T: RecordOrReplay,
556{
557 let Ok(state) = read_thread_signal_state(guest.pid(), guest.tid()) else {
558 return;
559 };
560 if state.pending
561 & state.blocked
562 & state.interrupting(kernel_installed_signal_mask(temporary_mask))
563 == 0
564 {
565 return;
566 }
567 // The cells go in the stack scratch, or at the top of the red zone when
568 // the thread runs on a stack it placed near the start of a page, which is
569 // valid. The guest's red-zone bytes go back once the injected call has
570 // returned. The kernel builds a signal frame below the red zone, and the
571 // guest runs no code before the bytes are back, so it never sees them
572 // change. Nothing is mapped, so the replayed address space stays the
573 // recording's. No window is excluded: the call has returned, and Linux
574 // touches only the cells. The 24 bytes fit wherever recording staged its
575 // own 24-byte mask copy (Claude re-review of
576 // https://github.com/rrnewton/hermit/pull/4087).
577 let Ok(Some(staged)) =
578 stage_private_cells(guest, PrivateCells::MaskAndZeroTimeout(temporary_mask), &[]).await
579 else {
580 return;
581 };
582 inject_signal_delivery(guest, staged.mask(), staged.after_mask()).await;
583 staged.release(guest);
584}
585
586/// Injects the zero-timeout ppoll with no descriptors that delivers a
587/// replayed call's signal under its temporary mask (`mask`), as recording ran
588/// the call itself: the kernel installs the mask, finds the signal pending,
589/// returns `ERESTARTNOHAND` and delivers the signal as the injected call
590/// returns, as it did after the recorded call, and the guest then gets the
591/// recorded result. ppoll takes the mask pointer directly, so the delivery
592/// needs no `{ sigmask, sigsetsize }` wrapper, which a pselect6 would.
593async fn inject_signal_delivery<G, T>(guest: &mut G, mask: usize, zero_timeout: usize)
594where
595 G: Guest<Detcore<T>>,
596 T: RecordOrReplay,
597{
598 let _ = guest
599 .inject(
600 syscalls::Ppoll::new()
601 .with_fds(None)
602 .with_nfds(0)
603 .with_timeout(AddrMut::from_raw(zero_timeout))
604 .with_sigmask(Addr::from_raw(mask))
605 .with_sigsetsize(KERNEL_SIGSET_SIZE),
606 )
607 .await;
608}
609
610pub(super) fn pselect6_fd_set_len(nfds: i32) -> Result<usize, Errno> {
611 let nfds = usize::try_from(nfds).map_err(|_| Errno::EINVAL)?;
612 let bits_per_word = std::mem::size_of::<libc::c_ulong>() * 8;
613 Ok(nfds.div_ceil(bits_per_word) * std::mem::size_of::<libc::c_ulong>())
614}
615
616fn pselect6_probe_result(result: Result<i64, Errno>) -> Result<i64, Errno> {
617 match result {
618 // The injected syscall runs outside the guest's original restart frame.
619 // Do not expose this kernel-internal restart instruction at the
620 // rewritten pselect6 call site.
621 Err(Errno::ERESTARTSYS) => Err(Errno::EINTR),
622 result => result,
623 }
624}
625
626/// Runs `probe`, a zero-timeout ppoll, and returns its result, `EINTR`
627/// included. Only `ERESTARTSYS`, which ppoll itself never returns, is retried:
628/// it is the backend's report that a signal stopped the probe before its
629/// `syscall` instruction ran, and the backend holds that signal for the
630/// guest's resume, so the probe runs again, as `inject_with_retry` ran it.
631///
632/// `inject_with_retry` also retried `EINTR`, which hung the call forever on a
633/// backend that runs it itself: where the probe's temporary mask lets in a
634/// pending caught signal, such a backend answers `EINTR` and installs the
635/// mask for that signal's delivery as the guest resumes, and the signal stays
636/// pending until then, so every retry found it again. Linux answers that case
637/// with ERESTARTNOHAND (`do_poll`, fs/select.c), keeps the temporary mask for
638/// the delivery (`poll_select_finish`), and the delivery to a handler makes
639/// it `EINTR` (`handle_signal`, arch/x86/kernel/signal.c), so `EINTR` is the
640/// guest's answer, as for pselect6's probes. A traced guest's probe gets the
641/// kernel's own ERESTARTNOHAND, which was never retried. A backend that runs
642/// the real call inside the guest's process gets `EINTR` once a handler has
643/// run for the signal, which the retry turned into a later probe's 0.
644async fn inject_ppoll_probe<G, T>(guest: &mut G, probe: syscalls::Ppoll) -> Result<i64, Errno>
645where
646 G: Guest<Detcore<T>>,
647 T: RecordOrReplay,
648{
649 loop {
650 let result = guest.inject(probe).await;
651 if !ppoll_probe_runs_again(result) {
652 return result;
653 }
654 }
655}
656
657/// Whether `inject_ppoll_probe` runs a probe again after `result`: only after
658/// the backend's report that it did not run (`ERESTARTSYS`).
659fn ppoll_probe_runs_again(result: Result<i64, Errno>) -> bool {
660 result == Err(Errno::ERESTARTSYS)
661}
662
663/// The pending signals that the temporary mask `mask` lets in, in the signal
664/// state `sets` a backend reports, when every one of them is a signal the
665/// process ignores (`crate::syscalls::ignored_at_generation`: set to SIG_IGN,
666/// or default-ignored with no handler), and so one Linux discards as a ppoll
667/// under `mask` returns (`get_signal`, kernel/signal.c); the empty set when
668/// the mask lets none in. `None` when it lets in one that is not ignored.
669fn ignored_signals_let_in(sets: &reverie::SignalSets, mask: KernelSigset) -> Option<KernelSigset> {
670 let let_in = (sets.pending | sets.shared_pending) & !mask;
671 let ignored = crate::syscalls::ignored_at_generation(sets.ignored, sets.caught);
672 (let_in & !ignored == 0).then_some(let_in)
673}
674
675/// [`ignored_signals_let_in`] for the calling thread, from the signal state
676/// its backend reports (`Guest::signal_sets`); `None` from a backend that
677/// reports none.
678fn ignored_signals_let_in_now<G, T>(guest: &G, mask: KernelSigset) -> Option<KernelSigset>
679where
680 G: Guest<Detcore<T>>,
681 T: RecordOrReplay,
682{
683 guest
684 .signal_sets(guest.tid())
685 .ok()
686 .and_then(|sets| ignored_signals_let_in(&sets, mask))
687}
688
689/// The refusal of a masked pselect6 that a backend running the call itself
690/// failed with ENOSYS for a pending signal its temporary mask lets in, which
691/// it cannot act on inside the call, as for ppoll
692/// (`Detcore::refuse_masked_ppoll_signal`).
693const MASKED_PSELECT6_SIGNAL_REASON: &str = "a pselect6 whose temporary signal mask lets in a \
694 pending signal that would stop the process, or an ignored one pending beside a signal \
695 that ends the call, cannot be served on this backend: it cannot act on that signal inside \
696 the call";
697
698/// The refusal of a masked pselect6 that a backend running the call itself
699/// failed with ENOSYS for its descriptor sets, as KVM's does for more than 64
700/// descriptors or for a selected standard output or error it captures, each
701/// before it looks at a signal (`masked_pselect6_refusal_reason`). Linux
702/// serves both: it clamps the count to its descriptor table
703/// (`core_sys_select`, fs/select.c:641-642 at v6.17) and never fails the
704/// call with ENOSYS.
705const MASKED_PSELECT6_DECLINED_REASON: &str = "a pselect6 with a temporary signal mask whose \
706 descriptor sets this backend does not serve (more than 64 descriptors, or a standard \
707 output or error it captures) cannot be served on this backend";
708
709/// The signals whose default action stops the process (`SIG_KERNEL_STOP_MASK`
710/// and `sig_kernel_stop`, include/linux/signal.h:422-424 and :448 at v6.17):
711/// SIGSTOP, SIGTSTP, SIGTTIN and SIGTTOU.
712const DEFAULT_STOP_SIGNALS: KernelSigset = (1 << (libc::SIGSTOP - 1))
713 | (1 << (libc::SIGTSTP - 1))
714 | (1 << (libc::SIGTTIN - 1))
715 | (1 << (libc::SIGTTOU - 1));
716
717/// Whether the signal state `sets` holds a pending signal that the temporary
718/// mask `mask` lets in (SIGKILL and SIGSTOP always: no mask blocks them) and
719/// that a backend running a masked ppoll or pselect6 itself cannot act on
720/// inside the call, so that it fails the call with ENOSYS: one the process
721/// ignores (the shared rule, `crate::syscalls::ignored_at_generation`), which
722/// Linux discards before it restarts the call, or one whose default action
723/// stops the process with no handler (`DEFAULT_STOP_SIGNALS`). A caught
724/// signal, or one whose default action ends the process, ends the call
725/// instead.
726fn lets_in_a_signal_the_backend_cannot_act_on(
727 sets: &reverie::SignalSets,
728 mask: KernelSigset,
729) -> bool {
730 let unblockable: KernelSigset = (1 << (libc::SIGKILL - 1)) | (1 << (libc::SIGSTOP - 1));
731 let let_in = (sets.pending | sets.shared_pending) & !(mask & !unblockable);
732 let ignored = crate::syscalls::ignored_at_generation(sets.ignored, sets.caught);
733 let stops = DEFAULT_STOP_SIGNALS & !sets.caught & !ignored;
734 let_in & (ignored | stops) != 0
735}
736
737/// Why a masked pselect6 of `nfds` descriptors that the backend failed with
738/// ENOSYS, under the temporary mask `mask`, is refused, from the signal state
739/// `sets` the backend reports for the thread, if it reports any. More
740/// descriptors than one machine word holds (`PSELECT6_INTERNAL_MAX_NFDS`)
741/// KVM's backend declines first, before it reads the sets or looks at a
742/// signal, so the call is refused for its descriptor sets whatever is
743/// pending. Otherwise it is refused for a pending signal the backend cannot
744/// act on inside the call (`lets_in_a_signal_the_backend_cannot_act_on`), or,
745/// with none pending, for the call's descriptor sets. A selected standard
746/// output or error the backend captures it also declines before it looks at
747/// a signal, and Detcore cannot see which descriptors it captures, so such a
748/// call with such a signal pending is refused for the signal. With no state
749/// reported, the signal.
750fn masked_pselect6_refusal_reason(
751 sets: Option<&reverie::SignalSets>,
752 mask: KernelSigset,
753 nfds: i32,
754) -> &'static str {
755 if nfds > PSELECT6_INTERNAL_MAX_NFDS {
756 return MASKED_PSELECT6_DECLINED_REASON;
757 }
758 match sets {
759 Some(sets) if !lets_in_a_signal_the_backend_cannot_act_on(sets, mask) => {
760 MASKED_PSELECT6_DECLINED_REASON
761 }
762 _ => MASKED_PSELECT6_SIGNAL_REASON,
763 }
764}
765
766/// [`masked_pselect6_refusal_reason`] for the calling thread, from the signal
767/// state its backend reports now (`Guest::signal_sets`).
768fn masked_pselect6_refusal_reason_now<G, T>(
769 guest: &G,
770 mask: KernelSigset,
771 nfds: i32,
772) -> &'static str
773where
774 G: Guest<Detcore<T>>,
775 T: RecordOrReplay,
776{
777 masked_pselect6_refusal_reason(guest.signal_sets(guest.tid()).ok().as_ref(), mask, nfds)
778}
779
780pub(super) fn read_pselect6_fd_set<T, G>(
781 guest: &mut G,
782 address: Option<AddrMut<'_, libc::fd_set>>,
783 len: usize,
784) -> Result<Option<Vec<u8>>, Error>
785where
786 T: RecordOrReplay,
787 G: Guest<Detcore<T>>,
788{
789 let Some(address) = address else {
790 return Ok(None);
791 };
792 let mut bytes = vec![0; len];
793 if len != 0 {
794 guest
795 .memory()
796 .read_exact(address.cast(), &mut bytes)
797 .map_err(|_| Errno::EFAULT)?;
798 }
799 Ok(Some(bytes))
800}
801
802pub(super) fn write_pselect6_fd_set<T, G>(
803 guest: &mut G,
804 address: Option<AddrMut<'_, libc::fd_set>>,
805 bytes: &Option<Vec<u8>>,
806) -> Result<(), Error>
807where
808 T: RecordOrReplay,
809 G: Guest<Detcore<T>>,
810{
811 if let (Some(address), Some(bytes)) = (address, bytes)
812 && !bytes.is_empty()
813 {
814 guest
815 .memory()
816 .write_exact(address.cast(), bytes)
817 .map_err(|_| Errno::EFAULT)?;
818 }
819 Ok(())
820}
821
822fn copy_pselect6_fd_set<T, G>(
823 guest: &mut G,
824 source: Option<AddrMut<'_, libc::fd_set>>,
825 destination: Option<AddrMut<'_, libc::fd_set>>,
826 len: usize,
827) -> Result<(), Error>
828where
829 T: RecordOrReplay,
830 G: Guest<Detcore<T>>,
831{
832 if let (Some(source), Some(destination)) = (source, destination)
833 && len != 0
834 {
835 let mut bytes = vec![0; len];
836 guest
837 .memory()
838 .read_exact(source.cast(), &mut bytes)
839 .map_err(|_| Errno::EFAULT)?;
840 guest
841 .memory()
842 .write_exact(destination.cast(), &bytes)
843 .map_err(|_| Errno::EFAULT)?;
844 }
845 Ok(())
846}
847
848pub(super) fn ppoll_timeout_duration(timeout: Timespec) -> Result<Duration, Errno> {
849 let seconds = u64::try_from(timeout.tv_sec).map_err(|_| Errno::EINVAL)?;
850 let nanoseconds = u32::try_from(timeout.tv_nsec).map_err(|_| Errno::EINVAL)?;
851 if nanoseconds >= 1_000_000_000 {
852 return Err(Errno::EINVAL);
853 }
854 Ok(Duration::new(seconds, nanoseconds))
855}
856
857/// The start time and deadline of a wait for `timeout`, or neither without
858/// one. Observing the virtual clock asks the scheduler, which publishes the
859/// thread's signal state and can end its turn, so a call whose first mask probe
860/// may restart it (`restart_if_first_probe_not_run`) calls this after the
861/// probe. `LogicalTime + Duration` saturates, where a nanosecond count cast to
862/// u64 would wrap a 2^64 ns timeout to none at all (Claude review of
863/// https://github.com/rrnewton/hermit/pull/4235).
864async fn ppoll_start<G: Guest<Detcore<T>>, T: RecordOrReplay>(
865 guest: &mut G,
866 timeout: Option<Duration>,
867) -> (Option<LogicalTime>, Option<LogicalTime>) {
868 match timeout {
869 Some(timeout) => {
870 let started_at = thread_observe_time(guest).await;
871 (Some(started_at), Some(started_at + timeout))
872 }
873 None => (None, None),
874 }
875}
876
877/// The temporary mask `call` names, if any, read by a kernel-access probe;
878/// EINVAL for a size the kernel rejects. `first` says the read is the call's
879/// first injection, so a signal that stops it restarts the call
880/// (`restart_if_first_probe_not_run`).
881async fn read_ppoll_mask<G: Guest<Detcore<T>>, T: RecordOrReplay>(
882 guest: &mut G,
883 call: syscalls::Ppoll,
884 first: bool,
885) -> Result<Option<KernelSigset>, Error> {
886 let Some(signal_mask) = call.sigmask() else {
887 return Ok(None);
888 };
889 if call.sigsetsize() != KERNEL_SIGSET_SIZE {
890 return Err(Errno::EINVAL.into());
891 }
892 let read = read_kernel_sigset(guest, signal_mask).await;
893 let signal_mask = if first {
894 crate::syscalls::signal::restart_if_first_probe_not_run(read)?
895 } else {
896 read?
897 };
898 super::signal_shadow::note_wait_mask(guest, signal_mask);
899 Ok(Some(signal_mask))
900}
901
902/// `select`'s timeout as Linux reads it. In Linux v7.1 `kern_select`
903/// (`fs/select.c`) first refuses a negative `tv_sec` or `tv_usec` with
904/// `EINVAL`, then passes `tv_sec + tv_usec / USEC_PER_SEC` seconds and
905/// `(tv_usec % USEC_PER_SEC) * NSEC_PER_USEC` nanoseconds to
906/// `poll_select_set_timeout`, which refuses only what `timespec64_valid`
907/// (`include/linux/time64.h`) refuses: so microseconds of a second or more
908/// fold into the seconds. (v6.12 has no separate sign check, so there a
909/// mixed-sign timeval such as `{2, -1000000}` normalizes to 1 s; v7.1 added
910/// the check because the signed sum could wrap a negative timeout into a
911/// valid one. This follows v7.1, the measured host behaviour.) A sum that
912/// would overflow the signed seconds is `EINVAL` here.
913#[cfg(target_arch = "x86_64")]
914pub(super) fn select_timeout_duration(timeout: libc::timeval) -> Result<Duration, Errno> {
915 if timeout.tv_sec < 0 || timeout.tv_usec < 0 {
916 return Err(Errno::EINVAL);
917 }
918 let seconds = timeout
919 .tv_sec
920 .checked_add(timeout.tv_usec / 1_000_000)
921 .ok_or(Errno::EINVAL)?;
922 let nanoseconds = (timeout.tv_usec % 1_000_000) * 1_000;
923 Ok(Duration::new(seconds as u64, nanoseconds as u32))
924}
925
926fn timespec_from_duration(duration: Duration) -> Timespec {
927 Timespec {
928 tv_sec: duration.as_secs() as libc::time_t,
929 tv_nsec: duration.subsec_nanos() as libc::c_long,
930 }
931}
932
933const SCM_TIMESTAMP_OLD: libc::c_int = 29;
934const SCM_TIMESTAMPNS_OLD: libc::c_int = 35;
935const SCM_TIMESTAMPING_OLD: libc::c_int = 37;
936const SCM_TIMESTAMP_NEW: libc::c_int = 63;
937const SCM_TIMESTAMPNS_NEW: libc::c_int = 64;
938const SCM_TIMESTAMPING_NEW: libc::c_int = 65;
939const MAX_CONTROL_BYTES: usize = 64 * 1024;
940
941#[derive(Clone, Copy)]
942enum SocketTimestampKind {
943 Timeval,
944 Timespec,
945 Timestamping,
946}
947
948#[derive(Clone, Copy)]
949struct SocketTimestampMessage {
950 data_offset: usize,
951 available_data_len: usize,
952 kind: SocketTimestampKind,
953}
954
955fn cmsg_align(length: usize) -> usize {
956 let alignment = std::mem::size_of::<usize>();
957 (length + alignment - 1) & !(alignment - 1)
958}
959
960fn read_control_value<T: Copy>(bytes: &[u8]) -> Option<T> {
961 if bytes.len() < std::mem::size_of::<T>() {
962 return None;
963 }
964 // SAFETY: the length check guarantees a complete T, and read_unaligned
965 // permits the control buffer's byte alignment.
966 Some(unsafe { bytes.as_ptr().cast::<T>().read_unaligned() })
967}
968
969fn write_control_value<T: Copy>(bytes: &mut [u8], value: T) -> bool {
970 if bytes.len() < std::mem::size_of::<T>() {
971 return false;
972 }
973 // SAFETY: the length check guarantees room for T, and write_unaligned
974 // permits the control buffer's byte alignment.
975 unsafe { bytes.as_mut_ptr().cast::<T>().write_unaligned(value) };
976 true
977}
978
979fn write_control_prefix<T: Copy>(bytes: &mut [u8], value: T) -> usize {
980 let value_len = std::mem::size_of::<T>();
981 let write_len = bytes.len().min(value_len);
982 // SAFETY: `value` is alive for this copy and the resulting byte view has
983 // exactly its initialized object representation.
984 let value_bytes =
985 unsafe { std::slice::from_raw_parts((&value as *const T).cast::<u8>(), value_len) };
986 bytes[..write_len].copy_from_slice(&value_bytes[..write_len]);
987 write_len
988}
989
990fn socket_timestamp_messages(control: &[u8]) -> Vec<SocketTimestampMessage> {
991 let header_len = cmsg_align(std::mem::size_of::<Cmsghdr>());
992 let mut messages = Vec::new();
993 let mut offset = 0usize;
994
995 while let Some(header_bytes) = control.get(offset..) {
996 let Some(header) = read_control_value::<Cmsghdr>(header_bytes) else {
997 break;
998 };
999 if header.cmsg_len < header_len {
1000 break;
1001 }
1002 let Some(end) = offset.checked_add(header.cmsg_len) else {
1003 break;
1004 };
1005
1006 if header.cmsg_level == libc::SOL_SOCKET {
1007 let data_offset = offset + header_len;
1008 let declared_data_len = header.cmsg_len - header_len;
1009 let available_data_len = control
1010 .len()
1011 .saturating_sub(data_offset)
1012 .min(declared_data_len);
1013 let kind = match header.cmsg_type {
1014 SCM_TIMESTAMP_OLD | SCM_TIMESTAMP_NEW => Some(SocketTimestampKind::Timeval),
1015 SCM_TIMESTAMPNS_OLD | SCM_TIMESTAMPNS_NEW => Some(SocketTimestampKind::Timespec),
1016 SCM_TIMESTAMPING_OLD | SCM_TIMESTAMPING_NEW => {
1017 Some(SocketTimestampKind::Timestamping)
1018 }
1019 _ => None,
1020 };
1021 if let Some(kind) = kind {
1022 messages.push(SocketTimestampMessage {
1023 data_offset,
1024 available_data_len,
1025 kind,
1026 });
1027 }
1028 }
1029
1030 if end > control.len() {
1031 break;
1032 }
1033
1034 let step = cmsg_align(header.cmsg_len);
1035 let Some(next) = offset.checked_add(step) else {
1036 break;
1037 };
1038 if next <= offset {
1039 break;
1040 }
1041 offset = next;
1042 }
1043 messages
1044}
1045
1046// AUTONOMOUS-BOT-IMPLEMENTED
1047// TODO-HUMAN-REVIEW(PR-901)
1048fn canonicalize_socket_timestamps(control: &mut [u8], now: LogicalTime) -> usize {
1049 let messages = socket_timestamp_messages(control);
1050 let timespec = libc::timespec {
1051 tv_sec: now.as_secs() as libc::time_t,
1052 tv_nsec: now.subsec_nanos() as libc::c_long,
1053 };
1054 let timeval = libc::timeval {
1055 tv_sec: timespec.tv_sec,
1056 tv_usec: (timespec.tv_nsec / 1_000) as libc::suseconds_t,
1057 };
1058
1059 for message in &messages {
1060 let available_end = message
1061 .data_offset
1062 .saturating_add(message.available_data_len)
1063 .min(control.len());
1064 let data = &mut control[message.data_offset..available_end];
1065 match message.kind {
1066 SocketTimestampKind::Timeval => {
1067 write_control_prefix(data, timeval);
1068 }
1069 SocketTimestampKind::Timespec => {
1070 write_control_prefix(data, timespec);
1071 }
1072 SocketTimestampKind::Timestamping => {
1073 let size = std::mem::size_of::<libc::timespec>();
1074 let zero = libc::timespec {
1075 tv_sec: 0,
1076 tv_nsec: 0,
1077 };
1078 for slot in 0..3 {
1079 let start = slot * size;
1080 if start >= data.len() {
1081 break;
1082 }
1083 let end = (start + size).min(data.len());
1084 let slot_bytes = &mut data[start..end];
1085 if slot_bytes.len() != size {
1086 slot_bytes.fill(0);
1087 continue;
1088 }
1089 let original = read_control_value::<libc::timespec>(slot_bytes)
1090 .expect("complete timestamping slot");
1091 let replacement = if original.tv_sec == 0 && original.tv_nsec == 0 {
1092 zero
1093 } else {
1094 timespec
1095 };
1096 let _ = write_control_value(slot_bytes, replacement);
1097 }
1098 }
1099 }
1100 }
1101 messages.len()
1102}
1103
1104fn sanitize_ppoll_signal_mask(mask: u64) -> u64 {
1105 let signal_bit = (reverie::PERF_EVENT_SIGNAL as usize) - 1;
1106 mask & !(1_u64 << signal_bit)
1107}
1108
1109/// The pollfd array a ppoll reads and writes, if it has one.
1110fn ppoll_fds_range(call: syscalls::Ppoll) -> Option<std::ops::Range<usize>> {
1111 call.fds().map(|fds| {
1112 let start = fds.as_raw();
1113 let len = usize::try_from(call.nfds())
1114 .unwrap_or(usize::MAX)
1115 .saturating_mul(std::mem::size_of::<libc::pollfd>());
1116 start..start.saturating_add(len)
1117 })
1118}
1119
1120/// Whether the kernel owns a ppoll's blocking wait: when threads are not
1121/// sequentialized, and for every record/replay call, masked or not, as for a
1122/// masked pselect6 (https://github.com/rrnewton/hermit/issues/4042). A masked
1123/// sequentialized run-mode wait still takes the probe path.
1124fn ppoll_uses_kernel_wait(sequentialize_threads: bool, recordreplay_modes: bool) -> bool {
1125 !sequentialize_threads || recordreplay_modes
1126}
1127
1128// Linux copies the outer wrapper before rejecting this private malformed
1129// timeout. Keep the operation and its closed purpose together: using this
1130// disposition with a valid timeout would bypass runtime mask interposition.
1131async fn validate_pselect6_wrapper<G, T>(
1132 guest: &mut G,
1133 argument: Addr<'_, libc::sigset_t>,
1134) -> Result<(), Error>
1135where
1136 T: reverie::Tool,
1137 G: Guest<T>,
1138{
1139 let mut stack = guest.stack().await;
1140 let validation_timeout = stack.reserve::<Timespec>();
1141 let _guard = stack.commit()?;
1142 guest.memory().write_value(
1143 validation_timeout,
1144 &Timespec {
1145 tv_sec: 0,
1146 tv_nsec: 1_000_000_000,
1147 },
1148 )?;
1149 let validation = syscalls::Pselect6::new()
1150 .with_nfds(0)
1151 .with_readfds(None)
1152 .with_writefds(None)
1153 .with_exceptfds(None)
1154 .with_timeout(Some(validation_timeout))
1155 .with_sigmask(Some(argument));
1156 match guest
1157 .inject_with_purpose(
1158 validation,
1159 reverie::InjectionPurpose::Pselect6WrapperValidation,
1160 )
1161 .await
1162 {
1163 Err(error) => match error.into_errno()? {
1164 Errno::EINVAL => Ok(()),
1165 // This is the call's first injection. A mechanism failure must not
1166 // be converted to a guest errno or a successful validation.
1167 errno => crate::syscalls::signal::restart_if_first_probe_not_run(Err(errno.into())),
1168 },
1169 // Success would mean the backend did not validate the probe.
1170 Ok(_) => Err(Errno::EIO.into()),
1171 }
1172}
1173
1174/// The result Detcore gives the guest for a `poll` it ran in the kernel. A
1175/// signal pending while the poll runs ends it with `ERESTART_RESTARTBLOCK`,
1176/// even with a zero timeout, which cannot wait (`do_poll` checks for a signal
1177/// before it honours the timeout), and with no handler to run the kernel
1178/// restarts it through `restart_syscall`. The poll returns the code Detcore's
1179/// own polls return for it instead (`NonblockableSyscall::kernel_restart_errno`).
1180/// A zero or infinite timeout keeps no deadline (`do_restart_poll` reruns the
1181/// poll as issued), so that code is `ERESTARTNOHAND`, which the kernel restarts
1182/// by running the call again; a positive timeout keeps Linux's own. A handler
1183/// turns either code into `EINTR`. Under a tracer a default-ignored `SIGCHLD`
1184/// is still queued, so a child that exits while its parent polls is such a
1185/// signal, and so is the backend's own timer signal under `--imprecise-timers`.
1186///
1187/// A poll that `keeps_record` (`RestartCall::KernelPoll`) always returns
1188/// `ERESTART_RESTARTBLOCK` here, even a restart whose time is up, and keeps
1189/// its record for `restart_syscall`; `kernel_poll_restart_code` then decides
1190/// the code the guest gets.
1191#[cfg(target_arch = "x86_64")]
1192fn kernel_poll_result(
1193 call: &syscalls::Poll,
1194 result: Result<i64, Error>,
1195 keeps_record: bool,
1196) -> Result<i64, Error> {
1197 if !poll_interrupted(&result) {
1198 result
1199 } else if keeps_record {
1200 Err(Errno::ERESTART_RESTARTBLOCK.into())
1201 } else {
1202 Err(call.kernel_restart_errno().into())
1203 }
1204}
1205
1206/// The code a `poll` with a positive timeout returns to the guest when a
1207/// signal stopped it before it ran (`before_run`,
1208/// `poll_stopped_before_it_ran`), from `kept`, the result
1209/// `kernel_poll_result` gave it. The backend holds such a signal for the
1210/// guest's resume, and passes a held signal on unreported when the guest's
1211/// call returns `ERESTART_RESTARTBLOCK`. Record then took no turn for its
1212/// delivery, while replay, which serves the poll from the recording without
1213/// running it, took one where the signal reached the guest at an ordinary
1214/// delivery stop, and refused at the next logged readmission. Such a poll
1215/// returns `ERESTARTNOHAND` instead, as a wait that holds a signal does
1216/// (`helpers::held_signal_restart_errno`), and still keeps its record. With no
1217/// handler to run the kernel then runs the guest's call again: a resumed
1218/// poll's `restart_syscall`, which finds the record, or the guest's own poll,
1219/// which never ran and starts its timeout again (Linux's restart would keep
1220/// the first end time; under `--no-sequentialize-threads` the end moves by
1221/// the delivery's latency). A handler turns either code into `EINTR`. A
1222/// signal that ends a poll that ran is not held: it reaches the guest at an
1223/// ordinary delivery stop, which the backend reports. Any other result passes
1224/// through.
1225///
1226/// Unlike `held_signal_restart_errno`, this does not know which signal the
1227/// backend holds, so a held `SIGSTOP` also gets `ERESTARTNOHAND`. That is
1228/// harmless: after the stop the kernel runs the guest's call again, as for
1229/// any other held signal above. A resumed poll's `restart_syscall` still finds
1230/// its record; a poll that never ran starts its timeout again, which loses
1231/// nothing under record and replay and, under `--no-sequentialize-threads`,
1232/// drops the end time Detcore read for it as described above. For a stop,
1233/// the guest's poll runs again only once the stop ends, so its end moves by
1234/// the whole length of the stop, not by a delivery's latency.
1235#[cfg(target_arch = "x86_64")]
1236fn kernel_poll_restart_code(kept: Result<i64, Error>, before_run: bool) -> Result<i64, Error> {
1237 match kept {
1238 Err(Error::Errno(Errno::ERESTART_RESTARTBLOCK)) if before_run => {
1239 Err(Errno::ERESTARTNOHAND.into())
1240 }
1241 kept => kept,
1242 }
1243}
1244
1245/// Whether a signal stopped a `poll` before it ran: the backend reports that
1246/// as `ERESTARTSYS` (`Guest::inject`), which Linux's poll never returns.
1247#[cfg(target_arch = "x86_64")]
1248fn poll_stopped_before_it_ran(raw: &Result<i64, Error>) -> bool {
1249 matches!(raw, Err(Error::Errno(Errno::ERESTARTSYS)))
1250}
1251
1252/// Whether a signal ended a `poll` Detcore ran in the kernel: Linux's own
1253/// `ERESTART_RESTARTBLOCK`, or `ERESTARTSYS` when the signal stopped the call
1254/// before it ran (`Guest::inject`). Linux's poll never returns `ERESTARTSYS`
1255/// (`do_poll` reports any signal as `-ERESTARTNOHAND`, which `poll` turns
1256/// into `ERESTART_RESTARTBLOCK`), so both are the same interruption, and
1257/// passing `ERESTARTSYS` on would let `SA_RESTART` rerun a poll that Linux
1258/// ends with `EINTR`, and rerun a `restart_syscall` that keeps no record.
1259#[cfg(target_arch = "x86_64")]
1260fn poll_interrupted(result: &Result<i64, Error>) -> bool {
1261 matches!(
1262 result,
1263 Err(Error::Errno(
1264 Errno::ERESTART_RESTARTBLOCK | Errno::ERESTARTSYS
1265 ))
1266 )
1267}
1268
1269/// Whether a `poll` with a nonzero timeout runs in the kernel: under record and
1270/// replay, whose replay cannot rely on the descriptors, and without
1271/// sequentialized threads. A plain or strict run (`--strict` refuses
1272/// `--no-sequentialize-threads`) emulates it on virtual time
1273/// (`handle_internal_poll`), so the host clock `kernel_poll_deadline` reads
1274/// never reaches such a run.
1275#[cfg(target_arch = "x86_64")]
1276fn polls_in_kernel(sequentialize_threads: bool, recordreplay_modes: bool) -> bool {
1277 !sequentialize_threads || recordreplay_modes
1278}
1279
1280/// The end of a positive timeout for a `poll` Detcore runs in the kernel
1281/// (`RestartCall::KernelPoll`), read when the poll is first issued; `None`
1282/// for a zero or infinite timeout, whose restart reruns the call
1283/// (`kernel_poll_result`). Under record and replay the end holds no instant
1284/// (`KernelPollDeadline`), so those modes read no host clock here.
1285#[cfg(target_arch = "x86_64")]
1286fn kernel_poll_deadline(
1287 call: &syscalls::Poll,
1288 recordreplay_modes: bool,
1289) -> Option<KernelPollDeadline> {
1290 call.restart_keeps_deadline().then(|| {
1291 KernelPollDeadline(
1292 (!recordreplay_modes)
1293 .then(|| Instant::now() + Duration::from_millis(call.timeout() as u64)),
1294 )
1295 })
1296}
1297
1298/// Whether `restart`, a `restart_syscall` at the interrupted poll's address,
1299/// still holds `call`'s pollfd array and length in its argument registers.
1300/// Linux's restart leaves the registers as the poll left them (it sets only
1301/// the syscall number and moves the instruction pointer back,
1302/// `arch_do_signal_or_restart`), so the kernel's restart block and the
1303/// registers describe the same poll.
1304#[cfg(target_arch = "x86_64")]
1305fn restart_names_poll(restart: &Syscall, call: &syscalls::Poll) -> bool {
1306 let (number, args) = restart.into_parts();
1307 number == Sysno::restart_syscall
1308 && args.arg0 == call.fds().map_or(0, |fds| fds.as_raw())
1309 && args.arg1 == call.nfds() as usize
1310}
1311
1312/// The timeout, in whole milliseconds rounded up, that a restarted kernel-run
1313/// `poll` waits from `now` until `end`: 0 once `end` has passed.
1314#[cfg(target_arch = "x86_64")]
1315fn remaining_poll_millis(now: Instant, end: Instant) -> i32 {
1316 let left = end.saturating_duration_since(now).as_nanos();
1317 i32::try_from(left.div_ceil(1_000_000)).unwrap_or(i32::MAX)
1318}
1319
1320impl<T: RecordOrReplay> Detcore<T> {
1321 /// poll syscall (MAYHANG)
1322 // TODO-HUMAN-REVIEW(PR-1023): Review zero-timeout poll scheduling across backends.
1323 #[cfg(target_arch = "x86_64")]
1324 pub async fn handle_poll<G: Guest<Self>>(
1325 &self,
1326 guest: &mut G,
1327
1328 call: syscalls::Poll,
1329 ) -> Result<i64, Error> {
1330 if self.cfg.sequentialize_threads && call.timeout() == 0 {
1331 // This cannot block, but still yield a scheduler turn so a polling thread cannot
1332 // monopolize the guest between preemptions.
1333 let yield_to_peer = self.cfg.backend.loopback_pollers_yield_to_peers
1334 && guest.thread_state().has_loopback_peer();
1335 resource_request(
1336 guest,
1337 zero_timeout_poll_request(guest.thread_state().dettid, yield_to_peer),
1338 )
1339 .await;
1340 let result = if self.cfg.recordreplay_modes {
1341 self.record_or_replay(guest, call).await
1342 } else {
1343 // A virtual timerfd among the fds is readable exactly when its
1344 // deadline has passed (`crate::syscalls::timerfd`).
1345 crate::syscalls::timerfd::publish_expired(guest).await;
1346 guest.inject(call).await
1347 };
1348 kernel_poll_result(&call, result.map_err(Error::from), false)
1349 } else if polls_in_kernel(self.cfg.sequentialize_threads, self.cfg.recordreplay_modes) {
1350 // In replay mode, we cannot assume the existence of FILES during replay.
1351 // Thus we must record the poll and replay it from the trace.
1352 let deadline = kernel_poll_deadline(&call, self.cfg.recordreplay_modes);
1353 Ok(self.handle_external_poll(guest, call, deadline).await?)
1354 } else {
1355 // TODO:
1356 // if is-external-poll { self.handle_external_poll(guest, call) }
1357 self.handle_internal_poll(guest, call).await
1358 }
1359 }
1360
1361 /// Record or replay a raw `select` or `pselect6` the way `poll` is: a zero
1362 /// timeout cannot block and takes an ordinary scheduler turn; any other call
1363 /// may wait in the kernel, so it runs as a blocking external operation. The
1364 /// recorder captures the descriptor sets and remaining time, so replay never
1365 /// asks the kernel about descriptors it did not recreate.
1366 #[cfg(target_arch = "x86_64")]
1367 async fn record_or_replay_select_family<G: Guest<Self>>(
1368 &self,
1369 guest: &mut G,
1370 call: Syscall,
1371 zero_timeout: bool,
1372 ) -> Result<i64, Error> {
1373 if zero_timeout {
1374 resource_request(guest, Resources::new(guest.thread_state().dettid)).await;
1375 Ok(self.record_or_replay(guest, call).await?)
1376 } else {
1377 self.record_or_replay_shielded_wait(guest, call).await
1378 }
1379 }
1380
1381 /// Refuses a large-nfds pselect6 with a temporary mask, or a record/replay
1382 /// one that may wait (`record_or_replay_pselect6_under_a_private_mask`),
1383 /// whose private copy has nowhere to go: the stack has no writable scratch below its red zone,
1384 /// and every red-zone window overlaps one of the call's own buffers
1385 /// (`stage_private_cells`). Forwarding the guest's wrapper instead
1386 /// would let a sibling change the mask the kernel installs after the
1387 /// scheduler has recorded the one it waits for, so the call could time out
1388 /// with a wrong answer (Codex re-check #4 of
1389 /// https://github.com/rrnewton/hermit/pull/4053). A fail-closed run stops
1390 /// with the policy refusal and this message. A run started with
1391 /// `--allow-unsupported-syscalls` runs on: the call keeps the guest's
1392 /// wrapper, whose mask a sibling may still change, so the scheduler records
1393 /// its sleeping mask as unknown (`Resources::blocked_signal_mask_unknown`),
1394 /// not the thread's own, and arms no entry barrier for it (Codex re-check
1395 /// #5).
1396 async fn refuse_unstageable_pselect6_mask<G: Guest<Self>>(
1397 &self,
1398 guest: &mut G,
1399 call: syscalls::Pselect6,
1400 ) -> Result<i64, Error> {
1401 use std::io::Write as _;
1402 let message = "unsupported: pselect6 with a temporary signal mask, on a stack with \
1403 no room below its red zone whose red zone the call's own buffers fill: Hermit \
1404 has nowhere to put the private copy of the mask the kernel must install \
1405 (https://github.com/rrnewton/hermit/pull/4053)";
1406 tracing::error!("[tid {}] {message}", guest.tid());
1407 if self.cfg.panic_on_unsupported_syscalls {
1408 let _ = writeln!(crate::util::RetryingStderr, "{message}");
1409 return self
1410 .refuse_unserviceable_operation(guest, Sysno::pselect6, Errno::EFAULT)
1411 .await;
1412 }
1413 self.record_or_replay_blocking_with_unknown_mask(guest, Syscall::Pselect6(call))
1414 .await
1415 }
1416
1417 /// Refuses a record/replay pselect6 with a temporary mask on a stack less
1418 /// than 24 bytes above unwritable memory, where replay could not deliver a
1419 /// signal that interrupts it (`replayed_pselect6_delivery_fits`). Recording
1420 /// and replay refuse it alike. A fail-closed run stops with the policy
1421 /// refusal and this message, whose result this returns. A run started
1422 /// with `--allow-unsupported-syscalls` records the call as before, and a
1423 /// replay of an interrupted one stops at the next syscall; this returns
1424 /// `None` for it.
1425 async fn refuse_undeliverable_pselect6_mask<G: Guest<Self>>(
1426 &self,
1427 guest: &mut G,
1428 ) -> Option<Result<i64, Error>> {
1429 use std::io::Write as _;
1430 let message = "unsupported: pselect6 with a temporary signal mask on a stack less than \
1431 24 bytes above unwritable memory: replay has nowhere to deliver a signal that \
1432 interrupts it (https://github.com/rrnewton/hermit/issues/4112)";
1433 tracing::error!("[tid {}] {message}", guest.tid());
1434 if !self.cfg.panic_on_unsupported_syscalls {
1435 return None;
1436 }
1437 let _ = writeln!(crate::util::RetryingStderr, "{message}");
1438 Some(
1439 self.refuse_unserviceable_operation(guest, Sysno::pselect6, Errno::EFAULT)
1440 .await,
1441 )
1442 }
1443
1444 /// Records or replays a masked pselect6 that may wait, under a private
1445 /// copy of the mask read at its entry (`mask`), staged clear of the fd
1446 /// sets and timeout the kernel reads and writes (`stage_private_cells`).
1447 /// The outer `Err` is a staging failure; a call with no room for the copy
1448 /// is refused (`refuse_unstageable_pselect6_mask`).
1449 async fn record_or_replay_pselect6_under_a_private_mask<G: Guest<Self>>(
1450 &self,
1451 guest: &mut G,
1452 call: syscalls::Pselect6,
1453 mask: KernelSigset,
1454 ) -> Result<Result<i64, Error>, Error> {
1455 let set_len = pselect6_fd_set_len(call.nfds()).unwrap_or(0);
1456 let live: Vec<std::ops::Range<usize>> = [
1457 call.readfds().map(|set| (set.as_raw(), set_len)),
1458 call.writefds().map(|set| (set.as_raw(), set_len)),
1459 call.exceptfds().map(|set| (set.as_raw(), set_len)),
1460 call.timeout()
1461 .map(|timeout| (timeout.as_raw(), std::mem::size_of::<Timespec>())),
1462 ]
1463 .into_iter()
1464 .flatten()
1465 .map(|(start, len)| start..start.saturating_add(len))
1466 .collect();
1467 let Some(staged) =
1468 stage_private_cells(guest, PrivateCells::Pselect6Mask(mask), &live).await?
1469 else {
1470 return Ok(self.refuse_unstageable_pselect6_mask(guest, call).await);
1471 };
1472 let result = self
1473 .record_or_replay_blocking_with_unknown_mask(
1474 guest,
1475 Syscall::Pselect6(call.with_sigmask(Addr::from_raw(staged.after_mask()))),
1476 )
1477 .await;
1478 staged.release(guest);
1479 Ok(result)
1480 }
1481
1482 /// pselect6 syscall (MAYHANG).
1483 // AUTONOMOUS-BOT-IMPLEMENTED
1484 // TODO-HUMAN-REVIEW(#686): Review scratch fd sets and scheduler polling.
1485 pub async fn handle_pselect6<G: Guest<Self>>(
1486 &self,
1487 guest: &mut G,
1488 call: syscalls::Pselect6,
1489 ) -> Result<i64, Error> {
1490 if self.cfg.recordreplay_modes {
1491 let zero_timeout = call.timeout().is_some_and(|timeout| {
1492 guest
1493 .memory()
1494 .read_value(timeout)
1495 .is_ok_and(|timeout: Timespec| timeout.tv_sec == 0 && timeout.tv_nsec == 0)
1496 });
1497 // A call with a `{ sigmask, sigsetsize }` wrapper may sleep under a
1498 // temporary mask, and the scheduler must not take the thread's
1499 // ordinary mask for it: it would arm the release barrier for a
1500 // signal the call blocks and wait for a wake Linux never delivers
1501 // (https://github.com/rrnewton/hermit/issues/3963). Nor can this turn
1502 // read the mask the kernel will install: the wrapper may sit in
1503 // memory a tracer read cannot reach though the kernel can, and a
1504 // sibling may rewrite the mask before the kernel copies it. So the
1505 // scheduler records no mask for such a call
1506 // (`Resources::blocked_signal_mask_unknown`). Without a wrapper the
1507 // call sleeps under the thread's own mask, which the scheduler reads.
1508 // A call that may wait sleeps under a private copy of its mask,
1509 // read here, at its entry, where Linux copies it. Handing the kernel
1510 // the guest's wrapper let a sibling turn that ran after the grant
1511 // change the mask the kernel copied, at a point host timing chose,
1512 // and replay could not tell (https://github.com/rrnewton/hermit/issues/4095).
1513 // Replay reads the mask at the same point and delivers a recorded
1514 // interruption under it. A zero-timeout call holds its turn until
1515 // it returns, so no sibling runs before the kernel copies the mask,
1516 // and replay reads it once the call is granted (review of
1517 // https://github.com/rrnewton/hermit/pull/4084). A zero timeout still
1518 // installs the mask and ends the call with ERESTARTNOHAND for a
1519 // pending signal it unblocks
1520 // (https://github.com/rrnewton/hermit/issues/4054).
1521 //
1522 // The entry read is the call's first injection, which a signal can
1523 // stop and restart (`restart_if_first_probe_not_run`), so it comes
1524 // before the refusal check: that logs, and the restarted call would
1525 // log again.
1526 // `None` inside when the call names no mask, or one the kernel
1527 // rejects or the tracer cannot read, though the kernel can: such a
1528 // call keeps the guest's own wrapper.
1529 let entry_mask = if call.sigmask().is_some() && !zero_timeout {
1530 Some(entry_pselect6_temporary_mask(guest, call).await?)
1531 } else {
1532 None
1533 };
1534 if !replayed_pselect6_delivery_fits(guest, call).await
1535 && let Some(refused) = self.refuse_undeliverable_pselect6_mask(guest).await
1536 {
1537 return refused;
1538 }
1539 let read_mask = self.cfg.replaying && call.sigmask().is_some();
1540 let mut temporary_mask = None;
1541 let result = if let Some(entry_mask) = entry_mask {
1542 if read_mask {
1543 temporary_mask = entry_mask;
1544 }
1545 match entry_mask {
1546 Some(mask) => {
1547 self.record_or_replay_pselect6_under_a_private_mask(guest, call, mask)
1548 .await?
1549 }
1550 // A wrapper that names no mask leaves the thread's own
1551 // mask in place, which the scheduler reads and the shield
1552 // covers. The kernel is handed no wrapper, which Linux
1553 // takes alike, so a sibling cannot write a mask into the
1554 // guest's before the kernel reads it.
1555 None if pselect6_names_no_mask(guest, call) => {
1556 self.record_or_replay_shielded_wait(
1557 guest,
1558 Syscall::Pselect6(call.with_sigmask(None)),
1559 )
1560 .await
1561 }
1562 None => {
1563 self.record_or_replay_blocking_with_unknown_mask(
1564 guest,
1565 Syscall::Pselect6(call),
1566 )
1567 .await
1568 }
1569 }
1570 } else if zero_timeout {
1571 resource_request(guest, Resources::new(guest.thread_state().dettid)).await;
1572 if read_mask {
1573 temporary_mask = replayed_pselect6_temporary_mask(guest, call);
1574 }
1575 self.record_or_replay(guest, Syscall::Pselect6(call))
1576 .await
1577 .map_err(Into::into)
1578 } else {
1579 self.record_or_replay_shielded_wait(guest, Syscall::Pselect6(call))
1580 .await
1581 };
1582 if let Some(mask) = temporary_mask
1583 && matches!(result, Err(Error::Errno(Errno::ERESTARTNOHAND)))
1584 {
1585 end_replayed_pselect6_under_its_temporary_mask(guest, mask).await;
1586 }
1587 return result;
1588 }
1589 if !self.cfg.sequentialize_threads {
1590 // A call with a mask waits under it; one that names none is
1591 // shielded as select is, and the kernel is handed no wrapper (as
1592 // under record), since a sibling runs concurrently here.
1593 if !pselect6_names_no_mask(guest, call) {
1594 return Ok(guest.inject(call).await?);
1595 }
1596 let may_wait = !call.timeout().is_some_and(|timeout| {
1597 guest
1598 .memory()
1599 .read_value(timeout)
1600 .is_ok_and(|timeout: Timespec| timeout.tv_sec == 0 && timeout.tv_nsec == 0)
1601 });
1602 return self
1603 .inject_shielded_wait(guest, call.with_sigmask(None), may_wait)
1604 .await;
1605 }
1606 // Under the in-guest delivery gate (A5 step 3c.1d) the forms follow
1607 // design D39 and D50, and no probe carries the guest's temporary mask
1608 // (IR-9): `gated_pselect6`.
1609 if self.cfg.in_guest_signal_delivery {
1610 return Box::pin(self.gated_pselect6(guest, call)).await;
1611 }
1612
1613 if call.nfds() < 0 {
1614 return Ok(guest.inject(call).await?);
1615 }
1616
1617 // Linux copies pselect6's outer { sigmask, sigsetsize } wrapper, then
1618 // validates the timeout, then reads the inner mask (fs/select.c,
1619 // https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/fs/select.c?h=v6.12#n800
1620 // and #n752). Linux checks the wrapper here; its words are read only
1621 // after the timeout, because a tracer read can fail where the kernel's
1622 // copy succeeds: a write-only page is readable on x86, but not to
1623 // process_vm_readv. Read first, a malformed timeout there returned
1624 // EFAULT, not Linux's EINVAL (dh-obsc8).
1625 let sigmask_wrapper = match call.sigmask() {
1626 Some(argument) => {
1627 // A split read can fall back to PTRACE_PEEKDATA for the final
1628 // word, bypassing PROT_NONE or reporting EIO for an unmapped
1629 // page. Have Linux validate both wrapper words first. It copies
1630 // this wrapper before rejecting a malformed timeout, without
1631 // reading the inner mask, changing it, waiting, or writing output.
1632 validate_pselect6_wrapper::<_, Self>(guest, argument).await?;
1633 Some(argument)
1634 }
1635 None => None,
1636 };
1637 let raw_timeout = match call.timeout() {
1638 Some(timeout) => {
1639 let timeout: Timespec = guest.memory().read_value(timeout)?;
1640 Some(timeout)
1641 }
1642 None => None,
1643 };
1644 let timeout = raw_timeout.map(ppoll_timeout_duration).transpose()?;
1645 // Word by word, as `read_kernel_sigset` reads a mask: the probe above
1646 // showed Linux can copy both words, and on ptrace a word-sized read
1647 // is PTRACE_PEEKDATA, which reaches a write-only page that
1648 // process_vm_readv refuses. Backends that read through
1649 // process_vm_readv at every size (DBT, SaBRe, LiteInst and
1650 // in-guest-trap) still fail there with EFAULT, as does the timeout's
1651 // own read on every tracer-read backend
1652 // (https://github.com/rrnewton/hermit/issues/4530).
1653 let sigmask_argument = match sigmask_wrapper {
1654 Some(argument) => {
1655 let sigmask: usize = guest.memory().read_value(argument.cast::<usize>())?;
1656 let second = argument
1657 .as_raw()
1658 .checked_add(std::mem::size_of::<usize>())
1659 .and_then(Addr::<usize>::from_raw)
1660 .ok_or(Errno::EFAULT)?;
1661 let sigsetsize: usize = guest.memory().read_value(second)?;
1662 Some(Pselect6SigmaskArg {
1663 sigmask,
1664 sigsetsize,
1665 })
1666 }
1667 None => None,
1668 };
1669 if let Some(argument) = sigmask_argument
1670 && argument.sigmask != 0
1671 {
1672 // After the wrapper's validation: not the call's first injection.
1673 self.observe_under_temporary_mask(
1674 guest,
1675 Addr::<libc::sigset_t>::from_raw(argument.sigmask),
1676 argument.sigsetsize,
1677 false,
1678 )
1679 .await?;
1680 }
1681 if timeout == Some(Duration::ZERO) {
1682 // A virtual timerfd among the sets is readable exactly when its
1683 // deadline has passed (`crate::syscalls::timerfd`).
1684 crate::syscalls::timerfd::publish_expired(guest).await;
1685 let result = guest.inject(call).await;
1686 // A backend that runs the call itself answers ENOSYS for a pending
1687 // signal the mask lets in that it cannot act on inside the call
1688 // (`settle_masked_pselect6_enosys`).
1689 if result == Err(Errno::ENOSYS)
1690 && let Some(argument) = sigmask_argument
1691 && argument.sigmask != 0
1692 && argument.sigsetsize == KERNEL_SIGSET_SIZE
1693 && let Some(mask_addr) = Addr::<libc::sigset_t>::from_raw(argument.sigmask)
1694 {
1695 let mask = read_kernel_sigset(guest, mask_addr).await?;
1696 return self
1697 .settle_masked_pselect6_enosys(guest, call, sanitize_ppoll_signal_mask(mask))
1698 .await;
1699 }
1700 return Ok(result?);
1701 }
1702
1703 // Linux clamps raw fd-set copies to the process fd table's current max_fds.
1704 // Its initial table holds one machine word; larger nfds values can therefore
1705 // require fewer bytes than a userspace calculation predicts. Keep those calls
1706 // under kernel ownership rather than over-reading the guest bitmap.
1707 if call.nfds() > PSELECT6_INTERNAL_MAX_NFDS {
1708 // The kernel sleeps under the inner mask, so record it for the
1709 // scheduler's SIGCHLD target choice (`Resources::blocked_signal_mask`).
1710 // A mask the kernel would reject (wrong size, or a pointer it cannot
1711 // read) fails the real call before it sleeps or installs anything, so
1712 // the thread's own mask is the right record there (`None`).
1713 let snapshot = match sigmask_argument {
1714 Some(argument)
1715 if argument.sigmask != 0 && argument.sigsetsize == KERNEL_SIGSET_SIZE =>
1716 {
1717 match Addr::<libc::sigset_t>::from_raw(argument.sigmask) {
1718 Some(mask_addr) => {
1719 let mask = read_kernel_sigset(guest, mask_addr).await.ok();
1720 if let Some(mask) = mask {
1721 super::signal_shadow::note_wait_mask(guest, mask);
1722 }
1723 mask
1724 }
1725 None => None,
1726 }
1727 }
1728 _ => None,
1729 };
1730 let Some(mask) = snapshot else {
1731 if sigmask_argument.is_none_or(|argument| argument.sigmask == 0) {
1732 // No mask, read in this turn: hand the kernel no wrapper,
1733 // as record does.
1734 return self
1735 .record_or_replay_shielded_wait(
1736 guest,
1737 Syscall::Pselect6(call.with_sigmask(None)),
1738 )
1739 .await;
1740 }
1741 return self
1742 .record_or_replay_blocking_with_mask(guest, Syscall::Pselect6(call), None)
1743 .await;
1744 };
1745 // The call sleeps under a private copy of the mask read here, not the
1746 // guest's wrapper: a sibling may rewrite the guest's buffer before the
1747 // released call reaches the kernel, and the scheduler waits for the
1748 // kernel to install exactly this mask (`background_entry_barriers`,
1749 // Codex review of https://github.com/rrnewton/hermit/pull/4053). The
1750 // wrapper and the mask were validated above, in Linux's order. The
1751 // copy stays readable until the call has rejoined.
1752 // The buffers Linux reads and writes, which the copy must not
1753 // overlap. The fd sets span at most their nominal length; Linux
1754 // may copy less.
1755 let set_len = pselect6_fd_set_len(call.nfds())?;
1756 let live: Vec<std::ops::Range<usize>> = [
1757 call.readfds().map(|set| (set.as_raw(), set_len)),
1758 call.writefds().map(|set| (set.as_raw(), set_len)),
1759 call.exceptfds().map(|set| (set.as_raw(), set_len)),
1760 call.timeout()
1761 .map(|timeout| (timeout.as_raw(), std::mem::size_of::<Timespec>())),
1762 ]
1763 .into_iter()
1764 .flatten()
1765 .map(|(start, len)| start..start.saturating_add(len))
1766 .collect();
1767 let Some(staged) =
1768 stage_private_cells(guest, PrivateCells::Pselect6Mask(mask), &live).await?
1769 else {
1770 return self.refuse_unstageable_pselect6_mask(guest, call).await;
1771 };
1772 let result = self
1773 .record_or_replay_blocking_with_mask(
1774 guest,
1775 Syscall::Pselect6(call.with_sigmask(Addr::from_raw(staged.after_mask()))),
1776 Some(kernel_installed_signal_mask(mask)),
1777 )
1778 .await;
1779 staged.release(guest);
1780 // Linux's pselect6 never fails with ENOSYS. A backend that runs
1781 // the call itself answers it for more descriptors than it serves,
1782 // which it declines before it looks at any signal: refused by name,
1783 // as the zero-timeout call is (`settle_masked_pselect6_enosys`),
1784 // never handed to the guest.
1785 if let Err(Error::Errno(Errno::ENOSYS)) = result {
1786 let reason = masked_pselect6_refusal_reason_now(guest, mask, call.nfds());
1787 return self
1788 .refuse_masked_wait_because(guest, Sysno::pselect6, reason)
1789 .await;
1790 }
1791 return result;
1792 }
1793
1794 // Linux wraps pselect6's temporary mask in { pointer, size }. Glibc supplies
1795 // the wrapper even when the inner pointer is null. A real mask must stay in
1796 // effect for the whole wait so an unblocked signal (make's jobserver unblocks
1797 // SIGCHLD) can interrupt it. Previously that forced the external-blocking path,
1798 // whose completion timing is host-decided and is a source of `make -jN`
1799 // execution-log divergence. With SIGCHLD admission now deterministic (scheduler
1800 // `sigchld_deferred`/`sigchld_ready`), honor the mask on each deterministic poll
1801 // probe instead: a pending unblocked signal is observed at a scheduler-decided
1802 // probe point rather than at host signal-arrival time.
1803 let sigmask = if let Some(argument) = sigmask_argument {
1804 if argument.sigmask != 0 {
1805 if argument.sigsetsize != KERNEL_SIGSET_SIZE {
1806 return Err(Errno::EINVAL.into());
1807 }
1808 let mask_addr =
1809 Addr::<libc::sigset_t>::from_raw(argument.sigmask).ok_or(Errno::EFAULT)?;
1810 let mask = read_kernel_sigset(guest, mask_addr).await?;
1811 super::signal_shadow::note_wait_mask(guest, mask);
1812 Some(sanitize_ppoll_signal_mask(mask))
1813 } else {
1814 None
1815 }
1816 } else {
1817 None
1818 };
1819 // The inner mask was snapshotted above. Do not let later guest mutations of the
1820 // outer wrapper change the meaning of a retry probe.
1821 let call = call.with_sigmask(None);
1822
1823 self.handle_internal_pselect6(guest, call, timeout, sigmask)
1824 .await
1825 }
1826
1827 async fn handle_internal_pselect6<G: Guest<Self>>(
1828 &self,
1829 guest: &mut G,
1830 call: syscalls::Pselect6,
1831 timeout: Option<Duration>,
1832 sigmask: Option<u64>,
1833 ) -> Result<i64, Error> {
1834 let len = pselect6_fd_set_len(call.nfds())?;
1835 // Under the in-guest delivery gate (A5 step 3c.1d, D50 reader (3)):
1836 // no probe carries the temporary mask (IR-9); each turn that finds
1837 // nothing ready decides from the thread's `/proc` status under the
1838 // temporary mask (`temporary_mask_check`), or under the guest's own
1839 // mask without one (`gated_wait_ends`); a masked call's first probe
1840 // and its check run in the call's own turn, before any request, so
1841 // that check sees exactly the instances pending at entry (IR-7,
1842 // round-1 ruling 2), and a result that first probe finds is returned
1843 // after one yielding empty request
1844 // (`temporary_mask_forms::yield_after_in_turn_result`, round-2 ruling
1845 // 5); the request publishes the temporary mask; and a set Detcore
1846 // cannot read is EFAULT only where Linux's copy faults
1847 // (`temporary_mask_forms::read_gated_fd_sets`).
1848 let gated = self.cfg.in_guest_signal_delivery;
1849 // A masked call under the gate observes the clock only after its first
1850 // probe and check (round-3 ruling 1,
1851 // `temporary_mask_forms::observe_deadline`): `deferred` holds the
1852 // timeout until then. Gate off, as before.
1853 let in_turn_first = gated && sigmask.is_some();
1854 let deferred = if in_turn_first { timeout } else { None };
1855 let deadline = match timeout {
1856 Some(timeout) if !in_turn_first => Some(thread_observe_time(guest).await + timeout),
1857 _ => None,
1858 };
1859 // Gate off, the three sets are read into locals as before the gate,
1860 // with no allocation (round-2 ruling 1).
1861 let (original_readfds, original_writefds, original_exceptfds) = if gated {
1862 // Boxed: the gate-off future stays its size.
1863 match Box::pin(temporary_mask_forms::read_gated_fd_sets(
1864 guest, "pselect6", call, len,
1865 ))
1866 .await
1867 {
1868 Ok(sets) => sets,
1869 Err(error) => {
1870 let deadline = match deferred {
1871 Some(_) => temporary_mask_forms::observe_deadline(guest, deferred)
1872 .await
1873 .map(|(_, deadline)| deadline),
1874 None => deadline,
1875 };
1876 self.write_pselect6_remaining(guest, call, deadline).await?;
1877 return Err(error);
1878 }
1879 }
1880 } else {
1881 let original_readfds = match read_pselect6_fd_set(guest, call.readfds(), len) {
1882 Ok(value) => value,
1883 Err(error) => {
1884 self.write_pselect6_remaining(guest, call, deadline).await?;
1885 return Err(error);
1886 }
1887 };
1888 let original_writefds = match read_pselect6_fd_set(guest, call.writefds(), len) {
1889 Ok(value) => value,
1890 Err(error) => {
1891 self.write_pselect6_remaining(guest, call, deadline).await?;
1892 return Err(error);
1893 }
1894 };
1895 let original_exceptfds = match read_pselect6_fd_set(guest, call.exceptfds(), len) {
1896 Ok(value) => value,
1897 Err(error) => {
1898 self.write_pselect6_remaining(guest, call, deadline).await?;
1899 return Err(error);
1900 }
1901 };
1902 (original_readfds, original_writefds, original_exceptfds)
1903 };
1904
1905 let mut stack = guest.stack().await;
1906 let readfds = call.readfds().map(|_| stack.reserve::<libc::fd_set>());
1907 let writefds = call.writefds().map(|_| stack.reserve::<libc::fd_set>());
1908 let exceptfds = call.exceptfds().map(|_| stack.reserve::<libc::fd_set>());
1909 // pselect6's timeout is a writable in-out kernel timespec, so the probe
1910 // needs a mutable scratch cell (re-zeroed each iteration below to keep
1911 // every probe a non-blocking poll).
1912 let probe_timeout = stack.reserve::<Timespec>();
1913 // Carry the temporary signal mask on every zero-timeout probe so the kernel
1914 // applies it atomically: a pending, mask-unblocked signal makes the probe return
1915 // EINTR at a deterministic scheduler point. The probe's wrapper points at scratch
1916 // memory the guard keeps alive across each injection.
1917 // On a backend whose kernel reports the guest's signal state, a
1918 // `KernelSignalWait` below decides interruption against the temporary
1919 // mask instead (`with_wait_mask`), and the probes carry no mask.
1920 let kernel_signal_wait = self.cfg.backend.supports_blocked_wait_signal_interruption;
1921 // The probes' mask cell and the mask it holds, kept to write back after
1922 // a removal in that cell (`remove_signals_discarded_under_a_temporary_mask_in`).
1923 let probe_mask_cell = sigmask
1924 .filter(|_| !kernel_signal_wait && !gated)
1925 .map(|mask| (stack.push(mask).as_raw(), mask));
1926 let probe_sigmask = probe_mask_cell.map(|(sigset, _)| {
1927 stack
1928 .push(Pselect6SigmaskArg {
1929 sigmask: sigset,
1930 sigsetsize: KERNEL_SIGSET_SIZE,
1931 })
1932 .cast()
1933 });
1934 // On a backend whose kernel reports the guest's signal state, only a
1935 // signal that would end the wait natively does; the mask cell lets the
1936 // wait block signals under the one scratch-stack guard
1937 // (https://github.com/rrnewton/hermit/issues/3146). A temporary mask is
1938 // the mask the call sleeps under, as Linux installs it for the whole
1939 // call: a signal it blocks never ends the call, and one it unblocks ends
1940 // it although the guest's own mask blocks it (`with_wait_mask`,
1941 // https://github.com/rrnewton/hermit/issues/3991). A restart resumes from
1942 // the remaining time written back below, so a default stop ends the wait
1943 // as on Linux. Unlike the other waits, it holds no signal until the call
1944 // returns, so a pending caught `SIGCHLD` ends it as on Linux, whoever
1945 // sent it, at a turn that host timing can choose: a known gap that this
1946 // wait had before (`KernelSignalWait::for_select`).
1947 let mut signals = kernel_signal_wait.then(|| {
1948 KernelSignalWait::for_select(guest)
1949 .with_wait_mask(sigmask.map(crate::syscalls::signal::kernel_installed_signal_mask))
1950 });
1951 // Set when a signal the temporary mask unblocks, pending in the kernel,
1952 // ends the call: the kernel must then deliver it under that mask (see
1953 // below).
1954 let mut deliver_under_temporary_mask = false;
1955 let mask_cell = signals.as_ref().map(|_| stack.reserve::<KernelSigset>());
1956 // This frame holds the scratch stack across every probe, so the
1957 // memory that makes an expired timerfd readable is reserved here
1958 // (`crate::syscalls::timerfd::publish_expired_in`).
1959 let timerfd_scratch = crate::syscalls::timerfd::reserve_scratch(&mut stack);
1960 let _guard = stack.commit()?;
1961 // The scheduler reads the effective mask while the wait parks
1962 // (`publish_wait_mask`).
1963 let mut published = PublishedWaitMask::default();
1964 if let Some(signals) = signals.as_ref() {
1965 publish_wait_mask(guest, signals.effective_mask(), &mut published).await;
1966 }
1967 let probe = call
1968 .with_readfds(readfds)
1969 .with_writefds(writefds)
1970 .with_exceptfds(exceptfds)
1971 .with_timeout(Some(probe_timeout))
1972 .with_sigmask(probe_sigmask);
1973
1974 let mut resources = Resources::new(guest.thread_state().dettid);
1975 resources.insert(ResourceID::InternalIOPolling, Permission::W);
1976 resources.fyi("pselect6");
1977 resources.poll_deadline = poll_deadline_for(guest, deadline);
1978 if gated {
1979 resources.blocked_signal_mask = sigmask;
1980 }
1981 // Keep the request metadata accurate, but do not make it eligible for
1982 // the scheduler's ERESTARTSYS wakeup. A cross-task signal must first be
1983 // checked against pselect6's snapshotted temporary mask and disposition.
1984 resources.set_signal_interrupt_errno(Errno::EINTR);
1985
1986 // A masked call's entry and end under the gate (A5 step 3c.1e A,
1987 // `temporary_mask_forms::TemporaryMaskCall`: IR-23 and D37's R2).
1988 // Boxed, and `None` with the gate off, so the gate-off future stays
1989 // its size.
1990 let mut masked: Option<Box<temporary_mask_forms::TemporaryMaskCall>> = None;
1991 // Every exit, including a `?` on a guest-memory write, must reach the mask
1992 // restore below, so the loop runs in its own async block.
1993 let result: Result<i64, Error> = {
1994 // Moved reborrows: a future holding a shared reference to a scratch
1995 // address would not be `Send`.
1996 let guest = &mut *guest;
1997 let signals = &mut signals;
1998 let resources = &mut resources;
1999 let published = &mut published;
2000 let deliver_under_temporary_mask = &mut deliver_under_temporary_mask;
2001 let masked = &mut masked;
2002 async move {
2003 // Under the gate a masked call's first probe and its check run
2004 // in the call's own turn (see `entry` above); an unmasked one,
2005 // which has no entry rule, requests before each probe as
2006 // `select` does (round-2 ruling 4).
2007 let mut first_turn = in_turn_first;
2008 let mut deadline = deadline;
2009 let mut deferred = deferred;
2010 // The at-entry set of the first check, from the entry before
2011 // the first timerfd publication (`TemporaryMaskCall::enter`),
2012 // and the deadline, observed after the first probe and check
2013 // (`observe_deadline`; round-3 ruling 1), or at the entry with
2014 // an armed virtual timerfd (IR-23(c)).
2015 if in_turn_first && let Some(mask) = sigmask {
2016 let call = Box::pin(temporary_mask_forms::TemporaryMaskCall::enter(
2017 guest, mask, deferred,
2018 ))
2019 .await?;
2020 if call.observes_deadline_at_entry() {
2021 deadline = call.entry_deadline().map(|(_, deadline)| deadline);
2022 deferred = None;
2023 }
2024 *masked = Some(Box::new(call));
2025 }
2026 let mut entry = masked.as_deref().map(|call| call.entry());
2027 macro_rules! settle_deadline {
2028 () => {
2029 if deferred.is_some() {
2030 deadline = Box::pin(temporary_mask_forms::observe_deadline(
2031 guest,
2032 deferred.take(),
2033 ))
2034 .await
2035 .map(|(_, deadline)| deadline);
2036 }
2037 };
2038 }
2039 loop {
2040 let in_turn = first_turn;
2041 if !in_turn && let Some(call) = masked.as_deref_mut() {
2042 call.note_request();
2043 }
2044 let signaled = !in_turn
2045 && matches!(
2046 resource_request(guest, resources.clone()).await,
2047 ResumeStatus::Signaled(_)
2048 );
2049 first_turn = false;
2050 // Virtual timerfds first (`crate::syscalls::timerfd`).
2051 let next_timerfd =
2052 crate::syscalls::timerfd::publish_expired_in(guest, Some(timerfd_scratch))
2053 .await;
2054 resources.poll_deadline =
2055 crate::syscalls::timerfd::poll_deadline_with(guest, deadline, next_timerfd);
2056 // A pending real-time timer expiration that this wait would
2057 // be ended by, under the guest's mask or the call's temporary
2058 // one (https://github.com/rrnewton/hermit/issues/4089).
2059 let pending = pending_real_time_among(guest, REAL_TIME_SIGNALS).await;
2060 if pending != 0 {
2061 let (observable, discarded) = match (signals.as_ref(), sigmask) {
2062 (Some(signals), _) => (signals.observable_now()?, 0),
2063 (None, Some(mask)) => unblocked_by(
2064 &read_thread_wait_signal_state(guest.pid(), guest.tid())?,
2065 mask,
2066 ),
2067 (None, None) => (0, 0),
2068 };
2069 act_on_pending_real_time(guest, pending, observable, discarded).await;
2070 }
2071 // Every turn probes before it looks at pending signals, as Linux
2072 // does: do_select polls every descriptor on each pass and returns
2073 // the count of ready ones before it tests signal_pending, and at
2074 // the start max_select_fd returns EBADF for a descriptor that is
2075 // not open (`core_sys_select`). So a descriptor that becomes ready
2076 // in a later turn in which a signal is also pending is reported
2077 // (round-12 Medium on https://github.com/rrnewton/hermit/pull/3361).
2078 // Each probe is a new select call, so a descriptor closed during
2079 // the wait gets EBADF, where Linux reports it ready. Without the
2080 // kernel's signal state, a scheduler `Signaled` answer still ends
2081 // the call before the probe.
2082 if signals.is_none() && signaled && !gated {
2083 self.write_pselect6_remaining(guest, call, deadline).await?;
2084 return Err(Errno::EINTR.into());
2085 }
2086 guest.memory().write_value(
2087 probe_timeout,
2088 &Timespec {
2089 tv_sec: 0,
2090 tv_nsec: 0,
2091 },
2092 )?;
2093 write_pselect6_fd_set(guest, probe.readfds(), &original_readfds)?;
2094 write_pselect6_fd_set(guest, probe.writefds(), &original_writefds)?;
2095 write_pselect6_fd_set(guest, probe.exceptfds(), &original_exceptfds)?;
2096
2097 let raw_result = match signals.as_mut() {
2098 // A signal stop that does not end the wait is absorbed (see
2099 // `KernelSignalWait`). Every probe runs before the turn's check
2100 // (`inject_first_probe`): a signal that would end the wait and
2101 // stops the probe is held, the probe runs again, and the signal
2102 // is delivered as the call returns. Once `block` has blocked
2103 // every blockable signal, a pending one cannot stop the probe.
2104 Some(signals) => match signals.inject_first_probe(guest, probe).await {
2105 Ok(result) => result,
2106 Err(error) => {
2107 self.write_pselect6_remaining(guest, call, deadline).await?;
2108 break Err(error);
2109 }
2110 },
2111 None => guest.inject(probe).await,
2112 };
2113 let mut result = pselect6_probe_result(raw_result);
2114 // A backend that runs the call itself answers ENOSYS for a
2115 // pending signal the mask lets in that it cannot act on, as
2116 // it does for ppoll (`settle_masked_ppoll_enosys`): ignored
2117 // ones alone are removed, in the probe's own cells, as
2118 // Linux discards them, and the probe runs again with its
2119 // cells as it read them. Anything else is refused by name.
2120 if result == Err(Errno::ENOSYS)
2121 && let Some((mask_cell, mask)) = probe_mask_cell
2122 {
2123 if let Some(set) = ignored_signals_let_in_now(guest, mask) {
2124 crate::syscalls::helpers::remove_signals_discarded_under_a_temporary_mask_in(
2125 guest,
2126 set,
2127 mask_cell,
2128 probe_timeout,
2129 )
2130 .await?;
2131 guest.memory().write_value(
2132 AddrMut::<KernelSigset>::from_raw(mask_cell).ok_or(Errno::EFAULT)?,
2133 &mask,
2134 )?;
2135 guest.memory().write_value(
2136 probe_timeout,
2137 &Timespec {
2138 tv_sec: 0,
2139 tv_nsec: 0,
2140 },
2141 )?;
2142 write_pselect6_fd_set(guest, probe.readfds(), &original_readfds)?;
2143 write_pselect6_fd_set(guest, probe.writefds(), &original_writefds)?;
2144 write_pselect6_fd_set(guest, probe.exceptfds(), &original_exceptfds)?;
2145 result = pselect6_probe_result(guest.inject(probe).await);
2146 }
2147 if result == Err(Errno::ENOSYS) {
2148 self.write_pselect6_remaining(guest, call, deadline).await?;
2149 let reason =
2150 masked_pselect6_refusal_reason_now(guest, mask, call.nfds());
2151 break self
2152 .refuse_masked_wait_because(guest, Sysno::pselect6, reason)
2153 .await;
2154 }
2155 }
2156 if result != Ok(0) {
2157 let copy_result = if result.is_ok() {
2158 self.copy_pselect6_results(guest, probe, call, len)
2159 } else {
2160 Ok(())
2161 };
2162 settle_deadline!();
2163 self.write_pselect6_remaining(guest, call, deadline).await?;
2164 // D37's R2 as the result is decided, before the yield
2165 // (A5 step 3c.1e A).
2166 let decided: Result<i64, Error> = match &result {
2167 Ok(count) => Ok(*count),
2168 Err(errno) => Err((*errno).into()),
2169 };
2170 if let Some(call) = masked.as_deref_mut()
2171 && let Err(error) = Box::pin(call.finish(guest, &decided)).await
2172 {
2173 break Err(error);
2174 }
2175 if in_turn {
2176 // Boxed: the gate-off future stays its size.
2177 Box::pin(temporary_mask_forms::yield_after_in_turn_result(guest)).await;
2178 }
2179 copy_result?;
2180 break result.map_err(Into::into);
2181 }
2182 // The turn's check, after its probe found no descriptor ready and no
2183 // error: only then does a pending signal end the call. A scheduler
2184 // `Signaled` answer is only a hint here; the kernel's state decides.
2185 // pselect6 returns ERESTARTNOHAND: EINTR after a handler, a restart
2186 // with the remaining timeout after a stop.
2187 if gated {
2188 let ends = match sigmask {
2189 Some(mask) => {
2190 // Boxed: the gate-off future stays its size.
2191 let check =
2192 Box::pin(crate::syscalls::helpers::temporary_mask_check(
2193 guest,
2194 mask,
2195 entry.take(),
2196 false,
2197 ))
2198 .await;
2199 if let (Ok(check), Some(call)) = (&check, masked.as_deref_mut()) {
2200 call.note_check(*check);
2201 }
2202 match check {
2203 Ok(crate::syscalls::helpers::TemporaryMaskCheck::Waits) => None,
2204 Ok(crate::syscalls::helpers::TemporaryMaskCheck::Fatal) => {
2205 Some(Errno::ERESTARTNOHAND)
2206 }
2207 Ok(_) => Some(Errno::EINTR),
2208 Err(error) => break Err(error),
2209 }
2210 }
2211 None => match crate::syscalls::helpers::gated_wait_ends(guest, None) {
2212 Ok(true) => Some(Errno::ERESTARTNOHAND),
2213 Ok(false) => None,
2214 Err(error) => break Err(error),
2215 },
2216 };
2217 settle_deadline!();
2218 if let Some(errno) = ends {
2219 self.write_pselect6_remaining(guest, call, deadline).await?;
2220 break Err(errno.into());
2221 }
2222 resources.poll_deadline = crate::syscalls::timerfd::poll_deadline_with(
2223 guest,
2224 deadline,
2225 next_timerfd,
2226 );
2227 }
2228 if let Some(signals) = signals.as_ref() {
2229 match signals.interrupted_with_state() {
2230 Ok((false, _)) => {}
2231 Ok((true, state)) => {
2232 self.write_pselect6_remaining(guest, call, deadline).await?;
2233 // A signal the backend holds, or one the guest's own
2234 // mask leaves unblocked, is delivered as the call
2235 // returns. One that only the temporary mask unblocks
2236 // stays pending under the guest's own mask, and must be
2237 // delivered under the temporary mask.
2238 *deliver_under_temporary_mask = !signals.holds_signal()
2239 && signals.interrupts_only_under_wait_mask(&state);
2240 break Err(Errno::ERESTARTNOHAND.into());
2241 }
2242 Err(error) => break Err(error),
2243 }
2244 }
2245 if let Some(signals) = signals.as_mut()
2246 && signals.needs_block()
2247 && let Err(error) = signals.block(guest, mask_cell).await
2248 {
2249 self.write_pselect6_remaining(guest, call, deadline).await?;
2250 break Err(error);
2251 }
2252 if let Some(signals) = signals.as_ref() {
2253 publish_wait_mask(guest, signals.effective_mask(), published).await;
2254 }
2255
2256 resources.poll_attempt += 1;
2257 if let Some(deadline) = deadline
2258 && thread_observe_time(guest).await >= deadline
2259 {
2260 let copy_result = self.copy_pselect6_results(guest, probe, call, len);
2261 self.write_pselect6_remaining(guest, call, Some(deadline))
2262 .await?;
2263 copy_result?;
2264 break Ok(0);
2265 }
2266 trace!(
2267 "Retry #{} for syscall due to result Ok(0): {}",
2268 resources.poll_attempt,
2269 probe.display(&guest.memory())
2270 );
2271 record_retry_event(guest, probe).await;
2272 }
2273 }
2274 .await
2275 };
2276 // D37's R2 at any other end of a masked call (A5 step 3c.1e A); a
2277 // no-op once it ran before a yield, and with the gate off.
2278 let result = match masked.as_deref_mut() {
2279 Some(call) => match Box::pin(call.finish(guest, &result)).await {
2280 Ok(()) => result,
2281 Err(error) => Err(error),
2282 },
2283 None => result,
2284 };
2285 let result = match &mut signals {
2286 Some(signals) => {
2287 let restored = signals.restore(guest, mask_cell).await;
2288 publish_wait_mask(guest, None, &mut published).await;
2289 result_after_restore(restored, result)?
2290 }
2291 None => result,
2292 };
2293 // Restoring the guest's mask cannot deliver that signal, which the mask
2294 // blocks, but check again that the backend holds nothing.
2295 if deliver_under_temporary_mask
2296 && !signals.as_ref().is_some_and(KernelSignalWait::holds_signal)
2297 && let Some(mask) = sigmask
2298 && matches!(result, Err(Error::Errno(Errno::ERESTARTNOHAND)))
2299 {
2300 // Linux delivers such a signal before it puts the guest's own mask
2301 // back, which may block it, and the handler's frame restores the
2302 // guest's mask when it returns. Only the kernel can do both, so the
2303 // thread makes one more call, a ppoll with no descriptors, a zero
2304 // timeout and the call's temporary mask, as replay delivers a
2305 // recorded call's signal (`inject_signal_delivery`): the kernel
2306 // installs the mask, finds the signal pending, returns
2307 // ERESTARTNOHAND and delivers it as the call returns, EINTR after a
2308 // handler (https://github.com/rrnewton/hermit/issues/3991).
2309 // - The mask is a private copy of the one this call read at entry
2310 // (`sigmask`), as Linux copies it once at entry; the guest's
2311 // buffer, which a sibling may have rewritten since, is not read
2312 // again (review of https://github.com/rrnewton/hermit/pull/4051).
2313 // - A zero timeout never sleeps, and Linux writes no remaining time
2314 // back for it; the guest's timeout already holds the remaining
2315 // time written above.
2316 // - A plain injection, not a tail injection, so the rest of this
2317 // handler and Detcore's post-syscall work still run.
2318 drop(_guard);
2319 // No room for the cells cannot happen in practice: this call's
2320 // probe cells, at least as large, were committed in the same stack
2321 // scratch above. Should it happen, the signal stays pending under
2322 // the guest's own mask and the call returns EINTR without its
2323 // handler run, as before this delivery existed.
2324 match stage_private_cells(guest, PrivateCells::MaskAndZeroTimeout(mask), &[]).await? {
2325 Some(staged) => {
2326 inject_signal_delivery(guest, staged.mask(), staged.after_mask()).await;
2327 staged.release(guest);
2328 }
2329 None => tracing::warn!(
2330 "[tid {}] no room to deliver a pselect6 signal under its temporary mask; \
2331 its handler runs only once the guest unblocks it",
2332 guest.tid()
2333 ),
2334 }
2335 }
2336 refuse_held_signal_loss(guest, Sysno::pselect6, result).await
2337 }
2338
2339 fn copy_pselect6_results<G: Guest<Self>>(
2340 &self,
2341 guest: &mut G,
2342 probe: syscalls::Pselect6,
2343 call: syscalls::Pselect6,
2344 len: usize,
2345 ) -> Result<(), Error> {
2346 copy_pselect6_fd_set(guest, probe.readfds(), call.readfds(), len)?;
2347 copy_pselect6_fd_set(guest, probe.writefds(), call.writefds(), len)?;
2348 copy_pselect6_fd_set(guest, probe.exceptfds(), call.exceptfds(), len)
2349 }
2350
2351 pub(super) async fn write_pselect6_remaining<G: Guest<Self>>(
2352 &self,
2353 guest: &mut G,
2354 call: syscalls::Pselect6,
2355 deadline: Option<LogicalTime>,
2356 ) -> Result<(), Error> {
2357 if let (Some(timeout), Some(deadline)) = (call.timeout(), deadline) {
2358 let now = thread_observe_time(guest).await;
2359 let remaining = deadline.as_nanos().saturating_sub(now.as_nanos());
2360 let remaining = Timespec {
2361 tv_sec: (remaining / 1_000_000_000) as libc::time_t,
2362 tv_nsec: (remaining % 1_000_000_000) as libc::c_long,
2363 };
2364 // pselect6's timeout is a writable in-out kernel timespec; reverie-syscalls
2365 // now types it as `AddrMut<Timespec>`, so the remaining time can be written
2366 // back directly without an unsafe pointer cast.
2367 if let Err(error) = guest.memory().write_value(timeout, &remaining) {
2368 // Linux preserves the pselect6 result when remaining-time copyout faults.
2369 trace!(?error, "ignoring pselect6 timeout writeback failure");
2370 }
2371 }
2372 Ok(())
2373 }
2374
2375 /// select syscall (MAYHANG).
2376 ///
2377 /// `select` is the classic `timeval` sibling of `pselect6` (which is already
2378 /// Determinized). It reuses the pselect6 fd-set scratch machinery, but takes
2379 /// a `struct timeval` timeout (which Linux updates in place with the time not
2380 /// slept) and carries no signal mask.
2381 // AUTONOMOUS-BOT-IMPLEMENTED
2382 // TODO-HUMAN-REVIEW(#800): Review select determinization mirroring pselect6.
2383 #[cfg(target_arch = "x86_64")]
2384 pub async fn handle_select<G: Guest<Self>>(
2385 &self,
2386 guest: &mut G,
2387 call: syscalls::Select,
2388 ) -> Result<i64, Error> {
2389 if self.cfg.recordreplay_modes {
2390 let zero_timeout = call.timeout().is_some_and(|timeout| {
2391 guest
2392 .memory()
2393 .read_value(timeout)
2394 .is_ok_and(|timeout: libc::timeval| timeout.tv_sec == 0 && timeout.tv_usec == 0)
2395 });
2396 return self
2397 .record_or_replay_select_family(guest, Syscall::Select(call), zero_timeout)
2398 .await;
2399 }
2400 if !self.cfg.sequentialize_threads {
2401 // A zero timeout is not shielded, as it is not under record: a
2402 // pending signal ends such a call with ERESTARTNOHAND and Linux
2403 // runs it again in place, which the guest does not see, and every
2404 // probe would pay for two mask changes. A timeout the tracer
2405 // cannot read is shielded.
2406 let may_wait = !call.timeout().is_some_and(|timeout| {
2407 guest
2408 .memory()
2409 .read_value(timeout)
2410 .is_ok_and(|timeout: libc::timeval| timeout.tv_sec == 0 && timeout.tv_usec == 0)
2411 });
2412 return self.inject_shielded_wait(guest, call, may_wait).await;
2413 }
2414
2415 if call.nfds() < 0 {
2416 return Ok(guest.inject(call).await?);
2417 }
2418
2419 let raw_timeout = match call.timeout() {
2420 Some(timeout) => {
2421 let timeout: libc::timeval = guest.memory().read_value(timeout)?;
2422 Some(timeout)
2423 }
2424 None => None,
2425 };
2426 if matches!(raw_timeout, Some(timeout) if timeout.tv_sec == 0 && timeout.tv_usec == 0) {
2427 // A zero timeout is a pure non-blocking poll; the kernel can service it directly,
2428 // once a virtual timerfd among the sets is readable exactly when its deadline
2429 // has passed (`crate::syscalls::timerfd`).
2430 crate::syscalls::timerfd::publish_expired(guest).await;
2431 return Ok(guest.inject(call).await?);
2432 }
2433
2434 // Mirror pselect6: keep large fd tables under kernel ownership rather than
2435 // over-reading the guest bitmap (Linux clamps raw fd-set copies to max_fds).
2436 if call.nfds() > PSELECT6_INTERNAL_MAX_NFDS {
2437 return self
2438 .record_or_replay_shielded_wait(guest, Syscall::Select(call))
2439 .await;
2440 }
2441
2442 let timeout = raw_timeout.map(select_timeout_duration).transpose()?;
2443 self.handle_internal_select(guest, call, timeout).await
2444 }
2445
2446 #[cfg(target_arch = "x86_64")]
2447 async fn handle_internal_select<G: Guest<Self>>(
2448 &self,
2449 guest: &mut G,
2450 call: syscalls::Select,
2451 timeout: Option<Duration>,
2452 ) -> Result<i64, Error> {
2453 let len = pselect6_fd_set_len(call.nfds())?;
2454 let deadline = match timeout {
2455 Some(timeout) => Some(thread_observe_time(guest).await + timeout),
2456 None => None,
2457 };
2458 let original_readfds = match read_pselect6_fd_set(guest, call.readfds(), len) {
2459 Ok(value) => value,
2460 Err(error) => {
2461 self.write_select_remaining(guest, call, deadline).await?;
2462 return Err(error);
2463 }
2464 };
2465 let original_writefds = match read_pselect6_fd_set(guest, call.writefds(), len) {
2466 Ok(value) => value,
2467 Err(error) => {
2468 self.write_select_remaining(guest, call, deadline).await?;
2469 return Err(error);
2470 }
2471 };
2472 let original_exceptfds = match read_pselect6_fd_set(guest, call.exceptfds(), len) {
2473 Ok(value) => value,
2474 Err(error) => {
2475 self.write_select_remaining(guest, call, deadline).await?;
2476 return Err(error);
2477 }
2478 };
2479
2480 let mut stack = guest.stack().await;
2481 let readfds = call.readfds().map(|_| stack.reserve::<libc::fd_set>());
2482 let writefds = call.writefds().map(|_| stack.reserve::<libc::fd_set>());
2483 let exceptfds = call.exceptfds().map(|_| stack.reserve::<libc::fd_set>());
2484 // select modifies its timeout in place, so the probe timeout must be a
2485 // writable scratch cell. It is re-zeroed each iteration to keep every
2486 // probe a non-blocking poll (a NULL timeout would block indefinitely).
2487 let probe_timeout = stack.reserve::<libc::timeval>();
2488 // On a backend whose kernel reports the guest's signal state, only a signal
2489 // that would end the wait natively does; the mask cell lets the wait block
2490 // signals under the one scratch-stack guard
2491 // (https://github.com/rrnewton/hermit/issues/3146). A restart resumes from
2492 // the remaining time written back below, so a default stop ends the wait
2493 // as on Linux. Unlike the other waits, it holds no signal until the call
2494 // returns, so a pending caught `SIGCHLD` ends it as on Linux, whoever
2495 // sent it, at a turn that host timing can choose: a known gap that this
2496 // wait had before (`KernelSignalWait::for_select`).
2497 let mut signals = self
2498 .cfg
2499 .backend
2500 .supports_blocked_wait_signal_interruption
2501 .then(|| KernelSignalWait::for_select(guest));
2502 let mask_cell = signals.as_ref().map(|_| stack.reserve::<KernelSigset>());
2503 // This frame holds the scratch stack across every probe, so the
2504 // memory that makes an expired timerfd readable is reserved here
2505 // (`crate::syscalls::timerfd::publish_expired_in`).
2506 let timerfd_scratch = crate::syscalls::timerfd::reserve_scratch(&mut stack);
2507 let _guard = stack.commit()?;
2508 // The scheduler reads the effective mask while the wait parks
2509 // (`publish_wait_mask`).
2510 let mut published = PublishedWaitMask::default();
2511 if let Some(signals) = signals.as_ref() {
2512 publish_wait_mask(guest, signals.effective_mask(), &mut published).await;
2513 }
2514 let probe = call
2515 .with_readfds(readfds)
2516 .with_writefds(writefds)
2517 .with_exceptfds(exceptfds)
2518 .with_timeout(Some(probe_timeout));
2519
2520 // Under the in-guest delivery gate (A5 step 3c.1d, D50 reader (4)): a
2521 // turn that finds nothing ready decides from the thread's `/proc`
2522 // status under the mask it published, its own (`gated_wait_ends`).
2523 let gated = self.cfg.in_guest_signal_delivery;
2524 let mut resources = Resources::new(guest.thread_state().dettid);
2525 resources.insert(ResourceID::InternalIOPolling, Permission::W);
2526 resources.fyi("select");
2527 resources.poll_deadline = poll_deadline_for(guest, deadline);
2528 // EINTR records select's interruption result. The scheduler only wakes
2529 // ERESTARTSYS requests: blocked and ignored signals still need a target-
2530 // side disposition check before this Signaled path can be used.
2531 resources.set_signal_interrupt_errno(Errno::EINTR);
2532
2533 // Every exit, including a `?` on a guest-memory write, must reach the mask
2534 // restore below, so the loop runs in its own async block.
2535 let result: Result<i64, Error> = {
2536 // Moved reborrows: a future holding a shared reference to a scratch
2537 // address would not be `Send`.
2538 let guest = &mut *guest;
2539 let signals = &mut signals;
2540 let resources = &mut resources;
2541 let published = &mut published;
2542 async move {
2543 loop {
2544 let signaled = matches!(
2545 resource_request(guest, resources.clone()).await,
2546 ResumeStatus::Signaled(_)
2547 );
2548 // Virtual timerfds first (`crate::syscalls::timerfd`).
2549 let next_timerfd =
2550 crate::syscalls::timerfd::publish_expired_in(guest, Some(timerfd_scratch))
2551 .await;
2552 resources.poll_deadline =
2553 crate::syscalls::timerfd::poll_deadline_with(guest, deadline, next_timerfd);
2554 // A pending real-time timer expiration that this wait would
2555 // be ended by (https://github.com/rrnewton/hermit/issues/4089).
2556 let pending = pending_real_time_among(guest, REAL_TIME_SIGNALS).await;
2557 if pending != 0
2558 && let Some(signals) = signals.as_ref()
2559 {
2560 let observable = signals.observable_now()?;
2561 act_on_pending_real_time(guest, pending, observable, 0).await;
2562 }
2563 // Every turn probes before it looks at pending signals, as Linux
2564 // does: do_select polls every descriptor on each pass and returns
2565 // the count of ready ones before it tests signal_pending, and at
2566 // the start max_select_fd returns EBADF for a descriptor that is
2567 // not open (`core_sys_select`). So a descriptor that becomes ready
2568 // in a later turn in which a signal is also pending is reported
2569 // (round-12 Medium on https://github.com/rrnewton/hermit/pull/3361).
2570 // Each probe is a new select call, so a descriptor closed during
2571 // the wait gets EBADF, where Linux reports it ready. Without the
2572 // kernel's signal state, a scheduler `Signaled` answer still ends
2573 // the call before the probe.
2574 if signals.is_none() && signaled && !gated {
2575 self.write_select_remaining(guest, call, deadline).await?;
2576 return Err(Errno::EINTR.into());
2577 }
2578 guest.memory().write_value(
2579 probe_timeout,
2580 &libc::timeval {
2581 tv_sec: 0,
2582 tv_usec: 0,
2583 },
2584 )?;
2585 write_pselect6_fd_set(guest, probe.readfds(), &original_readfds)?;
2586 write_pselect6_fd_set(guest, probe.writefds(), &original_writefds)?;
2587 write_pselect6_fd_set(guest, probe.exceptfds(), &original_exceptfds)?;
2588
2589 let result = match signals.as_mut() {
2590 // A signal stop that does not end the wait is absorbed (see
2591 // `KernelSignalWait`). Every probe runs before the turn's check
2592 // (`inject_first_probe`): a signal that would end the wait and
2593 // stops the probe is held, the probe runs again, and the signal
2594 // is delivered as the call returns. Once `block` has blocked
2595 // every blockable signal, a pending one cannot stop the probe.
2596 Some(signals) => match signals.inject_first_probe(guest, probe).await {
2597 Ok(result) => result,
2598 Err(error) => {
2599 self.write_select_remaining(guest, call, deadline).await?;
2600 break Err(error);
2601 }
2602 },
2603 None => guest.inject(probe).await,
2604 };
2605 if result != Ok(0) {
2606 let copy_result = if result.is_ok() {
2607 self.copy_select_results(guest, probe, call, len)
2608 } else {
2609 Ok(())
2610 };
2611 self.write_select_remaining(guest, call, deadline).await?;
2612 copy_result?;
2613 break result.map_err(Into::into);
2614 }
2615 // The turn's check, after its probe found no descriptor ready and no
2616 // error: only then does a pending signal end the call. A scheduler
2617 // `Signaled` answer is only a hint here; the kernel's state decides.
2618 // select returns ERESTARTNOHAND: EINTR after a handler, a restart
2619 // with the remaining timeout after a stop.
2620 if gated {
2621 match crate::syscalls::helpers::gated_wait_ends(guest, None) {
2622 Ok(true) => {
2623 self.write_select_remaining(guest, call, deadline).await?;
2624 break Err(Errno::ERESTARTNOHAND.into());
2625 }
2626 Ok(false) => {}
2627 Err(error) => break Err(error),
2628 }
2629 }
2630 if let Some(signals) = signals.as_ref() {
2631 match signals.interrupted_with_state() {
2632 Ok((false, _)) => {}
2633 Ok((true, _)) => {
2634 self.write_select_remaining(guest, call, deadline).await?;
2635 break Err(Errno::ERESTARTNOHAND.into());
2636 }
2637 Err(error) => break Err(error),
2638 }
2639 }
2640 if let Some(signals) = signals.as_mut()
2641 && signals.needs_block()
2642 && let Err(error) = signals.block(guest, mask_cell).await
2643 {
2644 self.write_select_remaining(guest, call, deadline).await?;
2645 break Err(error);
2646 }
2647 if let Some(signals) = signals.as_ref() {
2648 publish_wait_mask(guest, signals.effective_mask(), published).await;
2649 }
2650
2651 resources.poll_attempt += 1;
2652 if let Some(deadline) = deadline
2653 && thread_observe_time(guest).await >= deadline
2654 {
2655 let copy_result = self.copy_select_results(guest, probe, call, len);
2656 self.write_select_remaining(guest, call, Some(deadline))
2657 .await?;
2658 copy_result?;
2659 break Ok(0);
2660 }
2661 trace!(
2662 "Retry #{} for syscall due to result Ok(0): {}",
2663 resources.poll_attempt,
2664 probe.display(&guest.memory())
2665 );
2666 record_retry_event(guest, probe).await;
2667 }
2668 }
2669 .await
2670 };
2671 let result = match &mut signals {
2672 Some(signals) => {
2673 let restored = signals.restore(guest, mask_cell).await;
2674 publish_wait_mask(guest, None, &mut published).await;
2675 result_after_restore(restored, result)?
2676 }
2677 None => result,
2678 };
2679 refuse_held_signal_loss(guest, Sysno::select, result).await
2680 }
2681
2682 #[cfg(target_arch = "x86_64")]
2683 fn copy_select_results<G: Guest<Self>>(
2684 &self,
2685 guest: &mut G,
2686 probe: syscalls::Select,
2687 call: syscalls::Select,
2688 len: usize,
2689 ) -> Result<(), Error> {
2690 copy_pselect6_fd_set(guest, probe.readfds(), call.readfds(), len)?;
2691 copy_pselect6_fd_set(guest, probe.writefds(), call.writefds(), len)?;
2692 copy_pselect6_fd_set(guest, probe.exceptfds(), call.exceptfds(), len)
2693 }
2694
2695 #[cfg(target_arch = "x86_64")]
2696 pub(super) async fn write_select_remaining<G: Guest<Self>>(
2697 &self,
2698 guest: &mut G,
2699 call: syscalls::Select,
2700 deadline: Option<LogicalTime>,
2701 ) -> Result<(), Error> {
2702 if let (Some(timeout), Some(deadline)) = (call.timeout(), deadline) {
2703 let now = thread_observe_time(guest).await;
2704 let remaining = deadline.as_nanos().saturating_sub(now.as_nanos());
2705 let remaining = libc::timeval {
2706 tv_sec: (remaining / 1_000_000_000) as libc::time_t,
2707 tv_usec: ((remaining % 1_000_000_000) / 1_000) as libc::suseconds_t,
2708 };
2709 // select's timeout is a writable in-out kernel timeval reporting the
2710 // time not slept; derive it from deterministic virtual time.
2711 if let Err(error) = guest.memory().write_value(timeout, &remaining) {
2712 // Linux preserves the select result when remaining-time copyout faults.
2713 trace!(?error, "ignoring select timeout writeback failure");
2714 }
2715 }
2716 Ok(())
2717 }
2718
2719 /// ppoll syscall (MAYHANG)
2720 // TODO-HUMAN-REVIEW(PR-273)
2721 pub async fn handle_ppoll<G: Guest<Self>>(
2722 &self,
2723 guest: &mut G,
2724 call: syscalls::Ppoll,
2725 ) -> Result<i64, Error> {
2726 // A masked ppoll under the in-guest delivery gate (A5 step 3c.1d, D50
2727 // reader (5)): `gated_masked_ppoll`, which reads the timeout itself.
2728 if self.cfg.in_guest_signal_delivery
2729 && call.sigmask().is_some()
2730 && !ppoll_uses_kernel_wait(self.cfg.sequentialize_threads, self.cfg.recordreplay_modes)
2731 {
2732 return Box::pin(self.gated_masked_ppoll(guest, call)).await;
2733 }
2734 let timeout_address = call.timeout();
2735 let timeout = match timeout_address {
2736 Some(timeout) => Some(ppoll_timeout_duration(guest.memory().read_value(timeout)?)?),
2737 None => None,
2738 };
2739
2740 let result: Result<i64, Error> = if timeout == Some(Duration::ZERO) {
2741 // In run mode the mask's read below is the call's first injection
2742 // unless a virtual timerfd's expiration is published first, which
2743 // can inject only for a process holding an armed one. Under record
2744 // and replay a scheduler request comes first, which changes
2745 // scheduler state (it publishes the thread's signal state and can
2746 // grant another thread a turn), so a restart would repeat it: the
2747 // read keeps the not-run report there
2748 // (https://github.com/rrnewton/hermit/issues/4526).
2749 let first = !self.cfg.recordreplay_modes
2750 && !crate::syscalls::timerfd::holds_armed_virtual_timerfd(guest);
2751 if self.cfg.recordreplay_modes {
2752 resource_request(guest, Resources::new(guest.thread_state().dettid)).await;
2753 } else {
2754 // A virtual timerfd among the fds is readable exactly when its
2755 // deadline has passed (`crate::syscalls::timerfd`); before the
2756 // probe's copies take the scratch stack.
2757 crate::syscalls::timerfd::publish_expired(guest).await;
2758 }
2759 let signal_mask = read_ppoll_mask(guest, call, first).await?;
2760 let (probe, staged) = match self.stage_ppoll_probe(guest, call, signal_mask).await? {
2761 Some((probe, staged)) => (probe, Some(staged)),
2762 None => {
2763 if let Some(refused) = self.refuse_unstageable_ppoll_copy(guest).await {
2764 return refused;
2765 }
2766 // Runs on with the guest's own zero timeout and mask.
2767 (call, None)
2768 }
2769 };
2770 // The mask the probe installs, read at entry, for replay to deliver
2771 // under (https://github.com/rrnewton/hermit/issues/4054).
2772 let temporary_mask = if self.cfg.replaying {
2773 match call.sigmask() {
2774 Some(signal_mask) => read_kernel_sigset(guest, signal_mask)
2775 .await
2776 .ok()
2777 .map(sanitize_ppoll_signal_mask),
2778 None => None,
2779 }
2780 } else {
2781 None
2782 };
2783 if let Some(mask) = temporary_mask {
2784 super::signal_shadow::note_wait_mask(guest, mask);
2785 }
2786 // No `?` on the call's own result: an interruption must reach the
2787 // replay delivery below.
2788 let result: Result<i64, Error> = if self.cfg.recordreplay_modes {
2789 self.record_or_replay(guest, probe)
2790 .await
2791 .map_err(Into::into)
2792 } else {
2793 inject_ppoll_probe(guest, probe).await.map_err(Into::into)
2794 };
2795 if let Some(staged) = staged {
2796 staged.release(guest);
2797 }
2798 // A backend that runs the call itself answers ENOSYS for a pending
2799 // signal the mask lets in that it cannot act on inside the call
2800 // (`settle_masked_ppoll_enosys`); after the copies are given back,
2801 // whose stack the settling may use.
2802 let result = match (result, signal_mask) {
2803 (Err(Error::Errno(Errno::ENOSYS)), Some(signal_mask))
2804 if !self.cfg.recordreplay_modes =>
2805 {
2806 self.settle_masked_ppoll_enosys(guest, call, signal_mask, false)
2807 .await
2808 }
2809 (result, _) => result,
2810 };
2811 // Replay serves the probe from the recording, so the kernel never
2812 // installs its mask; deliver a recorded interruption under it, as
2813 // for pselect6.
2814 if let Some(mask) = temporary_mask
2815 && matches!(result, Err(Error::Errno(Errno::ERESTARTNOHAND)))
2816 {
2817 end_replayed_pselect6_under_its_temporary_mask(guest, mask).await;
2818 }
2819 // Linux does not write back an initially zero timeout. Besides matching the
2820 // kernel, omitting this write matters when the timeout aliases the pollfd array:
2821 // the injected probe may have just stored revents in those same bytes.
2822 result
2823 } else if ppoll_uses_kernel_wait(
2824 self.cfg.sequentialize_threads,
2825 self.cfg.recordreplay_modes,
2826 ) {
2827 // The kernel owns the blocking wait (`ppoll_uses_kernel_wait`). Use
2828 // a private copy only for the signal mask so raw ppoll can still
2829 // update the guest timeout.
2830 let mut staged = None;
2831 // The mask the real call sleeps under, recorded for the scheduler's
2832 // SIGCHLD target choice (`Resources::blocked_signal_mask`).
2833 let mut blocked_signal_mask = None;
2834 // The mask read at entry, for replay to deliver under.
2835 let mut temporary_mask = None;
2836 let call = if let Some(signal_mask) = call.sigmask() {
2837 if call.sigsetsize() != KERNEL_SIGSET_SIZE {
2838 return Err(Errno::EINVAL.into());
2839 }
2840 let signal_mask = crate::syscalls::signal::restart_if_first_probe_not_run(
2841 read_kernel_sigset(guest, signal_mask).await,
2842 )?;
2843 super::signal_shadow::note_wait_mask(guest, signal_mask);
2844 let signal_mask = sanitize_ppoll_signal_mask(signal_mask);
2845 blocked_signal_mask = Some(kernel_installed_signal_mask(signal_mask));
2846 temporary_mask = Some(signal_mask);
2847 // Linux writes revents into the pollfd array and the time left
2848 // into the timeout, so the copy overlaps neither.
2849 let live: Vec<std::ops::Range<usize>> = ppoll_fds_range(call)
2850 .into_iter()
2851 .chain(call.timeout().map(|timeout| {
2852 let start = timeout.as_raw();
2853 start..start.saturating_add(std::mem::size_of::<Timespec>())
2854 }))
2855 .collect();
2856 match stage_private_cells(guest, PrivateCells::Mask(signal_mask), &live).await? {
2857 Some(cells) => {
2858 let mask = cells.mask();
2859 staged = Some(cells);
2860 call.with_sigmask(Addr::from_raw(mask))
2861 }
2862 None => {
2863 if let Some(refused) = self.refuse_unstageable_ppoll_copy(guest).await {
2864 return refused;
2865 }
2866 // Runs on with the guest's own mask. A sibling may
2867 // change it while the call waits, which a masked
2868 // record/replay wait already allows for: its sleeping
2869 // mask is recorded as unknown.
2870 call
2871 }
2872 }
2873 } else {
2874 call
2875 };
2876 // A masked record/replay wait sleeps under a temporary mask the
2877 // scheduler records as unknown, as a masked pselect6's does
2878 // (`Resources::blocked_signal_mask_unknown`), although Hermit knows
2879 // it exactly. A known mask would let the scheduler wait for the
2880 // kernel to install it, and replay serves the call from the
2881 // recording without the kernel ever doing so; recording and replay
2882 // must schedule the call alike. The result has no `?`: an
2883 // interruption must reach the replay delivery below.
2884 let result: Result<i64, Error> = if temporary_mask.is_none() {
2885 // No mask: the call waits under the thread's own.
2886 self.record_or_replay_shielded_wait(guest, Syscall::Ppoll(call))
2887 .await
2888 } else if self.cfg.recordreplay_modes {
2889 self.record_or_replay_blocking_with_unknown_mask(guest, Syscall::Ppoll(call))
2890 .await
2891 } else {
2892 self.record_or_replay_blocking_with_mask(
2893 guest,
2894 Syscall::Ppoll(call),
2895 blocked_signal_mask,
2896 )
2897 .await
2898 };
2899 if let Some(staged) = staged {
2900 staged.release(guest);
2901 }
2902 // Linux's ppoll never fails with ENOSYS. A backend that runs the
2903 // call itself answers it for a masked wait it cannot keep under
2904 // the mask, and for a pending signal the mask lets in that it
2905 // cannot act on inside the call: refused by name
2906 // (`refuse_masked_ppoll_enosys`), never handed to the guest.
2907 if let (Err(Error::Errno(Errno::ENOSYS)), Some(mask)) = (&result, temporary_mask) {
2908 return self.refuse_masked_ppoll_enosys(guest, mask).await;
2909 }
2910 // Replay serves the call from the recording, so the kernel never
2911 // installs its mask; deliver a recorded interruption under it
2912 // (https://github.com/rrnewton/hermit/issues/3992).
2913 if self.cfg.replaying
2914 && let Some(mask) = temporary_mask
2915 && matches!(result, Err(Error::Errno(Errno::ERESTARTNOHAND)))
2916 {
2917 end_replayed_pselect6_under_its_temporary_mask(guest, mask).await;
2918 }
2919 result
2920 } else {
2921 self.handle_internal_ppoll(guest, call, timeout).await
2922 };
2923
2924 result
2925 }
2926
2927 /// The zero-timeout probe for `call`: it reads a private zero timeout,
2928 /// and a private, sanitized copy of the call's mask, staged by
2929 /// `stage_private_cells` clear of the pollfd array that Linux writes. So
2930 /// neither a sibling's write nor the guest's own mask can make the probe
2931 /// sleep or block Hermit's timer signal. `None` when the copies have
2932 /// nowhere to go (`refuse_unstageable_ppoll_copy`). `signal_mask` is the
2933 /// mask `read_ppoll_mask` read, if the call names one.
2934 async fn stage_ppoll_probe<G: Guest<Self>>(
2935 &self,
2936 guest: &mut G,
2937 call: syscalls::Ppoll,
2938 signal_mask: Option<KernelSigset>,
2939 ) -> Result<
2940 Option<(
2941 syscalls::Ppoll,
2942 StagedCells<<G::Stack as Stack>::StackGuard>,
2943 )>,
2944 Error,
2945 > {
2946 let signal_mask = match signal_mask {
2947 Some(signal_mask) => {
2948 // The probe's temporary mask lets the kernel take a pending
2949 // real-time timer expiration that every thread blocked when it
2950 // expired (https://github.com/rrnewton/hermit/issues/4089).
2951 observe_under_mask(guest, signal_mask).await?;
2952 Some(sanitize_ppoll_signal_mask(signal_mask))
2953 }
2954 None => None,
2955 };
2956 let cells = match signal_mask {
2957 Some(signal_mask) => PrivateCells::MaskAndZeroTimeout(signal_mask),
2958 None => PrivateCells::ZeroTimeout,
2959 };
2960 let live: Vec<std::ops::Range<usize>> = ppoll_fds_range(call).into_iter().collect();
2961 let Some(staged) = stage_private_cells(guest, cells, &live).await? else {
2962 return Ok(None);
2963 };
2964 let probe = match signal_mask {
2965 Some(_) => call
2966 .with_timeout(AddrMut::from_raw(staged.after_mask()))
2967 .with_sigmask(Addr::from_raw(staged.mask())),
2968 None => call.with_timeout(AddrMut::from_raw(staged.at)),
2969 };
2970 Ok(Some((probe, staged)))
2971 }
2972
2973 /// Refuses a ppoll whose private copies have nowhere to go
2974 /// (`stage_private_cells`): a zero-timeout probe's zero timeout and mask,
2975 /// or the mask of a call the kernel waits in. The stack has no writable
2976 /// scratch below its red zone, and every red-zone window overlaps the
2977 /// pollfd array or, for a kernel wait, the timeout
2978 /// (https://github.com/rrnewton/hermit/issues/4113). A fail-closed run
2979 /// stops with the policy refusal and this message, whose result this
2980 /// returns. A run started with `--allow-unsupported-syscalls` runs on with
2981 /// the guest's own timeout and mask, and this returns `None`.
2982 async fn refuse_unstageable_ppoll_copy<G: Guest<Self>>(
2983 &self,
2984 guest: &mut G,
2985 ) -> Option<Result<i64, Error>> {
2986 use std::io::Write as _;
2987 let message = "unsupported: ppoll on a stack with no room below its red zone whose \
2988 red zone the call's pollfd array and timeout fill: Hermit has nowhere to put its \
2989 private copy of the timeout and the signal mask \
2990 (https://github.com/rrnewton/hermit/issues/4113)";
2991 tracing::error!("[tid {}] {message}", guest.tid());
2992 if !self.cfg.panic_on_unsupported_syscalls {
2993 return None;
2994 }
2995 let _ = writeln!(crate::util::RetryingStderr, "{message}");
2996 Some(
2997 self.refuse_unserviceable_operation(guest, Sysno::ppoll, Errno::EFAULT)
2998 .await,
2999 )
3000 }
3001
3002 /// Handle a guest-internal `ppoll` using zero-time kernel probes.
3003 async fn handle_internal_ppoll<G: Guest<Self>>(
3004 &self,
3005 guest: &mut G,
3006 call: syscalls::Ppoll,
3007 timeout: Option<Duration>,
3008 ) -> Result<i64, Error> {
3009 debug_assert_ne!(timeout, Some(Duration::ZERO));
3010 let timeout_address = call.timeout();
3011
3012 // Every zero-timeout probe carries a private copy of the call's temporary
3013 // mask, so the kernel applies it atomically, as pselect6's probes do
3014 // (`handle_internal_pselect6`). A pending signal that the mask lets in
3015 // ends a probe with ERESTARTNOHAND at that probe's scheduler point, and
3016 // the kernel delivers it under the temporary mask and only then puts the
3017 // thread's own mask back, as Linux does for a ppoll that a signal
3018 // interrupts. That probe is the last injection before the guest
3019 // resumes, so nothing disturbs the pending restore
3020 // (https://github.com/rrnewton/reverie/issues/1003). A ppoll that would
3021 // block used to return ENOSYS here
3022 // (https://github.com/rrnewton/hermit/issues/4042).
3023 if call.sigmask().is_some() {
3024 // Only memory was read before: the mask's read is the call's
3025 // first injection. The virtual clock is observed right after it
3026 // (`ppoll_start`), before the observation under the mask, which
3027 // can ask the scheduler too: Linux takes the end time before it
3028 // installs the mask.
3029 let signal_mask = read_ppoll_mask(guest, call, true).await?;
3030 let (started_at, deadline) = ppoll_start(guest, timeout).await;
3031 let Some((probe, staged)) = self.stage_ppoll_probe(guest, call, signal_mask).await?
3032 else {
3033 // The guest's own mask could block Hermit's timer signal while
3034 // the call is parked, so a run that allows unsupported
3035 // operations gets the same answer as a mask it cannot honor.
3036 return match self.refuse_unstageable_ppoll_copy(guest).await {
3037 Some(refused) => refused,
3038 None => Err(Errno::ENOSYS.into()),
3039 };
3040 };
3041 // The copies are given back before the time left is written: their
3042 // red-zone window may cover the guest's timeout, which Linux never
3043 // sees.
3044 let result = if self.cfg.backend.supports_blocked_wait_signal_interruption {
3045 let result = self
3046 .wait_on_masked_ppoll_probe(
3047 guest,
3048 probe,
3049 staged.mask(),
3050 staged.timerfd_scratch,
3051 deadline,
3052 )
3053 .await;
3054 staged.release(guest);
3055 result
3056 } else {
3057 // Without the kernel's signal state, Hermit cannot tell whether
3058 // the backend holds a signal that should end the wait, so a
3059 // ppoll that would block fails closed, refused by name
3060 // (`refuse_masked_ppoll_wait`). An expired timerfd on virtual
3061 // time is made readable first, in the staged cells' scratch
3062 // (`crate::syscalls::timerfd`). A backend that runs the call
3063 // itself answers EINTR, with the mask installed for the
3064 // delivery, when a pending caught signal ends it
3065 // (`inject_ppoll_probe`), and ENOSYS for one it cannot act on
3066 // (`settle_masked_ppoll_enosys`, after the copies are given
3067 // back, whose stack the settling may use).
3068 crate::syscalls::timerfd::publish_expired_in(guest, staged.timerfd_scratch).await;
3069 let result = inject_ppoll_probe(guest, probe).await;
3070 staged.release(guest);
3071 match (result, signal_mask) {
3072 (Err(Errno::ENOSYS), Some(signal_mask)) => {
3073 self.settle_masked_ppoll_enosys(guest, call, signal_mask, true)
3074 .await
3075 }
3076 (result, _) if probe.syscall_would_have_blocked(result) => {
3077 self.refuse_masked_ppoll_wait(guest).await
3078 }
3079 (result, _) => result.map_err(Into::into),
3080 }
3081 };
3082 if let (Some(timeout_address), Some(timeout), Some(started_at)) =
3083 (timeout_address, timeout, started_at)
3084 {
3085 self.write_ppoll_remaining(guest, timeout_address, timeout, started_at)
3086 .await?;
3087 }
3088 return result;
3089 }
3090
3091 let (started_at, deadline) = ppoll_start(guest, timeout).await;
3092 let mut rsrc = Resources::new(guest.thread_state().dettid);
3093 rsrc.insert(ResourceID::InternalIOPolling, Permission::W);
3094 rsrc.fyi("ppoll");
3095 rsrc.set_signal_interrupt_errno(Errno::EINTR);
3096 rsrc.signal_state_task = guest_signal_state_task(guest);
3097 rsrc.relies_on_guest_held_marks = poll_targets_guest_held(
3098 guest,
3099 call.fds().map(|fds| fds.as_raw()),
3100 call.nfds() as usize,
3101 );
3102 rsrc.polls_guest_state_only = rsrc.relies_on_guest_held_marks;
3103 let result = retry_nonblocking_syscall_with_timeout(guest, call, rsrc, deadline).await;
3104 if let (Some(timeout_address), Some(timeout), Some(started_at)) =
3105 (timeout_address, timeout, started_at)
3106 {
3107 self.write_ppoll_remaining(guest, timeout_address, timeout, started_at)
3108 .await?;
3109 }
3110 result
3111 }
3112
3113 /// The answer to a ppoll with a temporary signal mask that would wait, on
3114 /// a backend that cannot interrupt a blocked wait for a signal
3115 /// (`BackendCapabilities::supports_blocked_wait_signal_interruption` is
3116 /// false): Hermit cannot keep the mask in place across the wait, so the
3117 /// call is refused by name through the unsupported-operation policy, on
3118 /// stderr when the run ends (`report_refusal_reason`); with unsupported
3119 /// operations allowed the guest gets ENOSYS, as before. Linux's ppoll
3120 /// never fails with ENOSYS.
3121 async fn refuse_masked_ppoll_wait<G: Guest<Self>>(&self, guest: &mut G) -> Result<i64, Error> {
3122 const REASON: &str = "a ppoll with a temporary signal mask that would wait cannot be \
3123 served on this backend: it cannot keep the mask in place across the wait";
3124 self.refuse_masked_wait_because(guest, Sysno::ppoll, REASON)
3125 .await
3126 }
3127
3128 /// The answer to a ppoll whose temporary signal mask lets in a pending
3129 /// signal that the backend, which runs the call itself, cannot act on
3130 /// inside the call: one that would stop the process, or an ignored one
3131 /// pending beside one that ends the call (`settle_masked_ppoll_enosys`).
3132 /// Refused by name as `refuse_masked_ppoll_wait` refuses a wait.
3133 async fn refuse_masked_ppoll_signal<G: Guest<Self>>(
3134 &self,
3135 guest: &mut G,
3136 ) -> Result<i64, Error> {
3137 const REASON: &str = "a ppoll whose temporary signal mask lets in a pending signal that \
3138 would stop the process, or an ignored one pending beside a signal that ends the \
3139 call, cannot be served on this backend: it cannot act on that signal inside the call";
3140 self.refuse_masked_wait_because(guest, Sysno::ppoll, REASON)
3141 .await
3142 }
3143
3144 /// A masked wait, `sysno`, refused by name for `reason`, through the
3145 /// unsupported-operation policy.
3146 async fn refuse_masked_wait_because<G: Guest<Self>>(
3147 &self,
3148 guest: &mut G,
3149 sysno: Sysno,
3150 reason: &'static str,
3151 ) -> Result<i64, Error> {
3152 crate::syscalls::helpers::report_refusal_reason(
3153 self.cfg.panic_on_unsupported_syscalls,
3154 &format!(
3155 "[dtid {}] {sysno} refused: {reason}",
3156 guest.thread_state().dettid
3157 ),
3158 );
3159 if !self.cfg.panic_on_unsupported_syscalls {
3160 crate::tool_global::report_unsupported_syscall(guest, sysno).await;
3161 }
3162 self.refuse_unserviceable_operation_because(guest, sysno, Errno::ENOSYS, reason)
3163 .await
3164 }
3165
3166 /// The answer to a masked ppoll that the kernel beneath waits in
3167 /// (`ppoll_uses_kernel_wait`) and the backend failed with ENOSYS, which
3168 /// Linux's ppoll never returns: a backend that runs the call itself
3169 /// declines a wait it cannot keep under the mask, and a pending signal
3170 /// the mask lets in that it cannot act on inside the call. Ignored ones
3171 /// alone, which Linux discards as the call returns before it restarts
3172 /// the call to wait again, are removed as it removes them
3173 /// (`discard_ignored_signals_let_in`), and the call is refused as a wait;
3174 /// anything else is refused as such a signal.
3175 async fn refuse_masked_ppoll_enosys<G: Guest<Self>>(
3176 &self,
3177 guest: &mut G,
3178 mask: KernelSigset,
3179 ) -> Result<i64, Error> {
3180 if self.discard_ignored_signals_let_in(guest, mask).await? {
3181 self.refuse_masked_ppoll_wait(guest).await
3182 } else {
3183 self.refuse_masked_ppoll_signal(guest).await
3184 }
3185 }
3186
3187 /// The answer to a zero-timeout probe of a masked ppoll, `call` with
3188 /// the temporary mask `signal_mask` as `read_ppoll_mask` read it, that the
3189 /// backend failed with ENOSYS, which Linux's ppoll never returns. A
3190 /// backend that runs the call itself answers it when a pending signal the
3191 /// mask lets in is one it cannot act on inside the call, where Linux
3192 /// returns ERESTARTNOHAND (`do_poll`, fs/select.c): it cannot discard an
3193 /// ignored signal, restart the call, or stop the process.
3194 ///
3195 /// When every such signal is one the process ignores (the shared rule,
3196 /// `crate::syscalls::ignored_at_generation`), Linux discards each as the
3197 /// call returns (`get_signal`, kernel/signal.c) and, having delivered
3198 /// nothing, restarts the call, which polls again
3199 /// (`arch_do_signal_or_restart`, arch/x86/kernel/signal.c). So does this:
3200 /// they are removed from the thread's queues
3201 /// (`discard_ignored_signals_let_in`), and a new probe runs, whose result
3202 /// is the call's. Where the call would wait (`waits`), a probe that finds
3203 /// nothing ready is refused as a wait (`refuse_masked_ppoll_wait`), as
3204 /// one without those signals is. Any other such signal, a stop or an
3205 /// ignored one beside a signal that ends the call, is refused by name
3206 /// (`refuse_masked_ppoll_signal`): which ignored signals Linux discards
3207 /// then depends on the handler's own mask, read nowhere here.
3208 async fn settle_masked_ppoll_enosys<G: Guest<Self>>(
3209 &self,
3210 guest: &mut G,
3211 call: syscalls::Ppoll,
3212 signal_mask: KernelSigset,
3213 waits: bool,
3214 ) -> Result<i64, Error> {
3215 if !self
3216 .discard_ignored_signals_let_in(guest, sanitize_ppoll_signal_mask(signal_mask))
3217 .await?
3218 {
3219 return self.refuse_masked_ppoll_signal(guest).await;
3220 }
3221 let again = match self
3222 .stage_ppoll_probe(guest, call, Some(signal_mask))
3223 .await?
3224 {
3225 Some((probe, staged)) => {
3226 let again = inject_ppoll_probe(guest, probe).await;
3227 staged.release(guest);
3228 again
3229 }
3230 None => {
3231 return match self.refuse_unstageable_ppoll_copy(guest).await {
3232 Some(refused) => refused,
3233 None => Err(Errno::ENOSYS.into()),
3234 };
3235 }
3236 };
3237 match again {
3238 Err(Errno::ENOSYS) => self.refuse_masked_ppoll_signal(guest).await,
3239 Ok(0) if waits => self.refuse_masked_ppoll_wait(guest).await,
3240 result => result.map_err(Into::into),
3241 }
3242 }
3243
3244 /// The answer to a zero-timeout masked pselect6, `call` with the
3245 /// temporary mask `mask`, that the backend failed with ENOSYS, as for
3246 /// ppoll (`settle_masked_ppoll_enosys`): when every pending signal the mask
3247 /// lets in is one the process ignores, they are removed, as Linux discards
3248 /// them before it restarts the call, and the call runs again, whose result
3249 /// is the answer. Anything else is refused by name: for a pending signal
3250 /// the backend cannot act on inside the call, or, with none, for the
3251 /// descriptor sets it declined (`masked_pselect6_refusal_reason`).
3252 async fn settle_masked_pselect6_enosys<G: Guest<Self>>(
3253 &self,
3254 guest: &mut G,
3255 call: syscalls::Pselect6,
3256 mask: KernelSigset,
3257 ) -> Result<i64, Error> {
3258 if self.discard_ignored_signals_let_in(guest, mask).await? {
3259 match guest.inject(call).await {
3260 Err(Errno::ENOSYS) => {}
3261 result => return Ok(result?),
3262 }
3263 }
3264 let reason = masked_pselect6_refusal_reason_now(guest, mask, call.nfds());
3265 self.refuse_masked_wait_because(guest, Sysno::pselect6, reason)
3266 .await
3267 }
3268
3269 /// Removes the pending signals the temporary mask `mask` lets in, when
3270 /// the backend reports this thread's signal state (`Guest::signal_sets`)
3271 /// and every one of them is a signal the process ignores
3272 /// (`ignored_signals_let_in`): Linux discards those as the call returns.
3273 /// True when it removed them, or there were none; false, removing
3274 /// nothing, when one is not ignored or the backend reports no state.
3275 async fn discard_ignored_signals_let_in<G: Guest<Self>>(
3276 &self,
3277 guest: &mut G,
3278 mask: KernelSigset,
3279 ) -> Result<bool, Error> {
3280 let Some(ignored) = ignored_signals_let_in_now(guest, mask) else {
3281 return Ok(false);
3282 };
3283 crate::syscalls::helpers::remove_signals_discarded_under_a_temporary_mask(guest, ignored)
3284 .await?;
3285 Ok(true)
3286 }
3287
3288 /// The wait of a ppoll with a temporary mask under sequentialized threads:
3289 /// `probe`, a zero-timeout copy of the call that reads the mask staged at
3290 /// `mask` (`prepare_ppoll_probe`), runs once per scheduler turn until it
3291 /// reports a ready descriptor or an error, until a signal that the mask
3292 /// lets in ends the wait, or until `deadline` passes, which returns 0.
3293 ///
3294 /// The probe carries the mask, so the kernel ends a probe with
3295 /// ERESTARTNOHAND for a pending signal the mask lets in, and delivers it
3296 /// under the mask as the call returns. A signal that the guest's own mask
3297 /// lets in can instead stop a probe before the kernel installs the mask;
3298 /// the backend then holds it, and `KernelSignalWait` decides against the
3299 /// call's mask whether it ends the wait (`with_wait_mask`). Without that,
3300 /// the wait ran on to its deadline, or forever, with the signal held. The
3301 /// backend delivers a held signal under the guest's own mask
3302 /// (https://github.com/rrnewton/reverie/issues/1011). A signal that only
3303 /// the mask lets in can be pending at a turn's check without having ended
3304 /// that turn's probe only if it arrived from outside the guest between
3305 /// the two; the next probe, which carries the mask, ends the call for it
3306 /// and the kernel delivers it under the mask. The wait never blocks
3307 /// signals (`KernelSignalWait::block`): a probe that a signal ended would
3308 /// then leave the all-blocked mask to be restored.
3309 ///
3310 /// Every probe runs before the turn's check, as Linux's do_poll polls every
3311 /// descriptor before it tests for a pending signal. The wait holds no
3312 /// signal until the call returns (`KernelSignalWait::for_select`): the
3313 /// kernel ends a probe for any pending signal the mask lets in, `SIGCHLD`
3314 /// included, so a `SIGCHLD` the kernel posts for a child ends it at a turn
3315 /// that host timing chooses, the known gap that select and pselect6 waits
3316 /// share (https://github.com/rrnewton/hermit/issues/3146).
3317 async fn wait_on_masked_ppoll_probe<G: Guest<Self>>(
3318 &self,
3319 guest: &mut G,
3320 probe: syscalls::Ppoll,
3321 mask: usize,
3322 timerfd_scratch: Option<crate::syscalls::timerfd::Scratch>,
3323 deadline: Option<LogicalTime>,
3324 ) -> Result<i64, Error> {
3325 let temporary_mask: KernelSigset = guest
3326 .memory()
3327 .read_value(Addr::<KernelSigset>::from_raw(mask).ok_or(Errno::EFAULT)?)?;
3328 let mut signals = KernelSignalWait::for_select(guest)
3329 .with_wait_mask(Some(crate::syscalls::signal::kernel_installed_signal_mask(
3330 temporary_mask,
3331 )))
3332 .with_deadline(deadline.is_some());
3333 let mut rsrc = Resources::new(guest.thread_state().dettid);
3334 rsrc.insert(ResourceID::InternalIOPolling, Permission::W);
3335 rsrc.fyi("ppoll");
3336 rsrc.poll_deadline = poll_deadline_for(guest, deadline);
3337 rsrc.relies_on_guest_held_marks = poll_targets_guest_held(
3338 guest,
3339 probe.fds().map(|fds| fds.as_raw()),
3340 probe.nfds() as usize,
3341 );
3342 rsrc.polls_guest_state_only = rsrc.relies_on_guest_held_marks;
3343 // As for pselect6: a scheduler `Signaled` answer wakes the wait, and
3344 // the probe and the kernel's state decide whether a signal ends it.
3345 rsrc.set_signal_interrupt_errno(Errno::EINTR);
3346 rsrc.signal_state_task = guest_signal_state_task(guest);
3347 // Between probes the kernel holds the guest's own mask, while Linux's
3348 // `blocked` is the temporary one for the whole call.
3349 let mut guest_held = GuestHeldPollProof::capture(guest, probe.into(), &mut rsrc);
3350 let mut published = PublishedWaitMask::default();
3351 publish_wait_mask(guest, signals.effective_mask(), &mut published).await;
3352 let result = loop {
3353 crate::syscalls::helpers::refresh_guest_poll_state(guest, probe.into(), &mut rsrc);
3354 // A guest-held proof this call already lost stays lost.
3355 guest_held.revalidate(guest, &mut rsrc);
3356 resource_request(guest, rsrc.clone()).await;
3357 guest_held.revalidate(guest, &mut rsrc);
3358 // Virtual timerfds first (`crate::syscalls::timerfd`), in the
3359 // staged cells' scratch while they hold the stack.
3360 let next_timerfd =
3361 crate::syscalls::timerfd::publish_expired_in(guest, timerfd_scratch).await;
3362 rsrc.poll_deadline =
3363 crate::syscalls::timerfd::poll_deadline_with(guest, deadline, next_timerfd);
3364 // A pending real-time timer expiration that the temporary mask lets
3365 // the kernel deliver or discard
3366 // (https://github.com/rrnewton/hermit/issues/4089).
3367 if let Err(error) = observe_under_mask(guest, temporary_mask).await {
3368 break Err(error);
3369 }
3370 let injected = match signals.inject_first_probe(guest, probe).await {
3371 Ok(result) => result,
3372 Err(error) => break Err(error),
3373 };
3374 if !probe.syscall_would_have_blocked(injected) {
3375 break injected.map_err(Into::into);
3376 }
3377 match signals.interrupted_with_state() {
3378 Ok((false, _)) => {}
3379 // The next probe ends the call for it (see above).
3380 Ok((true, state))
3381 if !signals.holds_signal()
3382 && signals.interrupts_only_under_wait_mask(&state) =>
3383 {
3384 continue;
3385 }
3386 Ok((true, _)) => break Err(Errno::ERESTARTNOHAND.into()),
3387 Err(error) => break Err(error),
3388 }
3389 rsrc.poll_attempt += 1;
3390 if let Some(deadline) = deadline
3391 && thread_observe_time(guest).await >= deadline
3392 {
3393 break Ok(0);
3394 }
3395 record_retry_event(guest, probe).await;
3396 };
3397 publish_wait_mask(guest, None, &mut published).await;
3398 refuse_held_signal_loss(guest, Sysno::ppoll, result).await
3399 }
3400
3401 #[cfg(test)]
3402 pub(crate) async fn masked_ppoll_probe_for_test<G: Guest<Self>>(
3403 &self,
3404 guest: &mut G,
3405 probe: syscalls::Ppoll,
3406 mask: usize,
3407 ) -> Result<i64, Error> {
3408 self.wait_on_masked_ppoll_probe(guest, probe, mask, None, None)
3409 .await
3410 }
3411
3412 pub(super) async fn write_ppoll_remaining<G: Guest<Self>>(
3413 &self,
3414 guest: &mut G,
3415 timeout_address: AddrMut<'_, Timespec>,
3416 timeout: Duration,
3417 started_at: LogicalTime,
3418 ) -> Result<(), Error> {
3419 let now = thread_observe_time(guest).await;
3420 let elapsed = Duration::from_nanos(now.as_nanos().saturating_sub(started_at.as_nanos()));
3421 let remaining = timeout.saturating_sub(elapsed);
3422 if let Err(error) = guest
3423 .memory()
3424 .write_value(timeout_address, ×pec_from_duration(remaining))
3425 {
3426 // Linux preserves the ppoll result when remaining-time copyout faults.
3427 trace!(?error, "ignoring ppoll timeout writeback failure");
3428 }
3429 Ok(())
3430 }
3431
3432 /// Handle a guest-internal poll call that can be fully determinized.
3433 // TODO-HUMAN-REVIEW(PR-1052): Review scheduler fairness for zero-timeout poll.
3434 #[cfg(target_arch = "x86_64")]
3435 pub async fn handle_internal_poll<G: Guest<Self>>(
3436 &self,
3437 guest: &mut G,
3438 call: syscalls::Poll,
3439 ) -> Result<i64, Error> {
3440 let timeout_millis = call.timeout();
3441 if timeout_millis == 0 {
3442 // A nonblocking poll can still be the synchronization point in a
3443 // userspace polling loop. Yield once before probing so backends
3444 // without PMU preemption cannot let that loop starve its producer.
3445 let yield_to_peer = self.cfg.backend.loopback_pollers_yield_to_peers
3446 && guest.thread_state().has_loopback_peer();
3447 resource_request(
3448 guest,
3449 zero_timeout_poll_request(guest.thread_state().dettid, yield_to_peer),
3450 )
3451 .await;
3452 crate::syscalls::timerfd::publish_expired(guest).await;
3453 Ok(guest.inject(call).await?) // Already non-blocking.
3454 } else {
3455 let maybe_timeout_ns = millis_duration_to_absolute_timeout(guest, timeout_millis).await;
3456 self.handle_internal_poll_until(guest, call, maybe_timeout_ns)
3457 .await
3458 }
3459 }
3460
3461 /// The blocking half of `handle_internal_poll`, waiting until `deadline`. A
3462 /// `restart_syscall` resumes an interrupted `poll` here with the deadline the
3463 /// wait kept, as Linux's `do_restart_poll` does
3464 /// (`Detcore::handle_restart_syscall`).
3465 #[cfg(target_arch = "x86_64")]
3466 pub(crate) async fn handle_internal_poll_until<G: Guest<Self>>(
3467 &self,
3468 guest: &mut G,
3469 call: syscalls::Poll,
3470 deadline: Option<LogicalTime>,
3471 ) -> Result<i64, Error> {
3472 let mut rsrc = Resources::new(guest.thread_state().dettid);
3473 rsrc.insert(ResourceID::InternalIOPolling, Permission::W);
3474 rsrc.fyi("poll");
3475 rsrc.set_signal_interrupt_errno(Errno::EINTR);
3476 rsrc.signal_state_task = guest_signal_state_task(guest);
3477 rsrc.relies_on_guest_held_marks = poll_targets_guest_held(
3478 guest,
3479 call.fds().map(|fds| fds.as_raw()),
3480 call.nfds() as usize,
3481 );
3482 rsrc.polls_guest_state_only = rsrc.relies_on_guest_held_marks;
3483 let result = retry_nonblocking_syscall_with_timeout(guest, call, rsrc, deadline).await;
3484 keep_restart_block(guest, &result, RestartCall::Poll(call), deadline).await;
3485 result
3486 }
3487
3488 /// Handle a poll syscall that deponds on external, nondeterminstic IO.
3489 ///
3490 /// `deadline` is the end of a positive timeout (`kernel_poll_deadline`).
3491 #[cfg(target_arch = "x86_64")]
3492 pub(crate) async fn handle_external_poll<G: Guest<Self>>(
3493 &self,
3494 guest: &mut G,
3495 call: syscalls::Poll,
3496 deadline: Option<KernelPollDeadline>,
3497 ) -> Result<i64, Error> {
3498 let len = call.nfds();
3499 let time_delta = Duration::from_millis(call.timeout() as u64);
3500
3501 if len == 0 && time_delta.is_zero() {
3502 let request = Self::sleep_request(guest, time_delta).await;
3503 resource_request(guest, request).await;
3504 Ok(0)
3505 } else if call.timeout() == 0 {
3506 // Not shielded, as no zero-timeout probe is: a signal pending at
3507 // its entry still ends it with a restart code.
3508 print_poll(&call);
3509 self.run_kernel_poll(guest, call, deadline).await
3510 } else {
3511 print_poll(&call);
3512 let result = self
3513 .record_or_replay_shielded_wait(guest, Syscall::Poll(call))
3514 .await;
3515 let result = kernel_poll_result(&call, result, deadline.is_some());
3516 // The shielded wait replaced `run_kernel_poll` on this path, but
3517 // a signal that ends the poll still leaves the guest's own
3518 // `restart_syscall` to resume it, so keep the same record
3519 // `run_kernel_poll_as` keeps.
3520 if let Some(deadline) = deadline {
3521 keep_restart_block(
3522 guest,
3523 &result,
3524 RestartCall::KernelPoll(call, deadline),
3525 None,
3526 )
3527 .await;
3528 }
3529 result
3530 }
3531 }
3532
3533 /// Runs `call` in the kernel through the recorder. A signal that ends a
3534 /// poll with a positive timeout there leaves `ERESTART_RESTARTBLOCK`, and
3535 /// with no handler Linux restarts it through `restart_syscall` until the
3536 /// end time it kept (`do_restart_poll`), so such a poll keeps `deadline`
3537 /// for that restart (`RestartCall::KernelPoll`). A poll without a deadline
3538 /// restarts by running again (`kernel_poll_result`), and one that a
3539 /// signal stopped before it ran returns `ERESTARTNOHAND`
3540 /// (`kernel_poll_restart_code`).
3541 #[cfg(target_arch = "x86_64")]
3542 async fn run_kernel_poll<G: Guest<Self>>(
3543 &self,
3544 guest: &mut G,
3545 call: syscalls::Poll,
3546 deadline: Option<KernelPollDeadline>,
3547 ) -> Result<i64, Error> {
3548 self.run_kernel_poll_as(guest, call, deadline, Syscall::Poll(call))
3549 .await
3550 }
3551
3552 /// `run_kernel_poll`, with `run` the call that runs for `call` (or that
3553 /// replay serves): the poll itself, or the guest's own `restart_syscall`
3554 /// that restarts it (`resume_kernel_poll`).
3555 #[cfg(target_arch = "x86_64")]
3556 async fn run_kernel_poll_as<G: Guest<Self>>(
3557 &self,
3558 guest: &mut G,
3559 call: syscalls::Poll,
3560 deadline: Option<KernelPollDeadline>,
3561 run: Syscall,
3562 ) -> Result<i64, Error> {
3563 debug_assert!(
3564 polls_in_kernel(self.cfg.sequentialize_threads, self.cfg.recordreplay_modes),
3565 "a plain or strict run emulates poll; it never runs one in the kernel"
3566 );
3567 let raw = self
3568 .record_or_replay_blocking_as(guest, Syscall::Poll(call), run)
3569 .await;
3570 let before_run = poll_stopped_before_it_ran(&raw);
3571 let result = kernel_poll_result(&call, raw, deadline.is_some());
3572 if let Some(deadline) = deadline {
3573 keep_restart_block(
3574 guest,
3575 &result,
3576 RestartCall::KernelPoll(call, deadline),
3577 None,
3578 )
3579 .await;
3580 }
3581 kernel_poll_restart_code(result, before_run)
3582 }
3583
3584 /// The kernel's restart of a `poll` with a positive timeout that a signal
3585 /// ended in the kernel (`RestartCall::KernelPoll`). `restart` is the
3586 /// guest's own `restart_syscall`. It runs in place: the poll ran in the
3587 /// kernel, so the thread's restart block describes it, and Linux's
3588 /// `do_restart_poll` waits until the end time the poll kept and writes its
3589 /// `revents`. Nothing runs in its stead, so no signal is taken off the queue
3590 /// before the restart runs, as the single step of a poll injected for it
3591 /// did: the kernel delivers pending signals in its own order when the
3592 /// restart returns. Record keeps the restart's outputs as the poll's, and
3593 /// replay serves them (`hermit_record_format::event::InterruptedPoll`).
3594 ///
3595 /// The restart names the poll in its argument registers, which Linux
3596 /// leaves as the poll left them (`restart_names_poll`). If it does not,
3597 /// the poll runs again with the time left until `deadline`, as before:
3598 /// rounded up to whole milliseconds, so the poll never ends before its
3599 /// deadline, and 0 once the deadline has passed. Under record and replay
3600 /// that poll keeps the timeout it was issued with (`KernelPollDeadline`).
3601 /// Either way it runs the recorded kernel call, never the zero-length
3602 /// sleep `handle_external_poll` makes of a poll of no descriptors.
3603 #[cfg(target_arch = "x86_64")]
3604 pub(crate) async fn resume_kernel_poll<G: Guest<Self>>(
3605 &self,
3606 guest: &mut G,
3607 call: syscalls::Poll,
3608 deadline: KernelPollDeadline,
3609 restart: Syscall,
3610 ) -> Result<i64, Error> {
3611 if restart_names_poll(&restart, &call) {
3612 return self
3613 .run_kernel_poll_as(guest, call, Some(deadline), restart)
3614 .await;
3615 }
3616 let call = match deadline.0 {
3617 Some(end) => call.with_timeout(remaining_poll_millis(Instant::now(), end)),
3618 None => call,
3619 };
3620 self.run_kernel_poll(guest, call, Some(deadline)).await
3621 }
3622
3623 /// epoll_create1 syscall
3624 pub async fn handle_epoll_create1<G: Guest<Self>>(
3625 &self,
3626 guest: &mut G,
3627 call: syscalls::EpollCreate1,
3628 ) -> Result<i64, Error> {
3629 let dettid = guest.thread_state().dettid;
3630 resource_request(guest, Resources::new(dettid)).await; // empty request
3631 let fd = self.record_or_replay(guest, call).await? as RawFd;
3632 // Register the epoll fd in the DetFd table like every other
3633 // fd-creating syscall (openat, eventfd2, pipe2, socket, ...). Without
3634 // this, later operations that consult the table via `with_detfd` /
3635 // `dup_fd` (F_GETFL, F_SETFD, F_DUPFD[_CLOEXEC], dup, ...) would fail
3636 // with EBADF even though the underlying kernel fd is valid. This broke,
3637 // for example, running rustup proxies (cargo/rustc) under hermit, whose
3638 // tokio runtime dups its epoll fd at startup.
3639 //
3640 // EPOLL_CLOEXEC shares the same bit value as O_CLOEXEC, so we can carry
3641 // the cloexec flag straight across.
3642 self.add_fd(
3643 guest,
3644 fd,
3645 OFlag::from_bits_truncate(call.flags().bits()),
3646 FdType::Epoll,
3647 )
3648 .await?;
3649 Ok(fd as i64)
3650 }
3651
3652 /// Apply an ADD, MOD, or DEL mutation to an epoll interest list.
3653 ///
3654 /// Determinism: strict execution serializes this mutation, so its result depends only on the
3655 /// operation, event payload, and deterministically reconstructed epoll/file-descriptor state.
3656 /// Record/replay records each answer and replays it; replay reapplies only the recorded
3657 /// successes, so its epoll sets hold what the recording's held
3658 /// (<https://github.com/rrnewton/hermit/issues/31>).
3659 pub async fn handle_epoll_ctl<G: Guest<Self>>(
3660 &self,
3661 guest: &mut G,
3662 call: syscalls::EpollCtl,
3663 ) -> Result<i64, Error> {
3664 let dettid = guest.thread_state().dettid;
3665 resource_request(guest, Resources::new(dettid)).await; // empty request
3666 Ok(self.record_or_replay(guest, call).await?)
3667 }
3668
3669 /// epoll_pwait syscall (MAYHANG)
3670 pub async fn handle_epoll_pwait<G: Guest<Self>>(
3671 &self,
3672 guest: &mut G,
3673 call: syscalls::EpollPwait,
3674 ) -> Result<i64, Error> {
3675 // This used to unconditionally inject the raw
3676 // call and wait for it to return. With an infinite timeout under
3677 // `--sequentialize-threads` that DEADLOCKS the whole guest: the calling
3678 // task holds the scheduler turn while blocked in the kernel, and the
3679 // only task that could ever satisfy the wait is sitting in the run queue
3680 // waiting for a turn that never comes. Observed as `cmake` configure
3681 // hanging forever at zero CPU with a grandchild frozen mid-`openat`;
3682 // hermit's own scheduler log ends at `COMMIT turn N, dettid <parent>`
3683 // injecting `epoll_pwait(..., -1, NULL, 8)` with `queue len 2`.
3684 //
3685 // WHO ACTUALLY REACHES THIS, measured rather than assumed. It is NOT
3686 // glibc's `epoll_wait(2)` on this architecture: glibc calls
3687 // `SYS_epoll_pwait` only where `__NR_epoll_wait` does not exist (arm64
3688 // and friends). x86_64 has it, and `strace` on glibc 2.34/x86_64 shows
3689 // a plain `epoll_wait` syscall, which `handle_epoll_wait` has always
3690 // handled correctly. The callers that land here are programs issuing
3691 // `epoll_pwait` DIRECTLY -- libuv does, which is how the original
3692 // `cmake` hang was found. With a NULL sigmask the two calls are
3693 // semantically identical, so route them together.
3694 //
3695 // A NON-NULL sigmask swaps the signal mask atomically for the duration
3696 // of the wait. Under record and replay a masked call that may wait is
3697 // a kernel wait outside the turn (`record_or_replay_masked_epoll_pwait`).
3698 // A sequentialized run on a backend whose kernel reports the guest's
3699 // signal state polls it under that mask
3700 // (`handle_internal_masked_epoll_pwait`). Otherwise, and for a zero
3701 // timeout, it keeps its turn: on another backend, or without
3702 // sequentialized threads, such a call can still block the scheduler
3703 // (https://github.com/rrnewton/hermit/issues/4041), and because it
3704 // never sleeps outside the runnable set the scheduler needs no record
3705 // of its temporary mask (`Resources::blocked_signal_mask`).
3706 if call.sigmask().is_some() && self.cfg.recordreplay_modes && call.timeout() != 0 {
3707 return self.record_or_replay_masked_epoll_pwait(guest, call).await;
3708 }
3709 // Under the in-guest delivery gate (A5 step 3c.1d, D39) a masked call
3710 // polls with no mask on its probes and decides under its temporary
3711 // mask, with D37's EINTR exception; a zero timeout makes one maskless
3712 // probe (`gated_masked_epoll_pwait`; IR-9).
3713 if call.sigmask().is_some()
3714 && self.cfg.sequentialize_threads
3715 && self.cfg.in_guest_signal_delivery
3716 {
3717 return Box::pin(self.gated_masked_epoll_pwait(guest, call)).await;
3718 }
3719 if call.sigmask().is_some()
3720 && call.timeout() != 0
3721 && self.cfg.sequentialize_threads
3722 && self.cfg.backend.supports_blocked_wait_signal_interruption
3723 {
3724 return self.handle_internal_masked_epoll_pwait(guest, call).await;
3725 }
3726 if call.sigmask().is_some() {
3727 self.observe_under_temporary_mask(guest, call.sigmask(), call.sigsetsize(), true)
3728 .await?;
3729 let dettid = guest.thread_state().dettid;
3730 resource_request(guest, Resources::new(dettid)).await; // empty request
3731 // The raw call runs in this turn. A zero timeout only probes, once a
3732 // virtual timerfd is readable exactly when its deadline has passed;
3733 // any other waits in the host (`crate::syscalls::timerfd`).
3734 if call.timeout() == 0 {
3735 crate::syscalls::timerfd::publish_expired(guest).await;
3736 } else {
3737 crate::syscalls::timerfd::refuse_host_wait(guest, "epoll_pwait with a signal mask");
3738 }
3739 return Ok(self.record_or_replay(guest, call).await?);
3740 }
3741 if self.cfg.recordreplay_modes && call.timeout() == 0 {
3742 // Cannot block, but still yield a scheduler turn so a polling thread
3743 // cannot monopolize the guest between preemptions.
3744 resource_request(guest, Resources::new(guest.thread_state().dettid)).await;
3745 Ok(self.record_or_replay(guest, call).await?)
3746 } else if !self.cfg.sequentialize_threads || self.cfg.recordreplay_modes {
3747 self.record_or_replay_shielded_wait(guest, Syscall::EpollPwait(call))
3748 .await
3749 } else {
3750 self.handle_internal_epoll_pwait(guest, call).await
3751 }
3752 }
3753
3754 /// A masked `epoll_pwait` that may wait, under sequentialized threads on a
3755 /// backend whose kernel reports the guest's signal state. It used to run
3756 /// the real call in the thread's turn, so nothing else ran while it waited
3757 /// and it returned 0 at once or at its timeout
3758 /// (https://github.com/rrnewton/hermit/issues/4041). It now polls, as an
3759 /// unmasked `epoll_pwait` does, with probes that carry no mask, and decides
3760 /// interruption against the temporary mask read at entry
3761 /// (`retry_blocking_wait_under_mask`): a signal the mask blocks never ends
3762 /// the call and is delivered after it, and one the mask lets in ends it
3763 /// with EINTR although the guest's own mask may block it. A zero-timeout
3764 /// `epoll_pwait` never looks at pending signals, so no probe could be ended
3765 /// by one in any case. A signal that only the temporary mask lets in stays
3766 /// pending under the guest's own mask once it is put back, and is delivered
3767 /// under the temporary mask (`inject_signal_delivery`), as for a masked
3768 /// pselect6. Linux checks the mask's size, then reads it, before it looks
3769 /// at the epoll descriptor.
3770 async fn handle_internal_masked_epoll_pwait<G: Guest<Self>>(
3771 &self,
3772 guest: &mut G,
3773 call: syscalls::EpollPwait,
3774 ) -> Result<i64, Error> {
3775 if call.sigsetsize() != KERNEL_SIGSET_SIZE {
3776 return Err(Errno::EINVAL.into());
3777 }
3778 let mask_address = call.sigmask().ok_or(Errno::EFAULT)?;
3779 let mask = crate::syscalls::signal::restart_if_first_probe_not_run(
3780 read_kernel_sigset(guest, mask_address.cast()).await,
3781 )?;
3782 super::signal_shadow::note_wait_mask(guest, mask);
3783 // A pending real-time timer expiration that the mask lets the kernel
3784 // deliver or discard (https://github.com/rrnewton/hermit/issues/4089).
3785 observe_under_mask(guest, mask).await?;
3786 let mask = sanitize_ppoll_signal_mask(mask);
3787 let deadline = millis_duration_to_absolute_timeout(guest, call.timeout()).await;
3788 self.wait_on_masked_epoll(
3789 guest,
3790 call.with_sigmask(None),
3791 mask,
3792 deadline,
3793 "epoll_pwait",
3794 )
3795 .await
3796 }
3797
3798 /// The wait of a masked epoll_pwait or epoll_pwait2 under sequentialized
3799 /// threads, once the call's temporary `mask` (sanitized) has been read at
3800 /// entry: `probe`, an unmasked epoll_pwait, polls until an event, an
3801 /// error, a signal that `mask` lets in (EINTR), or `deadline`, which
3802 /// returns 0 (`handle_internal_masked_epoll_pwait`).
3803 async fn wait_on_masked_epoll<G: Guest<Self>>(
3804 &self,
3805 guest: &mut G,
3806 probe: syscalls::EpollPwait,
3807 mask: KernelSigset,
3808 deadline: Option<LogicalTime>,
3809 fyi: &'static str,
3810 ) -> Result<i64, Error> {
3811 let mut rsrc = Resources::new(guest.thread_state().dettid);
3812 rsrc.insert(ResourceID::InternalIOPolling, Permission::W);
3813 rsrc.fyi(fyi);
3814 let (result, deliver) = retry_blocking_wait_under_mask(
3815 guest,
3816 probe,
3817 rsrc,
3818 // Both calls return 0 at their timeout.
3819 deadline.map(|deadline| (deadline, Ok(0))),
3820 Some(crate::syscalls::signal::kernel_installed_signal_mask(mask)),
3821 )
3822 .await;
3823 if deliver && matches!(result, Err(Error::Errno(Errno::EINTR))) {
3824 // The probes staged nothing, so they say nothing about room for
3825 // these 24 bytes. `stage_private_cells`, which tests a window by
3826 // reading it, finds none only on a stack whose pointer is less
3827 // than 24 bytes above unreadable memory and whose red zone is
3828 // unreadable too
3829 // (https://github.com/rrnewton/hermit/issues/4113). Then the signal
3830 // stays pending under the guest's own mask, and the call returns
3831 // EINTR without its handler run and logs this warning.
3832 match stage_private_cells(guest, PrivateCells::MaskAndZeroTimeout(mask), &[]).await? {
3833 Some(staged) => {
3834 inject_signal_delivery(guest, staged.mask(), staged.after_mask()).await;
3835 staged.release(guest);
3836 }
3837 None => tracing::warn!(
3838 "[tid {}] no room to deliver an {fyi} signal under its temporary mask; its \
3839 handler runs only once the guest unblocks it",
3840 guest.tid()
3841 ),
3842 }
3843 }
3844 result
3845 }
3846
3847 /// A masked `epoll_pwait` that may wait, under record and replay. As for a
3848 /// masked pselect6 or ppoll there, the kernel owns the wait outside the
3849 /// scheduler turn, so the timer's signal can end it as on Linux; it kept
3850 /// its turn before, so nothing else ran and the call returned at its
3851 /// timeout (https://github.com/rrnewton/hermit/issues/4041). The call sleeps
3852 /// under a private copy of the mask read at entry, which a sibling cannot
3853 /// rewrite, and the scheduler records its sleeping mask as unknown
3854 /// (`Resources::blocked_signal_mask_unknown`). Replay serves the call from
3855 /// the recording, so for a recorded EINTR it delivers the signal under that
3856 /// mask (`end_replayed_pselect6_under_its_temporary_mask`).
3857 async fn record_or_replay_masked_epoll_pwait<G: Guest<Self>>(
3858 &self,
3859 guest: &mut G,
3860 call: syscalls::EpollPwait,
3861 ) -> Result<i64, Error> {
3862 if call.sigsetsize() != KERNEL_SIGSET_SIZE {
3863 return Err(Errno::EINVAL.into());
3864 }
3865 let mask_address = call.sigmask().ok_or(Errno::EFAULT)?;
3866 let mask = crate::syscalls::signal::restart_if_first_probe_not_run(
3867 read_kernel_sigset(guest, mask_address.cast()).await,
3868 )?;
3869 super::signal_shadow::note_wait_mask(guest, mask);
3870 // The copy may go in the red zone on a stack with no scratch below it,
3871 // clear of the events buffer the kernel writes (`stage_private_cells`;
3872 // re-review of https://github.com/rrnewton/hermit/pull/4087).
3873 let live: Vec<std::ops::Range<usize>> = call
3874 .events()
3875 .map(|events| {
3876 let start = events.as_raw();
3877 let len = usize::try_from(call.maxevents().max(0)).unwrap_or(0)
3878 * std::mem::size_of::<libc::epoll_event>();
3879 start..start.saturating_add(len)
3880 })
3881 .into_iter()
3882 .collect();
3883 let Some(staged) = stage_private_cells(guest, PrivateCells::Mask(mask), &live).await?
3884 else {
3885 return self.refuse_unstageable_epoll_pwait_mask(guest, call).await;
3886 };
3887 let result = self
3888 .record_or_replay_blocking_with_unknown_mask(
3889 guest,
3890 Syscall::EpollPwait(call.with_sigmask(Addr::from_raw(staged.mask()))),
3891 )
3892 .await;
3893 staged.release(guest);
3894 if self.cfg.replaying && matches!(result, Err(Error::Errno(Errno::EINTR))) {
3895 end_replayed_pselect6_under_its_temporary_mask(guest, mask).await;
3896 }
3897 result
3898 }
3899
3900 /// Refuses a masked record/replay `epoll_pwait` whose private mask copy has
3901 /// nowhere to go: no stack scratch below the red zone, and every red-zone
3902 /// window overlaps the events buffer (`refuse_unstageable_pselect6_mask`
3903 /// for pselect6). A run that allows unsupported operations forwards the
3904 /// guest's own mask.
3905 async fn refuse_unstageable_epoll_pwait_mask<G: Guest<Self>>(
3906 &self,
3907 guest: &mut G,
3908 call: syscalls::EpollPwait,
3909 ) -> Result<i64, Error> {
3910 use std::io::Write as _;
3911 let message = "unsupported: epoll_pwait with a temporary signal mask, on a stack \
3912 with no room below its red zone whose red zone the call's events buffer fills: \
3913 Hermit has nowhere to put the private copy of the mask \
3914 (https://github.com/rrnewton/hermit/pull/4087)";
3915 tracing::error!("[tid {}] {message}", guest.tid());
3916 if self.cfg.panic_on_unsupported_syscalls {
3917 let _ = writeln!(crate::util::RetryingStderr, "{message}");
3918 return self
3919 .refuse_unserviceable_operation(guest, Sysno::epoll_pwait, Errno::EFAULT)
3920 .await;
3921 }
3922 self.record_or_replay_blocking_with_unknown_mask(guest, Syscall::EpollPwait(call))
3923 .await
3924 }
3925
3926 /// Handle a guest-internal `epoll_pwait` (NULL sigmask) that can be fully
3927 /// determinized. Mirrors `handle_internal_epoll_wait`.
3928 pub async fn handle_internal_epoll_pwait<G: Guest<Self>>(
3929 &self,
3930 guest: &mut G,
3931 call: syscalls::EpollPwait,
3932 ) -> Result<i64, Error> {
3933 let timeout_millis = call.timeout();
3934 if timeout_millis == 0 {
3935 // Cannot block, but must still yield a scheduler turn: a
3936 // zero-timeout polling loop that never requests a resource can
3937 // monopolize the guest between preemptions and starve the producer
3938 // it is polling for. `handle_poll` takes a turn for every
3939 // sequential mode, and the record/replay arm of `handle_epoll_pwait`
3940 // does the same; before this PR routed NULL-sigmask `epoll_pwait`
3941 // here, the old handler always made an empty request. Omitting it
3942 // only on the plain-strict path would be a scheduling regression,
3943 // not a refactor.
3944 if self.cfg.sequentialize_threads {
3945 let yield_to_peer = self.cfg.backend.loopback_pollers_yield_to_peers
3946 && guest.thread_state().has_loopback_peer();
3947 resource_request(
3948 guest,
3949 zero_timeout_poll_request(guest.thread_state().dettid, yield_to_peer),
3950 )
3951 .await;
3952 }
3953 crate::syscalls::timerfd::publish_expired(guest).await;
3954 Ok(guest.inject(call).await?) // Already non-blocking.
3955 } else {
3956 let maybe_timeout_ns = millis_duration_to_absolute_timeout(guest, timeout_millis).await;
3957 let mut rsrc = Resources::new(guest.thread_state().dettid);
3958 rsrc.insert(ResourceID::InternalIOPolling, Permission::W);
3959 rsrc.fyi("epoll_pwait");
3960 retry_nonblocking_syscall_with_timeout(guest, call, rsrc, maybe_timeout_ns).await
3961 }
3962 }
3963
3964 /// epoll_pwait2 syscall (MAYHANG).
3965 ///
3966 /// epoll_pwait2 is epoll_pwait with a `struct timespec *` timeout instead of
3967 /// an int-milliseconds timeout; recent glibc implements epoll_wait/
3968 /// epoll_pwait via epoll_pwait2 when the kernel supports it. The pinned
3969 /// Reverie revision has no typed variant, so it arrives as a raw
3970 /// `Syscall::Other` and is dispatched here by Sysno. Detcore treats it
3971 /// like epoll_pwait: under record and replay a call that may wait waits
3972 /// outside the turn; under sequentialized threads it polls
3973 /// (`handle_internal_epoll_pwait2`); otherwise, and for a zero timeout, a
3974 /// scheduler yield point is followed by the raw call.
3975 // AUTONOMOUS-BOT-IMPLEMENTED
3976 // TODO-HUMAN-REVIEW(#773)
3977 pub async fn handle_epoll_pwait2<G: Guest<Self>>(
3978 &self,
3979 guest: &mut G,
3980 call: Syscall,
3981 ) -> Result<i64, Error> {
3982 // epoll_pwait2(epfd, events, maxevents, timeout, sigmask, sigsetsize)
3983 let (_, args) = call.into_parts();
3984 // Under the in-guest delivery gate (A5 step 3c.1d, D39), as a masked
3985 // epoll_pwait (`gated_masked_epoll_pwait2`), whose D41 check comes
3986 // before anything else, the observation and the timeout's read below
3987 // included (round-2 ruling 3); its mask's read is then the call's
3988 // first injection.
3989 if !self.cfg.recordreplay_modes
3990 && self.cfg.sequentialize_threads
3991 && args.arg4 != 0
3992 && self.cfg.in_guest_signal_delivery
3993 {
3994 return Box::pin(self.gated_masked_epoll_pwait2(guest, args, true)).await;
3995 }
3996 // The call's first injection, if it makes one; when it does not, the
3997 // mask's read below is the first.
3998 let observed = self
3999 .observe_under_temporary_mask(
4000 guest,
4001 Addr::<libc::sigset_t>::from_raw(args.arg4),
4002 args.arg5,
4003 true,
4004 )
4005 .await?;
4006 // A NULL timeout waits forever; a zero timespec cannot wait and keeps
4007 // the turn.
4008 let zero_timeout = args.arg3 != 0
4009 && Addr::<Timespec>::from_raw(args.arg3)
4010 .and_then(|timeout| guest.memory().read_value(timeout).ok())
4011 .is_some_and(|timeout: Timespec| timeout.tv_sec == 0 && timeout.tv_nsec == 0);
4012 // Under record and replay, a call that may wait does so outside the
4013 // scheduler turn, as epoll_pwait's does: unmasked through the logged
4014 // background path, masked as a kernel wait under a private copy of its
4015 // mask (`record_or_replay_masked_epoll_pwait2`).
4016 if self.cfg.recordreplay_modes {
4017 if !zero_timeout {
4018 return if args.arg4 != 0 {
4019 self.record_or_replay_masked_epoll_pwait2(guest, args, !observed)
4020 .await
4021 } else {
4022 self.record_or_replay_shielded_wait(guest, call).await
4023 };
4024 }
4025 } else if self.cfg.sequentialize_threads
4026 && !zero_timeout
4027 && (args.arg4 == 0 || self.cfg.backend.supports_blocked_wait_signal_interruption)
4028 {
4029 // With threads sequentialized, a call that may wait polls, as
4030 // epoll_pwait does, rather than waiting in the kernel inside the
4031 // turn, where no other guest thread could run
4032 // (https://github.com/rrnewton/hermit/issues/4228). A masked call
4033 // on a backend whose kernel does not report the guest's signal
4034 // state keeps the turn, as a masked epoll_pwait does there.
4035 return self
4036 .handle_internal_epoll_pwait2(guest, args, !observed)
4037 .await;
4038 }
4039 let dettid = guest.thread_state().dettid;
4040 resource_request(guest, Resources::new(dettid)).await; // empty request
4041 // The raw call runs in this turn. A zero timeout only probes, once a
4042 // virtual timerfd is readable exactly when its deadline has passed;
4043 // any other wait here is in the host (`crate::syscalls::timerfd`).
4044 if zero_timeout {
4045 crate::syscalls::timerfd::publish_expired(guest).await;
4046 } else {
4047 crate::syscalls::timerfd::refuse_host_wait(guest, "epoll_pwait2 with a signal mask");
4048 }
4049 // Only a call that may wait under the thread's own mask needs the
4050 // shield; a masked one waits under its own temporary mask.
4051 let shield = if zero_timeout || args.arg4 != 0 {
4052 crate::syscalls::helpers::IgnoredSignalShield::none()
4053 } else {
4054 crate::syscalls::helpers::IgnoredSignalShield::raise(guest).await?
4055 };
4056 let result = self
4057 .record_or_replay(guest, call)
4058 .await
4059 .map_err(Error::from);
4060 shield.lower(guest).await?;
4061 result
4062 }
4063
4064 /// `record_or_replay_blocking` for a wait in the kernel under the thread's
4065 /// own mask, with the signals the guest ignores blocked for the wait
4066 /// (`IgnoredSignalShield`): an `epoll_wait`, or an `epoll_pwait` or
4067 /// `epoll_pwait2` without a mask, or a `poll`, `select`, or `ppoll` or
4068 /// `pselect6` naming no mask. Linux discards those signals at generation,
4069 /// so they never end the wait, but ptrace queues them.
4070 async fn record_or_replay_shielded_wait<G: Guest<Self>>(
4071 &self,
4072 guest: &mut G,
4073 call: Syscall,
4074 ) -> Result<i64, Error> {
4075 let shield = crate::syscalls::helpers::IgnoredSignalShield::raise(guest).await?;
4076 let result = self.record_or_replay_blocking(guest, call).await;
4077 shield.lower(guest).await?;
4078 result
4079 }
4080
4081 /// `guest.inject(call)` for a `select`, or a `pselect6` handed no mask,
4082 /// that waits in the kernel without sequentialized threads, with the
4083 /// signals the guest ignores blocked for the wait when it `may_wait`
4084 /// (`IgnoredSignalShield`).
4085 async fn inject_shielded_wait<G: Guest<Self>, S: SyscallInfo>(
4086 &self,
4087 guest: &mut G,
4088 call: S,
4089 may_wait: bool,
4090 ) -> Result<i64, Error> {
4091 let shield = if may_wait {
4092 crate::syscalls::helpers::IgnoredSignalShield::raise(guest).await?
4093 } else {
4094 crate::syscalls::helpers::IgnoredSignalShield::none()
4095 };
4096 let result = guest.inject(call).await.map_err(Error::from);
4097 shield.lower(guest).await?;
4098 result
4099 }
4100
4101 /// An epoll_pwait2 that may wait, under sequentialized threads: it polls
4102 /// with zero-timeout probes, as epoll_pwait does. A probe is an
4103 /// epoll_pwait with no mask, whose answer for a zero timeout is the same
4104 /// as epoll_pwait2's. Linux copies and checks the timespec before the
4105 /// mask (`record_or_replay_masked_epoll_pwait2`), then the mask's size
4106 /// and the mask, before it looks at the epoll descriptor. An unmasked
4107 /// call waits as an unmasked epoll_pwait does
4108 /// (`handle_internal_epoll_pwait`), a masked one as a masked epoll_pwait
4109 /// does (`wait_on_masked_epoll`). It used to make the real call in the
4110 /// thread's turn, so it returned 0 at its timeout, or never, when only
4111 /// another guest thread could make an event ready
4112 /// (https://github.com/rrnewton/hermit/issues/4228).
4113 /// `first` says the mask's read is the call's first injection
4114 /// (`restart_if_first_probe_not_run`).
4115 async fn handle_internal_epoll_pwait2<G: Guest<Self>>(
4116 &self,
4117 guest: &mut G,
4118 args: SyscallArgs,
4119 first: bool,
4120 ) -> Result<i64, Error> {
4121 let timeout = match Addr::<Timespec>::from_raw(args.arg3) {
4122 Some(timeout) => {
4123 let timeout: Timespec = guest.memory().read_value(timeout)?;
4124 Some(ppoll_timeout_duration(timeout)?)
4125 }
4126 None => None,
4127 };
4128 let probe = syscalls::EpollPwait::new()
4129 .with_epfd(args.arg0 as i32)
4130 .with_events(AddrMut::from_raw(args.arg1))
4131 .with_maxevents(args.arg2 as i32)
4132 .with_timeout(0)
4133 .with_sigmask(None)
4134 .with_sigsetsize(args.arg5);
4135 if args.arg4 == 0 {
4136 let (_, deadline) = ppoll_start(guest, timeout).await;
4137 let mut rsrc = Resources::new(guest.thread_state().dettid);
4138 rsrc.insert(ResourceID::InternalIOPolling, Permission::W);
4139 rsrc.fyi("epoll_pwait2");
4140 return retry_nonblocking_syscall_with_timeout(guest, probe, rsrc, deadline).await;
4141 }
4142 if args.arg5 != KERNEL_SIGSET_SIZE {
4143 return Err(Errno::EINVAL.into());
4144 }
4145 let mask_address = Addr::<libc::sigset_t>::from_raw(args.arg4).ok_or(Errno::EFAULT)?;
4146 let read = read_kernel_sigset(guest, mask_address).await;
4147 let mask = if first {
4148 crate::syscalls::signal::restart_if_first_probe_not_run(read)?
4149 } else {
4150 read?
4151 };
4152 super::signal_shadow::note_wait_mask(guest, mask);
4153 let (_, deadline) = ppoll_start(guest, timeout).await;
4154 // A pending real-time timer expiration that the mask lets the kernel
4155 // deliver or discard (https://github.com/rrnewton/hermit/issues/4089).
4156 observe_under_mask(guest, mask).await?;
4157 let mask = sanitize_ppoll_signal_mask(mask);
4158 self.wait_on_masked_epoll(guest, probe, mask, deadline, "epoll_pwait2")
4159 .await
4160 }
4161
4162 /// Before a call that sleeps or probes under the temporary mask at `mask`
4163 /// (`sigsetsize` bytes), refuse the run by name if the mask lets the kernel
4164 /// take a pending real-time timer expiration that every thread blocked when
4165 /// it expired, and discard the ones it lets the kernel discard
4166 /// (https://github.com/rrnewton/hermit/issues/4089). A mask the kernel would
4167 /// reject fails the call itself, which then observes nothing.
4168 ///
4169 /// Returns whether it made an injection, which it does only while a
4170 /// real-time expiration is kept pending. With `first`, its mask read is
4171 /// the call's first injection, so a signal that stops it restarts the call
4172 /// (`restart_if_first_probe_not_run`); any other failed read is left to
4173 /// the call itself, as before.
4174 async fn observe_under_temporary_mask<G: Guest<Self>>(
4175 &self,
4176 guest: &mut G,
4177 mask: Option<Addr<'_, libc::sigset_t>>,
4178 sigsetsize: usize,
4179 first: bool,
4180 ) -> Result<bool, Error> {
4181 let Some(mask) = mask else {
4182 return Ok(false);
4183 };
4184 if sigsetsize != KERNEL_SIGSET_SIZE
4185 || !crate::scheduler::real_time_expiration_kept_pending()
4186 {
4187 return Ok(false);
4188 }
4189 match read_kernel_sigset(guest, mask).await {
4190 Ok(mask) => observe_under_mask(guest, mask).await?,
4191 Err(error @ Error::Errno(Errno::ERESTARTSYS)) if first => {
4192 return crate::syscalls::signal::restart_if_first_probe_not_run(Err(error));
4193 }
4194 Err(_) => {}
4195 }
4196 Ok(true)
4197 }
4198
4199 /// `record_or_replay_masked_epoll_pwait` for `epoll_pwait2`, whose mask
4200 /// pointer and size are its fifth and sixth arguments.
4201 /// `first` says the mask's read is the call's first injection
4202 /// (`restart_if_first_probe_not_run`).
4203 async fn record_or_replay_masked_epoll_pwait2<G: Guest<Self>>(
4204 &self,
4205 guest: &mut G,
4206 args: SyscallArgs,
4207 first: bool,
4208 ) -> Result<i64, Error> {
4209 // Linux copies and checks the timespec before the mask, so an
4210 // unreadable timeout is EFAULT and an invalid one EINVAL whatever the
4211 // mask and its size (Claude review of
4212 // https://github.com/rrnewton/hermit/pull/4128).
4213 if let Some(timeout) = Addr::<Timespec>::from_raw(args.arg3) {
4214 let timeout: Timespec = guest.memory().read_value(timeout)?;
4215 ppoll_timeout_duration(timeout)?;
4216 }
4217 if args.arg5 != KERNEL_SIGSET_SIZE {
4218 return Err(Errno::EINVAL.into());
4219 }
4220 let mask_address = Addr::<libc::sigset_t>::from_raw(args.arg4).ok_or(Errno::EFAULT)?;
4221 let read = read_kernel_sigset(guest, mask_address).await;
4222 let mask = if first {
4223 crate::syscalls::signal::restart_if_first_probe_not_run(read)?
4224 } else {
4225 read?
4226 };
4227 super::signal_shadow::note_wait_mask(guest, mask);
4228 // The copy may go in the red zone on a stack with no scratch below it,
4229 // clear of the events buffer the kernel writes and of the timeout it
4230 // reads, as for epoll_pwait (`stage_private_cells`).
4231 let events_len = usize::try_from((args.arg2 as i32).max(0)).unwrap_or(0)
4232 * std::mem::size_of::<libc::epoll_event>();
4233 let live: Vec<std::ops::Range<usize>> = [
4234 (args.arg1, events_len),
4235 (args.arg3, std::mem::size_of::<Timespec>()),
4236 ]
4237 .into_iter()
4238 .filter(|&(start, _)| start != 0)
4239 .map(|(start, len)| start..start.saturating_add(len))
4240 .collect();
4241 let Some(staged) = stage_private_cells(guest, PrivateCells::Mask(mask), &live).await?
4242 else {
4243 return self.refuse_unstageable_epoll_pwait2_mask(guest, args).await;
4244 };
4245 let mut private = args;
4246 private.arg4 = staged.mask();
4247 let result = self
4248 .record_or_replay_blocking_with_unknown_mask(
4249 guest,
4250 Syscall::Other(Sysno::epoll_pwait2, private),
4251 )
4252 .await;
4253 staged.release(guest);
4254 if self.cfg.replaying && matches!(result, Err(Error::Errno(Errno::EINTR))) {
4255 end_replayed_pselect6_under_its_temporary_mask(guest, mask).await;
4256 }
4257 result
4258 }
4259
4260 /// `refuse_unstageable_epoll_pwait_mask` for `epoll_pwait2`, whose red
4261 /// zone the events buffer and the timeout fill.
4262 async fn refuse_unstageable_epoll_pwait2_mask<G: Guest<Self>>(
4263 &self,
4264 guest: &mut G,
4265 args: SyscallArgs,
4266 ) -> Result<i64, Error> {
4267 use std::io::Write as _;
4268 let message = "unsupported: epoll_pwait2 with a temporary signal mask, on a stack \
4269 with no room below its red zone whose red zone the call's events buffer and \
4270 timeout fill: Hermit has nowhere to put the private copy of the mask \
4271 (https://github.com/rrnewton/hermit/issues/4088)";
4272 tracing::error!("[tid {}] {message}", guest.tid());
4273 if self.cfg.panic_on_unsupported_syscalls {
4274 let _ = writeln!(crate::util::RetryingStderr, "{message}");
4275 return self
4276 .refuse_unserviceable_operation(guest, Sysno::epoll_pwait2, Errno::EFAULT)
4277 .await;
4278 }
4279 self.record_or_replay_blocking_with_unknown_mask(
4280 guest,
4281 Syscall::Other(Sysno::epoll_pwait2, args),
4282 )
4283 .await
4284 }
4285
4286 /// epoll_wait syscall (MAYHANG)
4287 #[cfg(target_arch = "x86_64")]
4288 pub async fn handle_epoll_wait<G: Guest<Self>>(
4289 &self,
4290 guest: &mut G,
4291 call: syscalls::EpollWait,
4292 ) -> Result<i64, Error> {
4293 if self.cfg.recordreplay_modes && call.timeout() == 0 {
4294 // This cannot block, but still yield a scheduler turn so a polling thread cannot
4295 // monopolize the guest between preemptions.
4296 resource_request(guest, Resources::new(guest.thread_state().dettid)).await;
4297 Ok(self.record_or_replay(guest, call).await?)
4298 } else if !self.cfg.sequentialize_threads || self.cfg.recordreplay_modes {
4299 self.record_or_replay_shielded_wait(guest, Syscall::EpollWait(call))
4300 .await
4301 } else {
4302 self.handle_internal_epoll_wait(guest, call).await
4303 }
4304 }
4305
4306 /// Handle a guest-internal `epoll_wait` call that can be fully determinized.
4307 #[cfg(target_arch = "x86_64")]
4308 pub async fn handle_internal_epoll_wait<G: Guest<Self>>(
4309 &self,
4310 guest: &mut G,
4311 call: syscalls::EpollWait,
4312 ) -> Result<i64, Error> {
4313 let timeout_millis = call.timeout();
4314 if timeout_millis == 0 {
4315 crate::syscalls::timerfd::publish_expired(guest).await;
4316 Ok(guest.inject(call).await?) // Already non-blocking.
4317 } else {
4318 let maybe_timeout_ns = millis_duration_to_absolute_timeout(guest, timeout_millis).await;
4319 let mut rsrc = Resources::new(guest.thread_state().dettid);
4320 rsrc.insert(ResourceID::InternalIOPolling, Permission::W);
4321 rsrc.fyi("epoll_wait");
4322 retry_nonblocking_syscall_with_timeout(guest, call, rsrc, maybe_timeout_ns).await
4323 }
4324 }
4325
4326 /// Connect system call (MAYHANG)
4327 /// Note that connect waits until a TCP handshake but does not wait for accept() on the other end.
4328 /// Nevertheless, it can block for a long time while waiting for connection, unless the socket
4329 /// is already nonblocking.
4330 pub async fn handle_connect<G: Guest<Self>>(
4331 &self,
4332 guest: &mut G,
4333 call: syscalls::Connect,
4334 ) -> Result<i64, Error> {
4335 let fd = call.fd();
4336 let uservaddr = call.uservaddr();
4337 let addrlen = call.addrlen();
4338
4339 if guest.config().sched_heuristic == SchedHeuristic::ConnectBind {
4340 trace!("Scheduling heuristic: reprioritizing connect");
4341 let resource = ResourceID::PriorityChangePoint(
4342 FIRST_PRIORITY,
4343 guest.thread_state().thread_logical_time.as_nanos(),
4344 guest.thread_state().committed_clock_value,
4345 Vec::new(),
4346 );
4347 let req = guest.thread_state().mk_request(resource, Permission::W);
4348 resource_request(guest, req).await;
4349 }
4350
4351 // Whether the connection completes in this turn.
4352 let in_turn = self.completes_in_turn(guest, call);
4353 guest.thread_state_mut().nonblocking_result_normalized = false;
4354 let result = self.execute_nonblockable_fd_syscall(guest, call).await;
4355 // A retried attempt's `EISCONN`, read as success, saw only that the
4356 // open file was connected, by an earlier attempt or another thread's
4357 // connect, not to which socket, nor that it read the address now
4358 // there (Codex review of https://github.com/rrnewton/hermit/pull/4862).
4359 let completed_by_another_attempt =
4360 std::mem::take(&mut guest.thread_state_mut().nonblocking_result_normalized);
4361 // What the address names, as the attempt that connected read it. A
4362 // connect that completes in its turn made that attempt in this turn,
4363 // after the last request it filed, so no other thread has rewritten
4364 // the address since; an earlier read could name another socket than
4365 // the one it reached, if another thread rewrote the address between
4366 // attempts (Codex review of
4367 // https://github.com/rrnewton/hermit/pull/4862). A connect outside its
4368 // turn may have reached any name (`Detcore::note_unix_connect`).
4369 let connected_to = uservaddr.map_or(
4370 crate::syscalls::scm_rights::UnixAddress::Unnamed,
4371 |address| {
4372 crate::syscalls::scm_rights::unix_address(
4373 &guest.memory(),
4374 address.as_raw(),
4375 usize::try_from(addrlen).unwrap_or(0),
4376 )
4377 },
4378 );
4379 if let Ok(0) = &result {
4380 // Pair it with nothing, and no name with anything.
4381 let in_turn = in_turn && !completed_by_another_attempt;
4382 self.note_unix_connect(guest, fd, &connected_to, in_turn);
4383 }
4384 if self.cfg.backend.loopback_pollers_yield_to_peers
4385 && connect_result_allows_peer_classification(&result)
4386 {
4387 // This metadata is a loopback-poll scheduling hint, not part of connect's semantics.
4388 // Let the kernel establish the authoritative result first, then classify the peer
4389 // best-effort so an invalid guest pointer or an untracked fd can never replace the
4390 // kernel's errno.
4391 let loopback_peer = (|| -> Result<Option<bool>, Error> {
4392 let Some(address) = uservaddr else {
4393 return Ok(None);
4394 };
4395 let addrlen = usize::try_from(addrlen).unwrap_or(0);
4396 if addrlen < std::mem::size_of::<u16>() {
4397 return Ok(None);
4398 }
4399 let family: u16 = guest.memory().read_value(address.cast())?;
4400 if family == libc::AF_INET as u16 {
4401 if addrlen < std::mem::size_of::<libc::sockaddr_in>() {
4402 return Ok(None);
4403 }
4404 let address: libc::sockaddr_in = guest.memory().read_value(address.cast())?;
4405 Ok(Some(
4406 Ipv4Addr::from(address.sin_addr.s_addr.to_ne_bytes()).is_loopback(),
4407 ))
4408 } else if family == libc::AF_INET6 as u16 {
4409 if addrlen < std::mem::size_of::<libc::sockaddr_in6>() {
4410 return Ok(None);
4411 }
4412 let address: libc::sockaddr_in6 = guest.memory().read_value(address.cast())?;
4413 Ok(Some(
4414 Ipv6Addr::from(address.sin6_addr.s6_addr).is_loopback(),
4415 ))
4416 } else {
4417 // This includes a successful AF_UNSPEC disconnect and successful connects
4418 // to non-IP families, neither of which has a loopback IP peer.
4419 Ok(Some(false))
4420 }
4421 })()
4422 .ok()
4423 .flatten();
4424 if let Some(loopback_peer) = loopback_peer {
4425 let _ = guest
4426 .thread_state()
4427 .with_detfd(fd, |detfd| detfd.set_loopback_peer(loopback_peer));
4428 }
4429 }
4430 if matches!(result, Ok(0)) {
4431 self.classify_socket_peer(guest, fd, SocketRole::Connected)
4432 .await;
4433 }
4434 // A connect ends what a guest-held socket was connected to: a datagram
4435 // socketpair end reconnected to another process's socket, or
4436 // disconnected, is no longer guest-held. A stream connection to a
4437 // listener this process made is (`classify_guest_held_peer`).
4438 if result.is_ok() {
4439 let _ = guest.thread_state().with_detfd(fd, |detfd| {
4440 if detfd.is_guest_held() {
4441 detfd.clear_guest_held();
4442 }
4443 });
4444 let connecting = guest
4445 .thread_state()
4446 .with_detfd(fd, |detfd| detfd.open_file_handle())
4447 .ok();
4448 let listener = self.listener_named(guest, &connected_to);
4449 let guest_created = crate::syscalls::scm_rights::both_ends_guest_made(
4450 in_turn && !completed_by_another_attempt,
4451 connecting.as_ref(),
4452 listener.as_ref(),
4453 );
4454 self.classify_guest_held_peer(guest, fd, guest_created)
4455 .await;
4456 }
4457
4458 result
4459 }
4460
4461 /// Mark guest-held (`DetFd::is_guest_held`) the pipes and sockets among
4462 /// `received`, descriptors a receive on `channel` just recorded from
4463 /// `SCM_RIGHTS`, whose object only guests hold. That needs `channel` to be
4464 /// guest-held, so the sender was a guest. Then a received descriptor is
4465 /// guest-held when this table already names its object, or the other end
4466 /// of the same pipe, through a guest-held description (the same host
4467 /// device and inode). A socket's peer credentials do not mark it here:
4468 /// they name its peer, not every holder of its open file.
4469 ///
4470 /// A received descriptor that joins the open file its sender's table
4471 /// modeled (`crate::syscalls::scm_rights`) shares that open file's mark,
4472 /// set or not, as a dup would. The mark it arrives with is sound: a join
4473 /// takes only what a guest's `sendmsg` or `sendmmsg` queued on the
4474 /// receiving socket's known peer (`queue_sent_rights`), so the sender is a
4475 /// guest; either the channel was guest-held, so the receiver is a guest
4476 /// too and every holder of the open file is one, or the send revoked every
4477 /// mark first (`note_descriptors_passed_out`, which both send paths run
4478 /// before they send). This function never sees a joined descriptor:
4479 /// `record_received_descriptors` returns only the descriptors it recorded
4480 /// as descriptions of their own (`Receipt::New`), so a join keeps its open
4481 /// file's mark as it is. The rule here holds for the open file all the
4482 /// same: the object or its other end is already guest-held in this table.
4483 /// A descriptor nothing queued names, such as one from a process outside
4484 /// the guest, is a description of its own and starts unmarked; this
4485 /// function marks it only on a guest-held channel, as a known object.
4486 /// Marks
4487 /// only ever claim that no process outside the guest holds the object;
4488 /// once a descriptor may have left the guest, the scheduler stops trusting
4489 /// every mark (`Scheduler::polls_only_guest_state`).
4490 async fn classify_received_descriptors<G: Guest<Self>>(
4491 &self,
4492 guest: &mut G,
4493 channel: RawFd,
4494 received: &[RawFd],
4495 ) {
4496 if received.is_empty() {
4497 return;
4498 }
4499 let from_a_guest = guest
4500 .thread_state()
4501 .with_detfd(channel, |detfd| detfd.is_guest_held())
4502 .unwrap_or(false);
4503 if !from_a_guest {
4504 return;
4505 }
4506 for &fd in received {
4507 let Ok((ty, stat)) = guest
4508 .thread_state()
4509 .with_detfd(fd, |detfd| (detfd.ty(), detfd.stat()))
4510 else {
4511 continue;
4512 };
4513 if !matches!(ty, FdType::Pipe | FdType::Socket) {
4514 continue;
4515 }
4516 let known = stat.is_some_and(|stat| {
4517 guest
4518 .thread_state()
4519 .names_guest_held_object(fd, stat.dev, stat.inode)
4520 });
4521 // A received socket's peer credentials name its peer, not every
4522 // holder of its open file: a host process may still hold the same
4523 // open file, having handed it in over a channel that is not
4524 // guest-held, from which a guest forwarded it here. So a socket
4525 // that joined nothing is marked only as a known object, never
4526 // from its credentials (Codex review of
4527 // https://github.com/rrnewton/hermit/pull/4862).
4528 if known {
4529 let _ = guest
4530 .thread_state()
4531 .with_detfd(fd, |detfd| detfd.set_guest_held());
4532 }
4533 }
4534 }
4535
4536 /// Mark socket `fd`, a connection `connect` or `accept` just made,
4537 /// guest-held (`DetFd::is_guest_held`) when guests alone hold both of its
4538 /// ends and its peer is the caller's own process.
4539 ///
4540 /// `guest_created` is the provenance, which the caller checks: the
4541 /// connecting socket and the listener are both sockets a guest made in
4542 /// this run and never passed out of the guest (`DetFd::is_guest_created`),
4543 /// the connecting socket being `fd` itself for a `connect` and, for an
4544 /// `accept`, the socket whose connection it took
4545 /// (`Detcore::note_unix_accept`). Only guests then hold the connecting
4546 /// socket, and only guests can accept from the listener, so only guests
4547 /// hold either end. Peer credentials alone prove neither: they name the
4548 /// process that connected or listened, not every holder of the open file,
4549 /// and a host process may have kept an alias of a socket it handed in
4550 /// (Codex review of https://github.com/rrnewton/hermit/pull/4862). On
4551 /// top of the provenance, `SO_PEERCRED` must name the caller's own
4552 /// process:
4553 ///
4554 /// - An accepted socket (`accept`, `accept4`): Linux takes its credentials
4555 /// from the task that connected, when it connected.
4556 /// - A connected stream or seqpacket socket (`connect`): Linux copies the
4557 /// credentials of whoever called `listen`.
4558 ///
4559 /// A received socket is never classified here
4560 /// (`classify_received_descriptors`): its credentials name its peer, but
4561 /// a host process may hold the received open file itself.
4562 ///
4563 /// A datagram socket's credentials are not its peer's, so it is never
4564 /// marked here. Nor is a listening socket: `listen` sets a listener's
4565 /// credentials to its caller's, so one this process listened on and then
4566 /// received names this process, yet any process that can name its
4567 /// address can connect and make it ready. Accept and connect never return
4568 /// a listener, and receipts are not classified here, so the
4569 /// `SO_ACCEPTCONN` test, which must read 0 and leaves a socket whose
4570 /// value cannot be read unmarked, is a guard against a future caller. A peer in
4571 /// another process, guest or not, and a socket with
4572 /// no peer credentials (any non-`AF_UNIX` socket, whose peer pid is 0) are
4573 /// left as they were. Best-effort and read-only: an injection that fails
4574 /// leaves the socket unmarked, and nothing the guest sees changes. Only
4575 /// plain runs read the mark for the scheduler's new idle-poller detection,
4576 /// so only they pay for it. Record/replay retains its earlier internal-FD
4577 /// polling and FIFO-open decisions without consulting these marks.
4578 pub(crate) async fn classify_guest_held_peer<G: Guest<Self>>(
4579 &self,
4580 guest: &mut G,
4581 fd: RawFd,
4582 guest_created: bool,
4583 ) {
4584 if !self.cfg.sequentialize_threads || self.cfg.recordreplay_modes || !guest_created {
4585 return;
4586 }
4587 // A datagram socket's credentials are its creator's, or its
4588 // socketpair's, whatever it is connected to now: only a connection's
4589 // name its peer.
4590 let Some(kind) = socket_option::<libc::c_int, _, _>(guest, fd, libc::SO_TYPE).await else {
4591 return;
4592 };
4593 if kind != libc::SOCK_STREAM && kind != libc::SOCK_SEQPACKET {
4594 return;
4595 }
4596 // A listener's credentials are its own (`unix_listen` sets them to
4597 // the caller's), and an outside client's connect makes it ready.
4598 if socket_option::<libc::c_int, _, _>(guest, fd, libc::SO_ACCEPTCONN).await != Some(0) {
4599 return;
4600 }
4601 let Some(credentials) =
4602 socket_option::<libc::ucred, _, _>(guest, fd, libc::SO_PEERCRED).await
4603 else {
4604 return;
4605 };
4606 let peer = credentials.pid;
4607 if peer <= 0 {
4608 return;
4609 }
4610 // The caller's own process id in the same PID namespace that
4611 // `SO_PEERCRED` translated the peer's into.
4612 let Ok(own) = guest.inject(syscalls::Getpid::new()).await else {
4613 return;
4614 };
4615 if i64::from(peer) == own {
4616 let _ = guest
4617 .thread_state()
4618 .with_detfd(fd, |detfd| detfd.set_guest_held());
4619 }
4620 }
4621
4622 /// Handles sendto, sendmsg, and sendmmsg syscalls (MAYHANG).
4623 pub async fn handle_sendrecv<
4624 G: Guest<Self>,
4625 C: SyscallInfo + NonblockableSyscall + Into<Syscall>,
4626 >(
4627 &self,
4628 guest: &mut G,
4629 call: C,
4630 ) -> Result<i64, Error> {
4631 self.execute_nonblockable_fd_syscall(guest, call).await
4632 }
4633
4634 /// Sends one message and invalidates process-wide flock knowledge after success.
4635 ///
4636 /// The guest can mutate shared message and control memory while this helper
4637 /// deschedules. Parsing before the syscall would not prove which descriptors the
4638 /// kernel later transferred, so a successful unbound send conservatively makes
4639 /// every cached open-file-description lock mode unknown.
4640 pub async fn handle_sendmsg<G: Guest<Self>>(
4641 &self,
4642 guest: &mut G,
4643 call: syscalls::Sendmsg,
4644 ) -> Result<i64, Error> {
4645 if self.accept_in_turn_enabled() {
4646 let headers: Vec<usize> = call
4647 .msg()
4648 .map(|header| header.as_raw())
4649 .into_iter()
4650 .collect();
4651 self.mark_exported_listeners(guest, &headers);
4652 }
4653 let headers: Vec<usize> = call
4654 .msg()
4655 .map(|header| header.as_raw())
4656 .into_iter()
4657 .collect();
4658 self.refuse_sending_virtual_timerfds(guest, &headers);
4659 // The placeholder descriptions it sends, counted in flight, and what
4660 // each attempt checks again, or its refusal by name
4661 // (`Detcore::inspect_placeholder_send`).
4662 let (copies, outside_turn) = match self
4663 .inspect_send_placeholders(guest, Sysno::sendmsg, call.fd(), &headers, call)
4664 .await
4665 {
4666 Ok(inspected) => inspected,
4667 Err(refusal) => return refusal,
4668 };
4669 // A per-process file's record sequence, which every holder of the
4670 // open file shares, moves to Detcore's global state before the
4671 // descriptor can reach another process.
4672 Box::pin(self.share_sent_record_sequences(guest, &headers)).await;
4673 let in_turn = self.completes_in_turn(guest, call);
4674 if outside_turn || !sent_descriptors(guest, &headers).0.is_empty() {
4675 Self::note_descriptors_passed_out(guest, call.fd(), &headers, outside_turn);
4676 }
4677 self.note_sockets_passed_out(guest, &headers);
4678 let result = self.execute_nonblockable_fd_syscall(guest, call).await;
4679 guest.thread_state_mut().send_inspection = None;
4680 if outside_turn
4681 && Self::may_send_placeholders(guest)
4682 && let Some(refusal) =
4683 Box::pin(self.placeholder_send_after(guest, Sysno::sendmsg, call.fd())).await
4684 {
4685 return refusal;
4686 }
4687 if result.is_err() && !copies.is_empty() {
4688 let _ = crate::tool_global::placeholder_copies(
4689 guest,
4690 crate::procfs::locks::PlaceholderCopies::Unsent(copies),
4691 )
4692 .await;
4693 }
4694 if crate::syscalls::scm_rights::found_its_peer_gone(&result) {
4695 let addressed = sends_to_a_name(guest, &headers);
4696 self.note_peer_gone(guest, call.fd(), addressed);
4697 }
4698 let result = result?;
4699 guest.thread_state().forget_flock_modes();
4700 if let Some(stream) = self.rights_peer_is_stream(guest, call.fd()) {
4701 if in_turn {
4702 // One message, of `result` bytes.
4703 let sent = sent_rights(guest, &headers, |_| usize::try_from(result).unwrap_or(0));
4704 let out_of_band = call.flags() & libc::MSG_OOB != 0;
4705 self.queue_sent_rights(guest, call.fd(), stream, sent, out_of_band)
4706 .await?;
4707 } else {
4708 self.lose_peer_lockstep(guest, call.fd());
4709 }
4710 }
4711 Ok(result)
4712 }
4713
4714 /// The placeholder step of a send `call` (`sysno`, on `socket`), whose
4715 /// messages' headers are at the guest addresses `headers`, where its
4716 /// `SCM_RIGHTS` descriptors can matter to the `/proc` directories Detcore
4717 /// serves (`Detcore::may_send_placeholders`): the inspection
4718 /// (`Detcore::inspect_placeholder_send`), kept for the attempts the call
4719 /// makes (`ThreadState::send_inspection`), with the descriptions it
4720 /// counted in flight, or the refusal by name; and, for every send,
4721 /// whether the call runs outside this thread's turn in a run that
4722 /// sequentializes threads (a blocking call), after which
4723 /// `Detcore::placeholder_send_after` checks what joined the table. The
4724 /// inspection is boxed: only a send that may pass a placeholder carries
4725 /// it.
4726 async fn inspect_send_placeholders<G, C>(
4727 &self,
4728 guest: &mut G,
4729 sysno: Sysno,
4730 socket: RawFd,
4731 headers: &[usize],
4732 call: C,
4733 ) -> Result<(Vec<crate::types::OpenFileId>, bool), Result<i64, Error>>
4734 where
4735 G: Guest<Self>,
4736 C: NonblockableSyscall + Into<Syscall> + Copy,
4737 {
4738 // Without sequentialized threads no thread has a turn: every check
4739 // Detcore makes may race the guest's other threads, that mode's named
4740 // residual, which a refusal here would not narrow.
4741 let outside_turn =
4742 self.cfg.sequentialize_threads && self.nonblockable_call_runs_outside_turn(guest, call);
4743 if !Self::may_send_placeholders(guest) {
4744 return Ok((Vec::new(), outside_turn));
4745 }
4746 let (copies, inspection) =
4747 Box::pin(self.inspect_placeholder_send(guest, sysno, socket, headers, outside_turn))
4748 .await?;
4749 guest.thread_state_mut().send_inspection = Some(inspection);
4750 Ok((copies, outside_turn))
4751 }
4752
4753 /// Note, for the scheduler, that a send on `sockfd` may pass
4754 /// descriptors over `SCM_RIGHTS` to a process outside
4755 /// the guest (`ThreadState::passed_descriptors_out_unreported`). A passed
4756 /// descriptor may be, or reach, an object some guest holds a guest-held
4757 /// description of, in any process's table, which a process outside
4758 /// Detcore's view could then make ready; so from then on the scheduler
4759 /// trusts no guest-held mark (`Scheduler::polls_only_guest_state`), and
4760 /// that covers polls already waiting. Control data read at entry cannot
4761 /// prove that no descriptor will be sent: a sibling may rewrite it before
4762 /// a later attempt. Linux imports each attempt's current msghdr and control
4763 /// data (`copy_msghdr_from_user`, `____sys_sendmsg`, Linux v6.12
4764 /// <https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/net/socket.c?h=v6.12#n2529>).
4765 /// A guest-held channel sends only to its guest peer, whatever `msg_name`
4766 /// says, but that exception requires an in-turn attempt: outside the turn
4767 /// even the destination descriptor may change before Linux imports it.
4768 /// Each in-turn retry checks the channel again immediately before sending.
4769 /// Revoke before the call regardless of its eventual result: sendmmsg can
4770 /// send a message before failing to write its length back to guest memory
4771 /// (Linux v6.12 `__sys_sendmmsg`,
4772 /// <https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/net/socket.c?h=v6.12#n2747>).
4773 pub(crate) fn note_descriptors_passed_out<G: Guest<Self>>(
4774 guest: &mut G,
4775 sockfd: RawFd,
4776 headers: &[usize],
4777 outside_turn: bool,
4778 ) {
4779 let reaches_a_guest = guest
4780 .thread_state()
4781 .with_detfd(sockfd, |detfd| detfd.is_guest_held())
4782 .unwrap_or(false);
4783 if reaches_a_guest && !outside_turn {
4784 return;
4785 }
4786 // No control data at entry proves nothing about what another thread
4787 // can install before a later attempt, and outside the turn even the
4788 // destination may change: the send counts as passing descriptors.
4789 // The descriptors the entry headers name also lose their
4790 // guest-created mark, as before.
4791 guest.thread_state_mut().passed_descriptors_out_unreported = true;
4792 let (sent, unread) = sent_descriptors(guest, headers);
4793 crate::reserved_input::before_descriptor_export(guest, &sent, unread)
4794 .unwrap_or_else(|error| panic!("reserved-input export publication failed: {error}"));
4795 if !sent.is_empty() || unread {
4796 clear_guest_created(guest, &sent);
4797 }
4798 }
4799
4800 /// Notes, for a `sendmsg` or `sendmmsg` `call` about to run outside the
4801 /// caller's turn in a plain sequentialized run, that it may pass
4802 /// descriptors out of the guest
4803 /// (`ThreadState::passed_descriptors_out_unreported`), whatever its
4804 /// descriptor and messages name now. The kernel imports them at a
4805 /// host-chosen moment, after other guest threads ran: one may have
4806 /// replaced the descriptor with an `AF_UNIX` socket that is not
4807 /// guest-held and rewritten a message to pass a guest-held one. Called
4808 /// where the call leaves the turn (`record_or_replay_blocking_resource_then`),
4809 /// so the request that leaves it carries the report, whatever the handler
4810 /// decided earlier and however many requests came between (Codex review
4811 /// of https://github.com/rrnewton/hermit/pull/4862).
4812 pub(crate) fn passes_descriptors_out_outside_the_turn<G: Guest<Self>>(
4813 guest: &mut G,
4814 call: Syscall,
4815 ) {
4816 let config = guest.config();
4817 if config.sequentialize_threads
4818 && !config.recordreplay_modes
4819 && matches!(call, Syscall::Sendmsg(_) | Syscall::Sendmmsg(_))
4820 {
4821 // The native ancillary import and descriptor lookup can happen
4822 // after another task changed them. The old poll-reliance report
4823 // is not a persistent-reference certificate.
4824 crate::reserved_input::before_descriptor_export(guest, &[], true).unwrap_or_else(
4825 |error| panic!("reserved-input export publication failed: {error}"),
4826 );
4827 guest.thread_state_mut().passed_descriptors_out_unreported = true;
4828 }
4829 }
4830
4831 /// Whether `call`, a `sendmsg` or `sendmmsg` about to make an attempt in
4832 /// the caller's turn, would pass descriptors out of the guest that this
4833 /// thread has not reported yet (`note_descriptors_passed_out`), read from
4834 /// its messages as they are now: a sibling may have rewritten them since
4835 /// the handler read them, or while an earlier attempt waited. If so, the
4836 /// pass-out is noted (`ThreadState::passed_descriptors_out_unreported`),
4837 /// and the caller reports it in a request of its own before the attempt
4838 /// runs. Only plain sequentialized runs read the marks, so only they
4839 /// check; once this thread has reported a pass-out the scheduler trusts no
4840 /// mark again, so it checks no more (Codex review of
4841 /// https://github.com/rrnewton/hermit/pull/4862).
4842 pub(crate) fn passes_descriptors_out_now<G: Guest<Self>>(guest: &mut G, call: Syscall) -> bool {
4843 let config = guest.config();
4844 if !config.sequentialize_threads || config.recordreplay_modes {
4845 return false;
4846 }
4847 let (sockfd, headers): (RawFd, Vec<usize>) = match call {
4848 Syscall::Sendmsg(call) => (
4849 call.fd(),
4850 call.msg()
4851 .map(|header| header.as_raw())
4852 .into_iter()
4853 .collect(),
4854 ),
4855 Syscall::Sendmmsg(call) => (
4856 call.sockfd(),
4857 call.msgvec().map_or_else(Vec::new, |vector| {
4858 (0..call.vlen().min(libc::UIO_MAXIOV as u32) as usize)
4859 .map(|index| vector.as_raw() + index * std::mem::size_of::<Mmsghdr>())
4860 .collect()
4861 }),
4862 ),
4863 _ => return false,
4864 };
4865 let reaches_a_guest = guest
4866 .thread_state()
4867 .with_detfd(sockfd, |detfd| detfd.is_guest_held())
4868 .unwrap_or(false);
4869 if reaches_a_guest {
4870 return false;
4871 }
4872 let (sent, unread) = sent_descriptors(guest, &headers);
4873 crate::reserved_input::before_descriptor_export(guest, &sent, unread)
4874 .unwrap_or_else(|error| panic!("reserved-input export publication failed: {error}"));
4875 if guest.thread_state().passed_descriptors_out_reported {
4876 return false;
4877 }
4878 if sent.is_empty() && !unread {
4879 return false;
4880 }
4881 guest.thread_state_mut().passed_descriptors_out_unreported = true;
4882 clear_guest_created(guest, &sent);
4883 true
4884 }
4885
4886 /// Mark every accept-in-turn listener (`DetFd::is_accept_in_turn_eligible`)
4887 /// these messages name in `SCM_RIGHTS` as exported, before they are sent
4888 /// and whatever the send returns: Linux may have queued the descriptor
4889 /// even when the call reports an error, and both phases must decide alike.
4890 /// `headers` are the guest addresses of the `msghdr`s: one for `sendmsg`,
4891 /// one per entry Linux could send for `sendmmsg`.
4892 ///
4893 /// An exported listener may be held by a process outside Detcore's view,
4894 /// so its accepts never take the temporary `O_NONBLOCK` again and keep the
4895 /// backgrounded path. An accept already waiting on it refuses at its next
4896 /// attempt (`ACCEPT_EXPORT_REFUSAL`). Both phases read the same guest
4897 /// memory here. When a header or control buffer cannot be read in full
4898 /// (Linux reads a write-only page, which Detcore cannot, and takes up to
4899 /// `optmem_max` bytes of control data, more than `MAX_CONTROL_BYTES`),
4900 /// every eligible listener in the table counts as exported. A thread that
4901 /// rewrites the control buffer while this one is descheduled can send a
4902 /// listener this did not see.
4903 // TODO-HUMAN-REVIEW(PR-3908)
4904 fn mark_exported_listeners<G: Guest<Self>>(&self, guest: &mut G, headers: &[usize]) {
4905 let (sent, unread) = sent_descriptors(guest, headers);
4906 if unread {
4907 // Linux can read memory Detcore cannot (a write-only page) and
4908 // accepts more control data than Detcore reads, so a message it
4909 // could not read may carry any listener.
4910 guest
4911 .thread_state()
4912 .mark_accept_in_turn_listeners_exported();
4913 return;
4914 }
4915 for fd in sent {
4916 let _ = guest.thread_state().with_detfd(fd, |detfd| {
4917 if detfd.is_accept_in_turn_eligible() {
4918 detfd.set_exported();
4919 }
4920 });
4921 }
4922 }
4923
4924 /// Refuse the run, by name, before messages at `headers` pass a timerfd
4925 /// over `SCM_RIGHTS` while timerfds run on Detcore's virtual clock, armed
4926 /// or not (one armed after it is sent would be modelled only by the
4927 /// sender), or an epoll descriptor while this process holds an armed one
4928 /// (its set may include it). The receiving process keeps no model of the
4929 /// timer, so its probes would never make it readable and it would wait
4930 /// where Linux wakes (`crate::syscalls::timerfd`). A message whose control
4931 /// data Detcore cannot read refuses too while this process holds an armed
4932 /// timerfd.
4933 fn refuse_sending_virtual_timerfds<G: Guest<Self>>(&self, guest: &mut G, headers: &[usize]) {
4934 if !crate::syscalls::timerfd::enabled(guest) {
4935 return;
4936 }
4937 let holds_armed = crate::syscalls::timerfd::has_virtual_timerfds(guest);
4938 let (sent, unread) = sent_descriptors(guest, headers);
4939 if unread && holds_armed {
4940 crate::syscalls::timerfd::refuse(format_args!(
4941 "a message whose SCM_RIGHTS control data Detcore cannot read may pass a timerfd to another process"
4942 ));
4943 }
4944 for fd in sent {
4945 match guest.thread_state().with_detfd(fd, |detfd| detfd.ty()) {
4946 Ok(FdType::Timerfd) => crate::syscalls::timerfd::refuse(format_args!(
4947 "a message passes timerfd {fd} over SCM_RIGHTS, and its receiver keeps no model of the timer"
4948 )),
4949 Ok(FdType::Epoll) if holds_armed => crate::syscalls::timerfd::refuse(format_args!(
4950 "a message passes epoll descriptor {fd} over SCM_RIGHTS while this process holds a timerfd its set may include"
4951 )),
4952 _ => {}
4953 }
4954 }
4955 }
4956}
4957
4958/// What the messages at `headers`, which a send completed in the caller's
4959/// turn reports sending, passed over `SCM_RIGHTS`: for each that carried
4960/// descriptors, those descriptors and the bytes `sent_bytes` says it sent
4961/// (`Detcore::queue_sent_rights`). After such a send no other guest thread
4962/// has run, so its header and control data are the ones the send read. A
4963/// message whose header or control data cannot be read in full marks the
4964/// whole as `unread`.
4965/// Withdraws `DetFd::set_guest_created` from each of `sent`, descriptors a
4966/// send passes out of the guest.
4967fn clear_guest_created<T, G>(guest: &G, sent: &[RawFd])
4968where
4969 T: RecordOrReplay,
4970 G: Guest<Detcore<T>>,
4971{
4972 for &fd in sent {
4973 let _ = guest
4974 .thread_state()
4975 .with_detfd(fd, |detfd| detfd.clear_guest_created());
4976 }
4977}
4978
4979fn sent_rights<T, G>(
4980 guest: &G,
4981 headers: &[usize],
4982 sent_bytes: impl Fn(usize) -> usize,
4983) -> SentRights
4984where
4985 T: RecordOrReplay,
4986 G: Guest<Detcore<T>>,
4987{
4988 let mut rights = SentRights::default();
4989 for &header in headers {
4990 match sent_descriptors(guest, &[header]) {
4991 (_, true) => rights.unread = true,
4992 (sent, false) => {
4993 // Whether the message named its destination: a header whose
4994 // name length cannot be read counts as one that did.
4995 let addressed = Addr::<Msghdr>::from_raw(header)
4996 .and_then(|header| guest.memory().read_value::<_, Msghdr>(header).ok())
4997 .is_none_or(|header| header.msg_namelen != 0);
4998 rights.messages.push((sent, sent_bytes(header), addressed));
4999 }
5000 }
5001 }
5002 rights
5003}
5004
5005/// Whether the first message at `headers` names its destination (a nonzero
5006/// `msg_namelen`); one whose header cannot be read counts as one that did
5007/// (`Detcore::note_peer_gone`).
5008fn sends_to_a_name<T, G>(guest: &G, headers: &[usize]) -> bool
5009where
5010 T: RecordOrReplay,
5011 G: Guest<Detcore<T>>,
5012{
5013 headers.first().is_none_or(|&header| {
5014 Addr::<Msghdr>::from_raw(header)
5015 .and_then(|header| guest.memory().read_value::<_, Msghdr>(header).ok())
5016 .is_none_or(|header| header.msg_namelen != 0)
5017 })
5018}
5019
5020/// The descriptors the messages at `headers` name in `SCM_RIGHTS`, and
5021/// whether a header or control buffer could not be read in full, in which case
5022/// the list may be incomplete (`mark_exported_listeners`).
5023pub(crate) fn sent_descriptors<T, G>(guest: &G, headers: &[usize]) -> (Vec<RawFd>, bool)
5024where
5025 T: RecordOrReplay,
5026 G: Guest<Detcore<T>>,
5027{
5028 let mut sent: Vec<RawFd> = Vec::new();
5029 let mut unread = false;
5030 for header in headers {
5031 // A null header or control buffer fails the call with EFAULT
5032 // before Linux reads any descriptor.
5033 let Some(header) = Addr::<Msghdr>::from_raw(*header) else {
5034 continue;
5035 };
5036 let Ok(header) = guest.memory().read_value::<_, Msghdr>(header) else {
5037 unread = true;
5038 continue;
5039 };
5040 let Some(control_address) = Addr::<u8>::from_raw(header.msg_control as usize) else {
5041 continue;
5042 };
5043 if header.msg_controllen > MAX_CONTROL_BYTES {
5044 unread = true;
5045 continue;
5046 }
5047 let mut control = vec![0; header.msg_controllen];
5048 if guest
5049 .memory()
5050 .read_exact(control_address, &mut control)
5051 .is_err()
5052 {
5053 unread = true;
5054 continue;
5055 }
5056 sent.extend(received_descriptors(&control));
5057 }
5058 (sent, unread)
5059}
5060
5061impl<T: RecordOrReplay> Detcore<T> {
5062 /// Sends a message batch and invalidates process-wide flock knowledge when the
5063 /// kernel reports at least one message sent. This intentionally includes
5064 /// descriptors named only by an unsent tail message: the mutable guest array is
5065 /// not stable across a possible deschedule, so narrower attribution is unsafe.
5066 pub async fn handle_sendmmsg<G: Guest<Self>>(
5067 &self,
5068 guest: &mut G,
5069 call: syscalls::Sendmmsg,
5070 ) -> Result<i64, Error> {
5071 if self.cfg.recordreplay_modes && is_accepted_connection(guest, call.sockfd()) {
5072 return self.refuse_log_served_mmsg(guest, Sysno::sendmmsg).await;
5073 }
5074 if self.accept_in_turn_enabled() {
5075 // Every entry Linux could send, sent or not: see `mark_exported_listeners`.
5076 // Reverie types the vector as `msghdr`s, but its entries are
5077 // `mmsghdr`s, each a `msghdr` followed by `msg_len`, so step by
5078 // the larger size.
5079 let headers: Vec<usize> = call.msgvec().map_or_else(Vec::new, |vector| {
5080 (0..call.vlen().min(libc::UIO_MAXIOV as u32) as usize)
5081 .map(|index| vector.as_raw() + index * std::mem::size_of::<Mmsghdr>())
5082 .collect()
5083 });
5084 self.mark_exported_listeners(guest, &headers);
5085 }
5086 // As for `sendmsg`: every entry Linux could send.
5087 let headers: Vec<usize> = call.msgvec().map_or_else(Vec::new, |vector| {
5088 (0..call.vlen().min(libc::UIO_MAXIOV as u32) as usize)
5089 .map(|index| vector.as_raw() + index * std::mem::size_of::<Mmsghdr>())
5090 .collect()
5091 });
5092 self.refuse_sending_virtual_timerfds(guest, &headers);
5093 // A placeholder sent in a batch is refused by name: which of its
5094 // messages the batch sent is not known (`descriptor_directory`). Each
5095 // attempt checks again that the batch passes none.
5096 let outside_turn = match self
5097 .inspect_send_placeholders(guest, Sysno::sendmmsg, call.sockfd(), &headers, call)
5098 .await
5099 {
5100 Ok((_, outside_turn)) => outside_turn,
5101 Err(refusal) => return refusal,
5102 };
5103 let in_turn = self.completes_in_turn(guest, call);
5104 if outside_turn || !sent_descriptors(guest, &headers).0.is_empty() {
5105 Self::note_descriptors_passed_out(guest, call.sockfd(), &headers, outside_turn);
5106 }
5107 self.note_sockets_passed_out(guest, &headers);
5108 let result = self.execute_nonblockable_fd_syscall(guest, call).await;
5109 guest.thread_state_mut().send_inspection = None;
5110 if outside_turn
5111 && Self::may_send_placeholders(guest)
5112 && let Some(refusal) =
5113 Box::pin(self.placeholder_send_after(guest, Sysno::sendmmsg, call.sockfd())).await
5114 {
5115 return refusal;
5116 }
5117 // A batch fails only where its first message did.
5118 if crate::syscalls::scm_rights::found_its_peer_gone(&result) {
5119 let addressed = sends_to_a_name(guest, &headers[..headers.len().min(1)]);
5120 self.note_peer_gone(guest, call.sockfd(), addressed);
5121 }
5122 let result = result?;
5123 if result > 0 {
5124 guest.thread_state().forget_flock_modes();
5125 }
5126 if let Some(stream) = self.rights_peer_is_stream(guest, call.sockfd()) {
5127 if in_turn {
5128 // The first `result` entries were sent, each of the bytes
5129 // Linux wrote back to its `msg_len`.
5130 let sent_headers =
5131 &headers[..usize::try_from(result).unwrap_or(0).min(headers.len())];
5132 let sent = sent_rights(guest, sent_headers, |header| {
5133 Addr::<u32>::from_raw(header + std::mem::offset_of!(Mmsghdr, msg_len))
5134 .and_then(|length| guest.memory().read_value(length).ok())
5135 .map_or(0, |length: u32| length as usize)
5136 });
5137 let out_of_band = call.flags() & libc::MSG_OOB != 0;
5138 self.queue_sent_rights(guest, call.sockfd(), stream, sent, out_of_band)
5139 .await?;
5140 } else {
5141 self.lose_peer_lockstep(guest, call.sockfd());
5142 }
5143 }
5144 Ok(result)
5145 }
5146
5147 /// Record and replay keep an accepted connection only in the log, and
5148 /// replay stands in an eventfd for it. Neither recorder nor replayer
5149 /// records the batched `sendmmsg` and `recvmmsg`, so replay would run them
5150 /// live against that eventfd and return something other than what the
5151 /// recording saw. Refuse them in both modes instead. On other sockets
5152 /// they still run live
5153 /// (https://github.com/rrnewton/hermit/issues/3883).
5154 // TODO-HUMAN-REVIEW(PR-3871): Refuse unrecorded batched I/O on accepted connections.
5155 async fn refuse_log_served_mmsg<G: Guest<Self>>(
5156 &self,
5157 guest: &mut G,
5158 sysno: Sysno,
5159 ) -> Result<i64, Error> {
5160 self.refuse_unserviceable_operation(guest, sysno, Errno::EOPNOTSUPP)
5161 .await
5162 }
5163
5164 // TODO-HUMAN-REVIEW(PR-912): Review receive-time capture across socket aliases.
5165 async fn observe_socket_receive<G: Guest<Self>>(
5166 &self,
5167 guest: &mut G,
5168 fd: i32,
5169 ) -> Result<LogicalTime, Error> {
5170 let timestamp = thread_observe_time(guest).await;
5171 guest.thread_state().with_detfd(fd, |detfd| {
5172 detfd.set_socket_receive_timestamp(timestamp);
5173 })?;
5174 Ok(timestamp)
5175 }
5176
5177 /// Receive one message, then record the descriptors it received over
5178 /// `SCM_RIGHTS` (`Detcore::record_received_descriptors`) and read their
5179 /// locks (`Detcore::refresh_description_locks`). Both read only the
5180 /// control bytes Linux reports writing, at the control buffer the
5181 /// message named before the call (`ReceiveGeometry`).
5182 pub async fn handle_recvmsg<G: Guest<Self>>(
5183 &self,
5184 guest: &mut G,
5185 call: syscalls::Recvmsg,
5186 ) -> Result<i64, Error> {
5187 let discovered = self.discovers_received_descriptors(guest);
5188 // Where the tool discovers received descriptors, what a receive
5189 // delivered is needed for the locks of every descriptor delivered
5190 // (with virtual time), and to mark the inotify instances among them
5191 // (`Detcore::marks_discovered_inotify_receipts`).
5192 let wanted = !discovered
5193 || self.cfg.virtualize_time
5194 || self.marks_discovered_inotify_receipts(guest);
5195 let in_turn = self.completes_in_turn(guest, call);
5196 Self::begin_receive_geometry(guest, wanted, &Syscall::Recvmsg(call));
5197 let result = self.receive_one_message(guest, call).await;
5198 let geometry = guest.thread_state_mut().receive_geometry.take().flatten();
5199 let from = ReceivedFrom {
5200 socket: call.sockfd(),
5201 peek: call.flags() & libc::MSG_PEEK != 0,
5202 messages: 1,
5203 };
5204 // A datagram or seqpacket receive takes its message before it copies
5205 // the bytes, and frees it if that copy fails.
5206 if let Err(Error::Errno(errno)) = &result {
5207 self.note_receive_error(guest, from, *errno, &Syscall::Recvmsg(call), in_turn);
5208 }
5209 let result = result?;
5210 let delivered = geometry
5211 .map(|geometry| geometry.delivered(&guest.memory(), result))
5212 .unwrap_or_default();
5213 let cloexec =
5214 Syscall::Recvmsg(call).into_parts().1.arg2 as i32 & libc::MSG_CMSG_CLOEXEC != 0;
5215 let recorded = self
5216 .record_received_descriptors(
5217 guest,
5218 &delivered,
5219 super::scm_rights::Channel::ScmRights,
5220 in_turn,
5221 cloexec,
5222 from,
5223 )
5224 .await?;
5225 self.classify_received_descriptors(guest, call.sockfd(), &recorded)
5226 .await;
5227 // Where the tool discovers received descriptors, recvmsg's refresh
5228 // covers every descriptor delivered, as before; elsewhere only those
5229 // this receipt recorded.
5230 let refreshed = if discovered {
5231 delivered.fds.as_slice()
5232 } else {
5233 recorded.as_slice()
5234 };
5235 self.refresh_received_locks(guest, refreshed, super::files::LockCall::Recvmsg)
5236 .await?;
5237 Ok(result)
5238 }
5239
5240 /// Keep the input geometry of `call` (a `recvmsg` or `recvmmsg`) in the
5241 /// thread state when `wanted`: read here, at entry, and again before each
5242 /// attempt of the internal polling loop, so that the geometry the caller
5243 /// takes when the call returns is that of the attempt that completed
5244 /// (`ThreadState::receive_geometry`).
5245 fn begin_receive_geometry<G: Guest<Self>>(guest: &mut G, wanted: bool, call: &Syscall) {
5246 guest.thread_state_mut().receive_geometry =
5247 wanted.then(|| ReceiveGeometry::before(&guest.memory(), call));
5248 }
5249
5250 /// A descriptor received over `SCM_RIGHTS` that Detcore models joins
5251 /// this process's table in `procfs::locks::LockLedger` with whatever OFD
5252 /// or flock locks its description holds, and is marked as received, so
5253 /// that a lock change through it invalidates the identity the sender's
5254 /// tables know the description by. One Detcore keeps no model of (not
5255 /// recorded, `Detcore::record_received_descriptors`) is left out, so its
5256 /// locks are hidden once no modelled table names the description (a
5257 /// named residual).
5258 ///
5259 /// Not under record and replay: they render no procfs files
5260 /// (`Detcore::renders_procfs`), so the ledger feeds nothing the guest
5261 /// reads, and the refresh reads each descriptor's fdinfo live, which in
5262 /// replay is a stand-in eventfd's wherever the recording held a file this
5263 /// process held nowhere else.
5264 pub(crate) async fn refresh_received_locks<G: Guest<Self>>(
5265 &self,
5266 guest: &mut G,
5267 fds: &[RawFd],
5268 call: super::files::LockCall,
5269 ) -> Result<(), Error> {
5270 if self.cfg.virtualize_time && !self.cfg.recordreplay_modes {
5271 for &fd in fds {
5272 self.refresh_description_locks(guest, fd, call, None)
5273 .await?;
5274 }
5275 }
5276 Ok(())
5277 }
5278
5279 // AUTONOMOUS-BOT-IMPLEMENTED
5280 // TODO-HUMAN-REVIEW(PR-901)
5281 /// Receive one message and replace host socket timestamps with logical time.
5282 async fn receive_one_message<G: Guest<Self>>(
5283 &self,
5284 guest: &mut G,
5285 call: syscalls::Recvmsg,
5286 ) -> Result<i64, Error> {
5287 // NETLINK_SOCK_DIAG replies carry host-assigned socket identities in
5288 // their msg_iov payload (not msg_control). Canonicalize the supported
5289 // fields before the binary reply reaches the guest.
5290 // The predicate is shared with the read/readv/recvfrom/recvmmsg paths so
5291 // the five receive syscalls cannot drift apart again.
5292 if self.sock_diag_reply_fd(guest, call.sockfd()) {
5293 return self.handle_sock_diag_recvmsg(guest, call).await;
5294 }
5295
5296 if !self.cfg.virtualize_time {
5297 return self.execute_nonblockable_fd_syscall(guest, call).await;
5298 }
5299
5300 let Some(message_address) = call.msg() else {
5301 return self
5302 .handle_socket_receive(guest, call, call.sockfd(), true)
5303 .await;
5304 };
5305 // Snapshot every input field before the receive. Linux permits the
5306 // control buffer to overlap this header, so rereading it afterward can
5307 // turn a successful consuming receive into an artificial EFAULT.
5308 let message: Msghdr = guest.memory().read_value(message_address)?;
5309 if message.msg_control.is_null() || message.msg_controllen == 0 {
5310 return self
5311 .handle_socket_receive(guest, call, call.sockfd(), true)
5312 .await;
5313 }
5314 let control_len = message.msg_controllen.min(MAX_CONTROL_BYTES);
5315 let control_address: AddrMut<'_, u8> =
5316 AddrMut::from_raw(message.msg_control as usize).ok_or(Errno::EFAULT)?;
5317 let mut control = vec![0; control_len];
5318 // Validate the output region before consuming a datagram.
5319 guest.memory().read_exact(control_address, &mut control)?;
5320
5321 let result = self.execute_nonblockable_fd_syscall(guest, call).await?;
5322 let now = self.observe_socket_receive(guest, call.sockfd()).await?;
5323 let mut control = vec![0; control_len];
5324 guest.memory().read_exact(control_address, &mut control)?;
5325 if socket_timestamp_messages(&control).is_empty() {
5326 return Ok(result);
5327 }
5328
5329 canonicalize_socket_timestamps(&mut control, now);
5330 guest.memory().write_exact(control_address, &control)?;
5331 Ok(result)
5332 }
5333
5334 // AUTONOMOUS-BOT-IMPLEMENTED
5335 // TODO-HUMAN-REVIEW(PR-1064)
5336 /// Whether replies received on `fd` must have their supported socket
5337 /// identities canonicalized.
5338 ///
5339 /// This is the single predicate every receive path consults. It exists as
5340 /// one function because the flag used to be tested inline in `recvmsg`
5341 /// alone, and four other receive syscalls reached the same dump without it
5342 /// (see [`Self::sanitize_sock_diag_segments`]).
5343 pub(crate) fn sock_diag_reply_fd<G: Guest<Self>>(&self, guest: &mut G, fd: RawFd) -> bool {
5344 self.cfg.virtualize_metadata
5345 && guest
5346 .thread_state()
5347 .with_detfd(fd, |detfd| detfd.is_sock_diag() || detfd.is_netlink_route())
5348 .unwrap_or(false)
5349 }
5350
5351 /// Whether this descriptor is specifically a `NETLINK_ROUTE` socket, which
5352 /// needs the link-counter sanitizer rather than the sock-diag one.
5353 fn netlink_route_reply_fd<G: Guest<Self>>(&self, guest: &mut G, fd: RawFd) -> bool {
5354 guest
5355 .thread_state()
5356 .with_detfd(fd, |detfd| detfd.is_netlink_route())
5357 .unwrap_or(false)
5358 }
5359
5360 /// Read an `iovec` array out of guest memory as plain `(address, capacity)`
5361 /// scalars, so no non-`Send` raw pointer is held across an await.
5362 fn read_iov_segments<G: Guest<Self>>(
5363 guest: &mut G,
5364 iov: usize,
5365 iovlen: usize,
5366 ) -> Result<Vec<(usize, usize)>, Error> {
5367 if iov == 0 || iovlen == 0 {
5368 return Ok(Vec::new());
5369 }
5370 let count = iovlen.min(libc::UIO_MAXIOV as usize);
5371 let address: AddrMut<'_, libc::iovec> = AddrMut::from_raw(iov).ok_or(Errno::EFAULT)?;
5372 // SAFETY: `iovec` is a plain C record; an all-zero value is a valid
5373 // staging value immediately overwritten by `read_values`.
5374 let mut iovecs: Vec<libc::iovec> =
5375 (0..count).map(|_| unsafe { std::mem::zeroed() }).collect();
5376 guest.memory().read_values(address.into(), &mut iovecs)?;
5377 Ok(iovecs
5378 .iter()
5379 .map(|iov| (iov.iov_base as usize, iov.iov_len))
5380 .collect())
5381 }
5382
5383 /// Canonicalize host-assigned identities in a `NETLINK_SOCK_DIAG` reply that
5384 /// the kernel has already written into guest memory.
5385 ///
5386 /// `segments` describes the destination buffers as `(address, capacity)` in
5387 /// the order the kernel filled them; `received` is the syscall's return
5388 /// value. The reply is gathered contiguously (it may be scattered across
5389 /// several buffers), sanitized via `crate::sock_diag` (fail-open,
5390 /// zero-only, never resizes), and written back preserving the original
5391 /// boundaries. Bounded by both each buffer's capacity and `received`, which
5392 /// under `MSG_TRUNC` can exceed the total capacity.
5393 ///
5394 /// Synchronous on purpose: every caller has already completed its receive,
5395 /// so nothing here awaits and no guest address outlives the borrow.
5396 fn sanitize_sock_diag_segments<G: Guest<Self>>(
5397 &self,
5398 guest: &mut G,
5399 fd: RawFd,
5400 segments: &[(usize, usize)],
5401 received: usize,
5402 ) -> Result<(), Error> {
5403 if received == 0 || segments.is_empty() {
5404 return Ok(());
5405 }
5406 let mut filled: Vec<(AddrMut<'_, u8>, usize)> = Vec::new();
5407 let mut buffer: Vec<u8> = Vec::with_capacity(received);
5408 let mut remaining = received;
5409 for &(base, capacity) in segments {
5410 if remaining == 0 {
5411 break;
5412 }
5413 if base == 0 || capacity == 0 {
5414 continue;
5415 }
5416 let take = capacity.min(remaining);
5417 let address: AddrMut<'_, u8> = AddrMut::from_raw(base).ok_or(Errno::EFAULT)?;
5418 let mut segment = vec![0u8; take];
5419 guest.memory().read_exact(address, &mut segment)?;
5420 buffer.extend_from_slice(&segment);
5421 filled.push((address, take));
5422 remaining -= take;
5423 }
5424
5425 // NETLINK_ROUTE and NETLINK_SOCK_DIAG replies need different
5426 // sanitizers: one zeroes live interface counters, the other determinizes
5427 // supported socket identities. The descriptor decides which, so a guest
5428 // holding both kinds of socket gets each handled correctly.
5429 let modified = if self.netlink_route_reply_fd(guest, fd) {
5430 crate::netlink_route::sanitize_route_link_stats(&mut buffer)
5431 } else {
5432 crate::sock_diag::sanitize_sock_diag_identities(&mut buffer)
5433 };
5434 if !modified {
5435 return Ok(());
5436 }
5437
5438 let mut offset = 0;
5439 for (address, len) in filled {
5440 guest
5441 .memory()
5442 .write_exact(address, &buffer[offset..offset + len])?;
5443 offset += len;
5444 }
5445 Ok(())
5446 }
5447
5448 /// `recvfrom` on a socket-diag descriptor: one destination buffer.
5449 ///
5450 /// `recv(2)` has no syscall of its own on x86_64 — glibc lowers it to
5451 /// `recvfrom` with a null address — so this covers `recv` as well, which is
5452 /// what Python's `socket.recv()` reaches.
5453 pub async fn handle_sock_diag_recvfrom<G: Guest<Self>>(
5454 &self,
5455 guest: &mut G,
5456 call: syscalls::Recvfrom,
5457 ) -> Result<i64, Error> {
5458 let fd = call.fd();
5459 let base = call.buf().map(|address| address.as_raw()).unwrap_or(0);
5460 let len = call.len();
5461 let result = self.handle_socket_receive(guest, call, fd, true).await?;
5462 let received = usize::try_from(result).unwrap_or(0);
5463 self.sanitize_sock_diag_segments(guest, fd, &[(base, len)], received)?;
5464 Ok(result)
5465 }
5466
5467 /// `read` on a socket-diag descriptor: one destination buffer.
5468 pub async fn handle_sock_diag_read<G: Guest<Self>>(
5469 &self,
5470 guest: &mut G,
5471 call: syscalls::Read,
5472 ) -> Result<i64, Error> {
5473 let fd = call.fd();
5474 let base = call.buf().map(|address| address.as_raw()).unwrap_or(0);
5475 let len = call.len();
5476 let result = self.handle_read(guest, call).await?;
5477 let received = usize::try_from(result).unwrap_or(0);
5478 self.sanitize_sock_diag_segments(guest, fd, &[(base, len)], received)?;
5479 Ok(result)
5480 }
5481
5482 /// Receive a `NETLINK_SOCK_DIAG` dump and zero the host-assigned socket
5483 /// inode numbers in the reply so `ss`-style enumeration is deterministic.
5484 ///
5485 /// The dump lands in `msg_iov` (netlink diag sockets carry no ancillary
5486 /// data), possibly scattered across several iovecs.
5487 async fn handle_sock_diag_recvmsg<G: Guest<Self>>(
5488 &self,
5489 guest: &mut G,
5490 call: syscalls::Recvmsg,
5491 ) -> Result<i64, Error> {
5492 let fd = call.sockfd();
5493 let Some(message_address) = call.msg() else {
5494 return self.handle_socket_receive(guest, call, fd, true).await;
5495 };
5496 // Snapshot the header's iovec pointer/count before the receive (they are
5497 // stable across it; the kernel fills the pointed-to buffers, not the
5498 // array). Scoped so the `msghdr`'s raw pointers are dropped before the
5499 // await below: holding one would make this future non-`Send`.
5500 let segments = {
5501 let message: Msghdr = guest.memory().read_value(message_address)?;
5502 Self::read_iov_segments(guest, message.msg_iov as usize, message.msg_iovlen)?
5503 };
5504
5505 let result = self.handle_socket_receive(guest, call, fd, true).await?;
5506 let received = usize::try_from(result).unwrap_or(0);
5507 self.sanitize_sock_diag_segments(guest, fd, &segments, received)?;
5508 Ok(result)
5509 }
5510
5511 /// `readv` on a socket-diag descriptor: an `iovec` array, no message header.
5512 pub async fn handle_sock_diag_readv<G: Guest<Self>>(
5513 &self,
5514 guest: &mut G,
5515 call: syscalls::Readv,
5516 ) -> Result<i64, Error> {
5517 let fd = call.fd();
5518 let iov = call.iov().map(|address| address.as_raw()).unwrap_or(0);
5519 let segments = Self::read_iov_segments(guest, iov, call.len())?;
5520 let result = self.handle_readv(guest, call).await?;
5521 let received = usize::try_from(result).unwrap_or(0);
5522 self.sanitize_sock_diag_segments(guest, fd, &segments, received)?;
5523 Ok(result)
5524 }
5525
5526 /// `recvmmsg` on a socket-diag descriptor.
5527 ///
5528 /// Each delivered `mmsghdr` is a separate datagram with its own byte count
5529 /// in `msg_len`, so each is gathered and sanitized independently; treating
5530 /// the batch as one buffer would let one message's length run into the
5531 /// next message's memory.
5532 pub async fn handle_sock_diag_recvmmsg<G: Guest<Self>>(
5533 &self,
5534 guest: &mut G,
5535 call: syscalls::Recvmmsg,
5536 ) -> Result<i64, Error> {
5537 let fd = call.fd();
5538 let Some(messages_address) = call.mmsg() else {
5539 return self.handle_recvmmsg(guest, call).await;
5540 };
5541 let vlen = call.vlen();
5542 if vlen == 0 || vlen > libc::UIO_MAXIOV as u32 {
5543 return self.handle_recvmmsg(guest, call).await;
5544 }
5545 let base = messages_address.as_raw();
5546
5547 // Snapshot each message's iovec geometry BEFORE the receive: the kernel
5548 // fills the pointed-to buffers and writes msg_len, but does not move the
5549 // iovec arrays themselves. Scoped, and reduced to plain scalars, so the
5550 // `mmsghdr` raw pointers are dropped before the await below: holding one
5551 // would make this future non-`Send`.
5552 let geometry: Vec<Vec<(usize, usize)>> = {
5553 // SAFETY: `mmsghdr` is a plain C record; an all-zero value is a
5554 // valid staging value immediately overwritten by `read_values`.
5555 let mut headers: Vec<Mmsghdr> = (0..vlen as usize)
5556 .map(|_| unsafe { std::mem::zeroed() })
5557 .collect();
5558 guest
5559 .memory()
5560 .read_values(messages_address.into(), &mut headers)?;
5561 let mut geometry = Vec::with_capacity(headers.len());
5562 for header in &headers {
5563 geometry.push(Self::read_iov_segments(
5564 guest,
5565 header.msg_hdr.msg_iov as usize,
5566 header.msg_hdr.msg_iovlen,
5567 )?);
5568 }
5569 geometry
5570 };
5571
5572 let result = self.handle_recvmmsg(guest, call).await?;
5573 let delivered = usize::try_from(result).unwrap_or(0).min(geometry.len());
5574 if delivered == 0 {
5575 return Ok(result);
5576 }
5577
5578 // Re-read the array for the per-message byte counts the kernel just
5579 // wrote. Also scoped: nothing awaits past this point, but keeping the
5580 // raw pointers contained keeps the rule visible.
5581 let counts: Vec<usize> = {
5582 let address: AddrMut<'_, Mmsghdr> = AddrMut::from_raw(base).ok_or(Errno::EFAULT)?;
5583 // SAFETY: as above.
5584 let mut headers: Vec<Mmsghdr> = (0..vlen as usize)
5585 .map(|_| unsafe { std::mem::zeroed() })
5586 .collect();
5587 guest.memory().read_values(address.into(), &mut headers)?;
5588 headers.iter().map(|h| h.msg_len as usize).collect()
5589 };
5590 for (index, segments) in geometry.iter().enumerate().take(delivered) {
5591 self.sanitize_sock_diag_segments(guest, fd, segments, counts[index])?;
5592 }
5593 Ok(result)
5594 }
5595
5596 // TODO-HUMAN-REVIEW(PR-912): Review receive-time capture across socket aliases.
5597 /// Handle a socket receive and retain one timestamp for every alias of its open file.
5598 pub async fn handle_socket_receive<
5599 G: Guest<Self>,
5600 C: SyscallInfo + NonblockableSyscall + Into<Syscall>,
5601 >(
5602 &self,
5603 guest: &mut G,
5604 call: C,
5605 fd: i32,
5606 zero_delivers_packet: bool,
5607 ) -> Result<i64, Error> {
5608 let result = self.execute_nonblockable_fd_syscall(guest, call).await?;
5609 if self.cfg.virtualize_time && (result > 0 || (result == 0 && zero_delivers_packet)) {
5610 self.observe_socket_receive(guest, fd).await?;
5611 }
5612 Ok(result)
5613 }
5614
5615 /// Receive a message batch, then record the descriptors it received over
5616 /// `SCM_RIGHTS` (`Detcore::record_received_descriptors`) and read their
5617 /// locks, as for `recvmsg`.
5618 pub async fn handle_recvmmsg<G: Guest<Self>>(
5619 &self,
5620 guest: &mut G,
5621 call: syscalls::Recvmmsg,
5622 ) -> Result<i64, Error> {
5623 // Where the tool discovers received descriptors, what a receive
5624 // delivered is needed only to mark the inotify instances among them
5625 // (`Detcore::marks_discovered_inotify_receipts`).
5626 let wanted = !self.discovers_received_descriptors(guest)
5627 || self.marks_discovered_inotify_receipts(guest);
5628 let in_turn = self.completes_in_turn(guest, call);
5629 Self::begin_receive_geometry(guest, wanted, &Syscall::Recvmmsg(call));
5630 let result = self.receive_message_batch(guest, call).await;
5631 let geometry = guest.thread_state_mut().receive_geometry.take().flatten();
5632 let mut from = ReceivedFrom {
5633 socket: call.fd(),
5634 peek: call.flags() & libc::MSG_PEEK as u32 != 0,
5635 messages: 1,
5636 };
5637 // As for `recvmsg`: an error before any message is the first
5638 // message's.
5639 if let Err(Error::Errno(errno)) = &result {
5640 self.note_receive_error(guest, from, *errno, &Syscall::Recvmmsg(call), in_turn);
5641 }
5642 let result = result?;
5643 from.messages = usize::try_from(result).unwrap_or(0);
5644 let delivered = geometry
5645 .map(|geometry| geometry.delivered(&guest.memory(), result))
5646 .unwrap_or_default();
5647 let cloexec =
5648 Syscall::Recvmmsg(call).into_parts().1.arg3 as i32 & libc::MSG_CMSG_CLOEXEC != 0;
5649 let recorded = self
5650 .record_received_descriptors(
5651 guest,
5652 &delivered,
5653 super::scm_rights::Channel::ScmRights,
5654 in_turn,
5655 cloexec,
5656 from,
5657 )
5658 .await?;
5659 self.classify_received_descriptors(guest, call.fd(), &recorded)
5660 .await;
5661 self.note_short_batch(guest, from, call.vlen() as usize);
5662 // Only the descriptors this receipt recorded.
5663 self.refresh_received_locks(guest, &recorded, super::files::LockCall::Recvmmsg)
5664 .await?;
5665 Ok(result)
5666 }
5667
5668 // AUTONOMOUS-BOT-IMPLEMENTED
5669 // TODO-HUMAN-REVIEW(PR-901)
5670 /// Receive a message batch and replace every host socket timestamp with logical time.
5671 async fn receive_message_batch<G: Guest<Self>>(
5672 &self,
5673 guest: &mut G,
5674 call: syscalls::Recvmmsg,
5675 ) -> Result<i64, Error> {
5676 if self.cfg.recordreplay_modes && is_accepted_connection(guest, call.fd()) {
5677 return self.refuse_log_served_mmsg(guest, Sysno::recvmmsg).await;
5678 }
5679 if !self.cfg.virtualize_time || call.vlen() > libc::UIO_MAXIOV as u32 {
5680 return self.execute_nonblockable_fd_syscall(guest, call).await;
5681 }
5682
5683 let Some(messages_address) = call.mmsg() else {
5684 return self.execute_nonblockable_fd_syscall(guest, call).await;
5685 };
5686 let controls = {
5687 // SAFETY: `mmsghdr` is a plain C record and an all-zero value is a valid
5688 // initialized staging value that is immediately overwritten by `read_values`.
5689 let mut messages: Vec<Mmsghdr> = (0..call.vlen())
5690 .map(|_| unsafe { std::mem::zeroed() })
5691 .collect();
5692 guest
5693 .memory()
5694 .read_values(messages_address.into(), &mut messages)?;
5695
5696 let mut controls = Vec::with_capacity(messages.len());
5697 for message in &messages {
5698 let header = &message.msg_hdr;
5699 if header.msg_control.is_null() || header.msg_controllen == 0 {
5700 controls.push(None);
5701 continue;
5702 }
5703 controls.push(Some((
5704 header.msg_control as usize,
5705 header.msg_controllen.min(MAX_CONTROL_BYTES),
5706 )));
5707 }
5708 controls
5709 };
5710
5711 let result = self.execute_nonblockable_fd_syscall(guest, call).await?;
5712 let delivered = usize::try_from(result).unwrap_or(0).min(controls.len());
5713 if delivered == 0 {
5714 return Ok(result);
5715 }
5716 let now = self.observe_socket_receive(guest, call.fd()).await?;
5717 let mut timestamped = Vec::new();
5718 for (address, length) in controls.into_iter().take(delivered).flatten() {
5719 let address: AddrMut<'_, u8> = AddrMut::from_raw(address).ok_or(Errno::EFAULT)?;
5720 let mut bytes = vec![0; length];
5721 guest.memory().read_exact(address, &mut bytes)?;
5722 if !socket_timestamp_messages(&bytes).is_empty() {
5723 timestamped.push((address, bytes));
5724 }
5725 }
5726 if timestamped.is_empty() {
5727 return Ok(result);
5728 }
5729
5730 for (address, mut bytes) in timestamped {
5731 canonicalize_socket_timestamps(&mut bytes, now);
5732 guest.memory().write_exact(address, &bytes)?;
5733 }
5734 Ok(result)
5735 }
5736}
5737
5738/// Read socket option `optname` (`SOL_SOCKET`) of guest socket `fd` through an
5739/// injected `getsockopt`, read-only. `None` when the injection fails or Linux
5740/// writes less than a whole `T`.
5741async fn socket_option<T, D, G>(guest: &mut G, fd: RawFd, optname: libc::c_int) -> Option<T>
5742where
5743 T: Copy,
5744 D: reverie::Tool,
5745 G: Guest<D>,
5746{
5747 let mut stack = guest.stack().await;
5748 let value: AddrMut<T> = stack.reserve();
5749 let length = stack.push(std::mem::size_of::<T>() as libc::socklen_t);
5750 let _guard = stack.commit().ok()?;
5751 let length = AddrMut::<libc::socklen_t>::from_raw(length.as_raw())?;
5752 let call = syscalls::Getsockopt::new()
5753 .with_fd(fd)
5754 .with_level(libc::SOL_SOCKET)
5755 .with_optname(optname)
5756 .with_optval(Some(value.cast()))
5757 .with_optlen(Some(length));
5758 guest.inject(call).await.ok()?;
5759 let written = guest.memory().read_value(length).ok()?;
5760 if (written as usize) < std::mem::size_of::<T>() {
5761 return None;
5762 }
5763 guest.memory().read_value(value).ok()
5764}
5765
5766#[cfg(test)]
5767mod tests {
5768 use super::*;
5769
5770 #[cfg(target_arch = "x86_64")]
5771 #[test]
5772 fn only_record_replay_and_unsequentialized_runs_poll_in_the_kernel() {
5773 // A plain or strict run sequentializes threads without record or replay.
5774 assert!(!polls_in_kernel(true, false));
5775 assert!(polls_in_kernel(true, true));
5776 assert!(polls_in_kernel(false, false));
5777 }
5778
5779 #[cfg(target_arch = "x86_64")]
5780 #[test]
5781 fn a_restarted_kernel_poll_waits_only_until_its_deadline() {
5782 // Only a positive timeout has an end to keep, and record and replay
5783 // read no clock.
5784 let poll = |timeout| syscalls::Poll::new().with_timeout(timeout);
5785 assert_eq!(kernel_poll_deadline(&poll(0), false), None);
5786 assert_eq!(kernel_poll_deadline(&poll(-1), false), None);
5787 assert_eq!(
5788 kernel_poll_deadline(&poll(100), true),
5789 Some(KernelPollDeadline(None))
5790 );
5791 let before = Instant::now();
5792 let end = kernel_poll_deadline(&poll(100), false)
5793 .and_then(|deadline| deadline.0)
5794 .unwrap();
5795 assert!(end >= before + Duration::from_millis(100));
5796 assert!(end <= Instant::now() + Duration::from_millis(100));
5797 // The restart waits for the time left, rounded up to whole milliseconds.
5798 let now = Instant::now();
5799 let end = now + Duration::from_millis(100);
5800 assert_eq!(remaining_poll_millis(now, end), 100);
5801 assert_eq!(
5802 remaining_poll_millis(now + Duration::from_nanos(1), end),
5803 100
5804 );
5805 assert_eq!(
5806 remaining_poll_millis(now + Duration::from_millis(99), end),
5807 1
5808 );
5809 // At or past the end it only reports what is ready.
5810 assert_eq!(remaining_poll_millis(end, end), 0);
5811 assert_eq!(remaining_poll_millis(end + Duration::from_secs(1), end), 0);
5812 // An end beyond the range of poll's timeout waits the longest it can.
5813 assert_eq!(
5814 remaining_poll_millis(now, now + Duration::from_secs(10_000_000)),
5815 i32::MAX
5816 );
5817 }
5818
5819 #[cfg(target_arch = "x86_64")]
5820 #[test]
5821 fn an_external_send_revokes_marks_before_mutable_control_data_is_read() {
5822 use std::os::fd::AsRawFd;
5823
5824 let mut guest = crate::syscalls::helpers::kernel_signal_wait_failures::fd_test_guest();
5825 let (socket, _peer) = std::os::unix::net::UnixStream::pair().unwrap();
5826 let fd = socket.as_raw_fd();
5827 guest
5828 .thread_state_mut()
5829 .add_fd(fd, nix::fcntl::OFlag::O_RDWR, FdType::Socket, None)
5830 .unwrap();
5831 // No control data at entry proves nothing about what another thread
5832 // can install while this send is descheduled before a later attempt.
5833 Detcore::note_descriptors_passed_out(&mut guest, fd, &[], false);
5834 assert!(guest.thread_state().passed_descriptors_out_unreported);
5835
5836 // A stable guest-held channel cannot export even changed control data.
5837 guest.thread_state_mut().passed_descriptors_out_unreported = false;
5838 guest
5839 .thread_state()
5840 .with_detfd(fd, |detfd| detfd.set_guest_held())
5841 .unwrap();
5842 Detcore::note_descriptors_passed_out(&mut guest, fd, &[], false);
5843 assert!(!guest.thread_state().passed_descriptors_out_unreported);
5844
5845 // Outside the turn, even a channel's descriptor identity can change
5846 // before the kernel imports it. Revoke before handing the call off.
5847 Detcore::note_descriptors_passed_out(&mut guest, fd, &[], true);
5848 assert!(guest.thread_state().passed_descriptors_out_unreported);
5849 }
5850
5851 #[test]
5852 fn a_kernel_poll_without_a_deadline_restarts_by_running_again() {
5853 // Linux's code, and a signal that stopped the call before it ran.
5854 let interrupted = [Errno::ERESTART_RESTARTBLOCK, Errno::ERESTARTSYS];
5855 for errno in interrupted {
5856 for timeout in [0, -1] {
5857 let call = syscalls::Poll::new().with_timeout(timeout);
5858 assert!(matches!(
5859 kernel_poll_result(&call, Err(Error::Errno(errno)), false),
5860 Err(Error::Errno(Errno::ERESTARTNOHAND))
5861 ));
5862 }
5863 // A positive timeout keeps its deadline in the kernel's restart block.
5864 let timed = syscalls::Poll::new().with_timeout(5);
5865 assert!(matches!(
5866 kernel_poll_result(&timed, Err(Error::Errno(errno)), false),
5867 Err(Error::Errno(Errno::ERESTART_RESTARTBLOCK))
5868 ));
5869 }
5870 // Every other result is the call's own.
5871 let zero = syscalls::Poll::new().with_timeout(0);
5872 assert!(matches!(kernel_poll_result(&zero, Ok(2), false), Ok(2)));
5873 assert!(matches!(
5874 kernel_poll_result(&zero, Err(Error::Errno(Errno::EINTR)), false),
5875 Err(Error::Errno(Errno::EINTR))
5876 ));
5877 assert!(matches!(
5878 kernel_poll_result(&zero, Err(Error::Errno(Errno::ERESTARTNOINTR)), false),
5879 Err(Error::Errno(Errno::ERESTARTNOINTR))
5880 ));
5881 }
5882
5883 #[cfg(target_arch = "x86_64")]
5884 #[test]
5885 fn a_kernel_poll_that_keeps_a_record_restarts_through_restart_syscall() {
5886 // Linux's code and a signal that stopped the call before it ran both
5887 // restart a kept record, even once its time is up (timeout 0).
5888 for errno in [Errno::ERESTART_RESTARTBLOCK, Errno::ERESTARTSYS] {
5889 for timeout in [100, 0] {
5890 let call = syscalls::Poll::new().with_timeout(timeout);
5891 assert!(matches!(
5892 kernel_poll_result(&call, Err(Error::Errno(errno)), true),
5893 Err(Error::Errno(Errno::ERESTART_RESTARTBLOCK))
5894 ));
5895 }
5896 }
5897 let call = syscalls::Poll::new().with_timeout(100);
5898 assert!(matches!(kernel_poll_result(&call, Ok(1), true), Ok(1)));
5899 assert!(matches!(
5900 kernel_poll_result(&call, Err(Error::Errno(Errno::EINTR)), true),
5901 Err(Error::Errno(Errno::EINTR))
5902 ));
5903 // A poll that a signal stopped before it ran gives the guest
5904 // ERESTARTNOHAND, so the backend reports the signal it holds; one that
5905 // ran keeps Linux's code.
5906 let restart = || Err(Error::Errno(Errno::ERESTART_RESTARTBLOCK));
5907 assert!(poll_stopped_before_it_ran(&Err(Error::Errno(
5908 Errno::ERESTARTSYS
5909 ))));
5910 for other in [Ok(0), restart(), Err(Error::Errno(Errno::EINTR))] {
5911 assert!(!poll_stopped_before_it_ran(&other), "{other:?}");
5912 }
5913 assert!(matches!(
5914 kernel_poll_restart_code(restart(), true),
5915 Err(Error::Errno(Errno::ERESTARTNOHAND))
5916 ));
5917 assert!(matches!(
5918 kernel_poll_restart_code(restart(), false),
5919 Err(Error::Errno(Errno::ERESTART_RESTARTBLOCK))
5920 ));
5921 for before_run in [true, false] {
5922 assert!(matches!(kernel_poll_restart_code(Ok(0), before_run), Ok(0)));
5923 assert!(matches!(
5924 kernel_poll_restart_code(Err(Error::Errno(Errno::EINTR)), before_run),
5925 Err(Error::Errno(Errno::EINTR))
5926 ));
5927 }
5928 }
5929
5930 #[test]
5931 fn zero_timeout_socket_poll_requests_a_strong_one_turn_yield() {
5932 let request = zero_timeout_poll_request(DetTid::from_raw(17), true);
5933
5934 assert_eq!(request.resources.len(), 1);
5935 assert_eq!(
5936 request.resources.get(&ResourceID::SchedYield),
5937 Some(&Permission::W)
5938 );
5939 assert_eq!(request.fyi, LOOPBACK_POLL_YIELD_FYI);
5940 }
5941
5942 #[test]
5943 fn zero_timeout_non_socket_poll_keeps_the_existing_empty_turn() {
5944 let request = zero_timeout_poll_request(DetTid::from_raw(17), false);
5945
5946 assert!(request.resources.is_empty());
5947 assert!(request.fyi.is_empty());
5948 }
5949
5950 #[test]
5951 fn connect_peer_classification_never_overrides_kernel_errors() {
5952 assert!(connect_result_allows_peer_classification(&Ok(0)));
5953 assert!(connect_result_allows_peer_classification(&Err(
5954 Error::Errno(Errno::EINPROGRESS)
5955 )));
5956 assert!(!connect_result_allows_peer_classification(&Err(
5957 Error::Errno(Errno::EALREADY)
5958 )));
5959 assert!(!connect_result_allows_peer_classification(&Err(
5960 Error::Errno(Errno::EBADF)
5961 )));
5962 assert!(!connect_result_allows_peer_classification(&Err(
5963 Error::Errno(Errno::EFAULT)
5964 )));
5965 }
5966
5967 #[test]
5968 fn ppoll_timeout_uses_timespec_units() {
5969 assert_eq!(
5970 ppoll_timeout_duration(Timespec {
5971 tv_sec: 2,
5972 tv_nsec: 345_678_901,
5973 }),
5974 Ok(Duration::new(2, 345_678_901))
5975 );
5976 assert_eq!(
5977 timespec_from_duration(Duration::new(2, 345_678_901)),
5978 Timespec {
5979 tv_sec: 2,
5980 tv_nsec: 345_678_901,
5981 }
5982 );
5983 }
5984
5985 #[test]
5986 fn pselect6_fd_set_lengths_follow_the_raw_linux_abi() {
5987 assert_eq!(PSELECT6_INTERNAL_MAX_NFDS, 64);
5988 assert_eq!(pselect6_fd_set_len(-1), Err(Errno::EINVAL));
5989 assert_eq!(pselect6_fd_set_len(0), Ok(0));
5990 assert_eq!(pselect6_fd_set_len(1), Ok(8));
5991 assert_eq!(pselect6_fd_set_len(65), Ok(16));
5992 assert_eq!(pselect6_fd_set_len(libc::FD_SETSIZE as i32), Ok(128));
5993 }
5994
5995 /// A ppoll probe runs again only after the backend's report that it did
5996 /// not run. EINTR, which a backend that runs the call itself answers with
5997 /// the temporary mask installed for a pending caught signal's delivery,
5998 /// is the guest's answer: retrying it found the signal again forever.
5999 #[test]
6000 fn ppoll_probes_run_again_only_after_a_not_run_report() {
6001 assert!(ppoll_probe_runs_again(Err(Errno::ERESTARTSYS)));
6002 for result in [
6003 Err(Errno::EINTR),
6004 Err(Errno::ERESTARTNOHAND),
6005 Err(Errno::ENOSYS),
6006 Err(Errno::EFAULT),
6007 Ok(0),
6008 Ok(1),
6009 ] {
6010 assert!(!ppoll_probe_runs_again(result), "{result:?}");
6011 }
6012 }
6013
6014 /// The pending signals a temporary mask lets in, when every one of them
6015 /// is ignored by the shared rule: set to SIG_IGN, or ignored by default
6016 /// with no handler, on either queue; none when the mask lets in a caught
6017 /// or default-fatal one, or a default-ignored one with a handler.
6018 #[test]
6019 fn a_temporary_mask_lets_in_only_ignored_signals_or_not() {
6020 let bit = |signal: i32| 1_u64 << (signal - 1);
6021 let sets = |pending: u64, shared: u64, ignored: u64, caught: u64| {
6022 reverie::SignalSets::new(pending, shared, bit(libc::SIGWINCH), ignored, caught)
6023 };
6024 let winch = bit(libc::SIGWINCH);
6025 let chld = bit(libc::SIGCHLD);
6026 let usr1 = bit(libc::SIGUSR1);
6027 let hup = bit(libc::SIGHUP);
6028 let none = 0;
6029 for (sets, mask, expected) in [
6030 (sets(winch, 0, 0, 0), none, Some(winch)),
6031 (sets(0, chld, 0, 0), none, Some(chld)),
6032 (sets(winch, chld, 0, 0), none, Some(winch | chld)),
6033 (sets(hup, 0, hup, 0), none, Some(hup)),
6034 (sets(winch | usr1, 0, 0, usr1), usr1, Some(winch)),
6035 (sets(winch, 0, 0, 0), winch, Some(0)),
6036 (sets(0, 0, 0, 0), none, Some(0)),
6037 (sets(winch | usr1, 0, 0, usr1), none, None),
6038 (sets(usr1, 0, 0, 0), none, None),
6039 (sets(winch, 0, 0, winch), none, None),
6040 (sets(bit(libc::SIGTSTP), 0, 0, 0), none, None),
6041 ] {
6042 assert_eq!(
6043 ignored_signals_let_in(&sets, mask),
6044 expected,
6045 "{sets:?} {mask:#x}"
6046 );
6047 }
6048 }
6049
6050 /// A masked pselect6 that the backend failed with ENOSYS is refused for a
6051 /// pending signal only when the mask lets in one the backend cannot act on
6052 /// inside the call: an ignored one, or one whose default action stops the
6053 /// process (SIGSTOP whatever the mask). With nothing pending, a blocked
6054 /// signal, or only a caught or default-fatal one let in, which would end
6055 /// the call instead, the backend declined the call's descriptor sets, as
6056 /// KVM's does for nfds 100. Before, every such call was refused for a
6057 /// pending signal. A backend that reports no signal state keeps that
6058 /// reason.
6059 #[test]
6060 fn a_masked_pselect6_refusal_names_a_signal_only_when_one_is_pending() {
6061 let bit = |signal: i32| 1_u64 << (signal - 1);
6062 let sets = |pending: u64, shared: u64, ignored: u64, caught: u64| {
6063 reverie::SignalSets::new(pending, shared, 0, ignored, caught)
6064 };
6065 let winch = bit(libc::SIGWINCH);
6066 let usr1 = bit(libc::SIGUSR1);
6067 let tstp = bit(libc::SIGTSTP);
6068 let stop = bit(libc::SIGSTOP);
6069 let term = bit(libc::SIGTERM);
6070 let none = 0;
6071 for (sets, mask, expected) in [
6072 (
6073 Some(sets(0, 0, 0, 0)),
6074 none,
6075 MASKED_PSELECT6_DECLINED_REASON,
6076 ),
6077 (
6078 Some(sets(usr1, 0, 0, usr1)),
6079 none,
6080 MASKED_PSELECT6_DECLINED_REASON,
6081 ),
6082 (
6083 Some(sets(0, term, 0, 0)),
6084 none,
6085 MASKED_PSELECT6_DECLINED_REASON,
6086 ),
6087 (
6088 Some(sets(winch, 0, 0, 0)),
6089 winch,
6090 MASKED_PSELECT6_DECLINED_REASON,
6091 ),
6092 (
6093 Some(sets(tstp, 0, 0, tstp)),
6094 none,
6095 MASKED_PSELECT6_DECLINED_REASON,
6096 ),
6097 (
6098 Some(sets(tstp, 0, tstp, 0)),
6099 none,
6100 MASKED_PSELECT6_SIGNAL_REASON,
6101 ),
6102 (
6103 Some(sets(winch, 0, 0, 0)),
6104 none,
6105 MASKED_PSELECT6_SIGNAL_REASON,
6106 ),
6107 (
6108 Some(sets(0, winch, 0, 0)),
6109 none,
6110 MASKED_PSELECT6_SIGNAL_REASON,
6111 ),
6112 (
6113 Some(sets(usr1 | winch, 0, 0, usr1)),
6114 none,
6115 MASKED_PSELECT6_SIGNAL_REASON,
6116 ),
6117 (
6118 Some(sets(tstp, 0, 0, 0)),
6119 none,
6120 MASKED_PSELECT6_SIGNAL_REASON,
6121 ),
6122 (
6123 Some(sets(stop, 0, 0, 0)),
6124 stop,
6125 MASKED_PSELECT6_SIGNAL_REASON,
6126 ),
6127 (None, none, MASKED_PSELECT6_SIGNAL_REASON),
6128 ] {
6129 for nfds in [0, 1, PSELECT6_INTERNAL_MAX_NFDS] {
6130 assert_eq!(
6131 masked_pselect6_refusal_reason(sets.as_ref(), mask, nfds),
6132 expected,
6133 "{sets:?} {mask:#x} {nfds}"
6134 );
6135 }
6136 }
6137 }
6138
6139 /// A masked pselect6 of more descriptors than one machine word holds is
6140 /// refused for its descriptor sets whatever is pending, a caught and an
6141 /// ignored signal together included: KVM's backend declines that count
6142 /// before it looks at any signal. Before, nfds 100 with such signals
6143 /// pending was refused for a signal.
6144 #[test]
6145 fn a_masked_pselect6_of_more_than_64_descriptors_is_refused_for_its_sets() {
6146 let bit = |signal: i32| 1_u64 << (signal - 1);
6147 let winch = bit(libc::SIGWINCH);
6148 let usr1 = bit(libc::SIGUSR1);
6149 let tstp = bit(libc::SIGTSTP);
6150 for sets in [
6151 Some(reverie::SignalSets::new(usr1 | winch, 0, 0, 0, usr1)),
6152 Some(reverie::SignalSets::new(winch, 0, 0, winch, 0)),
6153 Some(reverie::SignalSets::new(tstp, 0, 0, 0, 0)),
6154 Some(reverie::SignalSets::new(0, 0, 0, 0, 0)),
6155 None,
6156 ] {
6157 for nfds in [PSELECT6_INTERNAL_MAX_NFDS + 1, 100, 1024, i32::MAX] {
6158 assert_eq!(
6159 masked_pselect6_refusal_reason(sets.as_ref(), 0, nfds),
6160 MASKED_PSELECT6_DECLINED_REASON,
6161 "{sets:?} {nfds}"
6162 );
6163 }
6164 }
6165 }
6166
6167 #[test]
6168 fn pselect6_probe_result_maps_only_erestartsys_to_eintr() {
6169 assert_eq!(
6170 pselect6_probe_result(Err(Errno::ERESTARTSYS)),
6171 Err(Errno::EINTR)
6172 );
6173
6174 assert_eq!(pselect6_probe_result(Ok(0)), Ok(0));
6175 assert_eq!(pselect6_probe_result(Ok(1)), Ok(1));
6176 assert_eq!(pselect6_probe_result(Err(Errno::EBADF)), Err(Errno::EBADF));
6177 assert_eq!(
6178 pselect6_probe_result(Err(Errno::EINVAL)),
6179 Err(Errno::EINVAL)
6180 );
6181 assert_eq!(pselect6_probe_result(Err(Errno::EINTR)), Err(Errno::EINTR));
6182 }
6183
6184 #[test]
6185 fn select_timeout_folds_whole_seconds_out_of_microseconds() {
6186 let timeval = |tv_sec, tv_usec| libc::timeval { tv_sec, tv_usec };
6187 assert_eq!(
6188 select_timeout_duration(timeval(0, 1_500_000)),
6189 Ok(Duration::new(1, 500_000_000))
6190 );
6191 assert_eq!(
6192 select_timeout_duration(timeval(2, 3_000_001)),
6193 Ok(Duration::new(5, 1_000))
6194 );
6195 assert_eq!(
6196 select_timeout_duration(timeval(0, 999_999)),
6197 Ok(Duration::new(0, 999_999_000))
6198 );
6199 assert_eq!(select_timeout_duration(timeval(0, -1)), Err(Errno::EINVAL));
6200 assert_eq!(select_timeout_duration(timeval(-1, 0)), Err(Errno::EINVAL));
6201 // v7.1 refuses a negative field before normalizing, so neither mixed
6202 // sign is read as the 1 s it would sum to.
6203 assert_eq!(
6204 select_timeout_duration(timeval(2, -1_000_000)),
6205 Err(Errno::EINVAL)
6206 );
6207 assert_eq!(
6208 select_timeout_duration(timeval(-1, 2_000_000)),
6209 Err(Errno::EINVAL)
6210 );
6211 assert_eq!(select_timeout_duration(timeval(1, -1)), Err(Errno::EINVAL));
6212 assert_eq!(
6213 select_timeout_duration(timeval(libc::time_t::MAX, 1_000_000)),
6214 Err(Errno::EINVAL)
6215 );
6216 }
6217
6218 #[test]
6219 fn ppoll_signal_mask_keeps_reverie_preemption_unblocked() {
6220 let preemption_bit = 1_u64 << ((reverie::PERF_EVENT_SIGNAL as usize) - 1);
6221 assert_eq!(sanitize_ppoll_signal_mask(u64::MAX), !preemption_bit);
6222 }
6223
6224 #[test]
6225 fn ppoll_record_replay_waits_are_kernel_waits_and_run_mode_masked_waits_probe() {
6226 // Record/replay: the kernel owns the wait (issue 4042).
6227 assert!(ppoll_uses_kernel_wait(true, true));
6228 // A sequentialized run keeps the probe path.
6229 assert!(!ppoll_uses_kernel_wait(true, false));
6230 // Without sequentialization the kernel always owns it.
6231 assert!(ppoll_uses_kernel_wait(false, false));
6232 assert!(ppoll_uses_kernel_wait(false, true));
6233 }
6234
6235 #[test]
6236 fn ppoll_timeout_rejects_invalid_timespecs() {
6237 assert_eq!(
6238 ppoll_timeout_duration(Timespec {
6239 tv_sec: -1,
6240 tv_nsec: 0,
6241 }),
6242 Err(Errno::EINVAL)
6243 );
6244 assert_eq!(
6245 ppoll_timeout_duration(Timespec {
6246 tv_sec: 0,
6247 tv_nsec: 1_000_000_000,
6248 }),
6249 Err(Errno::EINVAL)
6250 );
6251 }
6252
6253 #[test]
6254 fn socket_timestamp_control_messages_use_logical_time() {
6255 let header_len = cmsg_align(std::mem::size_of::<Cmsghdr>());
6256 let timeval_len = std::mem::size_of::<libc::timeval>();
6257 let timespec_len = std::mem::size_of::<libc::timespec>();
6258 let first_len = header_len + timeval_len;
6259 let second_offset = cmsg_align(first_len);
6260 let second_len = header_len + timespec_len;
6261 let third_offset = second_offset + cmsg_align(second_len);
6262 let third_len = header_len + std::mem::size_of::<i32>();
6263 let mut control = vec![0; third_offset + cmsg_align(third_len)];
6264
6265 assert!(write_control_value(
6266 &mut control,
6267 Cmsghdr {
6268 cmsg_len: first_len,
6269 cmsg_level: libc::SOL_SOCKET,
6270 cmsg_type: SCM_TIMESTAMP_OLD,
6271 }
6272 ));
6273 assert!(write_control_value(
6274 &mut control[header_len..],
6275 libc::timeval {
6276 tv_sec: 99,
6277 tv_usec: 88,
6278 }
6279 ));
6280 assert!(write_control_value(
6281 &mut control[second_offset..],
6282 Cmsghdr {
6283 cmsg_len: second_len,
6284 cmsg_level: libc::SOL_SOCKET,
6285 cmsg_type: SCM_TIMESTAMPNS_OLD,
6286 }
6287 ));
6288 assert!(write_control_value(
6289 &mut control[second_offset + header_len..],
6290 libc::timespec {
6291 tv_sec: 77,
6292 tv_nsec: 66,
6293 }
6294 ));
6295 assert!(write_control_value(
6296 &mut control[third_offset..],
6297 Cmsghdr {
6298 cmsg_len: third_len,
6299 cmsg_level: libc::SOL_SOCKET,
6300 cmsg_type: libc::SCM_RIGHTS,
6301 }
6302 ));
6303 assert!(write_control_value(
6304 &mut control[third_offset + header_len..],
6305 42_i32
6306 ));
6307 let unrelated_message = control[third_offset..].to_vec();
6308
6309 assert_eq!(
6310 canonicalize_socket_timestamps(&mut control, LogicalTime::from_nanos(2_345_678_901)),
6311 2
6312 );
6313 let timeval = read_control_value::<libc::timeval>(&control[header_len..]).unwrap();
6314 assert_eq!(timeval.tv_sec, 2);
6315 assert_eq!(timeval.tv_usec, 345_678);
6316 let timespec =
6317 read_control_value::<libc::timespec>(&control[second_offset + header_len..]).unwrap();
6318 assert_eq!(timespec.tv_sec, 2);
6319 assert_eq!(timespec.tv_nsec, 345_678_901);
6320 assert_eq!(control[third_offset..], unrelated_message);
6321 }
6322
6323 #[test]
6324 fn truncated_timestamp_payload_prefix_is_rewritten() {
6325 let header_len = cmsg_align(std::mem::size_of::<Cmsghdr>());
6326 let full_len = header_len + std::mem::size_of::<libc::timeval>();
6327 let mut control = vec![0xaa; header_len + std::mem::size_of::<i32>()];
6328 assert!(write_control_value(
6329 &mut control,
6330 Cmsghdr {
6331 cmsg_len: full_len,
6332 cmsg_level: libc::SOL_SOCKET,
6333 cmsg_type: SCM_TIMESTAMP_OLD,
6334 }
6335 ));
6336
6337 assert_eq!(
6338 canonicalize_socket_timestamps(&mut control, LogicalTime::from_nanos(2_345_678_901)),
6339 1
6340 );
6341 assert_eq!(
6342 read_control_value::<i32>(&control[header_len..]),
6343 Some(2),
6344 "the visible timeval prefix must not retain host seconds"
6345 );
6346 }
6347
6348 #[test]
6349 fn timestamping_preserves_populated_source_slots() {
6350 let header_len = cmsg_align(std::mem::size_of::<Cmsghdr>());
6351 let timespec_len = std::mem::size_of::<libc::timespec>();
6352 let message_len = header_len + 3 * timespec_len;
6353 let mut control = vec![0; cmsg_align(message_len)];
6354 assert!(write_control_value(
6355 &mut control,
6356 Cmsghdr {
6357 cmsg_len: message_len,
6358 cmsg_level: libc::SOL_SOCKET,
6359 cmsg_type: SCM_TIMESTAMPING_OLD,
6360 }
6361 ));
6362 let zero = libc::timespec {
6363 tv_sec: 0,
6364 tv_nsec: 0,
6365 };
6366 let populated = libc::timespec {
6367 tv_sec: 99,
6368 tv_nsec: 88,
6369 };
6370 assert!(write_control_value(&mut control[header_len..], zero));
6371 assert!(write_control_value(
6372 &mut control[header_len + timespec_len..],
6373 populated
6374 ));
6375 assert!(write_control_value(
6376 &mut control[header_len + 2 * timespec_len..],
6377 populated
6378 ));
6379
6380 assert_eq!(
6381 canonicalize_socket_timestamps(&mut control, LogicalTime::from_nanos(2_345_678_901)),
6382 1
6383 );
6384 let first = read_control_value::<libc::timespec>(&control[header_len..]).unwrap();
6385 let second =
6386 read_control_value::<libc::timespec>(&control[header_len + timespec_len..]).unwrap();
6387 let third = read_control_value::<libc::timespec>(&control[header_len + 2 * timespec_len..])
6388 .unwrap();
6389 assert_eq!((first.tv_sec, first.tv_nsec), (0, 0));
6390 assert_eq!((second.tv_sec, second.tv_nsec), (2, 345_678_901));
6391 assert_eq!((third.tv_sec, third.tv_nsec), (2, 345_678_901));
6392 }
6393}