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;
let req = request.scmpreq;
let is_blocking = if !get_nonblock(&fd)? {
let ignore_restart = has_recv_timeout(&fd)?;
request.cache.add_sys_block(req, ignore_restart)?;
true
} else {
false
};
let result = safe_connect(&fd, &addr);
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
)
{
let _ = handle_safe_bind(request, &fd);
} else if sockaddr_family(&addr) == AddressFamily::Unix {
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))
}
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);
(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)
};
(addr, port)
} else {
return Ok(());
};
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(())
}