syd 3.52.0

rock-solid application kernel
Documentation
// 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),
    })
}