syd 3.58.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_send_timeout, peer_inode},
    ip::{IpProto, SockInfo},
    kernel::net::{handle_safe_bind, NetAddr},
    path::XPath,
    proc::util::proc_tgid,
    req::UNotifyEventRequest,
    unix::unix_path_bytes,
};

#[expect(clippy::cognitive_complexity)]
pub(crate) fn handle_connect(
    request: &UNotifyEventRequest,
    sock: SockInfo,
    addr: (SockaddrStorage, SockaddrStorage),
    target: (NetAddr, Option<IpProto>),
    allow_safe_bind: bool,
    is_nonblock: bool,
) -> Result<ScmpNotifResp, Errno> {
    let (addr, addr_arg) = addr;
    let (target, ip_proto) = target;

    // Record inode and TGID for UNIX sockets.
    let unix_ids = if sockaddr_family(&addr) == AddressFamily::Unix {
        match (fd_inode(sock.fd()), proc_tgid(request.scmpreq.pid())) {
            (_, Err(_)) => return Err(Errno::ESRCH),
            // Validate request after proc(5) read.
            (Ok(_), Ok(_)) if !request.is_valid() => return Err(Errno::ESRCH),
            (Ok(inode), Ok(pid)) => Some((inode, pid)),
            _ => None,
        }
    } else {
        None
    };

    // Record connector's TGID before connect(2) so accept(2) can find it.
    if let Some((ino, pid)) = unix_ids {
        let _ = request.add_unix2(
            ino,
            pid, // validated
            UnixVal::default(),
        );
    }

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

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

    if result.is_ok() {
        // AF_UNSPEC dissolves association.
        if sockaddr_family(&addr) == AddressFamily::Unspec {
            if let Ok(ino) = fd_inode(sock.fd()) {
                request.cache.unix_map.remove_if_mut_sync(&ino, |entry| {
                    entry.self_pid = None;
                    entry.addr.is_none() && entry.dest.is_empty() && entry.peer_pid.is_none()
                });
            }
        }

        // 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, ip_proto),
            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, sock.fd());
        } else if sockaddr_family(&addr) == AddressFamily::Unix {
            if let Some((inode, client_pid)) = unix_ids {
                // 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 = addr_arg.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::default();
                if let (Some(dev), Some(ino)) = (ddev, dino) {
                    if unix_val.dest.try_reserve(1).is_ok() {
                        unix_val.dest.push((dev, ino));
                    }
                }
                // Client PID was validated before connect(2).
                let _ = request.add_unix2(inode, client_pid, unix_val);

                // Record TGID of peer socket.
                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)) {
                    // Validate request after netlink(7) access.
                    if request.is_valid() {
                        let _ = request.set_unix_peer(peer, server_pid, client_pid);
                    }
                }
            }
        }
    }

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

fn do_connect<Fd: AsFd>(
    request: &UNotifyEventRequest,
    fd: Fd,
    addr: SockaddrStorage,
    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_send_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
}