diff --git a/src/diagnostics.rs b/src/diagnostics.rs index 14beafc6a3..95b5670636 100644 --- a/src/diagnostics.rs +++ b/src/diagnostics.rs @@ -144,6 +144,7 @@ pub enum NonHaltingDiagnostic { effective_failure_ordering: AtomicReadOrd, }, FileInProcOpened, + ConnectingSocketGetsockname, } /// Level of Miri specific diagnostics @@ -650,6 +651,8 @@ impl<'tcx> MiriMachine<'tcx> { | WeakMemoryOutdatedLoad { .. } => ("tracking was triggered here".to_string(), DiagLevel::Note), FileInProcOpened => ("open a file in `/proc`".to_string(), DiagLevel::Warning), + ConnectingSocketGetsockname => + ("Called `getsockname` on connecting socket".to_string(), DiagLevel::Warning), }; let title = match &e { @@ -698,12 +701,22 @@ impl<'tcx> MiriMachine<'tcx> { format!("GenMC currently does not model the failure ordering for `compare_exchange`. {was_upgraded_msg}. Miri with GenMC might miss bugs related to this memory access.") } FileInProcOpened => format!("files in `/proc` can bypass the Abstract Machine and might not work properly in Miri"), + ConnectingSocketGetsockname => format!("connecting sockets return unspecified socket addresses on Windows hosts") }; let notes = match &e { ProgressReport { block_count } => { vec![note!("so far, {block_count} basic blocks have been executed")] } + ConnectingSocketGetsockname => + vec![ + note!( + "Windows hosts do not provide `local_addr` information while the socket is still connecting, which might break the assumptions of code compiled for Unix targets" + ), + note!( + "an unspecified socket address (e.g. `0.0.0.0:0`) will be returned instead" + ), + ], _ => vec![], }; diff --git a/src/shims/unix/socket.rs b/src/shims/unix/socket.rs index 99a6378704..7f947ca085 100644 --- a/src/shims/unix/socket.rs +++ b/src/shims/unix/socket.rs @@ -1,6 +1,7 @@ use std::cell::{Cell, RefCell}; use std::io::Read; use std::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, SocketAddrV4, SocketAddrV6}; +use std::sync::atomic::AtomicBool; use std::time::Duration; use std::{io, iter}; @@ -975,9 +976,27 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { Ok(address) => address, Err(e) => return this.set_last_error_and_return_i32(e), }, + SocketState::Connecting(stream) | SocketState::Connected(stream) => { + if cfg!(windows) && matches!(&*state, SocketState::Connecting(_)) { + // FIXME: On Windows hosts `TcpStream::local_addr` returns `0.0.0.0:0` whilst + // the socket is connecting: + // + // This is problematic because UNIX targets could expect a real local address even + // for a connecting non-blocking socket. + + static DEDUP: AtomicBool = AtomicBool::new(false); + if !DEDUP.swap(true, std::sync::atomic::Ordering::Relaxed) { + this.emit_diagnostic(NonHaltingDiagnostic::ConnectingSocketGetsockname); + } + } + match stream.local_addr() { + Ok(address) => address, + Err(e) => return this.set_last_error_and_return_i32(e), + } + } // For non-bound sockets the POSIX manual says the returned address is unspecified. // Often this is 0.0.0.0:0 and thus we set it to this value. - _ => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)), + SocketState::Initial => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)), }; match this.write_socket_address(&address, address_ptr, address_len_ptr, "getsockname")? { diff --git a/tests/pass-dep/libc/libc-socket-no-blocking.rs b/tests/pass-dep/libc/libc-socket-no-blocking.rs index f43847733b..40c821e858 100644 --- a/tests/pass-dep/libc/libc-socket-no-blocking.rs +++ b/tests/pass-dep/libc/libc-socket-no-blocking.rs @@ -51,6 +51,8 @@ fn main() { test_send_recv_dontwait(); test_write_read_nonblock(); + test_getsockname_ipv4_connect_nonblock(); + test_getpeername_ipv4_nonblock(); test_getpeername_ipv4_nonblock_no_peer(); } @@ -574,6 +576,52 @@ fn test_write_read_nonblock() { server_thread.join().unwrap(); } +/// Test the `getsockname` syscall on a connecting IPv4 socket +/// which is not connected. +fn test_getsockname_ipv4_connect_nonblock() { + let client_sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; + + unsafe { + // Change client socket to be non-blocking. + errno_check(libc::fcntl(client_sockfd, libc::F_SETFL, libc::O_NONBLOCK)); + } + + // We cannot attempt to connect to a localhost address because + // it could be the case that a socket from another test is + // currently listening on `localhost:12321` because we bind to + // random ports everywhere. For `192.0.2.1` we know that nothing is + // listening because it's a blackhole address: + // + // The port `12321` is just a random non-zero port because Windows + // and Apple hosts return EADDRNOTAVAIL when attempting to connect to + // a zero port. + let addr = net::sock_addr_ipv4([192, 0, 2, 1], 12321); + + // Non-blocking connect should fail with EINPROGRESS. + let err = net::connect_ipv4(client_sockfd, addr).unwrap_err(); + assert_eq!(err.kind(), ErrorKind::InProgress); + + let (_, sock_addr) = net::sockname_ipv4(|storage, len| unsafe { + libc::getsockname(client_sockfd, storage, len) + }) + .unwrap(); + + // The unspecified IPv4 address. + let addr = net::sock_addr_ipv4([0, 0, 0, 0], 0); + + assert_eq!(addr.sin_family, sock_addr.sin_family); + if cfg!(windows_host) { + // On Windows hosts a connecting socket is bound to the unspecified address. + assert_eq!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); + } else { + // On UNIX hosts a connecting socket is bound to any local interface address + // but not the unspecified address. + assert_ne!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); + } + assert!(sock_addr.sin_port > 0); +} + /// Test that the `getpeername` syscall successfully returns the peer address /// for a non-blocking IPv4 socket whose connection has been successfully /// established before calling the syscall. diff --git a/tests/pass-dep/libc/libc-socket-no-blocking.windows_host.stderr b/tests/pass-dep/libc/libc-socket-no-blocking.windows_host.stderr new file mode 100644 index 0000000000..8173c12106 --- /dev/null +++ b/tests/pass-dep/libc/libc-socket-no-blocking.windows_host.stderr @@ -0,0 +1,21 @@ +warning: connecting sockets return unspecified socket addresses on Windows hosts + --> tests/pass-dep/libc/libc-socket-no-blocking.rs:LL:CC + | +LL | libc::getsockname(client_sockfd, storage, len) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Called `getsockname` on connecting socket + | + = note: Windows hosts do not provide `local_addr` information while the socket is still connecting, which might break the assumptions of code compiled for Unix targets + = note: an unspecified socket address (e.g. `0.0.0.0:0`) will be returned instead + = note: this is on thread `main` + = note: stack backtrace: + 0: test_getsockname_ipv4_connect_nonblock::{closure#0} + at tests/pass-dep/libc/libc-socket-no-blocking.rs:LL:CC + 1: libc_utils::net::sockname + at tests/pass-dep/libc/../../utils/libc.rs:LL:CC + 2: libc_utils::net::sockname_ipv4 + at tests/pass-dep/libc/../../utils/libc.rs:LL:CC + 3: test_getsockname_ipv4_connect_nonblock + at tests/pass-dep/libc/libc-socket-no-blocking.rs:LL:CC + 4: main + at tests/pass-dep/libc/libc-socket-no-blocking.rs:LL:CC + diff --git a/tests/pass-dep/libc/libc-socket.rs b/tests/pass-dep/libc/libc-socket.rs index 173edbc822..33f99371ee 100644 --- a/tests/pass-dep/libc/libc-socket.rs +++ b/tests/pass-dep/libc/libc-socket.rs @@ -41,6 +41,7 @@ fn main() { test_getsockname_ipv4(); test_getsockname_ipv4_random_port(); test_getsockname_ipv4_unbound(); + test_getsockname_ipv4_connect(); test_getsockname_ipv6(); test_getpeername_ipv4(); @@ -427,6 +428,34 @@ fn test_getsockname_ipv4_unbound() { assert_eq!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); } +/// Test the `getsockname` syscall on a connected IPv4 socket. +fn test_getsockname_ipv4_connect() { + let (server_sockfd, addr) = net::make_listener_ipv4().unwrap(); + let client_sockfd = + unsafe { errno_result(libc::socket(libc::AF_INET, libc::SOCK_STREAM, 0)).unwrap() }; + + // Spawn the server thread. + let server_thread = thread::spawn(move || net::accept_ipv4(server_sockfd).unwrap()); + + net::connect_ipv4(client_sockfd, addr).unwrap(); + + let (_, sock_addr) = net::sockname_ipv4(|storage, len| unsafe { + libc::getsockname(client_sockfd, storage, len) + }) + .unwrap(); + + // We want to ensure that the local address is not the unspecified address. + // Because the bound address could be of any local interface, we cannot + // test for localhost. + let addr = net::sock_addr_ipv4([0, 0, 0, 0], 0); + + assert_eq!(addr.sin_family, sock_addr.sin_family); + assert_ne!(addr.sin_addr.s_addr, sock_addr.sin_addr.s_addr); + assert!(sock_addr.sin_port > 0); + + server_thread.join().unwrap(); +} + /// Test the `getsockname` syscall on an IPv6 socket which is bound. /// The `getsockname` syscall should return the same address as to /// which the socket was bound to.