pselect read its timeout directly, passed the guest signal mask straight
to SetSignalMaskCatching_nid_no_patch, which dereferences it, and saved
and restored the descriptor sets with memcpy after a caught signal, so a
non-null pointer into unmapped or read-only memory crashed the host. It
now copies the timeout and the mask in with GuestCopyIn_nid_no_patch,
as FreeBSD's sys_pselect does before changing the mask, and saves and
restores the sets with GuestCopyIn_nid_no_patch and
GuestCopyOut_nid_no_patch, failing with EFAULT. A bad timeout or mask
leaves the thread's signal mask unchanged.
FreeBSD's kern_pselect installs the mask and kern_select sleeps with
PCATCH, so a pending signal that the new mask unblocks makes the call
return EINTR once the descriptors have been scanned. Here the handler ran
inside the mask swap and select then waited the full timeout, or forever
with no timeout. The swap now reports whether a handler ran, and pselect
then scans the descriptors once without waiting: a ready descriptor, a
zero timeout or an invalid argument keep select's answer, and otherwise
the call fails with EINTR and leaves the descriptor sets unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>