use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::os::unix::io::{AsRawFd, OwnedFd, RawFd};
use crate::seccomp::notif::read_child_mem;
use crate::sys::structs::{SeccompNotif, AF_INET, AF_INET6};
pub(crate) const MAX_SEND_BUF: usize = 64 << 20;
const MAX_CONTROL_BUF: usize = 16 << 10;
pub(crate) fn parse_ip_from_sockaddr(bytes: &[u8]) -> Option<IpAddr> {
if bytes.len() < 2 {
return None;
}
let family = u16::from_ne_bytes([bytes[0], bytes[1]]) as u32;
match family {
f if f == AF_INET => {
if bytes.len() < 8 {
return None;
}
Some(IpAddr::V4(Ipv4Addr::new(
bytes[4], bytes[5], bytes[6], bytes[7],
)))
}
f if f == AF_INET6 => {
if bytes.len() < 24 {
return None;
}
let mut addr_bytes = [0u8; 16];
addr_bytes.copy_from_slice(&bytes[8..24]);
Some(IpAddr::V6(Ipv6Addr::from(addr_bytes)).to_canonical())
}
_ => None,
}
}
pub(crate) fn sockaddr_is_ipv6(bytes: &[u8]) -> bool {
bytes.len() >= 2 && u16::from_ne_bytes([bytes[0], bytes[1]]) as u32 == AF_INET6
}
pub(crate) fn parse_port_from_sockaddr(bytes: &[u8]) -> Option<u16> {
if bytes.len() < 4 {
return None;
}
let family = u16::from_ne_bytes([bytes[0], bytes[1]]) as u32;
match family {
f if f == AF_INET || f == AF_INET6 => {
Some(u16::from_be_bytes([bytes[2], bytes[3]]))
}
_ => None,
}
}
pub(crate) fn set_port_in_sockaddr(bytes: &mut [u8], port: u16) {
if bytes.len() >= 4 {
let port_bytes = port.to_be_bytes();
bytes[2] = port_bytes[0];
bytes[3] = port_bytes[1];
}
}
fn translate_scm_rights(child_pid: u32, control: &[u8]) -> Result<(Vec<u8>, Vec<OwnedFd>), i32> {
const CMSG_HDR: usize = 16;
const FD: usize = std::mem::size_of::<i32>();
let mut out = control.to_vec();
let mut held: Vec<OwnedFd> = Vec::new();
let mut off = 0usize;
while off + CMSG_HDR <= out.len() {
let cmsg_len = usize::from_ne_bytes(out[off..off + 8].try_into().unwrap());
let level = i32::from_ne_bytes(out[off + 8..off + 12].try_into().unwrap());
let ctype = i32::from_ne_bytes(out[off + 12..off + 16].try_into().unwrap());
if cmsg_len < CMSG_HDR || cmsg_len > out.len() - off {
return Err(libc::EINVAL);
}
if level == libc::SOL_SOCKET {
if ctype == libc::SCM_RIGHTS {
let data_off = off + CMSG_HDR;
let nfds = (cmsg_len - CMSG_HDR) / FD;
for i in 0..nfds {
let p = data_off + i * FD;
let child_fd = i32::from_ne_bytes(out[p..p + FD].try_into().unwrap());
let sup_fd = crate::seccomp::notif::dup_fd_from_pid(child_pid, child_fd)
.map_err(|e| e.raw_os_error().unwrap_or(libc::EBADF))?;
out[p..p + FD].copy_from_slice(&sup_fd.as_raw_fd().to_ne_bytes());
held.push(sup_fd);
}
} else if ctype == libc::SCM_CREDENTIALS {
return Err(libc::EPERM);
}
}
off += (cmsg_len + 7) & !7;
}
Ok((out, held))
}
fn materialize_control(
notif: &SeccompNotif,
notif_fd: RawFd,
msg_control_ptr: u64,
msg_controllen: u64,
is_unix: bool,
) -> Result<(Option<Vec<u8>>, Vec<OwnedFd>), i32> {
if msg_control_ptr == 0 || msg_controllen == 0 {
return Ok((None, Vec::new()));
}
if msg_controllen as usize > MAX_CONTROL_BUF {
return Err(libc::EMSGSIZE);
}
let raw = read_child_mem(notif_fd, notif.id, notif.pid, msg_control_ptr, msg_controllen as usize)
.map_err(|_| libc::EIO)?;
if is_unix {
let (buf, fds) = translate_scm_rights(notif.pid, &raw)?;
Ok((Some(buf), fds))
} else {
Ok((Some(raw), Vec::new()))
}
}
pub(crate) fn named_unix_socket_path(addr_bytes: &[u8]) -> Option<std::path::PathBuf> {
if addr_bytes.len() < 3 {
return None;
}
let family = u16::from_ne_bytes([addr_bytes[0], addr_bytes[1]]);
if family != libc::AF_UNIX as u16 {
return None;
}
let sun_path = &addr_bytes[2..];
if sun_path[0] == 0 {
return None; }
let end = sun_path.iter().position(|&b| b == 0).unwrap_or(sun_path.len());
let raw = &sun_path[..end];
if raw.is_empty() {
return None;
}
std::str::from_utf8(raw).ok().map(std::path::PathBuf::from)
}
pub(crate) const MSGHDR_SIZE: usize = 56;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct ChildMsghdr {
pub(crate) name_ptr: u64,
pub(crate) namelen: u32,
pub(crate) iov_ptr: u64,
pub(crate) iovlen: u64,
pub(crate) control_ptr: u64,
pub(crate) controllen: u64,
}
impl ChildMsghdr {
pub(crate) fn parse(bytes: &[u8]) -> Option<Self> {
if bytes.len() < MSGHDR_SIZE {
return None;
}
Some(ChildMsghdr {
name_ptr: u64::from_ne_bytes(bytes[0..8].try_into().unwrap()),
namelen: u32::from_ne_bytes(bytes[8..12].try_into().unwrap()),
iov_ptr: u64::from_ne_bytes(bytes[16..24].try_into().unwrap()),
iovlen: u64::from_ne_bytes(bytes[24..32].try_into().unwrap()),
control_ptr: u64::from_ne_bytes(bytes[32..40].try_into().unwrap()),
controllen: u64::from_ne_bytes(bytes[40..48].try_into().unwrap()),
})
}
pub(crate) fn read(notif: &SeccompNotif, notif_fd: RawFd, ptr: u64) -> Result<Self, i32> {
match read_child_mem(notif_fd, notif.id, notif.pid, ptr, MSGHDR_SIZE) {
Ok(b) => Self::parse(&b).ok_or(libc::EFAULT),
Err(_) => Err(libc::EFAULT),
}
}
pub(crate) fn connected(&self) -> bool {
self.name_ptr == 0 || self.namelen == 0
}
}
const MMSGHDR_SIZE: usize = 64;
const MSG_LEN_OFFSET: usize = 56;
pub(crate) fn mmsg_entry_ptr(msgvec_ptr: u64, i: usize) -> u64 {
msgvec_ptr + (i * MMSGHDR_SIZE) as u64
}
pub(crate) fn mmsg_msglen_addr(entry_ptr: u64) -> u64 {
entry_ptr + MSG_LEN_OFFSET as u64
}
pub(crate) fn materialize_msg(
notif: &SeccompNotif,
notif_fd: RawFd,
hdr: &ChildMsghdr,
addr: Vec<u8>,
translate_unix_control: bool,
pinned: Option<OwnedFd>,
) -> Result<MaterializedMsg, i32> {
let iovlen = (hdr.iovlen as usize).min(1024);
let iov_bytes = read_child_mem(notif_fd, notif.id, notif.pid, hdr.iov_ptr, iovlen * 16)
.map_err(|_| libc::EIO)?;
let data = flatten_iovecs(notif, notif_fd, &iov_bytes, iovlen)?;
let (control, scm_fds) = materialize_control(
notif,
notif_fd,
hdr.control_ptr,
hdr.controllen,
translate_unix_control,
)?;
Ok(MaterializedMsg {
data,
control,
addr,
_scm_fds: scm_fds,
_pinned: pinned,
})
}
pub(crate) struct MaterializedMsg {
pub(crate) data: Vec<u8>,
pub(crate) control: Option<Vec<u8>>,
pub(crate) addr: Vec<u8>,
pub(crate) _scm_fds: Vec<OwnedFd>,
pub(crate) _pinned: Option<OwnedFd>,
}
fn flatten_iovecs(
notif: &SeccompNotif,
notif_fd: RawFd,
iov_bytes: &[u8],
iovlen: usize,
) -> Result<Vec<u8>, i32> {
let mut data: Vec<u8> = Vec::new();
for i in 0..iovlen {
let off = i * 16;
if off + 16 > iov_bytes.len() {
break;
}
let base = u64::from_ne_bytes(iov_bytes[off..off + 8].try_into().unwrap());
let len = u64::from_ne_bytes(iov_bytes[off + 8..off + 16].try_into().unwrap()) as usize;
if base == 0 || len == 0 {
continue;
}
if len > MAX_SEND_BUF || data.len().saturating_add(len) > MAX_SEND_BUF {
return Err(libc::EMSGSIZE);
}
data.extend_from_slice(
&read_child_mem(notif_fd, notif.id, notif.pid, base, len).map_err(|_| libc::EIO)?,
);
}
Ok(data)
}
#[cfg(test)]
mod tests {
use super::*;
fn cmsg_hdr(cmsg_len: usize, level: i32, ctype: i32) -> Vec<u8> {
let mut b = vec![0u8; 16];
b[0..8].copy_from_slice(&cmsg_len.to_ne_bytes());
b[8..12].copy_from_slice(&level.to_ne_bytes());
b[12..16].copy_from_slice(&ctype.to_ne_bytes());
b
}
#[test]
fn scm_rights_rejects_overflowing_cmsg_len() {
let buf = cmsg_hdr(usize::MAX - 7, libc::SOL_SOCKET, libc::SCM_RIGHTS);
assert_eq!(translate_scm_rights(0, &buf).map(drop), Err(libc::EINVAL));
}
#[test]
fn scm_rights_rejects_short_header() {
let buf = cmsg_hdr(8, libc::SOL_SOCKET, libc::SCM_RIGHTS); assert_eq!(translate_scm_rights(0, &buf).map(drop), Err(libc::EINVAL));
}
#[test]
fn scm_rights_rejects_cmsg_running_past_buffer() {
let buf = cmsg_hdr(17, libc::SOL_SOCKET, libc::SCM_RIGHTS); assert_eq!(translate_scm_rights(0, &buf).map(drop), Err(libc::EINVAL));
}
#[test]
fn scm_rights_rejects_credentials() {
let buf = cmsg_hdr(16, libc::SOL_SOCKET, libc::SCM_CREDENTIALS);
assert_eq!(translate_scm_rights(0, &buf).map(drop), Err(libc::EPERM));
}
#[test]
fn scm_rights_passes_through_empty_and_non_socket_cmsg() {
let (out, fds) = translate_scm_rights(0, &[]).unwrap();
assert!(out.is_empty() && fds.is_empty());
let buf = cmsg_hdr(16, libc::IPPROTO_IP, 2 );
let (out, fds) = translate_scm_rights(0, &buf).unwrap();
assert_eq!(out, buf);
assert!(fds.is_empty());
}
#[test]
fn child_msghdr_parses_lp64_fields() {
let mut b = vec![0u8; MSGHDR_SIZE];
b[0..8].copy_from_slice(&0x1111u64.to_ne_bytes());
b[8..12].copy_from_slice(&7u32.to_ne_bytes());
b[16..24].copy_from_slice(&0x2222u64.to_ne_bytes());
b[24..32].copy_from_slice(&3u64.to_ne_bytes());
b[32..40].copy_from_slice(&0x3333u64.to_ne_bytes());
b[40..48].copy_from_slice(&64u64.to_ne_bytes());
let h = ChildMsghdr::parse(&b).unwrap();
assert_eq!(
(h.name_ptr, h.namelen, h.iov_ptr, h.iovlen, h.control_ptr, h.controllen),
(0x1111, 7, 0x2222, 3, 0x3333, 64)
);
assert!(!h.connected());
}
#[test]
fn child_msghdr_rejects_short_buffer() {
assert!(ChildMsghdr::parse(&[0u8; MSGHDR_SIZE - 1]).is_none());
}
#[test]
fn child_msghdr_connected_on_null_or_empty_name() {
let b = vec![0u8; MSGHDR_SIZE];
assert!(ChildMsghdr::parse(&b).unwrap().connected());
let mut b2 = vec![0u8; MSGHDR_SIZE];
b2[0..8].copy_from_slice(&0x1111u64.to_ne_bytes());
assert!(ChildMsghdr::parse(&b2).unwrap().connected());
}
#[test]
fn mmsg_addressing_helpers() {
assert_eq!(mmsg_entry_ptr(0x1000, 0), 0x1000);
assert_eq!(mmsg_entry_ptr(0x1000, 2), 0x1000 + 2 * MMSGHDR_SIZE as u64);
assert_eq!(mmsg_msglen_addr(0x1000), 0x1000 + MSG_LEN_OFFSET as u64);
}
fn v6_sockaddr_bytes(ip: Ipv6Addr, port: u16) -> Vec<u8> {
let mut sa6: libc::sockaddr_in6 = unsafe { std::mem::zeroed() };
sa6.sin6_family = libc::AF_INET6 as u16;
sa6.sin6_port = port.to_be();
sa6.sin6_addr.s6_addr = ip.octets();
unsafe {
std::slice::from_raw_parts(
&sa6 as *const _ as *const u8,
std::mem::size_of::<libc::sockaddr_in6>(),
)
}
.to_vec()
}
#[test]
fn parse_ip_canonicalizes_v4_mapped_ipv6() {
let bytes = v6_sockaddr_bytes("::ffff:169.254.169.254".parse().unwrap(), 80);
assert_eq!(
parse_ip_from_sockaddr(&bytes),
Some("169.254.169.254".parse::<IpAddr>().unwrap())
);
}
#[test]
fn parse_ip_keeps_plain_ipv6_untouched() {
let bytes = v6_sockaddr_bytes(Ipv6Addr::LOCALHOST, 80);
assert_eq!(parse_ip_from_sockaddr(&bytes), Some(IpAddr::V6(Ipv6Addr::LOCALHOST)));
assert!(sockaddr_is_ipv6(&bytes));
}
#[test]
fn sockaddr_family_is_independent_of_canonical_ip() {
let mapped = v6_sockaddr_bytes("::ffff:1.2.3.4".parse().unwrap(), 80);
assert!(sockaddr_is_ipv6(&mapped));
let v4_family = (AF_INET as u16).to_ne_bytes();
assert!(!sockaddr_is_ipv6(&[v4_family[0], v4_family[1], 0, 0]));
}
}