Skip to content
Merged
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
18 changes: 16 additions & 2 deletions src/shims/unix/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -684,14 +684,28 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
shim_sig!(extern "C" fn(i32, *_, libc::size_t, i32) -> libc::ssize_t),
(link_name, abi, args),
)?;
this.send(socket, buffer, length, flags, dest)?;
this.send(socket, buffer, length, flags, /* dest_addr */ None, dest)?;
}
"sendto" => {
let [socket, buffer, length, flags, dest_addr, dest_addr_len] = &this.check_shim_sig(
shim_sig!(extern "C" fn(i32, *_, libc::size_t, i32, *libc::sockaddr, libc::socklen_t) -> libc::ssize_t),
(link_name, abi, args),
)?;
this.send(socket, buffer, length, flags, Some((dest_addr, dest_addr_len)), dest)?;
}
"recv" => {
let [socket, buffer, length, flags] = &this.check_shim_sig(
shim_sig!(extern "C" fn(i32, *_, libc::size_t, i32) -> libc::ssize_t),
(link_name, abi, args),
)?;
this.recv(socket, buffer, length, flags, dest)?;
this.recv(socket, buffer, length, flags, /* dest_addr */ None, dest)?;
}
"recvfrom" => {
let [socket, buffer, length, flags, dest_addr, dest_addr_len] = &this.check_shim_sig(
shim_sig!(extern "C" fn(i32, *_, libc::size_t, i32, *libc::sockaddr, *libc::socklen_t) -> libc::ssize_t),
(link_name, abi, args),
)?;
this.recv(socket, buffer, length, flags, Some((dest_addr, dest_addr_len)), dest)?;
}
"setsockopt" => {
let [socket, level, option_name, option_value, option_len] = &this.check_shim_sig(
Expand Down
82 changes: 70 additions & 12 deletions src/shims/unix/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,15 @@ pub trait UnixSocketFileDescription: UnixFileDescription {
throw_unsup_format!("cannot connect {}", self.name());
}

/// Receive data on the socket into the given buffer `ptr`.
/// Receive data on the socket into the given buffer `ptr` and return the socket
/// address from where the data originated.
/// `len` indicates how many bytes we should try to receive.
/// `is_peek` specifies whether the receive removes the bytes from the receive buffer
/// ([`false`]) or leaves them in the receive buffer ([`true`]).
/// `is_non_block` specifies whether the receive operation is non-blocking.
/// After a successful receive, `finish` should be called with the amount of bytes received.
/// After a successful receive, `finish` should be called with a tuple containing the
/// amount of bytes received and the socket address from where the data originated, should
/// it be provided by the protocol.
fn recv<'tcx>(
self: FileDescriptionRef<Self>,
_communicate_allowed: bool,
Expand All @@ -79,21 +82,24 @@ pub trait UnixSocketFileDescription: UnixFileDescription {
_is_peek: bool,
_is_non_block: bool,
_ecx: &mut MiriInterpCx<'tcx>,
_finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
_finish: DynMachineCallback<'tcx, Result<(usize, Option<SocketAddr>), IoError>>,
) -> InterpResult<'tcx> {
throw_unsup_format!("cannot receive from {}", self.name());
}

/// Send data from the given buffer `ptr` into the socket.
/// `len` indicates how many bytes we should try to send.
/// `is_non_block` specifies whether the send operation is non-blocking.
/// `dest_addr` optionally contains a socket address to which the data
/// should be sent instead of the default destination address.
/// After a successful send, `finish` should be called with the amount of bytes sent.
fn send<'tcx>(
self: FileDescriptionRef<Self>,
_communicate_allowed: bool,
_ptr: Pointer,
_len: usize,
_is_non_block: bool,
_dest_addr: Option<SocketAddr>,
_ecx: &mut MiriInterpCx<'tcx>,
_finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
) -> InterpResult<'tcx> {
Expand Down Expand Up @@ -429,7 +435,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// We only attempt a write if the address pointer is not a null pointer.
// If the address pointer is a null pointer the user isn't interested in the
// address and we don't need to write anything.
ecx.write_socket_address(&address, address_ptr, &address_len_place)?;
ecx.write_socket_address(Some(&address), address_ptr, &address_len_place)?;
}

// We need to create the scalar using the destination size since
Expand Down Expand Up @@ -492,6 +498,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
buffer: &OpTy<'tcx>,
length: &OpTy<'tcx>,
flags: &OpTy<'tcx>,
dest_addr: Option<(&OpTy<'tcx>, &OpTy<'tcx>)>,
// Location where the output scalar is written to.
dest: &MPlaceTy<'tcx>,
) -> InterpResult<'tcx> {
Expand All @@ -504,6 +511,33 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
this.read_scalar(length)?.to_uint(size_layout.size)?.try_into().unwrap();
let mut flags = this.read_scalar(flags)?.to_i32()?;

let dest_addr = match dest_addr {
Some((dest_addr, dest_addr_len)) => {
let dest_addr_ptr = this.read_pointer(dest_addr)?;
let socklen_layout = this.libc_ty_layout("socklen_t");
let dest_addr_len_int =
this.read_scalar(dest_addr_len)?.to_uint(socklen_layout.size)?;

if dest_addr_ptr == Pointer::null() || dest_addr_len_int == 0 {
// If the address pointer is a nullptr or the address length is zero we ignore
// the destination address. BSDs return EINVAL when the address length is zero
// and the address pointer is an invalid pointer; that is so far away from what
// POSIX documents for TCP sockets that we do not try to emulate it.
None
} else {
// If they are both non-null/zero, we read the address even if the socket does
// not care about it. This is stricter than what POSIX says (POSIX says the
// destination is ignored for TCP sockets), but most OSes seem to do something
// similar.
match this.read_socket_address(dest_addr, dest_addr_len, "sendto")? {
Ok(dest_addr) => Some(dest_addr),
Err(e) => return this.set_errno_and_return_neg1(e, dest),
}
}
}
None => None,
};

// Get the file handle
let Some(fd) = this.machine.fds.get(socket) else {
return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
Expand Down Expand Up @@ -550,6 +584,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
buffer_ptr,
length,
is_non_block,
dest_addr,
this,
callback!(
@capture<'tcx> {
Expand All @@ -571,6 +606,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
buffer: &OpTy<'tcx>,
length: &OpTy<'tcx>,
flags: &OpTy<'tcx>,
dest_addr: Option<(&OpTy<'tcx>, &OpTy<'tcx>)>,
// Location where the output scalar is written to.
dest: &MPlaceTy<'tcx>,
) -> InterpResult<'tcx> {
Expand All @@ -583,6 +619,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
this.read_scalar(length)?.to_uint(size_layout.size)?.try_into().unwrap();
let mut flags = this.read_scalar(flags)?.to_i32()?;

let dest_addr = match dest_addr {
Some((addr, addr_len)) =>
Some((this.read_pointer(addr)?, this.deref_pointer(addr_len)?)),
None => None,
};

// Get the file handle
let Some(fd) = this.machine.fds.get(socket) else {
return this.set_errno_and_return_neg1(LibcError("EBADF"), dest);
Expand Down Expand Up @@ -645,13 +687,29 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
this,
callback!(
@capture<'tcx> {
dest_addr: Option<(Pointer, MPlaceTy<'tcx>)>,
dest: MPlaceTy<'tcx>,
} |ecx, result: Result<usize, IoError>| {
match result {
Ok(bytes_sent) =>
ecx.write_scalar(Scalar::from_target_usize(bytes_sent.try_into().unwrap(), ecx), &dest),
Err(e) => ecx.set_errno_and_return_neg1(e, &dest)
} |ecx, result: Result<(usize, Option<SocketAddr>), IoError>| {
let (bytes_received, mut origin_addr_opt) = match result {
Ok(data) => data,
Err(e) => return ecx.set_errno_and_return_neg1(e, &dest)
};

if let Some((addr_ptr, addr_len_mplace)) = dest_addr {
if addr_ptr == Pointer::null() {
// When the `addr_ptr` is a null pointer the caller is not interested in
// the socket address.
origin_addr_opt = None;
}
// The recvfrom(3) man page specifies that "[...] the value stored in the object pointed to
// `address` is unspecified [...]" should the protocol not provide the source address of the message.
// In this case we do the same thing as the Linux implementation and simply don't write anything to the
// `addr_ptr` pointer and zero the `addr_len_mplace` place.
ecx.write_socket_address(origin_addr_opt.as_ref(), addr_ptr, &addr_len_mplace)?;
}

ecx.write_scalar(Scalar::from_target_usize(bytes_received.try_into().unwrap(), ecx), &dest)

}
),
)
Expand Down Expand Up @@ -794,7 +852,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
Err(e) => return this.set_errno_and_return_neg1_i32(e),
};

this.write_socket_address(&address, address_ptr, &address_len_place)
this.write_socket_address(Some(&address), address_ptr, &address_len_place)
.map(|()| Scalar::from_i32(0))
}

Expand Down Expand Up @@ -838,11 +896,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
};

ecx.write_socket_address(
&address,
Some(&address),
address_ptr,
&address_len_place,
)?;
ecx.write_scalar(Scalar::from_i32(0), &dest)
ecx.write_scalar(Scalar::from_i32(0), &dest)
}
),
)
Expand Down
41 changes: 38 additions & 3 deletions src/shims/unix/socket_address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// size of a layout is also stored in a u64.
let address_len: u64 =
this.read_scalar(address_len)?.to_int(address_len.layout.size)?.try_into().unwrap();
let address = this.deref_pointer(address)?;

// Ensure the pointer is actually dereferenceable for that length.
this.check_ptr_access(
address.ptr(),
Size::from_bytes(address_len),
CheckInAllocMsg::MemoryAccess,
)?;

// Initially, treat address as generic sockaddr just to extract the family field.
let address = this.deref_pointer(address)?;
if address_len < address.layout.size.bytes() {
// Address length should be at least as big as the generic sockaddr
return interp_ok(Err(LibcError("EINVAL")));
Expand Down Expand Up @@ -244,16 +251,31 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
/// on output it points to the non-truncated size of the written address in the
/// buffer pointed to by `address_ptr`.
///
/// If `address` is [`None`] we write an "empty" address (i.e., the `address_len_ptr`
/// is set to zero and the `address_ptr` is left untouched).
/// If `address` is `Some`, both output pointers must be valid!
///
/// If the address buffer doesn't fit the whole address, the address is truncated to not
/// overflow the buffer.
fn write_socket_address(
&mut self,
address: &SocketAddr,
address: Option<&SocketAddr>,
address_ptr: Pointer,
address_len_place: &MPlaceTy<'tcx>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();

if address.is_none() && this.ptr_is_null(address_len_place.ptr())? {
// It is okay for the len ptr to be null *if* we have nothing to write.
return interp_ok(());
}

// If the len ptr is not null or if we have an address to write, the length must be valid.
// This is intended to implement what POSIX says about the length pointer in `recvfrom``:
// > Either a null pointer, if address is a null pointer, or a pointer to a socklen_t object
// > which on input specifies the length of the supplied sockaddr structure, and on output
// > specifies the length of the sending address.

// We only support buffer lengths which can be stored in a u64 since the
// size of a layout in bytes is also stored in a u64.
let address_buffer_len: u64 = this
Expand All @@ -262,6 +284,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
.try_into()
.unwrap();

// Check that the buffer indeed has (at least) the given size.
this.check_ptr_access(
address_ptr,
Size::from_bytes(address_buffer_len),
CheckInAllocMsg::MemoryAccess,
)?;

let Some(address) = address else {
// We want to write an "empty" socket address. In this case we just set
// the address length to zero.
return this.write_int(0, address_len_place);
};

let address_buffer = match address {
SocketAddr::V4(address) => {
// IPv4 address bytes; already stored in network byte order.
Expand Down Expand Up @@ -446,7 +481,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
this.write_int(sockaddr_layout.size.bytes(), &addrlen_mplace)?;

let sockaddr_mplace = this.allocate(sockaddr_layout, MiriMemoryKind::Machine.into())?;
this.write_socket_address(&address, sockaddr_mplace.ptr(), &addrlen_mplace)?;
this.write_socket_address(Some(&address), sockaddr_mplace.ptr(), &addrlen_mplace)?;
this.write_pointer(sockaddr_mplace.ptr(), &addr_mplace)?;

let canonname_mplace = this.project_field_named(&addrinfo_mplace, "ai_canonname")?;
Expand Down
7 changes: 7 additions & 0 deletions src/shims/unix/solarish/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
)?;
this.connect(socket, address, address_len, dest)?;
}
"__xnet_sendto" => {
let [socket, buffer, length, flags, dest_addr, dest_addr_len] = &this.check_shim_sig(
shim_sig!(extern "C" fn(i32, *_, libc::size_t, i32, *libc::sockaddr, libc::socklen_t) -> libc::ssize_t),
(link_name, abi, args),
)?;
this.send(socket, buffer, length, flags, Some((dest_addr, dest_addr_len)), dest)?;
}
"__xnet_getaddrinfo" => {
let [node, service, hints, res] = &this.check_shim_sig(
shim_sig!(extern "C" fn(*_, *_, *_, **_) -> i32),
Expand Down
31 changes: 28 additions & 3 deletions src/shims/unix/tcp_socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,13 @@ impl FileDescription for TcpSocket {
/* is_peek */ false,
/* is_non_block */ false,
ecx,
finish,
callback!(
@capture<'tcx> {
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
} |this, result: Result<(usize, Option<SocketAddr>), IoError>| {
finish.call(this, result.map(|(bytes, _)| bytes))
}
),
)
}

Expand All @@ -182,7 +188,15 @@ impl FileDescription for TcpSocket {
ecx: &mut MiriInterpCx<'tcx>,
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
) -> InterpResult<'tcx> {
self.send(communicate_allowed, ptr, len, /* is_non_block */ false, ecx, finish)
self.send(
communicate_allowed,
ptr,
len,
/* is_non_block */ false,
/* dest_addr */ None,
ecx,
finish,
)
}

fn short_fd_operations(&self) -> bool {
Expand Down Expand Up @@ -585,6 +599,9 @@ impl UnixSocketFileDescription for TcpSocket {
ptr: Pointer,
len: usize,
is_non_block: bool,
// For stream-based sockets `send` and `sendto` are equivalent and
// we can thus ignore the destination address.
_dest_addr: Option<SocketAddr>,
ecx: &mut MiriInterpCx<'tcx>,
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
) -> InterpResult<'tcx> {
Expand Down Expand Up @@ -634,13 +651,21 @@ impl UnixSocketFileDescription for TcpSocket {
is_peek: bool,
is_non_block: bool,
ecx: &mut MiriInterpCx<'tcx>,
finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
finish: DynMachineCallback<'tcx, Result<(usize, Option<SocketAddr>), IoError>>,
) -> InterpResult<'tcx> {
assert!(communicate_allowed, "cannot have `TcpSocket` with isolation enabled!");

let is_non_block = is_non_block || self.is_non_block.get();
let deadline = ecx.action_deadline(is_non_block, self.read_timeout.get());

let finish = callback!(
@capture<'tcx> {
finish: DynMachineCallback<'tcx, Result<(usize, Option<SocketAddr>), IoError>>,
} |this, result: Result<usize, IoError>| {
// The source address of the data isn't available for stream-based sockets.
finish.call(this, result.map(|bytes| (bytes, None)))
}
);
let socket = self;
ecx.ensure_connected(
socket.clone(),
Expand Down
Loading
Loading