Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ pub enum NonHaltingDiagnostic {
effective_failure_ordering: AtomicReadOrd,
},
FileInProcOpened,
ConnectingSocketGetsockname,
}

/// Level of Miri specific diagnostics
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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![],
};

Expand Down
21 changes: 20 additions & 1 deletion src/shims/unix/socket.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down Expand Up @@ -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:
// <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getsockname#remarks>
// 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")? {
Expand Down
48 changes: 48 additions & 0 deletions tests/pass-dep/libc/libc-socket-no-blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -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:
// <https://www.rfc-editor.org/rfc/rfc5737>
// 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.
Comment thread
WhySoBad marked this conversation as resolved.
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.
Expand Down
21 changes: 21 additions & 0 deletions tests/pass-dep/libc/libc-socket-no-blocking.windows_host.stderr
Original file line number Diff line number Diff line change
@@ -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

29 changes: 29 additions & 0 deletions tests/pass-dep/libc/libc-socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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.
Comment on lines +448 to +449
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The socket API would let us bind before calling connect to set the local address, right?
But I guess that's not implemented yet / can't even be implemented with the mio API?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the socket API allows to do this but we cannot represent this with mio because we only have TcpStream::connect there.

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.
Expand Down