1use std::cell::{Cell, RefCell};2use std::io::Read;3use std::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, SocketAddrV4, SocketAddrV6};4use std::time::Duration;5use std::{io, iter};67use mio::Interest;8use 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::concurrency::blocking_io::InterestReceiver;16use crate::shims::files::{EvalContextExt as _, FdId, FileDescription, FileDescriptionRef};17use crate::shims::unix::UnixFileDescription;18use crate::*;1920#[derive(Debug, PartialEq)]21enum SocketFamily {22 // IPv4 internet protocols23 IPv4,24 // IPv6 internet protocols25 IPv6,26}2728#[derive(Debug)]29enum SocketState {30 /// No syscall after `socket` has been made.31 Initial,32 /// The `bind` syscall has been called on the socket.33 /// This is only reachable from the [`SocketState::Initial`] state.34 Bound(SocketAddr),35 /// The `listen` syscall has been called on the socket.36 /// This is only reachable from the [`SocketState::Bound`] state.37 Listening(TcpListener),38 /// The `connect` syscall has been called and we weren't yet able39 /// to ensure the connection is established. This is only reachable40 /// from the [`SocketState::Initial`] state.41 Connecting(TcpStream),42 /// The `connect` syscall has been called on the socket and43 /// we ensured that the connection is established, or44 /// the socket was created by the `accept` syscall.45 /// For a socket created using the `connect` syscall, this is46 /// only reachable from the [`SocketState::Connecting`] state.47 Connected(TcpStream),48}4950#[derive(Debug)]51struct Socket {52 /// Family of the socket, used to ensure socket only binds/connects to address of53 /// same family.54 family: SocketFamily,55 /// Current state of the inner socket.56 state: RefCell<SocketState>,57 /// Whether this fd is non-blocking or not.58 is_non_block: Cell<bool>,59}6061impl FileDescription for Socket {62 fn name(&self) -> &'static str {63 "socket"64 }6566 fn destroy<'tcx>(67 self,68 _self_id: FdId,69 communicate_allowed: bool,70 _ecx: &mut MiriInterpCx<'tcx>,71 ) -> InterpResult<'tcx, std::io::Result<()>> {72 assert!(communicate_allowed, "cannot have `Socket` with isolation enabled!");7374 interp_ok(Ok(()))75 }7677 fn read<'tcx>(78 self: FileDescriptionRef<Self>,79 communicate_allowed: bool,80 ptr: Pointer,81 len: usize,82 ecx: &mut MiriInterpCx<'tcx>,83 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,84 ) -> InterpResult<'tcx> {85 assert!(communicate_allowed, "cannot have `Socket` with isolation enabled!");8687 let socket = self;8889 ecx.ensure_connected(90 socket.clone(),91 !socket.is_non_block.get(),92 "read",93 callback!(94 @capture<'tcx> {95 socket: FileDescriptionRef<Socket>,96 ptr: Pointer,97 len: usize,98 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,99 } |this, result: Result<(), ()>| {100 if result.is_err() {101 return finish.call(this, Err(LibcError("ENOTCONN")))102 }103104 // Since `read` is the same as `recv` with no flags, we just treat105 // the `read` as a `recv` here.106107 if socket.is_non_block.get() {108 // We have a non-blocking socket and thus don't want to block until109 // we can read.110 let result = this.try_non_block_recv(&socket, ptr, len, /* should_peek */ false)?;111 finish.call(this, result)112 } else {113 // The socket is in blocking mode and thus the read call should block114 // until we can read some bytes from the socket.115 this.block_for_recv(socket, ptr, len, /* should_peek */ false, finish);116 interp_ok(())117 }118 }119 ),120 )121 }122123 fn write<'tcx>(124 self: FileDescriptionRef<Self>,125 communicate_allowed: bool,126 ptr: Pointer,127 len: usize,128 ecx: &mut MiriInterpCx<'tcx>,129 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,130 ) -> InterpResult<'tcx> {131 assert!(communicate_allowed, "cannot have `Socket` with isolation enabled!");132133 let socket = self;134135 ecx.ensure_connected(136 socket.clone(),137 !socket.is_non_block.get(),138 "write",139 callback!(140 @capture<'tcx> {141 socket: FileDescriptionRef<Socket>,142 ptr: Pointer,143 len: usize,144 finish: DynMachineCallback<'tcx, Result<usize, IoError>>145 } |this, result: Result<(), ()>| {146 if result.is_err() {147 return finish.call(this, Err(LibcError("ENOTCONN")))148 }149150 // Since `write` is the same as `send` with no flags, we just treat151 // the `write` as a `send` here.152153 if socket.is_non_block.get() {154 // We have a non-blocking socket and thus don't want to block until155 // we can write.156 let result = this.try_non_block_send(&socket, ptr, len)?;157 return finish.call(this, result)158 } else {159 // The socket is in blocking mode and thus the write call should block160 // until we can write some bytes into the socket.161 this.block_for_send(socket, ptr, len, finish);162 interp_ok(())163 }164 }165 ),166 )167 }168169 fn short_fd_operations(&self) -> bool {170 // Linux de-facto guarantees (or at least, applications like tokio assume [1, 2]) that171 // when a read/write on a streaming socket comes back short, the kernel buffer is172 // empty/full. SO we can't do short reads/writes here.173 //174 // [1]: https://github.com/tokio-rs/tokio/blob/6c03e03898d71eca976ee1ad8481cf112ae722ba/tokio/src/io/poll_evented.rs#L182175 // [2]: https://github.com/tokio-rs/tokio/blob/6c03e03898d71eca976ee1ad8481cf112ae722ba/tokio/src/io/poll_evented.rs#L240176 false177 }178179 fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {180 self181 }182183 fn get_flags<'tcx>(&self, ecx: &mut MiriInterpCx<'tcx>) -> InterpResult<'tcx, Scalar> {184 let mut flags = ecx.eval_libc_i32("O_RDWR");185186 if self.is_non_block.get() {187 flags |= ecx.eval_libc_i32("O_NONBLOCK");188 }189190 interp_ok(Scalar::from_i32(flags))191 }192193 fn set_flags<'tcx>(194 &self,195 mut flag: i32,196 ecx: &mut MiriInterpCx<'tcx>,197 ) -> InterpResult<'tcx, Scalar> {198 let o_nonblock = ecx.eval_libc_i32("O_NONBLOCK");199200 // O_NONBLOCK flag can be set / unset by user.201 if flag & o_nonblock == o_nonblock {202 self.is_non_block.set(true);203 flag &= !o_nonblock;204 } else {205 self.is_non_block.set(false);206 }207208 // Throw error if there is any unsupported flag.209 if flag != 0 {210 throw_unsup_format!("fcntl: only O_NONBLOCK is supported for sockets")211 }212213 interp_ok(Scalar::from_i32(0))214 }215}216217impl UnixFileDescription for Socket {218 fn ioctl<'tcx>(219 &self,220 op: Scalar,221 arg: Option<&OpTy<'tcx>>,222 ecx: &mut MiriInterpCx<'tcx>,223 ) -> InterpResult<'tcx, i32> {224 assert!(ecx.machine.communicate(), "cannot have `Socket` with isolation enabled!");225226 let fionbio = ecx.eval_libc("FIONBIO");227228 if op == fionbio {229 // On these OSes, Rust uses the ioctl, so we trust that it is reasonable and controls230 // the same internal flag as fcntl.231 if !matches!(ecx.tcx.sess.target.os, Os::Linux | Os::Android | Os::MacOs | Os::FreeBsd)232 {233 // FIONBIO cannot be used to change the blocking mode of a socket on solarish targets:234 // <https://github.com/rust-lang/rust/commit/dda5c97675b4f5b1f6fdab64606c8a1f21021b0a>235 // Since there might be more targets which do weird things with this option, we use236 // an allowlist instead of just denying solarish targets.237 throw_unsup_format!(238 "ioctl: setting FIONBIO on sockets is unsupported on target {}",239 ecx.tcx.sess.target.os240 );241 }242243 let Some(value_ptr) = arg else {244 throw_ub_format!("ioctl: setting FIONBIO on sockets requires a third argument");245 };246 let value = ecx.deref_pointer_as(value_ptr, ecx.machine.layouts.i32)?;247 let non_block = ecx.read_scalar(&value)?.to_i32()? != 0;248 self.is_non_block.set(non_block);249 return interp_ok(0);250 }251252 throw_unsup_format!("ioctl: unsupported operation {op:#x} on socket");253 }254}255256impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}257pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {258 /// For more information on the arguments see the socket manpage:259 /// <https://linux.die.net/man/2/socket>260 fn socket(261 &mut self,262 domain: &OpTy<'tcx>,263 type_: &OpTy<'tcx>,264 protocol: &OpTy<'tcx>,265 ) -> InterpResult<'tcx, Scalar> {266 let this = self.eval_context_mut();267268 let domain = this.read_scalar(domain)?.to_i32()?;269 let mut flags = this.read_scalar(type_)?.to_i32()?;270 let protocol = this.read_scalar(protocol)?.to_i32()?;271272 // Reject if isolation is enabled273 if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {274 this.reject_in_isolation("`socket`", reject_with)?;275 return this.set_last_error_and_return_i32(LibcError("EACCES"));276 }277278 let mut is_sock_nonblock = false;279280 // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so281 // if there is anything left at the end, that's an unsupported flag.282 if matches!(283 this.tcx.sess.target.os,284 Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos285 ) {286 // SOCK_NONBLOCK and SOCK_CLOEXEC only exist on Linux, Android, FreeBSD,287 // Solaris, and Illumos targets.288 let sock_nonblock = this.eval_libc_i32("SOCK_NONBLOCK");289 let sock_cloexec = this.eval_libc_i32("SOCK_CLOEXEC");290 if flags & sock_nonblock == sock_nonblock {291 is_sock_nonblock = true;292 flags &= !sock_nonblock;293 }294 if flags & sock_cloexec == sock_cloexec {295 // We don't support `exec` so we can ignore this.296 flags &= !sock_cloexec;297 }298 }299300 let family = if domain == this.eval_libc_i32("AF_INET") {301 SocketFamily::IPv4302 } else if domain == this.eval_libc_i32("AF_INET6") {303 SocketFamily::IPv6304 } else {305 throw_unsup_format!(306 "socket: domain {:#x} is unsupported, only AF_INET and \307 AF_INET6 are allowed.",308 domain309 );310 };311312 if flags != this.eval_libc_i32("SOCK_STREAM") {313 throw_unsup_format!(314 "socket: type {:#x} is unsupported, only SOCK_STREAM, \315 SOCK_CLOEXEC and SOCK_NONBLOCK are allowed",316 flags317 );318 }319 if protocol != 0 && protocol != this.eval_libc_i32("IPPROTO_TCP") {320 throw_unsup_format!(321 "socket: socket protocol {protocol} is unsupported, \322 only IPPROTO_TCP and 0 are allowed"323 );324 }325326 let fds = &mut this.machine.fds;327 let fd = fds.new_ref(Socket {328 family,329 state: RefCell::new(SocketState::Initial),330 is_non_block: Cell::new(is_sock_nonblock),331 });332333 interp_ok(Scalar::from_i32(fds.insert(fd)))334 }335336 fn bind(337 &mut self,338 socket: &OpTy<'tcx>,339 address: &OpTy<'tcx>,340 address_len: &OpTy<'tcx>,341 ) -> InterpResult<'tcx, Scalar> {342 let this = self.eval_context_mut();343344 let socket = this.read_scalar(socket)?.to_i32()?;345 let address = match this.socket_address(address, address_len, "bind")? {346 Ok(addr) => addr,347 Err(e) => return this.set_last_error_and_return_i32(e),348 };349350 // Get the file handle351 let Some(fd) = this.machine.fds.get(socket) else {352 return this.set_last_error_and_return_i32(LibcError("EBADF"));353 };354355 let Some(socket) = fd.downcast::<Socket>() else {356 // Man page specifies to return ENOTSOCK if `fd` is not a socket.357 return this.set_last_error_and_return_i32(LibcError("ENOTSOCK"));358 };359360 assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");361362 let mut state = socket.state.borrow_mut();363364 match *state {365 SocketState::Initial => {366 let address_family = match &address {367 SocketAddr::V4(_) => SocketFamily::IPv4,368 SocketAddr::V6(_) => SocketFamily::IPv6,369 };370371 if socket.family != address_family {372 // Attempted to bind an address from a family that doesn't match373 // the family of the socket.374 let err = if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android) {375 // Linux man page states that `EINVAL` is used when there is an address family mismatch.376 // See <https://man7.org/linux/man-pages/man2/bind.2.html>377 LibcError("EINVAL")378 } else {379 // POSIX man page states that `EAFNOSUPPORT` should be used when there is an address380 // family mismatch.381 // See <https://man7.org/linux/man-pages/man3/bind.3p.html>382 LibcError("EAFNOSUPPORT")383 };384 return this.set_last_error_and_return_i32(err);385 }386387 *state = SocketState::Bound(address);388 }389 SocketState::Connecting(_) | SocketState::Connected(_) =>390 throw_unsup_format!(391 "bind: socket is already connected and binding a392 connected socket is unsupported"393 ),394 SocketState::Bound(_) | SocketState::Listening(_) =>395 throw_unsup_format!(396 "bind: socket is already bound and binding a socket \397 multiple times is unsupported"398 ),399 }400401 interp_ok(Scalar::from_i32(0))402 }403404 fn listen(&mut self, socket: &OpTy<'tcx>, backlog: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {405 let this = self.eval_context_mut();406407 let socket = this.read_scalar(socket)?.to_i32()?;408 // Since the backlog value is just a performance hint we can ignore it.409 let _backlog = this.read_scalar(backlog)?.to_i32()?;410411 // Get the file handle412 let Some(fd) = this.machine.fds.get(socket) else {413 return this.set_last_error_and_return_i32(LibcError("EBADF"));414 };415416 let Some(socket) = fd.downcast::<Socket>() else {417 // Man page specifies to return ENOTSOCK if `fd` is not a socket.418 return this.set_last_error_and_return_i32(LibcError("ENOTSOCK"));419 };420421 assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");422423 let mut state = socket.state.borrow_mut();424425 match *state {426 SocketState::Bound(socket_addr) =>427 match TcpListener::bind(socket_addr) {428 Ok(listener) => *state = SocketState::Listening(listener),429 Err(e) => return this.set_last_error_and_return_i32(e),430 },431 SocketState::Initial => {432 throw_unsup_format!(433 "listen: listening on a socket which isn't bound is unsupported"434 )435 }436 SocketState::Listening(_) => {437 throw_unsup_format!("listen: listening on a socket multiple times is unsupported")438 }439 SocketState::Connecting(_) | SocketState::Connected(_) => {440 throw_unsup_format!("listen: listening on a connected socket is unsupported")441 }442 }443444 interp_ok(Scalar::from_i32(0))445 }446447 /// For more information on the arguments see the accept manpage:448 /// <https://linux.die.net/man/2/accept4>449 fn accept4(450 &mut self,451 socket: &OpTy<'tcx>,452 address: &OpTy<'tcx>,453 address_len: &OpTy<'tcx>,454 flags: Option<&OpTy<'tcx>>,455 // Location where the output scalar is written to.456 dest: &MPlaceTy<'tcx>,457 ) -> InterpResult<'tcx> {458 let this = self.eval_context_mut();459460 let socket = this.read_scalar(socket)?.to_i32()?;461 let address_ptr = this.read_pointer(address)?;462 let address_len_ptr = this.read_pointer(address_len)?;463 let mut flags =464 if let Some(flags) = flags { this.read_scalar(flags)?.to_i32()? } else { 0 };465466 // Get the file handle467 let Some(fd) = this.machine.fds.get(socket) else {468 return this.set_last_error_and_return(LibcError("EBADF"), dest);469 };470471 let Some(socket) = fd.downcast::<Socket>() else {472 // Man page specifies to return ENOTSOCK if `fd` is not a socket.473 return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest);474 };475476 assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");477478 if !matches!(*socket.state.borrow(), SocketState::Listening(_)) {479 throw_unsup_format!(480 "accept4: accepting incoming connections is only allowed when socket is listening"481 )482 };483484 let mut is_client_sock_nonblock = false;485486 // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so487 // if there is anything left at the end, that's an unsupported flag.488 if matches!(489 this.tcx.sess.target.os,490 Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos491 ) {492 // SOCK_NONBLOCK and SOCK_CLOEXEC only exist on Linux, Android, FreeBSD,493 // Solaris, and Illumos targets.494 let sock_nonblock = this.eval_libc_i32("SOCK_NONBLOCK");495 let sock_cloexec = this.eval_libc_i32("SOCK_CLOEXEC");496 if flags & sock_nonblock == sock_nonblock {497 is_client_sock_nonblock = true;498 flags &= !sock_nonblock;499 }500 if flags & sock_cloexec == sock_cloexec {501 // We don't support `exec` so we can ignore this.502 flags &= !sock_cloexec;503 }504 }505506 if flags != 0 {507 throw_unsup_format!(508 "accept4: flag {flags:#x} is unsupported, only SOCK_CLOEXEC \509 and SOCK_NONBLOCK are allowed",510 );511 }512513 if socket.is_non_block.get() {514 // We have a non-blocking socket and thus don't want to block until515 // we can accept an incoming connection.516 match this.try_non_block_accept(517 &socket,518 address_ptr,519 address_len_ptr,520 is_client_sock_nonblock,521 )? {522 Ok(sockfd) => {523 // We need to create the scalar using the destination size since524 // `syscall(SYS_accept4, ...)` returns a long which doesn't match525 // the int returned from the `accept`/`accept4` syscalls.526 // See <https://man7.org/linux/man-pages/man2/syscall.2.html>.527 this.write_scalar(Scalar::from_int(sockfd, dest.layout.size), dest)528 }529 Err(e) => this.set_last_error_and_return(e, dest),530 }531 } else {532 // The socket is in blocking mode and thus the accept call should block533 // until an incoming connection is ready.534 this.block_for_accept(535 socket,536 address_ptr,537 address_len_ptr,538 is_client_sock_nonblock,539 dest.clone(),540 );541 interp_ok(())542 }543 }544545 fn connect(546 &mut self,547 socket: &OpTy<'tcx>,548 address: &OpTy<'tcx>,549 address_len: &OpTy<'tcx>,550 // Location where the output scalar is written to.551 dest: &MPlaceTy<'tcx>,552 ) -> InterpResult<'tcx> {553 let this = self.eval_context_mut();554555 let socket = this.read_scalar(socket)?.to_i32()?;556 let address = match this.socket_address(address, address_len, "connect")? {557 Ok(address) => address,558 Err(e) => return this.set_last_error_and_return(e, dest),559 };560561 // Get the file handle562 let Some(fd) = this.machine.fds.get(socket) else {563 return this.set_last_error_and_return(LibcError("EBADF"), dest);564 };565566 let Some(socket) = fd.downcast::<Socket>() else {567 // Man page specifies to return ENOTSOCK if `fd` is not a socket568 return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest);569 };570571 assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");572573 match &*socket.state.borrow() {574 SocketState::Initial => { /* fall-through to below */ }575 // The socket is already in a connecting state.576 SocketState::Connecting(_) =>577 return this.set_last_error_and_return(LibcError("EALREADY"), dest),578 // We don't return EISCONN for already connected sockets, for which we're579 // sure that the connection is established, since TCP sockets are usually580 // allowed to be connected multiple times.581 _ =>582 throw_unsup_format!(583 "connect: connecting is only supported for sockets which are neither \584 bound, listening nor already connected"585 ),586 }587588 // Mio returns a potentially unconnected stream.589 // We can be ensured that the connection is established when590 // [`TcpStream::take_err`] and [`TcpStream::peer_addr`] both591 // don't return an error after receiving an [`Interest::WRITEABLE`]592 // event on the stream.593 match TcpStream::connect(address) {594 Ok(stream) => *socket.state.borrow_mut() = SocketState::Connecting(stream),595 Err(e) => return this.set_last_error_and_return(e, dest),596 };597598 if socket.is_non_block.get() {599 // We have a non-blocking socket and thus don't want to block until600 // the connection is established.601602 // Since the [`TcpStream::connect`] function of mio hides the EINPROGRESS603 // we just always return EINPROGRESS and check whether the connection succeeded604 // once we want to use the connected socket.605 this.set_last_error_and_return(LibcError("EINPROGRESS"), dest)606 } else {607 // The socket is in blocking mode and thus the connect call should block608 // until the connection with the server is established.609610 let dest = dest.clone();611612 this.ensure_connected(613 socket,614 /* should_wait */ true,615 "connect",616 callback!(617 @capture<'tcx> {618 dest: MPlaceTy<'tcx>619 } |this, result: Result<(), ()>| {620 if result.is_err() {621 this.set_last_error_and_return(LibcError("ENOTCONN"), &dest)622 } else {623 this.write_scalar(Scalar::from_i32(0), &dest)624 }625 }626 ),627 )628 }629 }630631 fn send(632 &mut self,633 socket: &OpTy<'tcx>,634 buffer: &OpTy<'tcx>,635 length: &OpTy<'tcx>,636 flags: &OpTy<'tcx>,637 // Location where the output scalar is written to.638 dest: &MPlaceTy<'tcx>,639 ) -> InterpResult<'tcx> {640 let this = self.eval_context_mut();641642 let socket = this.read_scalar(socket)?.to_i32()?;643 let buffer_ptr = this.read_pointer(buffer)?;644 let size_layout = this.libc_ty_layout("size_t");645 let length: usize =646 this.read_scalar(length)?.to_uint(size_layout.size)?.try_into().unwrap();647 let mut flags = this.read_scalar(flags)?.to_i32()?;648649 // Get the file handle650 let Some(fd) = this.machine.fds.get(socket) else {651 return this.set_last_error_and_return(LibcError("EBADF"), dest);652 };653654 let Some(socket) = fd.downcast::<Socket>() else {655 // Man page specifies to return ENOTSOCK if `fd` is not a socket656 return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest);657 };658659 let mut is_op_non_block = false;660661 // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so662 // if there is anything left at the end, that's an unsupported flag.663 if matches!(664 this.tcx.sess.target.os,665 Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos666 ) {667 // MSG_NOSIGNAL and MSG_DONTWAIT only exist on Linux, Android, FreeBSD,668 // Solaris, and Illumos targets.669 let msg_nosignal = this.eval_libc_i32("MSG_NOSIGNAL");670 let msg_dontwait = this.eval_libc_i32("MSG_DONTWAIT");671 if flags & msg_nosignal == msg_nosignal {672 // This is only needed to ensure that no EPIPE signal is sent when673 // trying to send into a stream which is no longer connected.674 // Since we don't support signals, we can ignore this.675 flags &= !msg_nosignal;676 }677 if flags & msg_dontwait == msg_dontwait {678 flags &= !msg_dontwait;679 is_op_non_block = true;680 }681 }682683 if flags != 0 {684 throw_unsup_format!(685 "send: flag {flags:#x} is unsupported, only MSG_NOSIGNAL and MSG_DONTWAIT are allowed",686 );687 }688689 // If either the operation or the socket is non-blocking, we don't want690 // to wait until the connection is established.691 let should_wait = !is_op_non_block && !socket.is_non_block.get();692 let dest = dest.clone();693694 this.ensure_connected(695 socket.clone(),696 should_wait,697 "send",698 callback!(699 @capture<'tcx> {700 socket: FileDescriptionRef<Socket>,701 flags: i32,702 buffer_ptr: Pointer,703 length: usize,704 is_op_non_block: bool,705 dest: MPlaceTy<'tcx>,706 } |this, result: Result<(), ()>| {707 if result.is_err() {708 return this.set_last_error_and_return(LibcError("ENOTCONN"), &dest)709 }710711 if is_op_non_block || socket.is_non_block.get() {712 // We have a non-blocking operation or a non-blocking socket and713 // thus don't want to block until we can send.714 match this.try_non_block_send(&socket, buffer_ptr, length)? {715 Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),716 Err(e) => this.set_last_error_and_return(e, &dest),717 }718 } else {719 // The socket is in blocking mode and thus the send call should block720 // until we can send some bytes into the socket.721 this.block_for_send(722 socket,723 buffer_ptr,724 length,725 callback!(@capture<'tcx> {726 dest: MPlaceTy<'tcx>727 } |this, result: Result<usize, IoError>| {728 match result {729 Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),730 Err(e) => this.set_last_error_and_return(e, &dest)731 }732 }),733 );734 interp_ok(())735 }736 }737 ),738 )739 }740741 fn recv(742 &mut self,743 socket: &OpTy<'tcx>,744 buffer: &OpTy<'tcx>,745 length: &OpTy<'tcx>,746 flags: &OpTy<'tcx>,747 // Location where the output scalar is written to.748 dest: &MPlaceTy<'tcx>,749 ) -> InterpResult<'tcx> {750 let this = self.eval_context_mut();751752 let socket = this.read_scalar(socket)?.to_i32()?;753 let buffer_ptr = this.read_pointer(buffer)?;754 let size_layout = this.libc_ty_layout("size_t");755 let length: usize =756 this.read_scalar(length)?.to_uint(size_layout.size)?.try_into().unwrap();757 let mut flags = this.read_scalar(flags)?.to_i32()?;758759 // Get the file handle760 let Some(fd) = this.machine.fds.get(socket) else {761 return this.set_last_error_and_return(LibcError("EBADF"), dest);762 };763764 let Some(socket) = fd.downcast::<Socket>() else {765 // Man page specifies to return ENOTSOCK if `fd` is not a socket766 return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest);767 };768769 let mut should_peek = false;770 let mut is_op_non_block = false;771772 // Interpret the flag. Every flag we recognize is "subtracted" from `flags`, so773 // if there is anything left at the end, that's an unsupported flag.774775 let msg_peek = this.eval_libc_i32("MSG_PEEK");776 if flags & msg_peek == msg_peek {777 should_peek = true;778 flags &= !msg_peek;779 }780781 if matches!(this.tcx.sess.target.os, Os::Linux | Os::Android | Os::FreeBsd | Os::Illumos) {782 // MSG_CMSG_CLOEXEC only exists on Linux, Android, FreeBSD,783 // and Illumos targets.784 let msg_cmsg_cloexec = this.eval_libc_i32("MSG_CMSG_CLOEXEC");785 if flags & msg_cmsg_cloexec == msg_cmsg_cloexec {786 // We don't support `exec` so we can ignore this.787 flags &= !msg_cmsg_cloexec;788 }789 }790791 if matches!(792 this.tcx.sess.target.os,793 Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos794 ) {795 // MSG_DONTWAIT only exists on Linux, Android, FreeBSD,796 // Solaris, and Illumos targets.797 let msg_dontwait = this.eval_libc_i32("MSG_DONTWAIT");798 if flags & msg_dontwait == msg_dontwait {799 flags &= !msg_dontwait;800 is_op_non_block = true;801 }802 }803804 if flags != 0 {805 throw_unsup_format!(806 "recv: flag {flags:#x} is unsupported, only MSG_PEEK, MSG_DONTWAIT \807 and MSG_CMSG_CLOEXEC are allowed",808 );809 }810811 // If either the operation or the socket is non-blocking, we don't want812 // to wait until the connection is established.813 let should_wait = !is_op_non_block && !socket.is_non_block.get();814 let dest = dest.clone();815816 this.ensure_connected(817 socket.clone(),818 should_wait,819 "recv",820 callback!(821 @capture<'tcx> {822 socket: FileDescriptionRef<Socket>,823 buffer_ptr: Pointer,824 length: usize,825 should_peek: bool,826 is_op_non_block: bool,827 dest: MPlaceTy<'tcx>,828 } |this, result: Result<(), ()>| {829 if result.is_err() {830 return this.set_last_error_and_return(LibcError("ENOTCONN"), &dest)831 }832833 if is_op_non_block || socket.is_non_block.get() {834 // We have a non-blocking operation or a non-blocking socket and835 // thus don't want to block until we can receive.836 match this.try_non_block_recv(&socket, buffer_ptr, length, should_peek)? {837 Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),838 Err(e) => this.set_last_error_and_return(e, &dest),839 }840 } else {841 // The socket is in blocking mode and thus the receive call should block842 // until we can receive some bytes from the socket.843 this.block_for_recv(844 socket,845 buffer_ptr,846 length,847 should_peek,848 callback!(@capture<'tcx> {849 dest: MPlaceTy<'tcx>850 } |this, result: Result<usize, IoError>| {851 match result {852 Ok(size) => this.write_scalar(Scalar::from_target_isize(size.try_into().unwrap(), this), &dest),853 Err(e) => this.set_last_error_and_return(e, &dest)854 }855 }),856 );857 interp_ok(())858 }859 }860 ),861 )862 }863864 fn setsockopt(865 &mut self,866 socket: &OpTy<'tcx>,867 level: &OpTy<'tcx>,868 option_name: &OpTy<'tcx>,869 option_value: &OpTy<'tcx>,870 option_len: &OpTy<'tcx>,871 ) -> InterpResult<'tcx, Scalar> {872 let this = self.eval_context_mut();873874 let socket = this.read_scalar(socket)?.to_i32()?;875 let level = this.read_scalar(level)?.to_i32()?;876 let option_name = this.read_scalar(option_name)?.to_i32()?;877 let socklen_layout = this.libc_ty_layout("socklen_t");878 let option_len = this.read_scalar(option_len)?.to_int(socklen_layout.size)?;879880 // Get the file handle881 let Some(fd) = this.machine.fds.get(socket) else {882 return this.set_last_error_and_return_i32(LibcError("EBADF"));883 };884885 let Some(_socket) = fd.downcast::<Socket>() else {886 // Man page specifies to return ENOTSOCK if `fd` is not a socket.887 return this.set_last_error_and_return_i32(LibcError("ENOTSOCK"));888 };889890 if level == this.eval_libc_i32("SOL_SOCKET") {891 let opt_so_reuseaddr = this.eval_libc_i32("SO_REUSEADDR");892893 if matches!(this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::NetBsd) {894 // SO_NOSIGPIPE only exists on MacOS, FreeBSD, and NetBSD.895 let opt_so_nosigpipe = this.eval_libc_i32("SO_NOSIGPIPE");896897 if option_name == opt_so_nosigpipe {898 if option_len != 4 {899 // Option value should be C-int which is usually 4 bytes.900 return this.set_last_error_and_return_i32(LibcError("EINVAL"));901 }902 let option_value =903 this.deref_pointer_as(option_value, this.machine.layouts.i32)?;904 let _val = this.read_scalar(&option_value)?.to_i32()?;905 // We entirely ignore this value since we do not support signals anyway.906907 return interp_ok(Scalar::from_i32(0));908 }909 }910911 if option_name == opt_so_reuseaddr {912 if option_len != 4 {913 // Option value should be C-int which is usually 4 bytes.914 return this.set_last_error_and_return_i32(LibcError("EINVAL"));915 }916 let option_value = this.deref_pointer_as(option_value, this.machine.layouts.i32)?;917 let _val = this.read_scalar(&option_value)?.to_i32()?;918 // We entirely ignore this: std always sets REUSEADDR for us, and in the end it's more of a919 // hint to bypass some arbitrary timeout anyway.920 return interp_ok(Scalar::from_i32(0));921 } else {922 throw_unsup_format!(923 "setsockopt: option {option_name:#x} is unsupported for level SOL_SOCKET",924 );925 }926 }927928 throw_unsup_format!(929 "setsockopt: level {level:#x} is unsupported, only SOL_SOCKET is allowed"930 );931 }932933 fn getsockname(934 &mut self,935 socket: &OpTy<'tcx>,936 address: &OpTy<'tcx>,937 address_len: &OpTy<'tcx>,938 ) -> InterpResult<'tcx, Scalar> {939 let this = self.eval_context_mut();940941 let socket = this.read_scalar(socket)?.to_i32()?;942 let address_ptr = this.read_pointer(address)?;943 let address_len_ptr = this.read_pointer(address_len)?;944945 // Get the file handle946 let Some(fd) = this.machine.fds.get(socket) else {947 return this.set_last_error_and_return_i32(LibcError("EBADF"));948 };949950 let Some(socket) = fd.downcast::<Socket>() else {951 // Man page specifies to return ENOTSOCK if `fd` is not a socket.952 return this.set_last_error_and_return_i32(LibcError("ENOTSOCK"));953 };954955 assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");956957 let state = socket.state.borrow();958959 let address = match &*state {960 SocketState::Bound(address) => {961 if address.port() == 0 {962 // The socket is bound to a zero-port which means it gets assigned a random963 // port. Since we don't yet have an underlying socket, we don't know what this964 // random port will be and thus this is unsupported.965 throw_unsup_format!(966 "getsockname: when the port is 0, getting the socket address before \967 calling `listen` or `connect` is unsupported"968 )969 }970971 *address972 }973 SocketState::Listening(listener) =>974 match listener.local_addr() {975 Ok(address) => address,976 Err(e) => return this.set_last_error_and_return_i32(e),977 },978 // For non-bound sockets the POSIX manual says the returned address is unspecified.979 // Often this is 0.0.0.0:0 and thus we set it to this value.980 _ => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)),981 };982983 match this.write_socket_address(&address, address_ptr, address_len_ptr, "getsockname")? {984 Ok(_) => interp_ok(Scalar::from_i32(0)),985 Err(e) => this.set_last_error_and_return_i32(e),986 }987 }988989 fn getpeername(990 &mut self,991 socket: &OpTy<'tcx>,992 address: &OpTy<'tcx>,993 address_len: &OpTy<'tcx>,994 // Location where the output scalar is written to.995 dest: &MPlaceTy<'tcx>,996 ) -> InterpResult<'tcx> {997 let this = self.eval_context_mut();998999 let socket = this.read_scalar(socket)?.to_i32()?;1000 let address_ptr = this.read_pointer(address)?;1001 let address_len_ptr = this.read_pointer(address_len)?;10021003 // Get the file handle1004 let Some(fd) = this.machine.fds.get(socket) else {1005 return this.set_last_error_and_return(LibcError("EBADF"), dest);1006 };10071008 let Some(socket) = fd.downcast::<Socket>() else {1009 // Man page specifies to return ENOTSOCK if `fd` is not a socket.1010 return this.set_last_error_and_return(LibcError("ENOTSOCK"), dest);1011 };10121013 assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");10141015 let dest = dest.clone();10161017 // It's only safe to call [`TcpStream::peer_addr`] after the socket is connected since1018 // UNIX targets should return ENOTCONN when the connection is not yet established.1019 this.ensure_connected(1020 socket.clone(),1021 /* should_wait */ false,1022 "getpeername",1023 callback!(1024 @capture<'tcx> {1025 socket: FileDescriptionRef<Socket>,1026 address_ptr: Pointer,1027 address_len_ptr: Pointer,1028 dest: MPlaceTy<'tcx>,1029 } |this, result: Result<(), ()>| {1030 if result.is_err() {1031 return this.set_last_error_and_return(LibcError("ENOTCONN"), &dest)1032 };10331034 let SocketState::Connected(stream) = &*socket.state.borrow() else {1035 unreachable!()1036 };10371038 let address = match stream.peer_addr() {1039 Ok(address) => address,1040 Err(e) => return this.set_last_error_and_return(e, &dest),1041 };10421043 match this.write_socket_address(1044 &address,1045 address_ptr,1046 address_len_ptr,1047 "getpeername",1048 )? {1049 Ok(_) => this.write_scalar(Scalar::from_i32(0), &dest),1050 Err(e) => this.set_last_error_and_return(e, &dest),1051 }1052 }1053 ),1054 )1055 }10561057 fn shutdown(&mut self, socket: &OpTy<'tcx>, how: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {1058 let this = self.eval_context_mut();10591060 let socket = this.read_scalar(socket)?.to_i32()?;1061 let how = this.read_scalar(how)?.to_i32()?;10621063 // Get the file handle1064 let Some(fd) = this.machine.fds.get(socket) else {1065 return this.set_last_error_and_return_i32(LibcError("EBADF"));1066 };10671068 let Some(socket) = fd.downcast::<Socket>() else {1069 // Man page specifies to return ENOTSOCK if `fd` is not a socket.1070 return this.set_last_error_and_return_i32(LibcError("ENOTSOCK"));1071 };10721073 assert!(this.machine.communicate(), "cannot have `Socket` with isolation enabled!");10741075 let state = socket.state.borrow();10761077 let (SocketState::Connecting(stream) | SocketState::Connected(stream)) = &*state else {1078 return this.set_last_error_and_return_i32(LibcError("ENOTCONN"));1079 };10801081 let shut_rd = this.eval_libc_i32("SHUT_RD");1082 let shut_wr = this.eval_libc_i32("SHUT_WR");1083 let shut_rdwr = this.eval_libc_i32("SHUT_RDWR");10841085 let how = match () {1086 _ if how == shut_rd => Shutdown::Read,1087 _ if how == shut_wr => Shutdown::Write,1088 _ if how == shut_rdwr => Shutdown::Both,1089 // An invalid value was passed to `how`.1090 _ => return this.set_last_error_and_return_i32(LibcError("EINVAL")),1091 };10921093 match stream.shutdown(how) {1094 Ok(_) => interp_ok(Scalar::from_i32(0)),1095 Err(e) => this.set_last_error_and_return_i32(e),1096 }1097 }1098}10991100impl<'tcx> EvalContextPrivExt<'tcx> for crate::MiriInterpCx<'tcx> {}1101trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {1102 /// Attempt to turn an address and length operand into a standard library socket address.1103 ///1104 /// Returns an IO error should the address length not match the address family length.1105 fn socket_address(1106 &self,1107 address: &OpTy<'tcx>,1108 address_len: &OpTy<'tcx>,1109 foreign_name: &'static str,1110 ) -> InterpResult<'tcx, Result<SocketAddr, IoError>> {1111 let this = self.eval_context_ref();11121113 let socklen_layout = this.libc_ty_layout("socklen_t");1114 // We only support address lengths which can be stored in a u64 since the1115 // size of a layout is also stored in a u64.1116 let address_len: u64 =1117 this.read_scalar(address_len)?.to_int(socklen_layout.size)?.try_into().unwrap();11181119 // Initially, treat address as generic sockaddr just to extract the family field.1120 let sockaddr_layout = this.libc_ty_layout("sockaddr");1121 if address_len < sockaddr_layout.size.bytes() {1122 // Address length should be at least as big as the generic sockaddr1123 return interp_ok(Err(LibcError("EINVAL")));1124 }1125 let address = this.deref_pointer_as(address, sockaddr_layout)?;11261127 let family_field = this.project_field_named(&address, "sa_family")?;1128 let family_layout = this.libc_ty_layout("sa_family_t");1129 let family = this.read_scalar(&family_field)?.to_int(family_layout.size)?;11301131 // Depending on the family, decide whether it's IPv4 or IPv6 and use specialized layout1132 // to extract address and port.1133 let socket_addr = if family == this.eval_libc_i32("AF_INET").into() {1134 let sockaddr_in_layout = this.libc_ty_layout("sockaddr_in");1135 if address_len != sockaddr_in_layout.size.bytes() {1136 // Address length should be exactly the length of an IPv4 address.1137 return interp_ok(Err(LibcError("EINVAL")));1138 }1139 let address = address.transmute(sockaddr_in_layout, this)?;11401141 let port_field = this.project_field_named(&address, "sin_port")?;1142 // Read bytes and treat them as big endian since port is stored in network byte order.1143 let port_bytes: [u8; 2] = this1144 .read_bytes_ptr_strip_provenance(port_field.ptr(), Size::from_bytes(2))?1145 .try_into()1146 .unwrap();1147 let port = u16::from_be_bytes(port_bytes);11481149 let addr_field = this.project_field_named(&address, "sin_addr")?;1150 let s_addr_field = this.project_field_named(&addr_field, "s_addr")?;1151 // Read bytes and treat them as big endian since address is stored in network byte order.1152 let addr_bytes: [u8; 4] = this1153 .read_bytes_ptr_strip_provenance(s_addr_field.ptr(), Size::from_bytes(4))?1154 .try_into()1155 .unwrap();1156 let addr_bits = u32::from_be_bytes(addr_bytes);11571158 SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from_bits(addr_bits), port))1159 } else if family == this.eval_libc_i32("AF_INET6").into() {1160 let sockaddr_in6_layout = this.libc_ty_layout("sockaddr_in6");1161 if address_len != sockaddr_in6_layout.size.bytes() {1162 // Address length should be exactly the length of an IPv6 address.1163 return interp_ok(Err(LibcError("EINVAL")));1164 }1165 // We cannot transmute since the `sockaddr_in6` layout is bigger than the `sockaddr` layout.1166 let address = address.offset(Size::ZERO, sockaddr_in6_layout, this)?;11671168 let port_field = this.project_field_named(&address, "sin6_port")?;1169 // Read bytes and treat them as big endian since port is stored in network byte order.1170 let port_bytes: [u8; 2] = this1171 .read_bytes_ptr_strip_provenance(port_field.ptr(), Size::from_bytes(2))?1172 .try_into()1173 .unwrap();1174 let port = u16::from_be_bytes(port_bytes);11751176 let addr_field = this.project_field_named(&address, "sin6_addr")?;1177 let s_addr_field = this1178 .project_field_named(&addr_field, "s6_addr")?1179 .transmute(this.machine.layouts.u128, this)?;1180 // Read bytes and treat them as big endian since address is stored in network byte order.1181 let addr_bytes: [u8; 16] = this1182 .read_bytes_ptr_strip_provenance(s_addr_field.ptr(), Size::from_bytes(16))?1183 .try_into()1184 .unwrap();1185 let addr_bits = u128::from_be_bytes(addr_bytes);11861187 let flowinfo_field = this.project_field_named(&address, "sin6_flowinfo")?;1188 // flowinfo doesn't get the big endian treatment as this field is stored in native byte order1189 // and not in network byte order.1190 let flowinfo = this.read_scalar(&flowinfo_field)?.to_u32()?;11911192 let scope_id_field = this.project_field_named(&address, "sin6_scope_id")?;1193 // scope_id doesn't get the big endian treatment as this field is stored in native byte order1194 // and not in network byte order.1195 let scope_id = this.read_scalar(&scope_id_field)?.to_u32()?;11961197 SocketAddr::V6(SocketAddrV6::new(1198 Ipv6Addr::from_bits(addr_bits),1199 port,1200 flowinfo,1201 scope_id,1202 ))1203 } else {1204 // Socket of other types shouldn't be created in a first place and1205 // thus also no address family of another type should be supported.1206 throw_unsup_format!(1207 "{foreign_name}: address family {family:#x} is unsupported, \1208 only AF_INET and AF_INET6 are allowed"1209 );1210 };12111212 interp_ok(Ok(socket_addr))1213 }12141215 /// Attempt to write a standard library socket address into a pointer.1216 ///1217 /// The `address_len_ptr` parameter serves both as input and output parameter.1218 /// On input, it points to the size of the buffer `address_ptr` points to, and1219 /// on output it points to the non-truncated size of the written address in the1220 /// buffer pointed to by `address_ptr`.1221 ///1222 /// If the address buffer doesn't fit the whole address, the address is truncated to not1223 /// overflow the buffer.1224 fn write_socket_address(1225 &mut self,1226 address: &SocketAddr,1227 address_ptr: Pointer,1228 address_len_ptr: Pointer,1229 foreign_name: &'static str,1230 ) -> InterpResult<'tcx, Result<(), IoError>> {1231 let this = self.eval_context_mut();12321233 if address_ptr == Pointer::null() || address_len_ptr == Pointer::null() {1234 // The POSIX man page doesn't account for the cases where the `address_ptr` or1235 // `address_len_ptr` could be null pointers. Thus, this behavior is undefined!1236 throw_ub_format!(1237 "{foreign_name}: writing a socket address but the address or the length pointer is a null pointer"1238 )1239 }12401241 let socklen_layout = this.libc_ty_layout("socklen_t");1242 let address_buffer_len_place = this.ptr_to_mplace(address_len_ptr, socklen_layout);1243 // We only support buffer lengths which can be stored in a u64 since the1244 // size of a layout in bytes is also stored in a u64.1245 let address_buffer_len: u64 = this1246 .read_scalar(&address_buffer_len_place)?1247 .to_int(socklen_layout.size)?1248 .try_into()1249 .unwrap();12501251 let (address_buffer, address_layout) = match address {1252 SocketAddr::V4(address) => {1253 // IPv4 address bytes; already stored in network byte order.1254 let address_bytes = address.ip().octets();1255 // Port needs to be manually turned into network byte order.1256 let port = address.port().to_be();12571258 let sockaddr_in_layout = this.libc_ty_layout("sockaddr_in");1259 // Allocate new buffer on the stack with the `sockaddr_in` layout.1260 // We need a temporary buffer as `address_ptr` might not point to a large enough1261 // buffer, in which case we have to truncate.1262 let address_buffer = this.allocate(sockaddr_in_layout, MemoryKind::Stack)?;1263 // Zero the whole buffer as some libc targets have additional fields which we fill1264 // with zero bytes (just like the standard library does it).1265 this.write_bytes_ptr(1266 address_buffer.ptr(),1267 iter::repeat_n(0, address_buffer.layout.size.bytes_usize()),1268 )?;12691270 let sin_family_field = this.project_field_named(&address_buffer, "sin_family")?;1271 // We cannot simply write the `AF_INET` scalar into the `sin_family_field` because on most1272 // systems the field has a layout of 16-bit whilst the scalar has a size of 32-bit.1273 // Since the `AF_INET` constant is chosen such that it can safely be converted into1274 // a 16-bit integer, we use the following logic to get a scalar of the right size.1275 let af_inet = this.eval_libc("AF_INET");1276 let address_family =1277 Scalar::from_int(af_inet.to_int(af_inet.size())?, sin_family_field.layout.size);1278 this.write_scalar(address_family, &sin_family_field)?;12791280 let sin_port_field = this.project_field_named(&address_buffer, "sin_port")?;1281 // Write the port in target native endianness bytes as we already converted it1282 // to big endian above.1283 this.write_bytes_ptr(sin_port_field.ptr(), port.to_ne_bytes())?;12841285 let sin_addr_field = this.project_field_named(&address_buffer, "sin_addr")?;1286 let s_addr_field = this.project_field_named(&sin_addr_field, "s_addr")?;1287 this.write_bytes_ptr(s_addr_field.ptr(), address_bytes)?;12881289 (address_buffer, sockaddr_in_layout)1290 }1291 SocketAddr::V6(address) => {1292 // IPv6 address bytes; already stored in network byte order.1293 let address_bytes = address.ip().octets();1294 // Port needs to be manually turned into network byte order.1295 let port = address.port().to_be();1296 // Flowinfo is stored in native byte order.1297 let flowinfo = address.flowinfo();1298 // Scope id is stored in native byte order.1299 let scope_id = address.scope_id();13001301 let sockaddr_in6_layout = this.libc_ty_layout("sockaddr_in6");1302 // Allocate new buffer on the stack with the `sockaddr_in6` layout.1303 // We need a temporary buffer as `address_ptr` might not point to a large enough1304 // buffer, in which case we have to truncate.1305 let address_buffer = this.allocate(sockaddr_in6_layout, MemoryKind::Stack)?;1306 // Zero the whole buffer as some libc targets have additional fields which we fill1307 // with zero bytes (just like the standard library does it).1308 this.write_bytes_ptr(1309 address_buffer.ptr(),1310 iter::repeat_n(0, address_buffer.layout.size.bytes_usize()),1311 )?;13121313 let sin6_family_field = this.project_field_named(&address_buffer, "sin6_family")?;1314 // We cannot simply write the `AF_INET6` scalar into the `sin6_family_field` because on most1315 // systems the field has a layout of 16-bit whilst the scalar has a size of 32-bit.1316 // Since the `AF_INET6` constant is chosen such that it can safely be converted into1317 // a 16-bit integer, we use the following logic to get a scalar of the right size.1318 let af_inet6 = this.eval_libc("AF_INET6");1319 let address_family = Scalar::from_int(1320 af_inet6.to_int(af_inet6.size())?,1321 sin6_family_field.layout.size,1322 );1323 this.write_scalar(address_family, &sin6_family_field)?;13241325 let sin6_port_field = this.project_field_named(&address_buffer, "sin6_port")?;1326 // Write the port in target native endianness bytes as we already converted it1327 // to big endian above.1328 this.write_bytes_ptr(sin6_port_field.ptr(), port.to_ne_bytes())?;13291330 let sin6_flowinfo_field =1331 this.project_field_named(&address_buffer, "sin6_flowinfo")?;1332 this.write_scalar(Scalar::from_u32(flowinfo), &sin6_flowinfo_field)?;13331334 let sin6_scope_id_field =1335 this.project_field_named(&address_buffer, "sin6_scope_id")?;1336 this.write_scalar(Scalar::from_u32(scope_id), &sin6_scope_id_field)?;13371338 let sin6_addr_field = this.project_field_named(&address_buffer, "sin6_addr")?;1339 let s6_addr_field = this.project_field_named(&sin6_addr_field, "s6_addr")?;1340 this.write_bytes_ptr(s6_addr_field.ptr(), address_bytes)?;13411342 (address_buffer, sockaddr_in6_layout)1343 }1344 };13451346 // Copy the truncated address into the pointer pointed to by `address_ptr`.1347 this.mem_copy(1348 address_buffer.ptr(),1349 address_ptr,1350 // Truncate the address to fit the provided buffer.1351 address_layout.size.min(Size::from_bytes(address_buffer_len)),1352 // The buffers are guaranteed to not overlap since the `address_buffer`1353 // was just newly allocated on the stack.1354 true,1355 )?;1356 // Deallocate the address buffer as it was only needed to construct the address and1357 // copy it into the buffer pointed to by `address_ptr`.1358 this.deallocate_ptr(address_buffer.ptr(), None, MemoryKind::Stack)?;1359 // Size of the non-truncated address.1360 let address_len = address_layout.size.bytes();13611362 this.write_scalar(1363 Scalar::from_uint(address_len, socklen_layout.size),1364 &address_buffer_len_place,1365 )?;13661367 interp_ok(Ok(()))1368 }13691370 /// Block the thread until there's an incoming connection or an error occurred.1371 ///1372 /// This recursively calls itself should the operation still block for some reason.1373 ///1374 /// **Note**: This function is only safe to call when having previously ensured1375 /// that the socket is in [`SocketState::Listening`].1376 fn block_for_accept(1377 &mut self,1378 socket: FileDescriptionRef<Socket>,1379 address_ptr: Pointer,1380 address_len_ptr: Pointer,1381 is_client_sock_nonblock: bool,1382 dest: MPlaceTy<'tcx>,1383 ) {1384 let this = self.eval_context_mut();1385 this.block_thread_for_io(1386 socket.clone(),1387 Interest::READABLE,1388 None,1389 callback!(@capture<'tcx> {1390 address_ptr: Pointer,1391 address_len_ptr: Pointer,1392 is_client_sock_nonblock: bool,1393 socket: FileDescriptionRef<Socket>,1394 dest: MPlaceTy<'tcx>,1395 } |this, kind: UnblockKind| {1396 assert_eq!(kind, UnblockKind::Ready);13971398 match this.try_non_block_accept(&socket, address_ptr, address_len_ptr, is_client_sock_nonblock)? {1399 Ok(sockfd) => {1400 // We need to create the scalar using the destination size since1401 // `syscall(SYS_accept4, ...)` returns a long which doesn't match1402 // the int returned from the `accept`/`accept4` syscalls.1403 // See <https://man7.org/linux/man-pages/man2/syscall.2.html>.1404 this.write_scalar(Scalar::from_int(sockfd, dest.layout.size), &dest)1405 },1406 Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {1407 // We need to block the thread again as it would still block.1408 this.block_for_accept(socket, address_ptr, address_len_ptr, is_client_sock_nonblock, dest);1409 interp_ok(())1410 }1411 Err(e) => this.set_last_error_and_return(e, &dest),1412 }1413 }),1414 );1415 }14161417 /// Attempt to accept an incoming connection on the listening socket in a1418 /// non-blocking manner.1419 ///1420 /// **Note**: This function is only safe to call when having previously ensured1421 /// that the socket is in [`SocketState::Listening`].1422 fn try_non_block_accept(1423 &mut self,1424 socket: &FileDescriptionRef<Socket>,1425 address_ptr: Pointer,1426 address_len_ptr: Pointer,1427 is_client_sock_nonblock: bool,1428 ) -> InterpResult<'tcx, Result<i32, IoError>> {1429 let this = self.eval_context_mut();14301431 let state = socket.state.borrow();1432 let SocketState::Listening(listener) = &*state else {1433 panic!(1434 "try_non_block_accept must only be called when socket is in `SocketState::Listening`"1435 )1436 };14371438 let (stream, addr) = match listener.accept() {1439 Ok(peer) => peer,1440 Err(e) => return interp_ok(Err(IoError::HostError(e))),1441 };14421443 let family = match addr {1444 SocketAddr::V4(_) => SocketFamily::IPv4,1445 SocketAddr::V6(_) => SocketFamily::IPv6,1446 };14471448 if address_ptr != Pointer::null() {1449 // We only attempt a write if the address pointer is not a null pointer.1450 // If the address pointer is a null pointer the user isn't interested in the1451 // address and we don't need to write anything.1452 if let Err(e) =1453 this.write_socket_address(&addr, address_ptr, address_len_ptr, "accept4")?1454 {1455 return interp_ok(Err(e));1456 };1457 }14581459 let fd = this.machine.fds.new_ref(Socket {1460 family,1461 state: RefCell::new(SocketState::Connected(stream)),1462 is_non_block: Cell::new(is_client_sock_nonblock),1463 });1464 let sockfd = this.machine.fds.insert(fd);1465 interp_ok(Ok(sockfd))1466 }14671468 /// Block the thread until we can send bytes into the connected socket1469 /// or an error occurred.1470 ///1471 /// This recursively calls itself should the operation still block for some reason.1472 ///1473 /// **Note**: This function is only safe to call when having previously ensured1474 /// that the socket is in [`SocketState::Connected`].1475 fn block_for_send(1476 &mut self,1477 socket: FileDescriptionRef<Socket>,1478 buffer_ptr: Pointer,1479 length: usize,1480 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,1481 ) {1482 let this = self.eval_context_mut();1483 this.block_thread_for_io(1484 socket.clone(),1485 Interest::WRITABLE,1486 None,1487 callback!(@capture<'tcx> {1488 socket: FileDescriptionRef<Socket>,1489 buffer_ptr: Pointer,1490 length: usize,1491 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,1492 } |this, kind: UnblockKind| {1493 assert_eq!(kind, UnblockKind::Ready);14941495 match this.try_non_block_send(&socket, buffer_ptr, length)? {1496 Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {1497 this.block_for_send(socket, buffer_ptr, length, finish);1498 interp_ok(())1499 },1500 result => finish.call(this, result)1501 }1502 }),1503 );1504 }15051506 /// Attempt to send bytes into the connected socket in a non-blocking manner.1507 ///1508 /// **Note**: This function is only safe to call when having previously ensured1509 /// that the socket is in [`SocketState::Connected`].1510 fn try_non_block_send(1511 &mut self,1512 socket: &FileDescriptionRef<Socket>,1513 buffer_ptr: Pointer,1514 length: usize,1515 ) -> InterpResult<'tcx, Result<usize, IoError>> {1516 let this = self.eval_context_mut();15171518 let SocketState::Connected(stream) = &mut *socket.state.borrow_mut() else {1519 panic!("try_non_block_send must only be called when the socket is connected")1520 };15211522 // This is a *non-blocking* write.1523 let result = this.write_to_host(stream, length, buffer_ptr)?;1524 // FIXME: When the host does a short write, we should emit an epoll edge -- at least for targets for which tokio assumes no short writes:1525 // <https://github.com/tokio-rs/tokio/blob/6c03e03898d71eca976ee1ad8481cf112ae722ba/tokio/src/io/poll_evented.rs#L240>1526 match result {1527 Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::NotConnected => {1528 // On Windows hosts, `send` can return WSAENOTCONN where EAGAIN or EWOULDBLOCK1529 // would be returned on UNIX-like systems. We thus remap this error to an EWOULDBLOCK.1530 interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into())))1531 }1532 Err(IoError::HostError(e))1533 if cfg!(windows)1534 && matches!(e.raw_os_error(), Some(/* WSAESHUTDOWN error code */ 10058)) =>1535 {1536 // FIXME: This is a temporary workaround for handling WSAESHUTDOWN errors1537 // on Windows. A discussion on how those errors should be handled can be found here:1538 // <https://rust-lang.zulipchat.com/#narrow/channel/219381-t-libs/topic/WSAESHUTDOWN.20error.20on.20Windows/near/591883531>1539 interp_ok(Err(IoError::HostError(io::ErrorKind::BrokenPipe.into())))1540 }1541 result => interp_ok(result),1542 }1543 }15441545 /// Block the thread until we can receive bytes from the connected socket1546 /// or an error occurred.1547 ///1548 /// This recursively calls itself should the operation still block for some reason.1549 ///1550 /// **Note**: This function is only safe to call when having previously ensured1551 /// that the socket is in [`SocketState::Connected`].1552 fn block_for_recv(1553 &mut self,1554 socket: FileDescriptionRef<Socket>,1555 buffer_ptr: Pointer,1556 length: usize,1557 should_peek: bool,1558 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,1559 ) {1560 let this = self.eval_context_mut();1561 this.block_thread_for_io(1562 socket.clone(),1563 Interest::READABLE,1564 None,1565 callback!(@capture<'tcx> {1566 socket: FileDescriptionRef<Socket>,1567 buffer_ptr: Pointer,1568 length: usize,1569 should_peek: bool,1570 finish: DynMachineCallback<'tcx, Result<usize, IoError>>,1571 } |this, kind: UnblockKind| {1572 assert_eq!(kind, UnblockKind::Ready);15731574 match this.try_non_block_recv(&socket, buffer_ptr, length, should_peek)? {1575 Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::WouldBlock => {1576 // We need to block the thread again as it would still block.1577 this.block_for_recv(socket, buffer_ptr, length, should_peek, finish);1578 interp_ok(())1579 },1580 result => finish.call(this, result)1581 }1582 }),1583 );1584 }15851586 /// Attempt to receive bytes from the connected socket in a non-blocking manner.1587 ///1588 /// **Note**: This function is only safe to call when having previously ensured1589 /// that the socket is in [`SocketState::Connected`].1590 fn try_non_block_recv(1591 &mut self,1592 socket: &FileDescriptionRef<Socket>,1593 buffer_ptr: Pointer,1594 length: usize,1595 should_peek: bool,1596 ) -> InterpResult<'tcx, Result<usize, IoError>> {1597 let this = self.eval_context_mut();15981599 let SocketState::Connected(stream) = &mut *socket.state.borrow_mut() else {1600 panic!("try_non_block_recv must only be called when the socket is connected")1601 };16021603 // This is a *non-blocking* read/peek.1604 let result = this.read_from_host(1605 |buf| {1606 if should_peek { stream.peek(buf) } else { stream.read(buf) }1607 },1608 length,1609 buffer_ptr,1610 )?;1611 // FIXME: When the host does a short read, we should emit an epoll edge -- at least for targets for which tokio assumes no short reads:1612 // <https://github.com/tokio-rs/tokio/blob/6c03e03898d71eca976ee1ad8481cf112ae722ba/tokio/src/io/poll_evented.rs#L182>1613 match result {1614 Err(IoError::HostError(e)) if e.kind() == io::ErrorKind::NotConnected => {1615 // On Windows hosts, `recv` can return WSAENOTCONN where EAGAIN or EWOULDBLOCK1616 // would be returned on UNIX-like systems. We thus remap this error to an EWOULDBLOCK.1617 interp_ok(Err(IoError::HostError(io::ErrorKind::WouldBlock.into())))1618 }1619 result => interp_ok(result),1620 }1621 }16221623 // Execute the provided callback function when the socket is either in1624 // [`SocketState::Connected`] or an error occurred.1625 /// If the socket is currently neither in the [`SocketState::Connecting`] nor1626 /// the [`SocketState::Connecting`] state, an ENOTCONN error is returned.1627 /// When the callback function is called with `Ok(_)`, then we're guaranteed1628 /// that the socket is in the [`SocketState::Connected`] state.1629 ///1630 /// This function can optionally also block until either an error occurred or1631 /// the socket reached the [`SocketState::Connected`] state.1632 fn ensure_connected(1633 &mut self,1634 socket: FileDescriptionRef<Socket>,1635 should_wait: bool,1636 foreign_name: &'static str,1637 action: DynMachineCallback<'tcx, Result<(), ()>>,1638 ) -> InterpResult<'tcx> {1639 let this = self.eval_context_mut();16401641 let state = socket.state.borrow();1642 match &*state {1643 SocketState::Connecting(_) => { /* fall-through to below */ }1644 SocketState::Connected(_) => {1645 drop(state);1646 return action.call(this, Ok(()));1647 }1648 _ => {1649 drop(state);1650 return action.call(this, Err(()));1651 }1652 };16531654 drop(state);16551656 // We're currently connecting. Since the underlying mio socket is non-blocking,1657 // the only way to determine whether we are done connecting is by polling.1658 // If we should wait until the connection is established, the timeout is `None`.1659 // Otherwise, we use a zero duration timeout, i.e. we return immediately1660 // (but we still go through the scheduler once -- which is fine).1661 let timeout = if should_wait {1662 None1663 } else {1664 Some((TimeoutClock::Monotonic, TimeoutAnchor::Absolute, Duration::ZERO))1665 };16661667 this.block_thread_for_io(1668 socket.clone(),1669 Interest::WRITABLE,1670 timeout,1671 callback!(1672 @capture<'tcx> {1673 socket: FileDescriptionRef<Socket>,1674 should_wait: bool,1675 foreign_name: &'static str,1676 action: DynMachineCallback<'tcx, Result<(), ()>>,1677 } |this, kind: UnblockKind| {1678 if UnblockKind::TimedOut == kind {1679 // We can only time out when `should_wait` is false.1680 // This then means that the socket is not yet connected.1681 assert!(!should_wait);1682 this.machine.blocking_io.deregister(socket.id(), InterestReceiver::UnblockThread(this.active_thread()));1683 return action.call(this, Err(()))1684 }16851686 // The thread woke up because it's ready, indicating a writeable or error event.16871688 let mut state = socket.state.borrow_mut();1689 let stream = match &*state {1690 SocketState::Connecting(stream) => stream,1691 SocketState::Connected(_) => {1692 drop(state);1693 // This can happen because we blocked the thread:1694 // maybe another thread "upgraded" the connection in the meantime.1695 return action.call(this, Ok(()))1696 },1697 _ => {1698 drop(state);1699 // We ensured that we only block when we're currently connecting.1700 // Since this thread just got rescheduled, it could be that another1701 // thread realized that the connection failed and we're thus in1702 // an "invalid state".1703 return action.call(this, Err(()))1704 }1705 };17061707 // Manually check whether there were any errors since calling `connect`.1708 if let Ok(Some(_)) = stream.take_error() {1709 // There was an error during connecting and thus we1710 // return ENOTCONN. It's the program's responsibility1711 // to read SO_ERROR itself.1712 //1713 // Go back to initial state since the only way of getting into the1714 // `Connecting` state is from the `Initial` state and at this point1715 // we know that the connection won't be established anymore.1716 //1717 // FIXME: We're currently just dropping the error information. Eventually1718 // we'll have to store it so that it can be recovered by the user.1719 *state = SocketState::Initial;1720 drop(state);1721 return action.call(this, Err(()))1722 }17231724 // There was no error during connecting. We still need to ensure that1725 // the wakeup wasn't spurious. We do this by attempting to read the1726 // peer address of the socket (following the advice given by mio):1727 // <https://docs.rs/mio/latest/mio/net/struct.TcpStream.html#notes>17281729 match stream.peer_addr() {1730 Ok(_) => { /* fall-through to below */},1731 Err(e) if matches!(e.kind(), io::ErrorKind::NotConnected | io::ErrorKind::InProgress) => {1732 // We received a spurious wakeup from the OS. This should be considered an OS bug:1733 // <https://github.com/tokio-rs/mio/issues/1942#issuecomment-4169378308>1734 panic!("{foreign_name}: received writable event from OS but socket is not yet connected")1735 },1736 Err(_) => {1737 // For all other errors the socket is connected. Since we're not interested in the1738 // peer address and only want to know whether the socket is connected, we can ignore1739 // the error and continue.1740 }1741 }17421743 // The connection is established.17441745 // Temporarily use dummy state to take ownership of the stream.1746 let SocketState::Connecting(stream) = std::mem::replace(&mut*state, SocketState::Initial) else {1747 // At the start of the function we ensured that we're currently connecting.1748 unreachable!()1749 };1750 *state = SocketState::Connected(stream);1751 drop(state);1752 action.call(this, Ok(()))1753 }1754 ),1755 );17561757 interp_ok(())1758 }1759}17601761impl VisitProvenance for FileDescriptionRef<Socket> {1762 // A socket doesn't contain any references to machine memory1763 // and thus we don't need to propagate the visit.1764 fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {}1765}17661767impl WithSource for Socket {1768 fn with_source(&self, f: &mut dyn FnMut(&mut dyn Source) -> io::Result<()>) -> io::Result<()> {1769 let mut state = self.state.borrow_mut();1770 match &mut *state {1771 SocketState::Listening(listener) => f(listener),1772 SocketState::Connecting(stream) | SocketState::Connected(stream) => f(stream),1773 // We never try adding a socket which is not backed by a real socket to the poll registry.1774 _ => unreachable!(),1775 }1776 }1777}