1use std::cell::{Cell, RefCell, RefMut};2use std::io;3use std::io::Read;4use std::net::{Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4};5use std::sync::atomic::AtomicBool;6use std::time::Duration;78use mio::event::Source;9use mio::net::{TcpListener, TcpStream};10use rustc_abi::Size;11use rustc_const_eval::interpret::{InterpResult, interp_ok};12use rustc_middle::throw_unsup_format;13use rustc_target::spec::Os;1415use crate::shims::files::{EvalContextExt as _, FdId, FileDescription, FileDescriptionRef};16use crate::shims::unix::UnixFileDescription;17use crate::shims::unix::linux_like::epoll::{EpollReadiness, EvalContextExt as _};18use crate::shims::unix::socket_address::EvalContextExt as _;19use crate::*;2021#[derive(Debug, PartialEq)]22enum SocketFamily {23 // IPv4 internet protocols24 IPv4,25 // IPv6 internet protocols26 IPv6,27}2829#[derive(Debug)]30enum SocketState {31 /// No syscall after `socket` has been made.32 Initial,33 /// The `bind` syscall has been called on the socket.34 /// This is only reachable from the [`SocketState::Initial`] state.35 Bound(SocketAddr),36 /// The `listen` syscall has been called on the socket.37 /// This is only reachable from the [`SocketState::Bound`] state.38 Listening(TcpListener),39 /// The `connect` syscall has been called and we weren't yet able40 /// to ensure the connection is established. This is only reachable41 /// from the [`SocketState::Initial`] state.42 Connecting(TcpStream),43 /// The `connect` syscall has been called on the socket and44 /// we ensured that the connection is established, or45 /// the socket was created by the `accept` syscall.46 /// For a socket created using the `connect` syscall, this is47 /// only reachable from the [`SocketState::Connecting`] state.48 Connected(TcpStream),49 /// The SO_ERROR socket option has been set after calling50 /// the `connect` syscall, indicating that the connection51 /// attempt failed. By the POSIX specification, a socket is52 /// is an unspecified state after a failed connection attempt53 /// and thus nothing (except destroying the socket) should be54 /// supported when a socket is in this state.55 ConnectionFailed(TcpStream),56}5758#[derive(Debug)]59struct Socket {60 /// Family of the socket, used to ensure socket only binds/connects to address of61 /// same family.62 family: SocketFamily,63 /// Current state of the inner socket.64 state: RefCell<SocketState>,65 /// Whether this fd is non-blocking or not.66 is_non_block: Cell<bool>,67 /// The current blocking I/O readiness of the file description.68 io_readiness: RefCell<BlockingIoSourceReadiness>,69 /// [`Some`] when the socket had an async error which has not yet been fetched via `SO_ERROR`.70 error: RefCell<Option<io::Error>>,71 /// Read timeout of the socket. [`None`] means that reads can block indefinitely.72 /// The timeout is applied to the monotonic clock (the Unix specification doesn't73 /// specify which clock to use, but the monotonic clock is more common for74 /// relative timeouts).75 /// This is ignored when the socket is non-blocking.76 read_timeout: Cell<Option<Duration>>,77 /// Write timeout of the socket. [`None`] means that writes can block indefinitely.78 /// The timeout is applied to the monotonic clock (the Unix specification doesn't79 /// specify which clock to use, but the monotonic clock is more common80 /// for relative timeouts).81 /// This is ignored when the socket is non-blocking.82 write_timeout: Cell<Option<Duration>>,83}8485impl FileDescription for Socket {86 fn name(&self) -> &'static str {87 "socket"88 }8990 fn destroy<'tcx>(91 self,92 self_id: FdId,93 communicate_allowed: bool,94 ecx: &mut MiriInterpCx<'tcx>,95 ) -> InterpResult<'tcx, io::Result<()>> {96 assert!(communicate_allowed, "cannot have `Socket` with isolation enabled!");9798 if matches!(99 &*self.state.borrow(),100 SocketState::Listening(_)101 | SocketState::Connecting(_)102 | SocketState::Connected(_)103 | SocketState::ConnectionFailed(_)104 ) {105 // There exists an associated host socket so we need to deregister it106 // from the blocking I/O manager.107 ecx.machine.blocking_io.deregister(self_id, self)108 };109110 interp_ok(Ok(()))111 }112113 fn read<'tcx>(114 self: FileDescriptionRef<Self>,115 communicate_allowed: bool,116 ptr: Pointer,117 len: usize,118 ecx: &mut MiriInterpCx<'tcx>,119 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,120 ) -> InterpResult<'tcx> {121 assert!(communicate_allowed, "cannot have `Socket` with isolation enabled!");122123 let socket = self;124 let deadline = ecx.action_deadline(socket.is_non_block.get(), socket.read_timeout.get());125126 ecx.ensure_connected(127 socket.clone(),128 deadline.clone(),129 "read",130 callback!(131 @capture<'tcx> {132 socket: FileDescriptionRef<Socket>,133 deadline: Option<Deadline>,134 ptr: Pointer,135 len: usize,136 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,137 } |this, result: Result<(), ()>| {138 if result.is_err() {139 return finish.call(this, Err(LibcError("ENOTCONN")))140 }141142 // Since `read` is the same as `recv` with no flags, we just treat143 // the `read` as a `recv` here.144145 if socket.is_non_block.get() {146 // We have a non-blocking socket and thus don't want to block until147 // we can read.148 let result = this.try_non_block_recv(&socket, ptr, len, /* should_peek */ false)?;149 finish.call(this, result)150 } else {151 // The socket is in blocking mode and thus the read call should block152 // until we can read some bytes from the socket or the timeout exceeded.153 this.block_for_recv(socket, deadline, ptr, len, /* should_peek */ false, finish)154 }155 }156 ),157 )158 }159160 fn write<'tcx>(161 self: FileDescriptionRef<Self>,162 communicate_allowed: bool,163 ptr: Pointer,164 len: usize,165 ecx: &mut MiriInterpCx<'tcx>,166 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,167 ) -> InterpResult<'tcx> {168 assert!(communicate_allowed, "cannot have `Socket` with isolation enabled!");169170 let socket = self;171 let deadline = ecx.action_deadline(socket.is_non_block.get(), socket.write_timeout.get());172173 ecx.ensure_connected(174 socket.clone(),175 deadline.clone(),176 "write",177 callback!(178 @capture<'tcx> {179 socket: FileDescriptionRef<Socket>,180 deadline: Option<Deadline>,181 ptr: Pointer,182 len: usize,183 finish: DynMachineCallback<'tcx, Result<usize, IoError>>184 } |this, result: Result<(), ()>| {185 if result.is_err() {186 return finish.call(this, Err(LibcError("ENOTCONN")))187 }188189 // Since `write` is the same as `send` with no flags, we just treat190 // the `write` as a `send` here.191192 if socket.is_non_block.get() {193 // We have a non-blocking socket and thus don't want to block until194 // we can write.195 let result = this.try_non_block_send(&socket, ptr, len)?;196 return finish.call(this, result)197 } else {198 // The socket is in blocking mode and thus the write call should block199 // until we can write some bytes into the socket or the timeout exceeded.200 this.block_for_send(socket, deadline, ptr, len, finish)201 }202 }203 ),204 )205 }206207 fn short_fd_operations(&self) -> bool {208 // Linux guarantees that when a read/write on a streaming socket comes back short,209 // the kernel buffer is empty/full:210 // See <https://man7.org/linux/man-pages/man7/epoll.7.html> in Q&A section.211 // So we can't do short reads/writes here.212 false213 }214215 fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {216 self217 }218219 fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {220 let mut flags = ecx.eval_libc_i32("O_RDWR");221222 if self.is_non_block.get() {223 flags |= ecx.eval_libc_i32("O_NONBLOCK");224 }225226 interp_ok(Scalar::from_i32(flags))227 }228229 fn set_flags<'tcx>(230 &self,231 mut flag: i32,232 ecx: &mut MiriInterpCx<'tcx>,233 ) -> InterpResult<'tcx, Scalar> {234 let o_nonblock = ecx.eval_libc_i32("O_NONBLOCK");235236 // O_NONBLOCK flag can be set / unset by user.237 if flag & o_nonblock == o_nonblock {238 self.is_non_block.set(true);239 flag &= !o_nonblock;240 } else {241 self.is_non_block.set(false);242 }243244 // Throw error if there is any unsupported flag.245 if flag != 0 {246 throw_unsup_format!("fcntl: only O_NONBLOCK is supported for sockets")247 }248249 interp_ok(Scalar::from_i32(0))250 }251}252253impl UnixFileDescription for Socket {254 fn ioctl<'tcx>(255 &self,256 op: Scalar,257 arg: Option<&OpTy<'tcx>>,258 ecx: &mut MiriInterpCx<'tcx>,259 ) -> InterpResult<'tcx, i32> {260 assert!(ecx.machine.communicate(), "cannot have `Socket` with isolation enabled!");261262 let fionbio = ecx.eval_libc("FIONBIO");263264 if op == fionbio {265 // On these OSes, Rust uses the ioctl, so we trust that it is reasonable and controls266 // the same internal flag as fcntl.267 if !matches!(ecx.tcx.sess.target.os, Os::Linux | Os::Android | Os::MacOs | Os::FreeBsd)268 {269 // FIONBIO cannot be used to change the blocking mode of a socket on solarish targets:270 // <https://github.com/rust-lang/rust/commit/dda5c97675b4f5b1f6fdab64606c8a1f21021b0a>271 // Since there might be more targets which do weird things with this option, we use272 // an allowlist instead of just denying solarish targets.273 throw_unsup_format!(274 "ioctl: setting FIONBIO on sockets is unsupported on target {}",275 ecx.tcx.sess.target.os276 );277 }278279 let Some(value_ptr) = arg else {280 throw_ub_format!("ioctl: setting FIONBIO on sockets requires a third argument");281 };282 let value = ecx.deref_pointer_as(value_ptr, ecx.machine.layouts.i32)?;283 let non_block = ecx.read_scalar(&value)?.to_i32()? != 0;284 self.is_non_block.set(non_block);285 return interp_ok(0);286 }287288 throw_unsup_format!("ioctl: unsupported operation {op:#x} on socket");289 }290291 fn epoll_active_events<'tcx>(&self) -> InterpResult<'tcx, EpollReadiness> {292 interp_ok(EpollReadiness::from(&*self.io_readiness.borrow()))293 }294}295296impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}297pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {298 /// For more information on the arguments see the socket manpage:299 /// <https://linux.die.net/man/2/socket>300 fn socket(301 &mut self,302 domain: &OpTy<'tcx>,303 type_: &OpTy<'tcx>,304 protocol: &OpTy<'tcx>,305 ) -> InterpResult<'tcx, Scalar> {306 let this = self.eval_context_mut();307308 let domain = this.read_scalar(domain)?.to_i32()?;309 let mut flags = this.read_scalar(type_)?.to_i32()?;310 let protocol = this.read_scalar(protocol)?.to_i32()?;311312 // Reject if isolation is enabled313 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {314 this.reject_in_isolation("`socket`", reject_with)?;315 return this.set_errno_and_return_neg1_i32(LibcError("EACCES"));316 }317318 let mut is_sock_nonblock = false;319320 // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so321 // if there is anything left at the end, that's an unsupported flag.322 if matches!(323 this.tcx.sess.target.os,324 Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos325 ) {326 // SOCK_NONBLOCK and SOCK_CLOEXEC only exist on Linux, Android, FreeBSD,327 // Solaris, and Illumos targets.328 let sock_nonblock = this.eval_libc_i32("SOCK_NONBLOCK");329 let sock_cloexec = this.eval_libc_i32("SOCK_CLOEXEC");330 if flags & sock_nonblock == sock_nonblock {331 is_sock_nonblock = true;332 flags &= !sock_nonblock;333 }334 if flags & sock_cloexec == sock_cloexec {335 // We don't support `exec` so we can ignore this.336 flags &= !sock_cloexec;337 }338 }339340 let family = if domain == this.eval_libc_i32("AF_INET") {341 SocketFamily::IPv4342 } else if domain == this.eval_libc_i32("AF_INET6") {343 SocketFamily::IPv6344 } else {345 throw_unsup_format!(346 "socket: domain {:#x} is unsupported, only AF_INET and \347 AF_INET6 are allowed.",348 domain349 );350 };351352 if flags != this.eval_libc_i32("SOCK_STREAM") {353 throw_unsup_format!(354 "socket: type {:#x} is unsupported, only SOCK_STREAM, \355 SOCK_CLOEXEC and SOCK_NONBLOCK are allowed",356 flags357 );358 }359 if protocol != 0 && protocol != this.eval_libc_i32("IPPROTO_TCP") {360 throw_unsup_format!(361 "socket: socket protocol {protocol} is unsupported, \362 only IPPROTO_TCP and 0 are allowed"363 );364 }365366 let fds = &mut this.machine.fds;367 let fd = fds.new_ref(Socket {368 family,369 state: RefCell::new(SocketState::Initial),370 is_non_block: Cell::new(is_sock_nonblock),371 io_readiness: RefCell::new(BlockingIoSourceReadiness::empty()),372 error: RefCell::new(None),373 read_timeout: Cell::new(None),374 write_timeout: Cell::new(None),375 });376377 interp_ok(Scalar::from_i32(fds.insert(fd)))378 }379380 fn bind(381 &mut self,382 socket: &OpTy<'tcx>,383 address: &OpTy<'tcx>,384 address_len: &OpTy<'tcx>,385 ) -> InterpResult<'tcx, Scalar> {386 let this = self.eval_context_mut();387388 let socket = this.read_scalar(socket)?.to_i32()?;389 let address = match this.read_socket_address(address, address_len, "bind")? {390 Ok(addr) => addr,391 Err(e) => return this.set_errno_and_return_neg1_i32(e),392 };393394 // Get the file handle395 let Some(fd) = this.machine.fds.get(socket) else {396 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));397 };398399 let Some(socket) = fd.downcast::<Socket>() else {400 // Man page specifies to return ENOTSOCK if `fd` is not a socket.401 return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));402 };403404 assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");405 this.ensure_not_failed(&socket, "bind")?;406407 let mut state = socket.state.borrow_mut();408409 match *state {410 SocketState::Initial => {411 let address_family = match &address {412 SocketAddr::V4(_) => SocketFamily::IPv4,413 SocketAddr::V6(_) => SocketFamily::IPv6,414 };415416 if socket.family != address_family {417 // Attempted to bind an address from a family that doesn't match418 // the family of the socket.419 let err = if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android) {420 // Linux man page states that `EINVAL` is used when there is an address family mismatch.421 // See <https://man7.org/linux/man-pages/man2/bind.2.html>422 LibcError("EINVAL")423 } else {424 // POSIX man page states that `EAFNOSUPPORT` should be used when there is an address425 // family mismatch.426 // See <https://man7.org/linux/man-pages/man3/bind.3p.html>427 LibcError("EAFNOSUPPORT")428 };429 return this.set_errno_and_return_neg1_i32(err);430 }431432 *state = SocketState::Bound(address);433 }434 SocketState::Connecting(_) | SocketState::Connected(_) =>435 throw_unsup_format!(436 "bind: socket is already connected and binding a437 connected socket is unsupported"438 ),439 SocketState::Bound(_) | SocketState::Listening(_) =>440 throw_unsup_format!(441 "bind: socket is already bound and binding a socket \442 multiple times is unsupported"443 ),444 SocketState::ConnectionFailed(_) => unreachable!(),445 }446447 interp_ok(Scalar::from_i32(0))448 }449450 fn listen(&mut self, socket: &OpTy<'tcx>, backlog: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {451 let this = self.eval_context_mut();452453 let socket = this.read_scalar(socket)?.to_i32()?;454 // Since the backlog value is just a performance hint we can ignore it.455 let _backlog = this.read_scalar(backlog)?.to_i32()?;456457 // Get the file handle458 let Some(fd) = this.machine.fds.get(socket) else {459 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));460 };461462 let Some(socket) = fd.downcast::<Socket>() else {463 // Man page specifies to return ENOTSOCK if `fd` is not a socket.464 return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));465 };466467 assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");468 this.ensure_not_failed(&socket, "listen")?;469470 let mut state = socket.state.borrow_mut();471472 match *state {473 SocketState::Bound(socket_addr) =>474 match TcpListener::bind(socket_addr) {475 Ok(listener) => {476 *state = SocketState::Listening(listener);477 drop(state);478 // Register the socket to the blocking I/O manager because479 // we now have an associated host socket.480 this.machine.blocking_io.register(socket);481 }482 Err(e) => return this.set_errno_and_return_neg1_i32(e),483 },484 SocketState::Initial => {485 throw_unsup_format!(486 "listen: listening on a socket which isn't bound is unsupported"487 )488 }489 SocketState::Listening(_) => {490 throw_unsup_format!("listen: listening on a socket multiple times is unsupported")491 }492 SocketState::Connecting(_) | SocketState::Connected(_) => {493 throw_unsup_format!("listen: listening on a connected socket is unsupported")494 }495 SocketState::ConnectionFailed(_) => unreachable!(),496 }497498 interp_ok(Scalar::from_i32(0))499 }500501 /// For more information on the arguments see the accept manpage:502 /// <https://linux.die.net/man/2/accept4>503 fn accept4(504 &mut self,505 socket: &OpTy<'tcx>,506 address: &OpTy<'tcx>,507 address_len: &OpTy<'tcx>,508 flags: Option<&OpTy<'tcx>>,509 // Location where the output scalar is written to.510 dest: &MPlaceTy<'tcx>,511 ) -> InterpResult<'tcx> {512 let this = self.eval_context_mut();513514 let socket = this.read_scalar(socket)?.to_i32()?;515 let address_ptr = this.read_pointer(address)?;516 let address_len_ptr = this.read_pointer(address_len)?;517 let mut flags =518 if let Some(flags) = flags { this.read_scalar(flags)?.to_i32()? } else { 0 };519520 // Get the file handle521 let Some(fd) = this.machine.fds.get(socket) else {522 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);523 };524525 let Some(socket) = fd.downcast::<Socket>() else {526 // Man page specifies to return ENOTSOCK if `fd` is not a socket.527 return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest);528 };529530 assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");531 this.ensure_not_failed(&socket, "accept4")?;532533 if !matches!(*socket.state.borrow(), SocketState::Listening(_)) {534 throw_unsup_format!(535 "accept4: accepting incoming connections is only allowed when socket is listening"536 )537 };538539 let mut is_client_sock_nonblock = false;540541 // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so542 // if there is anything left at the end, that's an unsupported flag.543 if matches!(544 this.tcx.sess.target.os,545 Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos546 ) {547 // SOCK_NONBLOCK and SOCK_CLOEXEC only exist on Linux, Android, FreeBSD,548 // Solaris, and Illumos targets.549 let sock_nonblock = this.eval_libc_i32("SOCK_NONBLOCK");550 let sock_cloexec = this.eval_libc_i32("SOCK_CLOEXEC");551 if flags & sock_nonblock == sock_nonblock {552 is_client_sock_nonblock = true;553 flags &= !sock_nonblock;554 }555 if flags & sock_cloexec == sock_cloexec {556 // We don't support `exec` so we can ignore this.557 flags &= !sock_cloexec;558 }559 }560561 if flags != 0 {562 throw_unsup_format!(563 "accept4: flag {flags:#x} is unsupported, only SOCK_CLOEXEC \564 and SOCK_NONBLOCK are allowed",565 );566 }567568 if socket.is_non_block.get() {569 // We have a non-blocking socket and thus don't want to block until570 // we can accept an incoming connection.571 match this.try_non_block_accept(572 &socket,573 address_ptr,574 address_len_ptr,575 is_client_sock_nonblock,576 )? {577 Ok(sockfd) => {578 // We need to create the scalar using the destination size since579 // `syscall(SYS_accept4, ...)` returns a long which doesn't match580 // the int returned from the `accept`/`accept4` syscalls.581 // See <https://man7.org/linux/man-pages/man2/syscall.2.html>.582 this.write_scalar(Scalar::from_int(sockfd, dest.layout.size), dest)583 }584 Err(e) => this.set_errno_and_return_neg1(e, dest),585 }586 } else {587 // The socket is in blocking mode and thus the accept call should block588 // until an incoming connection is ready.589590 if socket.read_timeout.get().is_some() {591 // Some Unixes like Linux also apply the SO_RCVTIMEO socket option592 // to `accept` calls:593 // <https://github.com/torvalds/linux/blob/HEAD/net/ipv4/inet_connection_sock.c#L668-L675>594 // This is currently not supported by Miri.595 throw_unsup_format!(596 "accept4: blocking accept is not supported when SO_RCVTIMEO is non-zero"597 )598 }599600 this.block_for_accept(601 socket,602 address_ptr,603 address_len_ptr,604 is_client_sock_nonblock,605 dest.clone(),606 )607 }608 }609610 fn connect(611 &mut self,612 socket: &OpTy<'tcx>,613 address: &OpTy<'tcx>,614 address_len: &OpTy<'tcx>,615 // Location where the output scalar is written to.616 dest: &MPlaceTy<'tcx>,617 ) -> InterpResult<'tcx> {618 let this = self.eval_context_mut();619620 let socket = this.read_scalar(socket)?.to_i32()?;621 let address = match this.read_socket_address(address, address_len, "connect")? {622 Ok(address) => address,623 Err(e) => return this.set_errno_and_return_neg1(e, dest),624 };625626 // Get the file handle627 let Some(fd) = this.machine.fds.get(socket) else {628 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);629 };630631 let Some(socket) = fd.downcast::<Socket>() else {632 // Man page specifies to return ENOTSOCK if `fd` is not a socket633 return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest);634 };635636 assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");637 this.ensure_not_failed(&socket, "connect")?;638639 match &*socket.state.borrow() {640 SocketState::Initial => { /* fall-through to below */ }641 // The socket is already in a connecting state.642 SocketState::Connecting(_) =>643 return this.set_errno_and_return_neg1(LibcError("EALREADY"), dest),644 // We don't return EISCONN for already connected sockets, for which we're645 // sure that the connection is established, since TCP sockets are usually646 // allowed to be connected multiple times.647 _ =>648 throw_unsup_format!(649 "connect: connecting is only supported for sockets which are neither \650 bound, listening nor already connected"651 ),652 }653654 // This begins establishing the connection, but does not block until the stream is fully connected.655 // We deal with that below.656 match TcpStream::connect(address) {657 Ok(stream) => {658 *socket.state.borrow_mut() = SocketState::Connecting(stream);659 // Register the socket to the blocking I/O manager because660 // we now have an associated host socket.661 this.machine.blocking_io.register(socket.clone());662 }663 Err(e) => return this.set_errno_and_return_neg1(e, dest),664 };665666 if socket.is_non_block.get() {667 // We have a non-blocking socket and thus don't want to block until668 // the connection is established.669670 // Since the [`TcpStream::connect`] function of mio hides the EINPROGRESS671 // we just always return EINPROGRESS and check whether the connection succeeded672 // once we want to use the connected socket.673 this.set_errno_and_return_neg1(LibcError("EINPROGRESS"), dest)674 } else {675 // The socket is in blocking mode and thus the connect call should block676 // until the connection with the server is established.677678 if socket.write_timeout.get().is_some() {679 // Some Unixes like Linux also apply the SO_SNDTIMEO socket option680 // to `connect` calls:681 // <https://github.com/torvalds/linux/blob/HEAD/net/ipv4/af_inet.c#L701-L710>682 // This is currently not supported by Miri.683 throw_unsup_format!(684 "connect: blocking connect is not supported when SO_SNDTIMEO is non-zero"685 )686 }687688 let dest = dest.clone();689 this.ensure_connected(690 socket.clone(),691 /* deadline */ None,692 "connect",693 callback!(694 @capture<'tcx> {695 socket: FileDescriptionRef<Socket>,696 dest: MPlaceTy<'tcx>697 } |this, result: Result<(), ()>| {698 if result.is_err() {699 // An error occurred whilst connecting. We know700 // that it has been consumed by `ensure_connected`701 // and is now stored in `socket.error`.702 let err = socket.error.take().unwrap();703 this.set_errno_and_return_neg1(err, &dest)704 } else {705 this.write_scalar(Scalar::from_i32(0), &dest)706 }707 }708 ),709 )710 }711 }712713 fn send(714 &mut self,715 socket: &OpTy<'tcx>,716 buffer: &OpTy<'tcx>,717 length: &OpTy<'tcx>,718 flags: &OpTy<'tcx>,719 // Location where the output scalar is written to.720 dest: &MPlaceTy<'tcx>,721 ) -> InterpResult<'tcx> {722 let this = self.eval_context_mut();723724 let socket = this.read_scalar(socket)?.to_i32()?;725 let buffer_ptr = this.read_pointer(buffer)?;726 let size_layout = this.libc_ty_layout("size_t");727 let length: usize =728 this.read_scalar(length)?.to_uint(size_layout.size)?.try_into().unwrap();729 let mut flags = this.read_scalar(flags)?.to_i32()?;730731 // Get the file handle732 let Some(fd) = this.machine.fds.get(socket) else {733 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);734 };735736 let Some(socket) = fd.downcast::<Socket>() else {737 // Man page specifies to return ENOTSOCK if `fd` is not a socket738 return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest);739 };740741 let mut is_op_non_block = false;742743 // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so744 // if there is anything left at the end, that's an unsupported flag.745 if matches!(746 this.tcx.sess.target.os,747 Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos748 ) {749 // MSG_NOSIGNAL and MSG_DONTWAIT only exist on Linux, Android, FreeBSD,750 // Solaris, and Illumos targets.751 let msg_nosignal = this.eval_libc_i32("MSG_NOSIGNAL");752 let msg_dontwait = this.eval_libc_i32("MSG_DONTWAIT");753 if flags & msg_nosignal == msg_nosignal {754 // This is only needed to ensure that no EPIPE signal is sent when755 // trying to send into a stream which is no longer connected.756 // Since we don't support signals, we can ignore this.757 flags &= !msg_nosignal;758 }759 if flags & msg_dontwait == msg_dontwait {760 flags &= !msg_dontwait;761 is_op_non_block = true;762 }763 }764765 if flags != 0 {766 throw_unsup_format!(767 "send: flag {flags:#x} is unsupported, only MSG_NOSIGNAL and MSG_DONTWAIT are allowed",768 );769 }770771 let is_non_block = is_op_non_block || socket.is_non_block.get();772 let deadline = this.action_deadline(is_non_block, socket.write_timeout.get());773 let dest = dest.clone();774775 this.ensure_connected(776 socket.clone(),777 deadline.clone(),778 "send",779 callback!(780 @capture<'tcx> {781 socket: FileDescriptionRef<Socket>,782 deadline: Option<Deadline>,783 flags: i32,784 buffer_ptr: Pointer,785 length: usize,786 is_non_block: bool,787 dest: MPlaceTy<'tcx>,788 } |this, result: Result<(), ()>| {789 if result.is_err() {790 return this.set_errno_and_return_neg1(LibcError("ENOTCONN"), &dest)791 }792793 if is_non_block {794 // We have a non-blocking operation or a non-blocking socket and795 // thus don't want to block until we can send.796 match this.try_non_block_send(&socket, buffer_ptr, length)? {797 Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),798 Err(e) => this.set_errno_and_return_neg1(e, &dest),799 }800 } else {801 // The socket is in blocking mode and thus the send call should block802 // until we can send some bytes into the socket or the timeout exceeded.803 this.block_for_send(804 socket,805 deadline,806 buffer_ptr,807 length,808 callback!(@capture<'tcx> {809 dest: MPlaceTy<'tcx>810 } |this, result: Result<usize, IoError>| {811 match result {812 Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),813 Err(e) => this.set_errno_and_return_neg1(e, &dest)814 }815 }),816 )817 }818 }819 ),820 )821 }822823 fn recv(824 &mut self,825 socket: &OpTy<'tcx>,826 buffer: &OpTy<'tcx>,827 length: &OpTy<'tcx>,828 flags: &OpTy<'tcx>,829 // Location where the output scalar is written to.830 dest: &MPlaceTy<'tcx>,831 ) -> InterpResult<'tcx> {832 let this = self.eval_context_mut();833834 let socket = this.read_scalar(socket)?.to_i32()?;835 let buffer_ptr = this.read_pointer(buffer)?;836 let size_layout = this.libc_ty_layout("size_t");837 let length: usize =838 this.read_scalar(length)?.to_uint(size_layout.size)?.try_into().unwrap();839 let mut flags = this.read_scalar(flags)?.to_i32()?;840841 // Get the file handle842 let Some(fd) = this.machine.fds.get(socket) else {843 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);844 };845846 let Some(socket) = fd.downcast::<Socket>() else {847 // Man page specifies to return ENOTSOCK if `fd` is not a socket848 return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest);849 };850851 let mut should_peek = false;852 let mut is_op_non_block = false;853854 // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so855 // if there is anything left at the end, that's an unsupported flag.856857 let msg_peek = this.eval_libc_i32("MSG_PEEK");858 if flags & msg_peek == msg_peek {859 should_peek = true;860 flags &= !msg_peek;861 }862863 if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android | Os::FreeBsd | Os::Illumos) {864 // MSG_CMSG_CLOEXEC only exists on Linux, Android, FreeBSD,865 // and Illumos targets.866 let msg_cmsg_cloexec = this.eval_libc_i32("MSG_CMSG_CLOEXEC");867 if flags & msg_cmsg_cloexec == msg_cmsg_cloexec {868 // We don't support `exec` so we can ignore this.869 flags &= !msg_cmsg_cloexec;870 }871 }872873 if matches!(874 this.tcx.sess.target.os,875 Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos876 ) {877 // MSG_DONTWAIT only exists on Linux, Android, FreeBSD,878 // Solaris, and Illumos targets.879 let msg_dontwait = this.eval_libc_i32("MSG_DONTWAIT");880 if flags & msg_dontwait == msg_dontwait {881 flags &= !msg_dontwait;882 is_op_non_block = true;883 }884 }885886 if flags != 0 {887 throw_unsup_format!(888 "recv: flag {flags:#x} is unsupported, only MSG_PEEK, MSG_DONTWAIT \889 and MSG_CMSG_CLOEXEC are allowed",890 );891 }892893 let is_non_block = is_op_non_block || socket.is_non_block.get();894 let deadline = this.action_deadline(is_non_block, socket.read_timeout.get());895 let dest = dest.clone();896897 this.ensure_connected(898 socket.clone(),899 deadline.clone(),900 "recv",901 callback!(902 @capture<'tcx> {903 socket: FileDescriptionRef<Socket>,904 deadline: Option<Deadline>,905 buffer_ptr: Pointer,906 length: usize,907 should_peek: bool,908 is_non_block: bool,909 dest: MPlaceTy<'tcx>,910 } |this, result: Result<(), ()>| {911 if result.is_err() {912 return this.set_errno_and_return_neg1(LibcError("ENOTCONN"), &dest)913 }914915 if is_non_block {916 // We have a non-blocking operation or a non-blocking socket and917 // thus don't want to block until we can receive.918 match this.try_non_block_recv(&socket, buffer_ptr, length, should_peek)? {919 Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),920 Err(e) => this.set_errno_and_return_neg1(e, &dest),921 }922 } else {923 // The socket is in blocking mode and thus the receive call should block924 // until we can receive some bytes from the socket or the timeout exceeded.925 this.block_for_recv(926 socket,927 deadline,928 buffer_ptr,929 length,930 should_peek,931 callback!(@capture<'tcx> {932 dest: MPlaceTy<'tcx>933 } |this, result: Result<usize, IoError>| {934 match result {935 Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),936 Err(e) => this.set_errno_and_return_neg1(e, &dest)937 }938 }),939 )940 }941 }942 ),943 )944 }945946 fn setsockopt(947 &mut self,948 socket: &OpTy<'tcx>,949 level: &OpTy<'tcx>,950 option_name: &OpTy<'tcx>,951 option_value: &OpTy<'tcx>,952 option_len: &OpTy<'tcx>,953 ) -> InterpResult<'tcx, Scalar> {954 let this = self.eval_context_mut();955956 let socket = this.read_scalar(socket)?.to_i32()?;957 let level = this.read_scalar(level)?.to_i32()?;958 let option_name = this.read_scalar(option_name)?.to_i32()?;959 let option_value_ptr = this.read_pointer(option_value)?;960 let socklen_layout = this.libc_ty_layout("socklen_t");961 let option_len = this.read_scalar(option_len)?.to_int(socklen_layout.size)?;962963 // Get the file handle964 let Some(fd) = this.machine.fds.get(socket) else {965 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));966 };967968 let Some(socket) = fd.downcast::<Socket>() else {969 // Man page specifies to return ENOTSOCK if `fd` is not a socket.970 return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));971 };972973 if level == this.eval_libc_i32("SOL_SOCKET") {974 let opt_so_rcvtimeo = this.eval_libc_i32("SO_RCVTIMEO");975 let opt_so_sndtimeo = this.eval_libc_i32("SO_SNDTIMEO");976 let opt_so_reuseaddr = this.eval_libc_i32("SO_REUSEADDR");977978 if matches!(this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::NetBsd) {979 // SO_NOSIGPIPE only exists on MacOS, FreeBSD, and NetBSD.980 let opt_so_nosigpipe = this.eval_libc_i32("SO_NOSIGPIPE");981982 if option_name == opt_so_nosigpipe {983 if option_len != 4 {984 // Option value should be C-int which is usually 4 bytes.985 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));986 }987 let option_value =988 this.ptr_to_mplace(option_value_ptr, this.machine.layouts.i32);989 let _val = this.read_scalar(&option_value)?.to_i32()?;990 // We entirely ignore this value since we do not support signals anyway.991992 return interp_ok(Scalar::from_i32(0));993 }994 }995996 if option_name == opt_so_rcvtimeo || option_name == opt_so_sndtimeo {997 let timeval_layout = this.libc_ty_layout("timeval");998 let option_value = this.ptr_to_mplace(option_value_ptr, timeval_layout);9991000 let timeout = match this.read_timeval(&option_value)? {1001 None => return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")),1002 Some(Duration::ZERO) => None,1003 Some(duration) => Some(duration),1004 };10051006 if option_name == opt_so_rcvtimeo {1007 socket.read_timeout.set(timeout);1008 } else {1009 socket.write_timeout.set(timeout);1010 }10111012 return interp_ok(Scalar::from_i32(0));1013 }10141015 if option_name == opt_so_reuseaddr {1016 if option_len != 4 {1017 // Option value should be C-int which is usually 4 bytes.1018 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));1019 }1020 let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.i32);1021 let _val = this.read_scalar(&option_value)?.to_i32()?;1022 // We entirely ignore this: std always sets REUSEADDR for us, and in the end it's more of a1023 // hint to bypass some arbitrary timeout anyway.1024 return interp_ok(Scalar::from_i32(0));1025 } else {1026 throw_unsup_format!(1027 "setsockopt: option {option_name:#x} is unsupported for level SOL_SOCKET",1028 );1029 }1030 } else if level == this.eval_libc_i32("IPPROTO_IP") {1031 let opt_ip_ttl = this.eval_libc_i32("IP_TTL");10321033 if option_name == opt_ip_ttl {1034 if option_len != 4 {1035 // Option value should be C-uint which is usually 4 bytes.1036 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));1037 }1038 let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.u32);1039 let ttl = this.read_scalar(&option_value)?.to_u32()?;10401041 let result = match &*socket.state.borrow() {1042 SocketState::Initial | SocketState::Bound(_) =>1043 throw_unsup_format!(1044 "setsockopt: setting option IP_TTL on level IPPROTO_IP is only supported \1045 on connected and listening sockets"1046 ),1047 SocketState::Listening(listener) => listener.set_ttl(ttl),1048 SocketState::Connecting(stream) | SocketState::Connected(stream) =>1049 stream.set_ttl(ttl),1050 SocketState::ConnectionFailed(_) => unreachable!(),1051 };10521053 return match result {1054 Ok(_) => interp_ok(Scalar::from_i32(0)),1055 Err(e) => this.set_errno_and_return_neg1_i32(e),1056 };1057 } else {1058 throw_unsup_format!(1059 "setsockopt: option {option_name:#x} is unsupported for level IPPROTO_IP",1060 );1061 }1062 } else if level == this.eval_libc_i32("IPPROTO_TCP") {1063 let opt_tcp_nodelay = this.eval_libc_i32("TCP_NODELAY");10641065 if option_name == opt_tcp_nodelay {1066 if option_len != 4 {1067 // Option value should be C-int which is usually 4 bytes.1068 return this.set_errno_and_return_neg1_i32(LibcError("EINVAL"));1069 }1070 let option_value = this.ptr_to_mplace(option_value_ptr, this.machine.layouts.i32);1071 let nodelay = this.read_scalar(&option_value)?.to_i32()? != 0;10721073 let result = match &*socket.state.borrow() {1074 SocketState::Initial | SocketState::Bound(_) | SocketState::Listening(_) =>1075 throw_unsup_format!(1076 "setsockopt: setting option TCP_NODELAY on level IPPROTO_TCP is only supported \1077 on connected sockets"1078 ),1079 SocketState::Connecting(stream) | SocketState::Connected(stream) =>1080 stream.set_nodelay(nodelay),1081 SocketState::ConnectionFailed(_) => unreachable!(),1082 };10831084 return match result {1085 Ok(_) => interp_ok(Scalar::from_i32(0)),1086 Err(e) => this.set_errno_and_return_neg1_i32(e),1087 };1088 } else {1089 throw_unsup_format!(1090 "setsockopt: option {option_name:#x} is unsupported for level IPPROTO_TCP"1091 );1092 }1093 }10941095 throw_unsup_format!(1096 "setsockopt: level {level:#x} is unsupported, only SOL_SOCKET, IPPROTO_IP \1097 and IPPROTO_TCP are allowed"1098 );1099 }11001101 fn getsockopt(1102 &mut self,1103 socket: &OpTy<'tcx>,1104 level: &OpTy<'tcx>,1105 option_name: &OpTy<'tcx>,1106 option_value: &OpTy<'tcx>,1107 option_len: &OpTy<'tcx>,1108 ) -> InterpResult<'tcx, Scalar> {1109 let this = self.eval_context_mut();11101111 let socket = this.read_scalar(socket)?.to_i32()?;1112 let level = this.read_scalar(level)?.to_i32()?;1113 let option_name = this.read_scalar(option_name)?.to_i32()?;1114 // These two pointers are used to return the value: `len_ptr` initially stores how much space1115 // is available. If the actual value fits into that space, it is written to1116 // `value_ptr` and `len_ptr` is updated to represent how many bytes1117 // were actually written. If the value does not fit, it is silently truncated.1118 // Also see <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getsockopt.html>.1119 let option_value_ptr = this.read_pointer(option_value)?;1120 let option_len_ptr = this.read_pointer(option_len)?;11211122 // Get the file handle1123 let Some(fd) = this.machine.fds.get(socket) else {1124 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));1125 };11261127 let Some(socket) = fd.downcast::<Socket>() else {1128 // Man page specifies to return ENOTSOCK if `fd` is not a socket.1129 return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));1130 };11311132 if option_value_ptr == Pointer::null() || option_len_ptr == Pointer::null() {1133 // This socket option returns a value and thus we need to return EFAULT1134 // when either the value or the length pointers are null pointers.1135 return this.set_errno_and_return_neg1_i32(LibcError("EFAULT"));1136 }11371138 let socklen_layout = this.libc_ty_layout("socklen_t");1139 let option_len_ptr_mplace = this.ptr_to_mplace(option_len_ptr, socklen_layout);1140 let option_len: usize = this1141 .read_scalar(&option_len_ptr_mplace)?1142 .to_int(socklen_layout.size)?1143 .try_into()1144 .unwrap();11451146 // We need a temporary buffer as `option_value_ptr` might not point to a large enough1147 // buffer, in which case we have to truncate.1148 let value_buffer = if level == this.eval_libc_i32("SOL_SOCKET") {1149 let opt_so_error = this.eval_libc_i32("SO_ERROR");1150 let opt_so_rcvtimeo = this.eval_libc_i32("SO_RCVTIMEO");1151 let opt_so_sndtimeo = this.eval_libc_i32("SO_SNDTIMEO");11521153 if option_name == opt_so_error {1154 // Reading SO_ERROR should always return the latest async error. Because our stored1155 // `socket.error` could be outdated, we attempt to update it here.1156 this.update_last_error(&socket);11571158 let return_value = match socket.error.take() {1159 Some(err) => this.io_error_to_errnum(err)?.to_i32()?,1160 // If there is no error, we return 0 as the option value.1161 None => 0,1162 };11631164 // Clear our own stored error -- it was either `take`n above or it is outdated.1165 socket.error.replace(None);11661167 // We know there is no longer an async error and thus we need to update the1168 // I/O and epoll readiness of the socket.1169 socket.io_readiness.borrow_mut().error = false;1170 this.update_epoll_active_events(socket, /* force_edge */ false)?;11711172 // Allocate new buffer on the stack with the `i32` layout.1173 let value_buffer = this.allocate(this.machine.layouts.i32, MemoryKind::Stack)?;1174 this.write_int(return_value, &value_buffer)?;1175 value_buffer1176 } else if option_name == opt_so_rcvtimeo || option_name == opt_so_sndtimeo {1177 let timeout = if option_name == opt_so_rcvtimeo {1178 socket.read_timeout.get()1179 } else {1180 socket.write_timeout.get()1181 }1182 .unwrap_or_default();11831184 let secs = timeout.as_secs();1185 let usecs = timeout.subsec_micros();11861187 let timeval_layout = this.libc_ty_layout("timeval");1188 // Allocate new buffer on the stack with the `timeval` layout.1189 let timeval_buffer = this.allocate(timeval_layout, MemoryKind::Stack)?;11901191 let sec_field = this.project_field_named(&timeval_buffer, "tv_sec")?;1192 this.write_int(secs, &sec_field)?;11931194 let usec_field = this.project_field_named(&timeval_buffer, "tv_usec")?;1195 this.write_int(usecs, &usec_field)?;11961197 timeval_buffer1198 } else {1199 throw_unsup_format!(1200 "getsockopt: option {option_name:#x} is unsupported for level SOL_SOCKET",1201 );1202 }1203 } else if level == this.eval_libc_i32("IPPROTO_IP") {1204 let opt_ip_ttl = this.eval_libc_i32("IP_TTL");12051206 if option_name == opt_ip_ttl {1207 let ttl = match &*socket.state.borrow() {1208 SocketState::Initial | SocketState::Bound(_) =>1209 throw_unsup_format!(1210 "getsockopt: reading option IP_TTL on level IPPROTO_IP is only supported \1211 on connected and listening sockets"1212 ),1213 SocketState::Listening(listener) => listener.ttl(),1214 SocketState::Connecting(stream) | SocketState::Connected(stream) =>1215 stream.ttl(),1216 SocketState::ConnectionFailed(_) => unreachable!(),1217 };12181219 let ttl = match ttl {1220 Ok(ttl) => ttl,1221 Err(e) => return this.set_errno_and_return_neg1_i32(e),1222 };12231224 // Allocate new buffer on the stack with the `u32` layout.1225 let value_buffer = this.allocate(this.machine.layouts.u32, MemoryKind::Stack)?;1226 this.write_int(ttl, &value_buffer)?;1227 value_buffer1228 } else {1229 throw_unsup_format!(1230 "getsockopt: option {option_name:#x} is unsupported for level IPPROTO_IP",1231 );1232 }1233 } else if level == this.eval_libc_i32("IPPROTO_TCP") {1234 let opt_tcp_nodelay = this.eval_libc_i32("TCP_NODELAY");12351236 if option_name == opt_tcp_nodelay {1237 let nodelay = match &*socket.state.borrow() {1238 SocketState::Initial | SocketState::Bound(_) | SocketState::Listening(_) =>1239 throw_unsup_format!(1240 "getsockopt: reading option TCP_NODELAY on level IPPROTO_TCP is only supported \1241 on connected sockets"1242 ),1243 SocketState::Connecting(stream) | SocketState::Connected(stream) =>1244 stream.nodelay(),1245 SocketState::ConnectionFailed(_) => unreachable!(),1246 };12471248 let nodelay = match nodelay {1249 Ok(nodelay) => nodelay,1250 Err(e) => return this.set_errno_and_return_neg1_i32(e),1251 };12521253 // Allocate new buffer on the stack with the `i32` layout.1254 let value_buffer = this.allocate(this.machine.layouts.i32, MemoryKind::Stack)?;1255 this.write_int(i32::from(nodelay), &value_buffer)?;1256 value_buffer1257 } else {1258 throw_unsup_format!(1259 "getsockopt: option {option_name:#x} is unsupported for level IPPROTO_TCP"1260 );1261 }1262 } else {1263 throw_unsup_format!(1264 "getsockopt: level {level:#x} is unsupported, only SOL_SOCKET, IPPROTO_IP \1265 and IPPROTO_TCP are allowed"1266 )1267 };12681269 // Truncated size of the output value.1270 let output_value_len = value_buffer.layout.size.min(Size::from_bytes(option_len));1271 // Copy the truncated value into the buffer pointed to by `option_value_ptr`.1272 this.mem_copy(1273 value_buffer.ptr(),1274 option_value_ptr,1275 // Truncate the value to fit the provided buffer.1276 output_value_len,1277 // The buffers are guaranteed to not overlap since the `value_buffer`1278 // was just newly allocated on the stack.1279 true,1280 )?;1281 // Deallocate the value buffer as it was only needed to store the value and1282 // copy it into the buffer pointed to by `option_value_ptr`.1283 this.deallocate_ptr(value_buffer.ptr(), None, MemoryKind::Stack)?;12841285 // On output, the length pointer contains the amount of bytes written -- not the size1286 // of the value before truncation.1287 this.write_scalar(1288 Scalar::from_uint(output_value_len.bytes(), socklen_layout.size),1289 &option_len_ptr_mplace,1290 )?;12911292 interp_ok(Scalar::from_i32(0))1293 }12941295 fn getsockname(1296 &mut self,1297 socket: &OpTy<'tcx>,1298 address: &OpTy<'tcx>,1299 address_len: &OpTy<'tcx>,1300 ) -> InterpResult<'tcx, Scalar> {1301 let this = self.eval_context_mut();13021303 let socket = this.read_scalar(socket)?.to_i32()?;1304 let address_ptr = this.read_pointer(address)?;1305 let address_len_ptr = this.read_pointer(address_len)?;13061307 // Get the file handle1308 let Some(fd) = this.machine.fds.get(socket) else {1309 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));1310 };13111312 let Some(socket) = fd.downcast::<Socket>() else {1313 // Man page specifies to return ENOTSOCK if `fd` is not a socket.1314 return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));1315 };13161317 assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");1318 this.ensure_not_failed(&socket, "getsockname")?;13191320 let state = socket.state.borrow();13211322 let address = match &*state {1323 SocketState::Bound(address) => {1324 if address.port() == 0 {1325 // The socket is bound to a zero-port which means it gets assigned a random1326 // port. Since we don't yet have an underlying socket, we don't know what this1327 // random port will be and thus this is unsupported.1328 throw_unsup_format!(1329 "getsockname: when the port is 0, getting the socket address before \1330 calling `listen` or `connect` is unsupported"1331 )1332 }13331334 *address1335 }1336 SocketState::Listening(listener) =>1337 match listener.local_addr() {1338 Ok(address) => address,1339 Err(e) => return this.set_errno_and_return_neg1_i32(e),1340 },1341 SocketState::Connecting(stream) | SocketState::Connected(stream) => {1342 if cfg!(windows) && matches!(&*state, SocketState::Connecting(_)) {1343 // FIXME: On Windows hosts `TcpStream::local_addr` returns `0.0.0.0:0` whilst1344 // the socket is connecting:1345 // <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getsockname#remarks>1346 // This is problematic because UNIX targets could expect a real local address even1347 // for a connecting non-blocking socket.13481349 static DEDUP: AtomicBool = AtomicBool::new(false);1350 if !DEDUP.swap(true, std::sync::atomic::Ordering::Relaxed) {1351 this.emit_diagnostic(NonHaltingDiagnostic::ConnectingSocketGetsockname);1352 }1353 }1354 match stream.local_addr() {1355 Ok(address) => address,1356 Err(e) => return this.set_errno_and_return_neg1_i32(e),1357 }1358 }1359 // For non-bound sockets the POSIX manual says the returned address is unspecified.1360 // Often this is 0.0.0.0:0 and thus we set it to this value.1361 SocketState::Initial => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)),1362 SocketState::ConnectionFailed(_) => unreachable!(),1363 };13641365 this.write_socket_address(&address, address_ptr, address_len_ptr, "getsockname")1366 .map(|_| Scalar::from_i32(0))1367 }13681369 fn getpeername(1370 &mut self,1371 socket: &OpTy<'tcx>,1372 address: &OpTy<'tcx>,1373 address_len: &OpTy<'tcx>,1374 // Location where the output scalar is written to.1375 dest: &MPlaceTy<'tcx>,1376 ) -> InterpResult<'tcx> {1377 let this = self.eval_context_mut();13781379 let socket = this.read_scalar(socket)?.to_i32()?;1380 let address_ptr = this.read_pointer(address)?;1381 let address_len_ptr = this.read_pointer(address_len)?;13821383 // Get the file handle1384 let Some(fd) = this.machine.fds.get(socket) else {1385 return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);1386 };13871388 let Some(socket) = fd.downcast::<Socket>() else {1389 // Man page specifies to return ENOTSOCK if `fd` is not a socket.1390 return this.set_errno_and_return_neg1(LibcError("ENOTSOCK"), dest);1391 };13921393 assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");13941395 let dest = dest.clone();13961397 // It's only safe to call [`TcpStream::peer_addr`] after the socket is connected since1398 // UNIX targets should return ENOTCONN when the connection is not yet established.1399 this.ensure_connected(1400 socket.clone(),1401 // Check whether the socket is connected without blocking.1402 Some(this.machine.monotonic_clock.now().into()),1403 "getpeername",1404 callback!(1405 @capture<'tcx> {1406 socket: FileDescriptionRef<Socket>,1407 address_ptr: Pointer,1408 address_len_ptr: Pointer,1409 dest: MPlaceTy<'tcx>,1410 } |this, result: Result<(), ()>| {1411 if result.is_err() {1412 return this.set_errno_and_return_neg1(LibcError("ENOTCONN"), &dest)1413 };14141415 let SocketState::Connected(stream) = &*socket.state.borrow() else {1416 unreachable!()1417 };14181419 let address = match stream.peer_addr() {1420 Ok(address) => address,1421 Err(e) => return this.set_errno_and_return_neg1(e, &dest),1422 };14231424 this.write_socket_address(1425 &address,1426 address_ptr,1427 address_len_ptr,1428 "getpeername",1429 )?;1430 this.write_scalar(Scalar::from_i32(0), &dest)1431 }1432 ),1433 )1434 }14351436 fn shutdown(&mut self, socket: &OpTy<'tcx>, how: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {1437 let this = self.eval_context_mut();14381439 let socket = this.read_scalar(socket)?.to_i32()?;1440 let how = this.read_scalar(how)?.to_i32()?;14411442 // Get the file handle1443 let Some(fd) = this.machine.fds.get(socket) else {1444 return this.set_errno_and_return_neg1_i32(LibcError("EBADF"));1445 };14461447 let Some(socket) = fd.downcast::<Socket>() else {1448 // Man page specifies to return ENOTSOCK if `fd` is not a socket.1449 return this.set_errno_and_return_neg1_i32(LibcError("ENOTSOCK"));1450 };14511452 assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");1453 this.ensure_not_failed(&socket, "shutdown")?;14541455 let state = socket.state.borrow();14561457 let (SocketState::Connecting(stream) | SocketState::Connected(stream)) = &*state else {1458 return this.set_errno_and_return_neg1_i32(LibcError("ENOTCONN"));1459 };14601461 let is_read_shutdown = how == this.eval_libc_i32("SHUT_RD");1462 let is_write_shutdown = how == this.eval_libc_i32("SHUT_WR");1463 let is_read_write_shutdown = how == this.eval_libc_i32("SHUT_RDWR");14641465 let how = match () {1466 _ if is_read_shutdown => Shutdown::Read,1467 _ if is_write_shutdown => Shutdown::Write,1468 _ if is_read_write_shutdown => Shutdown::Both,1469 // An invalid value was passed to `how`.1470 _ => return this.set_errno_and_return_neg1_i32(LibcError("EINVAL")),1471 };14721473 if let Err(e) = stream.shutdown(how) {1474 return this.set_errno_and_return_neg1_i32(e);1475 };14761477 drop(state);14781479 // Because we map cross platform mio readiness to epoll readiness and1480 // the different platforms don't treat `shutdown` the same way, we set1481 // the readiness after a `shutdown` manually to achieve more consistent1482 // epoll readiness. Otherwise we do not generate enough epoll events1483 // on partial shutdowns on Windows hosts.1484 let mut readiness = socket.io_readiness.borrow_mut();1485 // Closing the read end of a socket causes an EPOLLRDHUP event.1486 readiness.read_closed |= is_read_shutdown || is_read_write_shutdown;1487 // Only shutting down the write end doesn't cause an EPOLLHUP event1488 // and thus we won't set the `write_closed` readiness for it here.1489 readiness.write_closed |= is_read_write_shutdown;1490 // The Linux kernel also sets EPOLLIN when both ends of a socket are closed:1491 // <https://github.com/torvalds/linux/blob/HEAD/net/ipv4/tcp.c#L584-L588>1492 readiness.readable |= is_read_write_shutdown;14931494 drop(readiness);14951496 // Update the epoll readiness for the socket.1497 this.update_epoll_active_events(socket, /* force_edge */ false)?;14981499 interp_ok(Scalar::from_i32(0))1500 }1501}15021503impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}1504trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {1505 /// Get the deadline for an action (e.g. reading or writing).1506 /// When `is_non_block` is [`true`], the returned deadline is "now", i.e.,1507 /// we wake up immediately if the action cannot be completed.1508 /// If `action_timeout` is `Some(duration)`, the returned deadline is in the1509 /// future be the specified `duration`. Otherwise, no deadline ([`None`]) is1510 /// returned, indicating that the action can block indefinitely.1511 fn action_deadline(1512 &self,1513 is_non_block: bool,1514 action_timeout: Option<Duration>,1515 ) -> Option<Deadline> {1516 let this = self.eval_context_ref();15171518 if is_non_block {1519 // Non-blocking sockets always have a zero timeout.1520 Some(this.machine.monotonic_clock.now().into())1521 } else {1522 action_timeout1523 .map(|duration| this.machine.monotonic_clock.now().add_lossy(duration).into())1524 }1525 }15261527 /// Block the thread until there's an incoming connection or an error occurred.1528 ///1529 /// This recursively calls itself should the operation still block for some reason.1530 ///1531 /// **Note**: This function is only safe to call when having previously ensured1532 /// that the socket is in [`SocketState::Listening`].1533 fn block_for_accept(1534 &mut self,1535 socket: FileDescriptionRef<Socket>,1536 address_ptr: Pointer,1537 address_len_ptr: Pointer,1538 is_client_sock_nonblock: bool,1539 dest: MPlaceTy<'tcx>,1540 ) -> InterpResult<'tcx> {1541 let this = self.eval_context_mut();1542 this.block_thread_for_io(1543 socket.clone(),1544 BlockingIoInterest::Read,1545 /* deadline */ None,1546 callback!(@capture<'tcx> {1547 socket: FileDescriptionRef<Socket>,1548 address_ptr: Pointer,1549 address_len_ptr: Pointer,1550 is_client_sock_nonblock: bool,1551 dest: MPlaceTy<'tcx>,1552 } |this, kind: UnblockKind| {1553 // Remove the blocking I/O interest for unblocking this thread.1554 this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());15551556 match kind {1557 UnblockKind::Ready => { /* fall-through to below */ },1558 // When the read timeout is exceeded EAGAIN/EWOULDBLOCK is returned.1559 UnblockKind::TimedOut => return this.set_errno_and_return_neg1(LibcError("EWOULDBLOCK"), &dest)1560 }15611562 match this.try_non_block_accept(&socket, address_ptr, address_len_ptr, is_client_sock_nonblock)? {1563 Ok(sockfd) => {1564 // We need to create the scalar using the destination size since1565 // `syscall(SYS_accept4, ...)` returns a long which doesn't match1566 // the int returned from the `accept`/`accept4` syscalls.1567 // See <https://man7.org/linux/man-pages/man2/syscall.2.html>.1568 this.write_scalar(Scalar::from_int(sockfd, dest.layout.size), &dest)1569 },1570 Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {1571 // We need to block the thread again as it would still block.1572 this.block_for_accept(socket, address_ptr, address_len_ptr, is_client_sock_nonblock, dest)1573 }1574 Err(e) => this.set_errno_and_return_neg1(e, &dest),1575 }1576 }),1577 )1578 }15791580 /// Attempt to accept an incoming connection on the listening socket in a1581 /// non-blocking manner.1582 ///1583 /// **Note**: This function is only safe to call when having previously ensured1584 /// that the socket is in [`SocketState::Listening`].1585 fn try_non_block_accept(1586 &mut self,1587 socket: &FileDescriptionRef<Socket>,1588 address_ptr: Pointer,1589 address_len_ptr: Pointer,1590 is_client_sock_nonblock: bool,1591 ) -> InterpResult<'tcx, Result<i32, IoError>> {1592 let this = self.eval_context_mut();15931594 let state = socket.state.borrow();1595 let SocketState::Listening(listener) = &*state else {1596 panic!(1597 "try_non_block_accept must only be called when socket is in `SocketState::Listening`"1598 )1599 };16001601 let (stream, addr) = match listener.accept() {1602 Ok(peer) => peer,1603 Err(e) if e.kind() == io::ErrorKind::WouldBlock => {1604 // We know that the source is not readable so we need to update its readiness.1605 socket.io_readiness.borrow_mut().readable = false;1606 this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?;16071608 return interp_ok(Err(IoError::HostError(e)));1609 }1610 Err(e) => return interp_ok(Err(IoError::HostError(e))),1611 };16121613 let family = match addr {1614 SocketAddr::V4(_) => SocketFamily::IPv4,1615 SocketAddr::V6(_) => SocketFamily::IPv6,1616 };16171618 if address_ptr != Pointer::null() {1619 // We only attempt a write if the address pointer is not a null pointer.1620 // If the address pointer is a null pointer the user isn't interested in the1621 // address and we don't need to write anything.1622 this.write_socket_address(&addr, address_ptr, address_len_ptr, "accept4")?;1623 }16241625 let fd = this.machine.fds.new_ref(Socket {1626 family,1627 state: RefCell::new(SocketState::Connected(stream)),1628 is_non_block: Cell::new(is_client_sock_nonblock),1629 io_readiness: RefCell::new(BlockingIoSourceReadiness::empty()),1630 error: RefCell::new(None),1631 read_timeout: Cell::new(None),1632 write_timeout: Cell::new(None),1633 });1634 // Register the socket to the blocking I/O manager because1635 // there is an associated host socket.1636 this.machine.blocking_io.register(fd.clone());1637 let sockfd = this.machine.fds.insert(fd);1638 interp_ok(Ok(sockfd))1639 }16401641 /// Block the thread until we can send bytes into the connected socket1642 /// or an error occurred.1643 ///1644 /// This recursively calls itself should the operation still block for some reason.1645 ///1646 /// **Note**: This function is only safe to call when having previously ensured1647 /// that the socket is in [`SocketState::Connected`].1648 fn block_for_send(1649 &mut self,1650 socket: FileDescriptionRef<Socket>,1651 deadline: Option<Deadline>,1652 buffer_ptr: Pointer,1653 length: usize,1654 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,1655 ) -> InterpResult<'tcx> {1656 let this = self.eval_context_mut();1657 this.block_thread_for_io(1658 socket.clone(),1659 BlockingIoInterest::Write,1660 deadline.clone(),1661 callback!(@capture<'tcx> {1662 socket: FileDescriptionRef<Socket>,1663 deadline: Option<Deadline>,1664 buffer_ptr: Pointer,1665 length: usize,1666 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,1667 } |this, kind: UnblockKind| {1668 // Remove the blocking I/O interest for unblocking this thread.1669 this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());16701671 match kind {1672 UnblockKind::Ready => { /* fall-through to below */ },1673 // When the write timeout is exceeded EAGAIN/EWOULDBLOCK is returned.1674 UnblockKind::TimedOut => return finish.call(this, Err(LibcError("EWOULDBLOCK")))1675 }16761677 match this.try_non_block_send(&socket, buffer_ptr, length)? {1678 Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {1679 // We need to block the thread again as it would still block.1680 this.block_for_send(socket, deadline, buffer_ptr, length, finish)1681 },1682 result => finish.call(this, result)1683 }1684 }),1685 )1686 }16871688 /// Attempt to send bytes into the connected socket in a non-blocking manner.1689 ///1690 /// **Note**: This function is only safe to call when having previously ensured1691 /// that the socket is in [`SocketState::Connected`].1692 fn try_non_block_send(1693 &mut self,1694 socket: &FileDescriptionRef<Socket>,1695 buffer_ptr: Pointer,1696 length: usize,1697 ) -> InterpResult<'tcx, Result<usize, IoError>> {1698 let this = self.eval_context_mut();16991700 let SocketState::Connected(stream) = &mut *socket.state.borrow_mut() else {1701 panic!("try_non_block_send must only be called when the socket is connected")1702 };17031704 // This is a *non-blocking* write.1705 let result = this.write_to_host(stream, length, buffer_ptr)?;1706 match result {1707 Err(IoError::HostError(e))1708 if matches!(e.kind(), io::ErrorKind::NotConnected | io::ErrorKind::WouldBlock) =>1709 {1710 // We know that the source is not writable so we need to update it's readiness.1711 socket.io_readiness.borrow_mut().writable = false;1712 this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?;17131714 // On Windows hosts, `send` can return WSAENOTCONN where EAGAIN or EWOULDBLOCK1715 // would be returned on UNIX-like systems. We thus remap this error to an EWOULDBLOCK.1716 interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into())))1717 }1718 Ok(bytes_written) if bytes_written < length => {1719 // We had a short write. On Unix hosts using the `epoll` and `kqueue` backends, a1720 // short write means that the write buffer is full. We update the readiness1721 // accordingly, which means that next time we see "writable" we will report an epoll1722 // edge. Some applications (e.g. tokio) rely on this behavior; see1723 // <https://github.com/tokio-rs/tokio/blob/HEAD/tokio/src/io/poll_evented.rs#L244-L264>.1724 if cfg!(any(1725 // epoll1726 target_os = "android",1727 target_os = "illumos",1728 target_os = "linux",1729 target_os = "redox",1730 // kqueue1731 target_os = "dragonfly",1732 target_os = "freebsd",1733 target_os = "ios",1734 target_os = "macos",1735 target_os = "netbsd",1736 target_os = "openbsd",1737 target_os = "tvos",1738 target_os = "visionos",1739 target_os = "watchos",1740 )) {1741 socket.io_readiness.borrow_mut().writable = false;1742 this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?;1743 } else {1744 // On hosts which don't use the `epoll` or `kqueue` backends, a short write1745 // doesn't imply a full write buffer. However, the target we are emulating might1746 // guarantee this behavior. To prevent applications from being stuck on such1747 // targets waiting on a new readiness event, we emit a new edge which still1748 // contains a writable readiness. This should trick the applications into trying1749 // another write which would then return EWOULDBLOCK should it really be full.1750 // This results in an unrealistic execution but we don't have another way of1751 // finding out whether the write buffer is full. The "default case" of linux1752 // host and linux target isn't affected by this.1753 this.update_epoll_active_events(socket.clone(), /* force_edge */ true)?;1754 }1755 interp_ok(result)1756 }1757 result => interp_ok(result),1758 }1759 }17601761 /// Block the thread until we can receive bytes from the connected socket1762 /// or an error occurred.1763 ///1764 /// This recursively calls itself should the operation still block for some reason.1765 ///1766 /// **Note**: This function is only safe to call when having previously ensured1767 /// that the socket is in [`SocketState::Connected`].1768 fn block_for_recv(1769 &mut self,1770 socket: FileDescriptionRef<Socket>,1771 deadline: Option<Deadline>,1772 buffer_ptr: Pointer,1773 length: usize,1774 should_peek: bool,1775 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,1776 ) -> InterpResult<'tcx> {1777 let this = self.eval_context_mut();1778 this.block_thread_for_io(1779 socket.clone(),1780 BlockingIoInterest::Read,1781 deadline.clone(),1782 callback!(@capture<'tcx> {1783 socket: FileDescriptionRef<Socket>,1784 deadline: Option<Deadline>,1785 buffer_ptr: Pointer,1786 length: usize,1787 should_peek: bool,1788 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,1789 } |this, kind: UnblockKind| {1790 // Remove the blocking I/O interest for unblocking this thread.1791 this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());17921793 match kind {1794 UnblockKind::Ready => { /* fall-through to below */ },1795 // When the read timeout is exceeded EAGAIN/EWOULDBLOCK is returned.1796 UnblockKind::TimedOut => return finish.call(this, Err(LibcError("EWOULDBLOCK")))1797 }17981799 match this.try_non_block_recv(&socket, buffer_ptr, length, should_peek)? {1800 Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {1801 // We need to block the thread again as it would still block.1802 this.block_for_recv(socket, deadline, buffer_ptr, length, should_peek, finish)1803 },1804 result => finish.call(this, result)1805 }1806 }),1807 )1808 }18091810 /// Attempt to receive bytes from the connected socket in a non-blocking manner.1811 ///1812 /// **Note**: This function is only safe to call when having previously ensured1813 /// that the socket is in [`SocketState::Connected`].1814 fn try_non_block_recv(1815 &mut self,1816 socket: &FileDescriptionRef<Socket>,1817 buffer_ptr: Pointer,1818 length: usize,1819 should_peek: bool,1820 ) -> InterpResult<'tcx, Result<usize, IoError>> {1821 let this = self.eval_context_mut();18221823 let SocketState::Connected(stream) = &mut *socket.state.borrow_mut() else {1824 panic!("try_non_block_recv must only be called when the socket is connected")1825 };18261827 // This is a *non-blocking* read/peek.1828 let result = this.read_from_host(1829 |buf| {1830 if should_peek { stream.peek(buf) } else { stream.read(buf) }1831 },1832 length,1833 buffer_ptr,1834 )?;1835 match result {1836 Err(IoError::HostError(e))1837 if matches!(e.kind(), io::ErrorKind::NotConnected | io::ErrorKind::WouldBlock) =>1838 {1839 // We know that the source is not readable so we need to update it's readiness.1840 socket.io_readiness.borrow_mut().readable = false;1841 this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?;18421843 // On Windows hosts, `recv` can return WSAENOTCONN where EAGAIN or EWOULDBLOCK1844 // would be returned on UNIX-like systems. We thus remap this error to an EWOULDBLOCK.1845 interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into())))1846 }1847 Ok(bytes_read) if !should_peek && bytes_read < length && bytes_read > 0 => {1848 // We had a short read (and were not peeking). (Note that reading 0 bytes is guaranteed1849 // to indicate EOF, and can never happen spuriously, so we have to exclude that case.)1850 // On Unix hosts using the `epoll` and `kqueue` backends, a short read means that the1851 // read buffer is empty. We update the readiness accordingly, which means that next time1852 // we see "readable" we will report an epoll edge. Some applications (e.g. tokio) rely on1853 // this behavior; see1854 // <https://github.com/tokio-rs/tokio/blob/HEAD/tokio/src/io/poll_evented.rs#L190-L210>1855 if cfg!(any(1856 // epoll1857 target_os = "android",1858 target_os = "illumos",1859 target_os = "linux",1860 target_os = "redox",1861 // kqueue1862 target_os = "dragonfly",1863 target_os = "freebsd",1864 target_os = "ios",1865 target_os = "macos",1866 target_os = "netbsd",1867 target_os = "openbsd",1868 target_os = "tvos",1869 target_os = "visionos",1870 target_os = "watchos",1871 )) {1872 socket.io_readiness.borrow_mut().readable = false;1873 this.update_epoll_active_events(socket.clone(), /* force_edge */ false)?;1874 } else {1875 // On hosts which don't use the `epoll` or `kqueue` backends, a short read1876 // doesn't imply an empty read buffer. However, the target we are emulating1877 // might guarantee this behavior. To prevent applications from being stuck on1878 // such targets waiting on a new readiness event, we emit a new edge which still1879 // contains a readable readiness. This should trick the applications into trying1880 // another read which would then return EWOULDBLOCK should it really be empty.1881 // This results in an unrealistic execution but we don't have another way of1882 // finding out whether the read buffer is empty. The "default case" of linux1883 // host and linux target isn't affected by this.1884 this.update_epoll_active_events(socket.clone(), /* force_edge */ true)?;1885 }1886 interp_ok(result)1887 }1888 result => interp_ok(result),1889 }1890 }18911892 // Execute the provided callback function when the socket is either in1893 // [`SocketState::Connected`] or an error occurred.1894 /// If the socket is currently neither in the [`SocketState::Connecting`] nor1895 /// the [`SocketState::Connecting`] state, [`Err`] is returned.1896 /// When the callback function is called with [`Ok`], then we're guaranteed1897 /// that the socket is in the [`SocketState::Connected`] state.1898 ///1899 /// This method internally calls `ensure_not_failed` and thus an unsupported1900 /// error is thrown should `socket` be in [`SocketState::ConnectionFailed`].1901 ///1902 /// This function can optionally also block until either an error occurred or1903 /// the socket reached the [`SocketState::Connected`] state.1904 fn ensure_connected(1905 &mut self,1906 socket: FileDescriptionRef<Socket>,1907 deadline: Option<Deadline>,1908 foreign_name: &'static str,1909 action: DynMachineCallback<'tcx, Result<(), ()>>,1910 ) -> InterpResult<'tcx> {1911 let this = self.eval_context_mut();19121913 let state = socket.state.borrow();1914 match &*state {1915 SocketState::Connecting(_) => { /* fall-through to below */ }1916 SocketState::Connected(_) => {1917 drop(state);1918 return action.call(this, Ok(()));1919 }1920 _ => {1921 drop(state);1922 this.ensure_not_failed(&socket, foreign_name)?;1923 return action.call(this, Err(()));1924 }1925 };19261927 drop(state);19281929 // We're currently connecting. Since the underlying mio socket is non-blocking,1930 // the only way to determine whether we are done connecting is by polling.19311932 this.block_thread_for_io(1933 socket.clone(),1934 BlockingIoInterest::Write,1935 deadline,1936 callback!(1937 @capture<'tcx> {1938 socket: FileDescriptionRef<Socket>,1939 foreign_name: &'static str,1940 action: DynMachineCallback<'tcx, Result<(), ()>>,1941 } |this, kind: UnblockKind| {1942 // Remove the blocking I/O interest for unblocking this thread.1943 this.machine.blocking_io.remove_blocked_thread(socket.id(), this.machine.threads.active_thread());19441945 if UnblockKind::TimedOut == kind {1946 // This then means that the socket is not yet connected.1947 return action.call(this, Err(()))1948 }19491950 // The thread woke up because it's ready, indicating a writeable or error event.19511952 let state = socket.state.borrow();1953 match &*state {1954 SocketState::Connecting(_) => { /* fall-through to below */ },1955 SocketState::Connected(_) => {1956 drop(state);1957 // This can happen because we blocked the thread:1958 // maybe another thread "upgraded" the connection in the meantime.1959 return action.call(this, Ok(()))1960 },1961 _ => {1962 drop(state);1963 // We ensured that we only block when we're currently connecting.1964 // Since this thread just got rescheduled, it could be that another1965 // thread realized that the connection failed and we're thus in1966 // an "invalid state".1967 this.ensure_not_failed(&socket, foreign_name)?;1968 return action.call(this, Err(()))1969 }1970 };19711972 drop(state);19731974 // Set `socket.error` if `socket` currently has an error.1975 this.update_last_error(&socket);19761977 if socket.error.borrow().is_some() {1978 // There was an error during connecting.1979 // It's the program's responsibility to read SO_ERROR itself.1980 return action.call(this, Err(()))1981 }19821983 // There was no error during connecting. Mio advises also reading the peer address1984 // to ensure that socket is actually connected and that it wasn't a spurious wake-up:1985 // <https://docs.rs/mio/latest/mio/net/struct.TcpStream.html#notes>1986 //1987 // Attempting to read the peer address would introduce an edge-case where the1988 // write end of the socket could already be shutdown before it received a1989 // writable event. When we then call [`TcpStream::peer_addr`] we receive an1990 // error. This would need extra state for storing whether the write end was1991 // manually closed using `shutdown`.1992 // Also, tokio doesn't read the peer address and everything seems to be fine,1993 // so we don't do that either:1994 // <https://github.com/tokio-rs/mio/issues/1942#issuecomment-4162607761>1995 // In other words, we are assuming that there will be no spurious1996 // wakeups while establishing the connection.19971998 // The connection is established.19992000 // Temporarily use dummy state to take ownership of the stream.
Findings
✓ No findings reported for this file.