1use std::cell::{Cell, OnceCell, RefCell};
6use std::collections::VecDeque;
7use std::io::{self, ErrorKind, Read};
8use std::rc::Rc;
9
10use rustc_target::spec::Os;
11
12use crate::concurrency::VClock;
13use crate::shims::files::{
14 EvalContextExt as _, FileDescription, FileDescriptionRef, WeakFileDescriptionRef,
15};
16use crate::shims::readiness::DelayedReadinessUpdates;
17use crate::shims::unix::UnixFileDescription;
18use crate::shims::unix::socket::UnixSocketFileDescription;
19use crate::*;
20
21const MAX_SOCKETPAIR_BUFFER_CAPACITY: usize = 0x34000;
25
26#[derive(Debug, PartialEq)]
27enum VirtualSocketType {
28 Socketpair,
30 PipeRead,
32 PipeWrite,
34}
35
36#[derive(Debug)]
38struct VirtualSocket {
39 readbuf: Option<RefCell<Buffer>>,
42 peer_fd: OnceCell<WeakFileDescriptionRef<VirtualSocket>>,
46 peer_lost_data: Cell<bool>,
50 blocked_read_tid: RefCell<Vec<ThreadId>>,
53 blocked_write_tid: RefCell<Vec<ThreadId>>,
56 is_nonblock: Cell<bool>,
58 fd_type: VirtualSocketType,
60 delayed_readiness_updates: Rc<DelayedReadinessUpdates>,
63 watched: ReadinessWatched,
65}
66
67#[derive(Debug)]
68struct Buffer {
69 buf: VecDeque<u8>,
70 clock: VClock,
71}
72
73impl Buffer {
74 fn new() -> Self {
75 Buffer { buf: VecDeque::new(), clock: VClock::default() }
76 }
77}
78
79impl VirtualSocket {
80 fn peer_fd(&self) -> &WeakFileDescriptionRef<VirtualSocket> {
81 self.peer_fd.get().unwrap()
82 }
83}
84
85impl Drop for VirtualSocket {
86 fn drop(&mut self) {
87 if let Some(peer_fd) = self.peer_fd().upgrade() {
88 if let Some(readbuf) = &self.readbuf {
91 if !readbuf.borrow().buf.is_empty() {
92 peer_fd.peer_lost_data.set(true);
93 }
94 }
95 self.delayed_readiness_updates.add(peer_fd);
97 }
98 }
99}
100
101impl FileDescription for VirtualSocket {
102 fn name(&self) -> &'static str {
103 match self.fd_type {
104 VirtualSocketType::Socketpair => "socketpair",
105 VirtualSocketType::PipeRead | VirtualSocketType::PipeWrite => "pipe",
106 }
107 }
108
109 fn metadata<'tcx>(
110 &self,
111 ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
112 let mode_name = match self.fd_type {
113 VirtualSocketType::Socketpair => "S_IFSOCK",
114 VirtualSocketType::PipeRead | VirtualSocketType::PipeWrite => "S_IFIFO",
115 };
116 interp_ok(Either::Right(mode_name))
117 }
118
119 fn read<'tcx>(
120 self: FileDescriptionRef<Self>,
121 _communicate_allowed: bool,
122 ptr: Pointer,
123 len: usize,
124 ecx: &mut MiriInterpCx<'tcx>,
125 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
126 ) -> InterpResult<'tcx> {
127 ecx.virtual_socket_read(self, ptr, len, false, finish)
128 }
129
130 fn write<'tcx>(
131 self: FileDescriptionRef<Self>,
132 _communicate_allowed: bool,
133 ptr: Pointer,
134 len: usize,
135 ecx: &mut MiriInterpCx<'tcx>,
136 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
137 ) -> InterpResult<'tcx> {
138 ecx.virtual_socket_write(self, ptr, len, false, finish)
139 }
140
141 fn short_fd_operations(&self) -> bool {
142 false
147 }
148
149 fn as_unix<'tcx>(
150 self: FileDescriptionRef<Self>,
151 _ecx: &MiriInterpCx<'tcx>,
152 ) -> FileDescriptionRef<dyn UnixFileDescription> {
153 self
154 }
155
156 fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
157 let mut flags = 0;
158
159 match self.fd_type {
164 VirtualSocketType::Socketpair => {
165 flags |= ecx.eval_libc_i32("O_RDWR");
166 }
167 VirtualSocketType::PipeRead => {
168 flags |= ecx.eval_libc_i32("O_RDONLY");
169 }
170 VirtualSocketType::PipeWrite => {
171 flags |= ecx.eval_libc_i32("O_WRONLY");
172 }
173 }
174
175 if self.is_nonblock.get() {
177 flags |= ecx.eval_libc_i32("O_NONBLOCK");
178 }
179
180 interp_ok(Scalar::from_i32(flags))
181 }
182
183 fn set_flags<'tcx>(
184 &self,
185 mut flag: i32,
186 ecx: &mut MiriInterpCx<'tcx>,
187 ) -> InterpResult<'tcx, Scalar> {
188 let o_nonblock = ecx.eval_libc_i32("O_NONBLOCK");
189
190 if flag & o_nonblock == o_nonblock {
192 self.is_nonblock.set(true);
193 flag &= !o_nonblock;
194 } else {
195 self.is_nonblock.set(false);
196 }
197
198 if flag != 0 {
200 throw_unsup_format!(
201 "fcntl: only O_NONBLOCK is supported for F_SETFL on socketpairs and pipes"
202 )
203 }
204
205 interp_ok(Scalar::from_i32(0))
206 }
207
208 fn readiness_watched(&self) -> Option<&ReadinessWatched> {
209 Some(&self.watched)
210 }
211
212 fn readiness(&self) -> Readiness {
213 let mut readiness = Readiness::EMPTY;
217
218 if let Some(readbuf) = &self.readbuf {
220 if !readbuf.borrow().buf.is_empty() {
221 readiness.readable = true;
222 }
223 } else {
224 readiness.readable = true;
226 }
227
228 if let Some(peer_fd) = self.peer_fd().upgrade() {
230 if let Some(writebuf) = &peer_fd.readbuf {
231 let data_size = writebuf.borrow().buf.len();
232 let available_space = MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(data_size);
233 if available_space != 0 {
234 readiness.writable = true;
235 }
236 } else {
237 readiness.writable = true;
239 }
240 } else {
241 readiness.read_closed = true;
244 readiness.write_closed = true;
245 readiness.readable = true;
249 readiness.writable = true;
250 if self.peer_lost_data.get() {
252 readiness.error = true;
253 }
254 }
255 readiness
256 }
257}
258
259impl UnixFileDescription for VirtualSocket {
260 fn ioctl<'tcx>(
261 &self,
262 op: Scalar,
263 arg: Option<&OpTy<'tcx>>,
264 ecx: &mut MiriInterpCx<'tcx>,
265 ) -> InterpResult<'tcx, i32> {
266 match self.fd_type {
267 VirtualSocketType::Socketpair => { }
268 VirtualSocketType::PipeRead | VirtualSocketType::PipeWrite => {
269 throw_unsup_format!("cannot use ioctl on pipe");
273 }
274 }
275
276 let fionbio = ecx.eval_libc("FIONBIO");
277
278 if op == fionbio {
279 if !matches!(ecx.tcx.sess.target.os, Os::Linux | Os::Android | Os::MacOs | Os::FreeBsd)
282 {
283 throw_unsup_format!(
288 "ioctl: setting FIONBIO on sockets is unsupported on target {}",
289 ecx.tcx.sess.target.os
290 );
291 }
292
293 let Some(value_ptr) = arg else {
294 throw_ub_format!("ioctl: setting FIONBIO on sockets requires a third argument");
295 };
296 let value = ecx.deref_pointer_as(value_ptr, ecx.machine.layouts.i32)?;
297 let non_block = ecx.read_scalar(&value)?.to_i32()? != 0;
298 self.is_nonblock.set(non_block);
299 return interp_ok(0);
300 }
301
302 throw_unsup_format!("ioctl: unsupported operation {op:#x} on socket");
303 }
304
305 fn as_socket<'tcx>(
306 self: FileDescriptionRef<Self>,
307 _ecx: &MiriInterpCx<'tcx>,
308 ) -> Option<FileDescriptionRef<dyn UnixSocketFileDescription>> {
309 match self.fd_type {
310 VirtualSocketType::Socketpair => Some(self),
311 VirtualSocketType::PipeRead | VirtualSocketType::PipeWrite => None,
312 }
313 }
314}
315
316impl UnixSocketFileDescription for VirtualSocket {
317 fn send<'tcx>(
318 self: FileDescriptionRef<Self>,
319 _communicate_allowed: bool,
320 ptr: Pointer,
321 len: usize,
322 is_non_block: bool,
323 ecx: &mut MiriInterpCx<'tcx>,
324 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
325 ) -> InterpResult<'tcx> {
326 ecx.virtual_socket_write(self, ptr, len, is_non_block, finish)
327 }
328
329 fn recv<'tcx>(
330 self: FileDescriptionRef<Self>,
331 _communicate_allowed: bool,
332 ptr: Pointer,
333 len: usize,
334 is_peek: bool,
335 is_non_block: bool,
336 ecx: &mut MiriInterpCx<'tcx>,
337 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
338 ) -> InterpResult<'tcx> {
339 if is_peek {
340 throw_unsup_format!("socketpair: virtual sockets don't support peeking")
341 }
342
343 ecx.virtual_socket_read(self, ptr, len, is_non_block, finish)
344 }
345}
346
347impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
348trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
349 fn virtual_socket_write(
355 &mut self,
356 socket: FileDescriptionRef<VirtualSocket>,
357 ptr: Pointer,
358 len: usize,
359 is_non_block: bool,
360 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
361 ) -> InterpResult<'tcx> {
362 let this = self.eval_context_mut();
363
364 if len == 0 {
367 return finish.call(this, Ok(0));
368 }
369
370 let Some(peer_fd) = socket.peer_fd().upgrade() else {
372 return finish.call(this, Err(ErrorKind::BrokenPipe.into()));
375 };
376
377 let Some(writebuf) = &peer_fd.readbuf else {
378 return finish.call(this, Err(IoError::LibcError("EBADF")));
380 };
381
382 let available_space =
384 MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(writebuf.borrow().buf.len());
385 if available_space == 0 {
386 if socket.is_nonblock.get() || is_non_block {
387 return finish.call(this, Err(ErrorKind::WouldBlock.into()));
389 } else {
390 socket.blocked_write_tid.borrow_mut().push(this.active_thread());
391 this.block_thread(
392 BlockReason::VirtualSocket,
393 None,
394 callback!(
395 @capture<'tcx> {
396 socket: FileDescriptionRef<VirtualSocket>,
397 ptr: Pointer,
398 len: usize,
399 is_non_block: bool,
400 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
401 }
402 |this, unblock: UnblockKind| {
403 assert_eq!(unblock, UnblockKind::Ready);
404 this.virtual_socket_write(socket, ptr, len, is_non_block, finish)
405 }
406 ),
407 );
408 }
409 } else {
410 let mut writebuf = writebuf.borrow_mut();
412 this.release_clock(|clock| {
414 writebuf.clock.join(clock);
415 })?;
416 let write_size = len.min(available_space);
418 let actual_write_size =
419 this.write_to_host(&mut writebuf.buf, write_size, ptr)?.unwrap();
420 assert_eq!(actual_write_size, write_size);
421
422 drop(writebuf);
424
425 let waiting_threads = std::mem::take(&mut *peer_fd.blocked_read_tid.borrow_mut());
427 for thread_id in waiting_threads {
429 this.unblock_thread(thread_id, BlockReason::VirtualSocket)?;
430 }
431 this.update_fd_readiness(socket, ReadinessUpdateFlags::DEFAULT)?;
435 this.update_fd_readiness(peer_fd, ReadinessUpdateFlags::FORCE_EDGE)?;
436
437 return finish.call(this, Ok(write_size));
438 }
439 interp_ok(())
440 }
441
442 fn virtual_socket_read(
448 &mut self,
449 socket: FileDescriptionRef<VirtualSocket>,
450 ptr: Pointer,
451 len: usize,
452 is_non_block: bool,
453 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
454 ) -> InterpResult<'tcx> {
455 let this = self.eval_context_mut();
456
457 if len == 0 {
459 return finish.call(this, Ok(0));
460 }
461
462 let Some(readbuf) = &socket.readbuf else {
463 throw_unsup_format!("reading from the write end of a pipe")
466 };
467
468 if readbuf.borrow_mut().buf.is_empty() {
469 if socket.peer_fd().upgrade().is_none() {
470 return finish.call(this, Ok(0));
473 } else if socket.is_nonblock.get() || is_non_block {
474 return finish.call(this, Err(ErrorKind::WouldBlock.into()));
480 } else {
481 socket.blocked_read_tid.borrow_mut().push(this.active_thread());
482 this.block_thread(
483 BlockReason::VirtualSocket,
484 None,
485 callback!(
486 @capture<'tcx> {
487 socket: FileDescriptionRef<VirtualSocket>,
488 ptr: Pointer,
489 len: usize,
490 is_non_block: bool,
491 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
492 }
493 |this, unblock: UnblockKind| {
494 assert_eq!(unblock, UnblockKind::Ready);
495 this.virtual_socket_read(socket, ptr, len, is_non_block, finish)
496 }
497 ),
498 );
499 }
500 } else {
501 let mut readbuf = readbuf.borrow_mut();
503 this.acquire_clock(&readbuf.clock)?;
507
508 let read_size = this.read_from_host(|buf| readbuf.buf.read(buf), len, ptr)?.unwrap();
511 let readbuf_now_empty = readbuf.buf.is_empty();
512
513 drop(readbuf);
515
516 if let Some(peer_fd) = socket.peer_fd().upgrade() {
524 let waiting_threads = std::mem::take(&mut *peer_fd.blocked_write_tid.borrow_mut());
526 for thread_id in waiting_threads {
528 this.unblock_thread(thread_id, BlockReason::VirtualSocket)?;
529 }
530 this.update_fd_readiness(
535 peer_fd,
536 if readbuf_now_empty {
537 ReadinessUpdateFlags::FORCE_EDGE
538 } else {
539 ReadinessUpdateFlags::DEFAULT
540 },
541 )?;
542 };
543 this.update_fd_readiness(socket, ReadinessUpdateFlags::DEFAULT)?;
545
546 return finish.call(this, Ok(read_size));
547 }
548 interp_ok(())
549 }
550}
551
552impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
553pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
554 fn socketpair(
557 &mut self,
558 domain: &OpTy<'tcx>,
559 type_: &OpTy<'tcx>,
560 protocol: &OpTy<'tcx>,
561 sv: &OpTy<'tcx>,
562 ) -> InterpResult<'tcx, Scalar> {
563 let this = self.eval_context_mut();
564
565 let domain = this.read_scalar(domain)?.to_i32()?;
566 let mut flags = this.read_scalar(type_)?.to_i32()?;
567 let protocol = this.read_scalar(protocol)?.to_i32()?;
568 let sv = this.deref_pointer_as(sv, this.machine.layouts.i32)?;
570
571 let mut is_sock_nonblock = false;
572
573 if matches!(
576 this.tcx.sess.target.os,
577 Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos
578 ) {
579 let sock_nonblock = this.eval_libc_i32("SOCK_NONBLOCK");
582 let sock_cloexec = this.eval_libc_i32("SOCK_CLOEXEC");
583 if flags & sock_nonblock == sock_nonblock {
584 is_sock_nonblock = true;
585 flags &= !sock_nonblock;
586 }
587 if flags & sock_cloexec == sock_cloexec {
588 flags &= !sock_cloexec;
589 }
590 }
591
592 if domain != this.eval_libc_i32("AF_UNIX") && domain != this.eval_libc_i32("AF_LOCAL") {
596 throw_unsup_format!(
597 "socketpair: domain {:#x} is unsupported, only AF_UNIX \
598 and AF_LOCAL are allowed",
599 domain
600 );
601 } else if flags != this.eval_libc_i32("SOCK_STREAM") {
602 throw_unsup_format!(
603 "socketpair: type {:#x} is unsupported, only SOCK_STREAM, \
604 SOCK_CLOEXEC and SOCK_NONBLOCK are allowed",
605 flags
606 );
607 } else if protocol != 0 {
608 throw_unsup_format!(
609 "socketpair: socket protocol {protocol} is unsupported, \
610 only 0 is allowed",
611 );
612 }
613
614 let fds = &mut this.machine.fds;
616 let fd0 = fds.new_ref(VirtualSocket {
617 readbuf: Some(RefCell::new(Buffer::new())),
618 peer_fd: OnceCell::new(),
619 peer_lost_data: Cell::new(false),
620 blocked_read_tid: RefCell::new(Vec::new()),
621 blocked_write_tid: RefCell::new(Vec::new()),
622 is_nonblock: Cell::new(is_sock_nonblock),
623 fd_type: VirtualSocketType::Socketpair,
624 delayed_readiness_updates: Rc::clone(&this.machine.delayed_readiness_updates),
625 watched: ReadinessWatched::default(),
626 });
627 let fd1 = fds.new_ref(VirtualSocket {
628 readbuf: Some(RefCell::new(Buffer::new())),
629 peer_fd: OnceCell::new(),
630 peer_lost_data: Cell::new(false),
631 blocked_read_tid: RefCell::new(Vec::new()),
632 blocked_write_tid: RefCell::new(Vec::new()),
633 is_nonblock: Cell::new(is_sock_nonblock),
634 fd_type: VirtualSocketType::Socketpair,
635 delayed_readiness_updates: Rc::clone(&this.machine.delayed_readiness_updates),
636 watched: ReadinessWatched::default(),
637 });
638
639 fd0.peer_fd.set(FileDescriptionRef::downgrade(&fd1)).unwrap();
641 fd1.peer_fd.set(FileDescriptionRef::downgrade(&fd0)).unwrap();
642
643 let sv0 = fds.insert(fd0);
645 let sv1 = fds.insert(fd1);
646
647 let sv0 = Scalar::from_int(sv0, sv.layout.size);
649 let sv1 = Scalar::from_int(sv1, sv.layout.size);
650 this.write_scalar(sv0, &sv)?;
651 this.write_scalar(sv1, &sv.offset(sv.layout.size, sv.layout, this)?)?;
652
653 interp_ok(Scalar::from_i32(0))
654 }
655
656 fn pipe2(
657 &mut self,
658 pipefd: &OpTy<'tcx>,
659 flags: Option<&OpTy<'tcx>>,
660 ) -> InterpResult<'tcx, Scalar> {
661 let this = self.eval_context_mut();
662
663 let pipefd = this.deref_pointer_as(pipefd, this.machine.layouts.i32)?;
664 let mut flags = match flags {
665 Some(flags) => this.read_scalar(flags)?.to_i32()?,
666 None => 0,
667 };
668
669 let cloexec = this.eval_libc_i32("O_CLOEXEC");
670 let o_nonblock = this.eval_libc_i32("O_NONBLOCK");
671
672 let mut is_nonblock = false;
675 if flags & o_nonblock == o_nonblock {
676 is_nonblock = true;
677 flags &= !o_nonblock;
678 }
679 if flags & cloexec == cloexec {
681 flags &= !cloexec;
682 }
683 if flags != 0 {
684 throw_unsup_format!("unsupported flags in `pipe2`");
685 }
686
687 let fds = &mut this.machine.fds;
690 let fd0 = fds.new_ref(VirtualSocket {
691 readbuf: Some(RefCell::new(Buffer::new())),
692 peer_fd: OnceCell::new(),
693 peer_lost_data: Cell::new(false),
694 blocked_read_tid: RefCell::new(Vec::new()),
695 blocked_write_tid: RefCell::new(Vec::new()),
696 is_nonblock: Cell::new(is_nonblock),
697 fd_type: VirtualSocketType::PipeRead,
698 delayed_readiness_updates: Rc::clone(&this.machine.delayed_readiness_updates),
699 watched: ReadinessWatched::default(),
700 });
701 let fd1 = fds.new_ref(VirtualSocket {
702 readbuf: None,
703 peer_fd: OnceCell::new(),
704 peer_lost_data: Cell::new(false),
705 blocked_read_tid: RefCell::new(Vec::new()),
706 blocked_write_tid: RefCell::new(Vec::new()),
707 is_nonblock: Cell::new(is_nonblock),
708 fd_type: VirtualSocketType::PipeWrite,
709 delayed_readiness_updates: Rc::clone(&this.machine.delayed_readiness_updates),
710 watched: ReadinessWatched::default(),
711 });
712
713 fd0.peer_fd.set(FileDescriptionRef::downgrade(&fd1)).unwrap();
715 fd1.peer_fd.set(FileDescriptionRef::downgrade(&fd0)).unwrap();
716
717 let pipefd0 = fds.insert(fd0);
719 let pipefd1 = fds.insert(fd1);
720
721 let pipefd0 = Scalar::from_int(pipefd0, pipefd.layout.size);
723 let pipefd1 = Scalar::from_int(pipefd1, pipefd.layout.size);
724 this.write_scalar(pipefd0, &pipefd)?;
725 this.write_scalar(pipefd1, &pipefd.offset(pipefd.layout.size, pipefd.layout, this)?)?;
726
727 interp_ok(Scalar::from_i32(0))
728 }
729}