1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// Syd: rock-solid application kernel
// src/kernel/net/sendto.rs: sendto(2) handler
//
// Copyright (c) 2023, 2024, 2025, 2026 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0
// SAFETY: This module has been liberated from unsafe code!
#![forbid(unsafe_code)]
use libseccomp::ScmpNotifResp;
use nix::{errno::Errno, sys::socket::SockaddrStorage};
use crate::{
compat::{send, sendto, sockaddr_family, AddressFamily, MsgFlags, SockType},
cookie::safe_getzerocopy,
fd::has_send_timeout,
ip::{SockInfo, SocketCall},
kernel::net::{check_send_state, handle_safe_bind_send, max_send_len, to_msgflags, SockOpts},
req::UNotifyEventRequest,
};
pub(crate) fn handle_sendto(
request: &UNotifyEventRequest,
mut sock: SockInfo,
sock_opts: SockOpts,
addr: Option<(SockaddrStorage, SockaddrStorage)>,
args: &[u64; 6],
) -> Result<ScmpNotifResp, Errno> {
let SockOpts {
flags: _,
options,
is_nonblock,
} = sock_opts;
// Truncate flags to 32-bit keeping unknown flags.
// Linux ignores MSG_CMSG_COMPAT for sendto(2).
let flags = to_msgflags(args[3]).difference(MsgFlags::MSG_CMSG_COMPAT);
// Reject MSG_ZEROCOPY which Syd can't support with current design.
// Syd sends copies of messages therefore completions never arrive.
// Linux ignores MSG_ZEROCOPY if SO_ZEROCOPY isn't set on socket.
let flags = if flags.contains(MsgFlags::MSG_ZEROCOPY) {
if safe_getzerocopy(sock.fd())? {
return Err(Errno::ENOBUFS);
}
flags.difference(MsgFlags::MSG_ZEROCOPY)
} else {
flags
};
// Reject MSG_OOB as necessary.
let restrict_oob = !options.allow_unsafe_oob();
if restrict_oob && flags.contains(MsgFlags::MSG_OOB) {
return Err(Errno::EOPNOTSUPP);
}
// Length argument to sendto(2) is not trusted.
let count = usize::try_from(args[2]).or(Err(Errno::EINVAL))?;
// Linux rejects oversized atomic sends before destination lookup.
// Addressed sends are checked in syscall_network_handler.
if addr.is_none() {
sock.check_send_len(count)?;
}
// Cap count at MAX_RW_COUNT and maximum socket send buffer.
let len = count.min(max_send_len());
// Linux sends readable prefix for byte streams, messages are atomic.
// Linux rejects oversized IPv4 datagrams before reading payload.
// Linux returns EPIPE for unconnected streams before reading payload.
// Linux reports socket errors before parse errors.
let buf = match read_data(request, &mut sock, args[1], len) {
Ok(buf) => buf,
Err(Errno::EFAULT) => {
if args[2] > 0xFFFF
&& sock.get_dom()? == AddressFamily::Inet
&& matches!(sock.get_stype()?, SockType::Datagram | SockType::Raw)
{
return Err(Errno::EMSGSIZE);
}
check_send_state(request, &mut sock, flags, addr.is_some(), is_nonblock)?;
return Err(Errno::EFAULT);
}
Err(errno) => return Err(errno),
};
// Record sender PID for SCM_PIDFD/SO_PASSCRED fixup at recvmsg(2).
//
// To avoid races, this must be done before sendto(2) and on errors
// the entry will be removed back again.
let req = request.scmpreq;
let addr_unix = match addr.as_ref() {
Some((addr, _)) => sockaddr_family(addr) == AddressFamily::Unix,
None => sock.get_dom()? == AddressFamily::Unix,
};
let unix_data = if addr_unix {
let unix = addr
.as_ref()
.and_then(|(_, addr_arg)| addr_arg.as_unix_addr());
// Ignore errors: UNIX socket diagnostics may not be supported.
// `unix` is None for connection-mode sockets.
request.add_send(sock.fd(), req.pid(), unix).ok()
} else {
None
};
// Record blocking call so it can get invalidated.
let is_blocking = if !is_nonblock && !flags.contains(MsgFlags::MSG_DONTWAIT) {
let ignore_restart = has_send_timeout(sock.fd())?;
// Record the blocking call.
request.cache.add_sys_block(req, ignore_restart)?;
true
} else {
false
};
// Perform sendmsg(2).
let result = if let Some((ref addr, _)) = addr {
// Connection-less socket.
sendto(sock.fd(), &buf, addr, flags)
} else {
// Connection mode socket, no address specified.
send(sock.fd(), &buf, flags)
};
// Remove invalidation record.
if is_blocking {
request.cache.del_sys_block(req.id)?;
}
// Delete sender record on errors.
if result.is_err() {
if let Some((inode, dest)) = unix_data {
let _ = request.del_send(inode, dest);
}
}
// Handle allow_safe_bind.
// Ignore errors as sendto has already succeeded.
if result.is_ok() && options.allow_safe_bind() {
if let Some((ref addr, _)) = addr {
let _ = handle_safe_bind_send(request, SocketCall::SendTo, &mut sock, addr);
}
}
// Send SIGPIPE for EPIPE unless MSG_NOSIGNAL is set.
#[expect(clippy::cast_possible_wrap)]
Ok(match result {
Ok(n) => request.return_syscall(n as i64),
Err(Errno::EPIPE) if !flags.contains(MsgFlags::MSG_NOSIGNAL) => {
if sock.get_send_sigpipe()? {
request.pidfd_kill(libc::SIGPIPE)?;
}
request.fail_syscall(Errno::EPIPE)
}
Err(errno) => request.fail_syscall(errno),
})
}
// Read send data from sandbox process memory.
//
// Linux sends readable prefix for byte streams, messages are atomic.
fn read_data(
request: &UNotifyEventRequest,
sock: &mut SockInfo,
addr_remote: u64,
len: usize,
) -> Result<Vec<u8>, Errno> {
if sock.get_stream_send()? {
let buf = request.read_vec(addr_remote, len)?;
if buf.is_empty() && len != 0 {
return Err(Errno::EFAULT);
}
Ok(buf)
} else {
request.read_vec_all(addr_remote, len)
}
}