syd 3.52.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

use std::{
    net::IpAddr,
    os::fd::{AsFd, AsRawFd},
};

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

use crate::{
    cache::UnixVal,
    compat::{sockaddr_family, AddressFamily},
    cookie::safe_connect,
    fd::{get_nonblock, has_recv_timeout, SafeOwnedFd},
    info,
    path::XPath,
    req::UNotifyEventRequest,
    sandbox::{Action, AddressPattern, Capability, CidrRule},
    unix::unix_path_bytes,
};

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

    // Record blocking call so it can get invalidated.
    let req = request.scmpreq;
    let is_blocking = if !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
    };

    // 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)?;
    }

    if result.is_ok() {
        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);
        }
    }

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

// Handle allow_safe_bind for connect.
fn handle_safe_bind<Fd: AsFd>(request: &UNotifyEventRequest, fd: Fd) -> Result<(), Errno> {
    let addr = getsockname::<SockaddrStorage>(fd.as_fd().as_raw_fd())?;

    let (addr, port) = if let Some(addr) = addr.as_sockaddr_in() {
        let port = addr.port();
        if port == 0 {
            return Ok(());
        }

        let addr = IpNet::new_assert(IpAddr::V4(addr.ip()), 32);

        // Allow implicit bind with safe_bind.
        (addr, port)
    } else if let Some(addr) = addr.as_sockaddr_in6() {
        let port = addr.port();
        if port == 0 {
            return Ok(());
        }

        let addr = addr.ip();
        let addr = if let Some(addr) = addr.to_ipv4_mapped() {
            IpNet::new_assert(IpAddr::V4(addr), 32)
        } else {
            IpNet::new_assert(IpAddr::V6(addr), 128)
        };

        // Allow implicit bind with safe_bind.
        (addr, port)
    } else {
        return Ok(());
    };

    // Configure sandbox:
    // Remove and re-add the address so repeated binds to the
    // same address cannot overflow the vector.
    let addr = AddressPattern {
        addr,
        port: port.into(),
    };
    info!("ctx": "connect", "op": "allow_safe_bind",
        "sys": "connect", "pid": request.scmpreq.pid().as_raw(), "rule": &addr,
        "msg": format!("add rule `allow/net/connect+{addr}' after connect"));

    let rule = CidrRule {
        act: Action::Allow,
        cap: Capability::CAP_NET_CONNECT,
        pat: addr,
    };

    let mut sandbox = request.get_mut_sandbox();
    if let Some(idx) = sandbox.cidr_rules.iter().position(|r| *r == rule) {
        sandbox.cidr_rules.remove(idx);
    }
    sandbox.cidr_rules.push_front(rule)?;

    Ok(())
}