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
// 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},
config::MAX_RW_COUNT,
fd::{has_recv_timeout, SafeOwnedFd},
ip::SocketCall,
kernel::net::{get_port, handle_safe_bind2, to_msgflags, SockOpts},
req::UNotifyEventRequest,
};
pub(crate) fn handle_sendto(
fd: SafeOwnedFd,
request: &UNotifyEventRequest,
args: &[u64; 6],
opts: SockOpts,
addr: Option<(SockaddrStorage, SockaddrStorage)>,
) -> Result<ScmpNotifResp, Errno> {
let SockOpts {
sock_dom,
flags: _,
options,
is_nonblock,
} = opts;
// Truncate flags to 32-bit keeping unknown flags.
let flags = to_msgflags(args[3]);
// Reject MSG_OOB as necessary.
let restrict_oob = !options.allow_unsafe_oob();
if restrict_oob && flags.contains(MsgFlags::MSG_OOB) {
// Signal no support to let the sandbox process handle the error
// gracefully. This is consistent with the Linux kernel.
return Err(Errno::EOPNOTSUPP);
}
// The length argument to the sendto(2) call must not be fully
// trusted, it can be overly large, and allocating a Vector of that
// capacity may overflow. It is valid for the length to be zero to
// send an empty message. Buffer read from sandbox process MUST be
// zeroized on drop.
let len = usize::try_from(args[2])
.or(Err(Errno::EINVAL))?
.min(*MAX_RW_COUNT); // Cap count at MAX_RW_COUNT.
// read_vec_all_zeroed returns an empty vector with zero length
// without performing any memory reads.
let buf = request.read_vec_all_zeroed(args[1], len)?;
// 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 = addr
.as_ref()
.map(|(addr, _)| sockaddr_family(addr) == AddressFamily::Unix)
.unwrap_or(sock_dom == AddressFamily::Unix);
let unix_data = if addr_unix {
let unix = addr
.as_ref()
.and_then(|(_, argaddr)| argaddr.as_unix_addr());
// Ignore errors: UNIX socket diagnostics may not be supported.
// `unix` is None for connection-mode sockets.
request.add_send(&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_recv_timeout(&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(&fd, &buf, addr, flags)
} else {
// Connection mode socket, no address specified.
send(&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()
&& matches!(sock_dom, AddressFamily::Inet | AddressFamily::Inet6)
{
if let Some((ref addr, _)) = addr {
match get_port(&fd) {
Ok(port) if port != 0 => {
let _ = handle_safe_bind2(request, SocketCall::SendTo, addr, port);
}
_ => {}
}
}
}
// 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) => {
request.pidfd_kill(libc::SIGPIPE)?;
request.fail_syscall(Errno::EPIPE)
}
Err(errno) => request.fail_syscall(errno),
})
}