syd 3.55.0

rock-solid application kernel
Documentation
//
// Syd: rock-solid application kernel
// src/kernel/net/connect.rs: connect(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 std::os::fd::AsFd;

use libseccomp::ScmpNotifResp;
use nix::{errno::Errno, sys::socket::SockaddrStorage};

use crate::{
    cache::UnixVal,
    compat::{sockaddr_family, AddressFamily},
    cookie::safe_connect,
    fd::{fd_inode, has_recv_timeout, peer_inode, SafeOwnedFd},
    kernel::net::{handle_safe_bind, NetAddr},
    path::XPath,
    proc::proc_tgid,
    req::UNotifyEventRequest,
    unix::unix_path_bytes,
};

pub(crate) fn handle_connect(
    fd: SafeOwnedFd,
    addr: (SockaddrStorage, SockaddrStorage),
    request: &UNotifyEventRequest,
    target: NetAddr,
    allow_safe_bind: bool,
    is_nonblock: bool,
) -> Result<ScmpNotifResp, Errno> {
    let (addr, argaddr) = addr;

    // Record connector's PID before connect(2) so accept(2) can find it.
    let ino = if sockaddr_family(&addr) == AddressFamily::Unix {
        let peer = argaddr.as_unix_addr().filter(|u| u.path().is_some());
        if let (Ok(ino), Ok(pid)) = (fd_inode(&fd), proc_tgid(request.scmpreq.pid())) {
            let _ = request.add_unix2(
                ino,
                pid,
                UnixVal {
                    peer: peer.copied(),
                    ..UnixVal::default()
                },
            );
            Some(ino)
        } else {
            None
        }
    } else {
        None
    };

    // Call the underlying connect(2) system call.
    let result = do_connect(&fd, addr, request, is_nonblock);

    // Remove stale Unix map entry on connect failure.
    if result.is_err() {
        if let Some(ino) = ino {
            request.cache.unix_map.remove_if_sync(&ino, |entry| {
                entry.peer = None;
                entry.self_pid = None;
                entry.addr.is_none() && entry.dest.is_empty() && entry.peer_pid.is_none()
            });
        }
    }

    if result.is_ok() {
        // Move domain on connect as necessary.
        // This happens before trace/allow_safe_bind, so the address is
        // going to be allowlisted in the new domain.
        match target {
            NetAddr::Inet(ip, port) => request.get_sandbox().move_on_connect_inet(ip, port),
            NetAddr::Unix(name) => request.get_sandbox().move_on_connect_unix(&name),
            NetAddr::UnixPath(root) => request.get_sandbox().move_on_connect_unix(root.abs()),
            NetAddr::UnixUnnamed => request
                .get_sandbox()
                .move_on_connect_unix(XPath::from_bytes(b"!unnamed")),
            NetAddr::None => {}
        }

        if allow_safe_bind
            && matches!(
                sockaddr_family(&addr),
                AddressFamily::Inet | AddressFamily::Inet6
            )
        {
            // Handle allow_safe_bind.
            // Ignore errors as connect has already succeeded.
            let _ = handle_safe_bind(request, &fd);
        } else if sockaddr_family(&addr) == AddressFamily::Unix {
            // Handle SO_PASSCRED inode tracking and getpeername(2).
            // Look up destination's device and inode to disambiguate at recv(2).
            // Ignore errors as connect(2) has already succeeded.
            let unix_peer = argaddr.as_unix_addr().filter(|u| u.path().is_some());
            let (ddev, dino) = unix_peer
                .and_then(unix_path_bytes)
                .map(XPath::from_bytes)
                .and_then(|path| request.lookup_unix_vfs_id(path).ok())
                .map_or((None, None), |(dev, ino)| (Some(dev), Some(ino)));
            let mut unix_val = UnixVal {
                peer: unix_peer.copied(),
                ..UnixVal::default()
            };
            if let (Some(dev), Some(ino)) = (ddev, dino) {
                if unix_val.dest.try_reserve(1).is_ok() {
                    unix_val.dest.push((dev, ino));
                }
            }
            let _ = request.add_unix(&fd, request.scmpreq.pid(), unix_val);

            // Record TGID of the peer socket.
            if let (Ok(inode), Ok(client_pid)) = (fd_inode(&fd), proc_tgid(request.scmpreq.pid())) {
                let server_pid = unix_peer
                    .and_then(unix_path_bytes)
                    .map(XPath::from_bytes)
                    .and_then(|p| request.unix_owner(p));
                if let (Some(server_pid), Ok(peer)) = (server_pid, peer_inode(inode)) {
                    let _ = request.set_unix_peer(peer, server_pid, client_pid);
                }
            }
        }
    }

    result.map(|_| request.return_syscall(0))
}

fn do_connect<Fd: AsFd>(
    fd: Fd,
    addr: SockaddrStorage,
    request: &UNotifyEventRequest,
    is_nonblock: bool,
) -> Result<(), Errno> {
    // Record blocking call so it can get invalidated.
    let req = request.scmpreq;
    let is_blocking = if !is_nonblock {
        let ignore_restart = has_recv_timeout(&fd)?;

        // Record the blocking call.
        request.cache.add_sys_block(req, ignore_restart)?;

        true
    } else {
        false
    };

    // All done, call underlying system call.
    let result = safe_connect(fd, &addr);

    // Remove invalidation record.
    if is_blocking {
        request.cache.del_sys_block(req.id)?;
    }

    result
}