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
// 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::{get_nonblock, has_recv_timeout, SafeOwnedFd},
kernel::net::to_msgflags,
req::UNotifyEventRequest,
};
pub(crate) fn handle_sendto(
fd: SafeOwnedFd,
args: &[u64; 6],
request: &UNotifyEventRequest,
sock_dom: AddressFamily,
addr: Option<(SockaddrStorage, SockaddrStorage)>,
restrict_oob: bool,
) -> Result<ScmpNotifResp, Errno> {
// Truncate flags to 32-bit keeping unknown flags.
let flags = to_msgflags(args[3]);
// Reject MSG_OOB as necessary.
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 !flags.contains(MsgFlags::MSG_DONTWAIT) && !get_nonblock(&fd)? {
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);
}
}
// 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),
})
}