1use std::cell::{Cell, OnceCell, RefCell};
6use std::collections::VecDeque;
7use std::io::{self, ErrorKind, Read};
8
9use rustc_target::spec::Os;
10
11use crate::concurrency::VClock;
12use crate::shims::files::{
13 EvalContextExt as _, FdId, FileDescription, FileDescriptionRef, WeakFileDescriptionRef,
14};
15use crate::shims::unix::UnixFileDescription;
16use crate::shims::unix::socket::UnixSocketFileDescription;
17use crate::*;
18
19const MAX_SOCKETPAIR_BUFFER_CAPACITY: usize = 0x34000;
23
24#[derive(Debug, PartialEq)]
25enum VirtualSocketType {
26 Socketpair,
28 PipeRead,
30 PipeWrite,
32}
33
34#[derive(Debug)]
36struct VirtualSocket {
37 readbuf: Option<RefCell<Buffer>>,
40 peer_fd: OnceCell<WeakFileDescriptionRef<VirtualSocket>>,
44 peer_lost_data: Cell<bool>,
48 blocked_read_tid: RefCell<Vec<ThreadId>>,
51 blocked_write_tid: RefCell<Vec<ThreadId>>,
54 is_nonblock: Cell<bool>,
56 fd_type: VirtualSocketType,
58}
59
60#[derive(Debug)]
61struct Buffer {
62 buf: VecDeque<u8>,
63 clock: VClock,
64}
65
66impl Buffer {
67 fn new() -> Self {
68 Buffer { buf: VecDeque::new(), clock: VClock::default() }
69 }
70}
71
72impl VirtualSocket {
73 fn peer_fd(&self) -> &WeakFileDescriptionRef<VirtualSocket> {
74 self.peer_fd.get().unwrap()
75 }
76}
77
78impl FileDescription for VirtualSocket {
79 fn name(&self) -> &'static str {
80 match self.fd_type {
81 VirtualSocketType::Socketpair => "socketpair",
82 VirtualSocketType::PipeRead | VirtualSocketType::PipeWrite => "pipe",
83 }
84 }
85
86 fn metadata<'tcx>(
87 &self,
88 ) -> InterpResult<'tcx, Either<io::Result<std::fs::Metadata>, &'static str>> {
89 let mode_name = match self.fd_type {
90 VirtualSocketType::Socketpair => "S_IFSOCK",
91 VirtualSocketType::PipeRead | VirtualSocketType::PipeWrite => "S_IFIFO",
92 };
93 interp_ok(Either::Right(mode_name))
94 }
95
96 fn destroy<'tcx>(
97 self,
98 _self_id: FdId,
99 _communicate_allowed: bool,
100 ecx: &mut MiriInterpCx<'tcx>,
101 ) -> InterpResult<'tcx, io::Result<()>> {
102 if let Some(peer_fd) = self.peer_fd().upgrade() {
103 if let Some(readbuf) = &self.readbuf {
106 if !readbuf.borrow().buf.is_empty() {
107 peer_fd.peer_lost_data.set(true);
108 }
109 }
110 ecx.update_fd_readiness(peer_fd, false)?;
112 }
113 interp_ok(Ok(()))
114 }
115
116 fn read<'tcx>(
117 self: FileDescriptionRef<Self>,
118 _communicate_allowed: bool,
119 ptr: Pointer,
120 len: usize,
121 ecx: &mut MiriInterpCx<'tcx>,
122 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
123 ) -> InterpResult<'tcx> {
124 ecx.virtual_socket_read(self, ptr, len, false, finish)
125 }
126
127 fn write<'tcx>(
128 self: FileDescriptionRef<Self>,
129 _communicate_allowed: bool,
130 ptr: Pointer,
131 len: usize,
132 ecx: &mut MiriInterpCx<'tcx>,
133 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
134 ) -> InterpResult<'tcx> {
135 ecx.virtual_socket_write(self, ptr, len, false, finish)
136 }
137
138 fn short_fd_operations(&self) -> bool {
139 false
144 }
145
146 fn as_unix<'tcx>(
147 self: FileDescriptionRef<Self>,
148 _ecx: &MiriInterpCx<'tcx>,
149 ) -> FileDescriptionRef<dyn UnixFileDescription> {
150 self
151 }
152
153 fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {
154 let mut flags = 0;
155
156 match self.fd_type {
161 VirtualSocketType::Socketpair => {
162 flags |= ecx.eval_libc_i32("O_RDWR");
163 }
164 VirtualSocketType::PipeRead => {
165 flags |= ecx.eval_libc_i32("O_RDONLY");
166 }
167 VirtualSocketType::PipeWrite => {
168 flags |= ecx.eval_libc_i32("O_WRONLY");
169 }
170 }
171
172 if self.is_nonblock.get() {
174 flags |= ecx.eval_libc_i32("O_NONBLOCK");
175 }
176
177 interp_ok(Scalar::from_i32(flags))
178 }
179
180 fn set_flags<'tcx>(
181 &self,
182 mut flag: i32,
183 ecx: &mut MiriInterpCx<'tcx>,
184 ) -> InterpResult<'tcx, Scalar> {
185 let o_nonblock = ecx.eval_libc_i32("O_NONBLOCK");
186
187 if flag & o_nonblock == o_nonblock {
189 self.is_nonblock.set(true);
190 flag &= !o_nonblock;
191 } else {
192 self.is_nonblock.set(false);
193 }
194
195 if flag != 0 {
197 throw_unsup_format!(
198 "fcntl: only O_NONBLOCK is supported for F_SETFL on socketpairs and pipes"
199 )
200 }
201
202 interp_ok(Scalar::from_i32(0))
203 }
204
205 fn readiness<'tcx>(&self) -> InterpResult<'tcx, Readiness> {
206 let mut readiness = Readiness::EMPTY;
210
211 if let Some(readbuf) = &self.readbuf {
213 if !readbuf.borrow().buf.is_empty() {
214 readiness.readable = true;
215 }
216 } else {
217 readiness.readable = true;
219 }
220
221 if let Some(peer_fd) = self.peer_fd().upgrade() {
223 if let Some(writebuf) = &peer_fd.readbuf {
224 let data_size = writebuf.borrow().buf.len();
225 let available_space = MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(data_size);
226 if available_space != 0 {
227 readiness.writable = true;
228 }
229 } else {
230 readiness.writable = true;
232 }
233 } else {
234 readiness.read_closed = true;
237 readiness.write_closed = true;
238 readiness.readable = true;
242 readiness.writable = true;
243 if self.peer_lost_data.get() {
245 readiness.error = true;
246 }
247 }
248 interp_ok(readiness)
249 }
250}
251
252impl UnixFileDescription for VirtualSocket {
253 fn ioctl<'tcx>(
254 &self,
255 op: Scalar,
256 arg: Option<&OpTy<'tcx>>,
257 ecx: &mut MiriInterpCx<'tcx>,
258 ) -> InterpResult<'tcx, i32> {
259 match self.fd_type {
260 VirtualSocketType::Socketpair => { }
261 VirtualSocketType::PipeRead | VirtualSocketType::PipeWrite => {
262 throw_unsup_format!("cannot use ioctl on pipe");
266 }
267 }
268
269 let fionbio = ecx.eval_libc("FIONBIO");
270
271 if op == fionbio {
272 if !matches!(ecx.tcx.sess.target.os, Os::Linux | Os::Android | Os::MacOs | Os::FreeBsd)
275 {
276 throw_unsup_format!(
281 "ioctl: setting FIONBIO on sockets is unsupported on target {}",
282 ecx.tcx.sess.target.os
283 );
284 }
285
286 let Some(value_ptr) = arg else {
287 throw_ub_format!("ioctl: setting FIONBIO on sockets requires a third argument");
288 };
289 let value = ecx.deref_pointer_as(value_ptr, ecx.machine.layouts.i32)?;
290 let non_block = ecx.read_scalar(&value)?.to_i32()? != 0;
291 self.is_nonblock.set(non_block);
292 return interp_ok(0);
293 }
294
295 throw_unsup_format!("ioctl: unsupported operation {op:#x} on socket");
296 }
297
298 fn as_socket<'tcx>(
299 self: FileDescriptionRef<Self>,
300 _ecx: &MiriInterpCx<'tcx>,
301 ) -> Option<FileDescriptionRef<dyn UnixSocketFileDescription>> {
302 match self.fd_type {
303 VirtualSocketType::Socketpair => Some(self),
304 VirtualSocketType::PipeRead | VirtualSocketType::PipeWrite => None,
305 }
306 }
307}
308
309impl UnixSocketFileDescription for VirtualSocket {
310 fn send<'tcx>(
311 self: FileDescriptionRef<Self>,
312 _communicate_allowed: bool,
313 ptr: Pointer,
314 len: usize,
315 is_non_block: bool,
316 ecx: &mut MiriInterpCx<'tcx>,
317 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
318 ) -> InterpResult<'tcx> {
319 ecx.virtual_socket_write(self, ptr, len, is_non_block, finish)
320 }
321
322 fn recv<'tcx>(
323 self: FileDescriptionRef<Self>,
324 _communicate_allowed: bool,
325 ptr: Pointer,
326 len: usize,
327 is_peek: bool,
328 is_non_block: bool,
329 ecx: &mut MiriInterpCx<'tcx>,
330 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
331 ) -> InterpResult<'tcx> {
332 if is_peek {
333 throw_unsup_format!("socketpair: virtual sockets don't support peeking")
334 }
335
336 ecx.virtual_socket_read(self, ptr, len, is_non_block, finish)
337 }
338}
339
340impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}
341trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
342 fn virtual_socket_write(
348 &mut self,
349 socket: FileDescriptionRef<VirtualSocket>,
350 ptr: Pointer,
351 len: usize,
352 is_non_block: bool,
353 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
354 ) -> InterpResult<'tcx> {
355 let this = self.eval_context_mut();
356
357 if len == 0 {
360 return finish.call(this, Ok(0));
361 }
362
363 let Some(peer_fd) = socket.peer_fd().upgrade() else {
365 return finish.call(this, Err(ErrorKind::BrokenPipe.into()));
368 };
369
370 let Some(writebuf) = &peer_fd.readbuf else {
371 return finish.call(this, Err(IoError::LibcError("EBADF")));
373 };
374
375 let available_space =
377 MAX_SOCKETPAIR_BUFFER_CAPACITY.strict_sub(writebuf.borrow().buf.len());
378 if available_space == 0 {
379 if socket.is_nonblock.get() || is_non_block {
380 return finish.call(this, Err(ErrorKind::WouldBlock.into()));
382 } else {
383 socket.blocked_write_tid.borrow_mut().push(this.active_thread());
384 let weak_socket = FileDescriptionRef::downgrade(&socket);
387 this.block_thread(
388 BlockReason::VirtualSocket,
389 None,
390 callback!(
391 @capture<'tcx> {
392 weak_socket: WeakFileDescriptionRef<VirtualSocket>,
393 ptr: Pointer,
394 len: usize,
395 is_non_block: bool,
396 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
397 }
398 |this, unblock: UnblockKind| {
399 assert_eq!(unblock, UnblockKind::Ready);
400 let socket = weak_socket.upgrade().unwrap();
403 this.virtual_socket_write(socket, ptr, len, is_non_block, finish)
404 }
405 ),
406 );
407 }
408 } else {
409 let mut writebuf = writebuf.borrow_mut();
411 this.release_clock(|clock| {
413 writebuf.clock.join(clock);
414 })?;
415 let write_size = len.min(available_space);
417 let actual_write_size =
418 this.write_to_host(&mut writebuf.buf, write_size, ptr)?.unwrap();
419 assert_eq!(actual_write_size, write_size);
420
421 drop(writebuf);
423
424 let waiting_threads = std::mem::take(&mut *peer_fd.blocked_read_tid.borrow_mut());
426 for thread_id in waiting_threads {
428 this.unblock_thread(thread_id, BlockReason::VirtualSocket)?;
429 }
430 this.update_fd_readiness(socket, false)?;
434 this.update_fd_readiness(peer_fd, true)?;
435
436 return finish.call(this, Ok(write_size));
437 }
438 interp_ok(())
439 }
440
441 fn virtual_socket_read(
447 &mut self,
448 socket: FileDescriptionRef<VirtualSocket>,
449 ptr: Pointer,
450 len: usize,
451 is_non_block: bool,
452 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
453 ) -> InterpResult<'tcx> {
454 let this = self.eval_context_mut();
455
456 if len == 0 {
458 return finish.call(this, Ok(0));
459 }
460
461 let Some(readbuf) = &socket.readbuf else {
462 throw_unsup_format!("reading from the write end of a pipe")
465 };
466
467 if readbuf.borrow_mut().buf.is_empty() {
468 if socket.peer_fd().upgrade().is_none() {
469 return finish.call(this, Ok(0));
472 } else if socket.is_nonblock.get() || is_non_block {
473 return finish.call(this, Err(ErrorKind::WouldBlock.into()));
479 } else {
480 socket.blocked_read_tid.borrow_mut().push(this.active_thread());
481 let weak_socket = FileDescriptionRef::downgrade(&socket);
484 this.block_thread(
485 BlockReason::VirtualSocket,
486 None,
487 callback!(
488 @capture<'tcx> {
489 weak_socket: WeakFileDescriptionRef<VirtualSocket>,
490 ptr: Pointer,
491 len: usize,
492 is_non_block: bool,
493 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
494 }
495 |this, unblock: UnblockKind| {
496 assert_eq!(unblock, UnblockKind::Ready);
497 let socket = weak_socket.upgrade().unwrap();
500 this.virtual_socket_read(socket, ptr, len, is_non_block, finish)
501 }
502 ),
503 );
504 }
505 } else {
506 let mut readbuf = readbuf.borrow_mut();
508 this.acquire_clock(&readbuf.clock)?;
512
513 let read_size = this.read_from_host(|buf| readbuf.buf.read(buf), len, ptr)?.unwrap();
516 let readbuf_now_empty = readbuf.buf.is_empty();
517
518 drop(readbuf);
520
521 if let Some(peer_fd) = socket.peer_fd().upgrade() {
529 let waiting_threads = std::mem::take(&mut *peer_fd.blocked_write_tid.borrow_mut());
531 for thread_id in waiting_threads {
533 this.unblock_thread(thread_id, BlockReason::VirtualSocket)?;
534 }
535 this.update_fd_readiness(peer_fd, readbuf_now_empty)?;
540 };
541 this.update_fd_readiness(socket, false)?;
543
544 return finish.call(this, Ok(read_size));
545 }
546 interp_ok(())
547 }
548}
549
550impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
551pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
552 fn socketpair(
555 &mut self,
556 domain: &OpTy<'tcx>,
557 type_: &OpTy<'tcx>,
558 protocol: &OpTy<'tcx>,
559 sv: &OpTy<'tcx>,
560 ) -> InterpResult<'tcx, Scalar> {
561 let this = self.eval_context_mut();
562
563 let domain = this.read_scalar(domain)?.to_i32()?;
564 let mut flags = this.read_scalar(type_)?.to_i32()?;
565 let protocol = this.read_scalar(protocol)?.to_i32()?;
566 let sv = this.deref_pointer_as(sv, this.machine.layouts.i32)?;
568
569 let mut is_sock_nonblock = false;
570
571 if matches!(
574 this.tcx.sess.target.os,
575 Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos
576 ) {
577 let sock_nonblock = this.eval_libc_i32("SOCK_NONBLOCK");
580 let sock_cloexec = this.eval_libc_i32("SOCK_CLOEXEC");
581 if flags & sock_nonblock == sock_nonblock {
582 is_sock_nonblock = true;
583 flags &= !sock_nonblock;
584 }
585 if flags & sock_cloexec == sock_cloexec {
586 flags &= !sock_cloexec;
587 }
588 }
589
590 if domain != this.eval_libc_i32("AF_UNIX") && domain != this.eval_libc_i32("AF_LOCAL") {
594 throw_unsup_format!(
595 "socketpair: domain {:#x} is unsupported, only AF_UNIX \
596 and AF_LOCAL are allowed",
597 domain
598 );
599 } else if flags != this.eval_libc_i32("SOCK_STREAM") {
600 throw_unsup_format!(
601 "socketpair: type {:#x} is unsupported, only SOCK_STREAM, \
602 SOCK_CLOEXEC and SOCK_NONBLOCK are allowed",
603 flags
604 );
605 } else if protocol != 0 {
606 throw_unsup_format!(
607 "socketpair: socket protocol {protocol} is unsupported, \
608 only 0 is allowed",
609 );
610 }
611
612 let fds = &mut this.machine.fds;
614 let fd0 = fds.new_ref(VirtualSocket {
615 readbuf: Some(RefCell::new(Buffer::new())),
616 peer_fd: OnceCell::new(),
617 peer_lost_data: Cell::new(false),
618 blocked_read_tid: RefCell::new(Vec::new()),
619 blocked_write_tid: RefCell::new(Vec::new()),
620 is_nonblock: Cell::new(is_sock_nonblock),
621 fd_type: VirtualSocketType::Socketpair,
622 });
623 let fd1 = fds.new_ref(VirtualSocket {
624 readbuf: Some(RefCell::new(Buffer::new())),
625 peer_fd: OnceCell::new(),
626 peer_lost_data: Cell::new(false),
627 blocked_read_tid: RefCell::new(Vec::new()),
628 blocked_write_tid: RefCell::new(Vec::new()),
629 is_nonblock: Cell::new(is_sock_nonblock),
630 fd_type: VirtualSocketType::Socketpair,
631 });
632
633 fd0.peer_fd.set(FileDescriptionRef::downgrade(&fd1)).unwrap();
635 fd1.peer_fd.set(FileDescriptionRef::downgrade(&fd0)).unwrap();
636
637 let sv0 = fds.insert(fd0);
639 let sv1 = fds.insert(fd1);
640
641 let sv0 = Scalar::from_int(sv0, sv.layout.size);
643 let sv1 = Scalar::from_int(sv1, sv.layout.size);
644 this.write_scalar(sv0, &sv)?;
645 this.write_scalar(sv1, &sv.offset(sv.layout.size, sv.layout, this)?)?;
646
647 interp_ok(Scalar::from_i32(0))
648 }
649
650 fn pipe2(
651 &mut self,
652 pipefd: &OpTy<'tcx>,
653 flags: Option<&OpTy<'tcx>>,
654 ) -> InterpResult<'tcx, Scalar> {
655 let this = self.eval_context_mut();
656
657 let pipefd = this.deref_pointer_as(pipefd, this.machine.layouts.i32)?;
658 let mut flags = match flags {
659 Some(flags) => this.read_scalar(flags)?.to_i32()?,
660 None => 0,
661 };
662
663 let cloexec = this.eval_libc_i32("O_CLOEXEC");
664 let o_nonblock = this.eval_libc_i32("O_NONBLOCK");
665
666 let mut is_nonblock = false;
669 if flags & o_nonblock == o_nonblock {
670 is_nonblock = true;
671 flags &= !o_nonblock;
672 }
673 if flags & cloexec == cloexec {
675 flags &= !cloexec;
676 }
677 if flags != 0 {
678 throw_unsup_format!("unsupported flags in `pipe2`");
679 }
680
681 let fds = &mut this.machine.fds;
684 let fd0 = fds.new_ref(VirtualSocket {
685 readbuf: Some(RefCell::new(Buffer::new())),
686 peer_fd: OnceCell::new(),
687 peer_lost_data: Cell::new(false),
688 blocked_read_tid: RefCell::new(Vec::new()),
689 blocked_write_tid: RefCell::new(Vec::new()),
690 is_nonblock: Cell::new(is_nonblock),
691 fd_type: VirtualSocketType::PipeRead,
692 });
693 let fd1 = fds.new_ref(VirtualSocket {
694 readbuf: None,
695 peer_fd: OnceCell::new(),
696 peer_lost_data: Cell::new(false),
697 blocked_read_tid: RefCell::new(Vec::new()),
698 blocked_write_tid: RefCell::new(Vec::new()),
699 is_nonblock: Cell::new(is_nonblock),
700 fd_type: VirtualSocketType::PipeWrite,
701 });
702
703 fd0.peer_fd.set(FileDescriptionRef::downgrade(&fd1)).unwrap();
705 fd1.peer_fd.set(FileDescriptionRef::downgrade(&fd0)).unwrap();
706
707 let pipefd0 = fds.insert(fd0);
709 let pipefd1 = fds.insert(fd1);
710
711 let pipefd0 = Scalar::from_int(pipefd0, pipefd.layout.size);
713 let pipefd1 = Scalar::from_int(pipefd1, pipefd.layout.size);
714 this.write_scalar(pipefd0, &pipefd)?;
715 this.write_scalar(pipefd1, &pipefd.offset(pipefd.layout.size, pipefd.layout, this)?)?;
716
717 interp_ok(Scalar::from_i32(0))
718 }
719}