#![cfg(any(
target_os = "linux",
target_os = "freebsd",
target_os = "dragonfly",
target_os = "netbsd",
target_os = "illumos",
target_os = "solaris",
))]
pub(super) const fn cmsg_align(len: usize) -> usize {
let align = core::mem::size_of::<usize>();
(len + align - 1) & !(align - 1)
}
pub(super) unsafe trait CmsgPlatform {
type Hdr;
type Cred;
type Msghdr;
const TARGET_LEVEL: i32;
const TARGET_TYPE: i32;
fn cmsg_len(hdr: &Self::Hdr) -> usize;
fn cmsg_level(hdr: &Self::Hdr) -> i32;
fn cmsg_type(hdr: &Self::Hdr) -> i32;
fn msg_control(mhdr: &Self::Msghdr) -> *const u8;
fn msg_controllen(mhdr: &Self::Msghdr) -> usize;
unsafe fn extract_pid_uid(data: *const u8, len: usize) -> Option<(u32, u32)>;
fn cmsg_hdr_size() -> usize {
cmsg_align(core::mem::size_of::<Self::Hdr>())
}
}
pub(super) struct CmsgIter<'a, P: CmsgPlatform> {
base: *const u8,
controllen: usize,
cursor: usize,
_marker: core::marker::PhantomData<&'a P::Msghdr>,
}
impl<'a, P: CmsgPlatform> CmsgIter<'a, P> {
pub(super) unsafe fn new(mhdr: &'a P::Msghdr) -> Self {
let base = P::msg_control(mhdr);
let controllen = P::msg_controllen(mhdr);
debug_assert!(
!base.is_null() || controllen == 0,
"msg_control is null but msg_controllen is non-zero"
);
Self {
base,
controllen,
cursor: 0,
_marker: core::marker::PhantomData,
}
}
}
impl<'a, P: CmsgPlatform> Iterator for CmsgIter<'a, P>
where
P::Hdr: 'a,
{
type Item = (&'a P::Hdr, *const u8);
fn next(&mut self) -> Option<(&'a P::Hdr, *const u8)> {
if self.base.is_null() {
return None;
}
let next_min = self.cursor.checked_add(P::cmsg_hdr_size())?;
if next_min > self.controllen {
return None;
}
let hdr_ptr = unsafe { self.base.add(self.cursor) } as *const P::Hdr;
let hdr: &P::Hdr = unsafe { &*hdr_ptr };
let len = P::cmsg_len(hdr);
if len > self.controllen - self.cursor {
return None;
}
let data_ptr = unsafe { self.base.add(self.cursor + P::cmsg_hdr_size()) };
let advance = core::cmp::max(cmsg_align(len), P::cmsg_hdr_size());
self.cursor = self.cursor.saturating_add(advance);
Some((hdr, data_ptr))
}
}
pub(super) fn find_credential<P: CmsgPlatform>(mhdr: &P::Msghdr) -> Option<(u32, u32)> {
let needed = P::cmsg_hdr_size() + core::mem::size_of::<P::Cred>();
let iter = unsafe { CmsgIter::<P>::new(mhdr) };
for (hdr, data_ptr) in iter {
if P::cmsg_level(hdr) != P::TARGET_LEVEL || P::cmsg_type(hdr) != P::TARGET_TYPE {
continue;
}
if P::cmsg_len(hdr) < needed {
return None;
}
let payload_len = P::cmsg_len(hdr).saturating_sub(P::cmsg_hdr_size());
return unsafe { P::extract_pid_uid(data_ptr, payload_len) };
}
None
}
#[cfg(all(test, target_os = "linux"))]
mod miri_cmsg_tests {
use super::super::plat;
use super::*;
use core::mem;
fn cmsg_hdr_size() -> usize {
<plat::LinuxCmsg as CmsgPlatform>::cmsg_hdr_size()
}
fn cmsg_space_ucred() -> usize {
cmsg_align(cmsg_hdr_size() + mem::size_of::<plat::Ucred>())
}
#[repr(align(8))]
struct AlignedBuf([u8; 512]);
fn write_scm_credentials(buf: &mut [u8], offset: usize, pid: i32, uid: u32, gid: u32) {
let hdr_size = cmsg_hdr_size();
let total = cmsg_space_ucred();
let slice = &mut buf[offset..offset + total];
slice.fill(0);
let cmsg_len: usize = hdr_size + mem::size_of::<plat::Ucred>();
slice[..mem::size_of::<usize>()].copy_from_slice(&cmsg_len.to_ne_bytes());
slice[mem::size_of::<usize>()..mem::size_of::<usize>() + 4]
.copy_from_slice(&plat::SOL_SOCKET.to_ne_bytes());
slice[mem::size_of::<usize>() + 4..mem::size_of::<usize>() + 8]
.copy_from_slice(&plat::SCM_CREDENTIALS.to_ne_bytes());
let ucred_off = hdr_size;
slice[ucred_off..ucred_off + 4].copy_from_slice(&pid.to_ne_bytes());
slice[ucred_off + 4..ucred_off + 8].copy_from_slice(&uid.to_ne_bytes());
slice[ucred_off + 8..ucred_off + 12].copy_from_slice(&gid.to_ne_bytes());
}
fn make_mhdr(anc_buf: &[u8], controllen: usize) -> plat::Msghdr {
plat::Msghdr {
msg_name: core::ptr::null_mut(),
msg_namelen: 0,
_pad1: 0,
msg_iov: core::ptr::null_mut(),
msg_iovlen: 0,
msg_control: anc_buf.as_ptr() as *mut _,
msg_controllen: controllen,
msg_flags: 0,
_pad2: 0,
}
}
#[test]
fn empty_buffer_returns_none() {
let buf = [];
let mhdr = make_mhdr(&buf, 0);
let result = plat::peer_pid_after_recv(0, &mhdr);
assert_eq!(result, None);
}
#[test]
fn single_scm_credentials_returns_pid_uid() {
let mut abuf = AlignedBuf([0u8; 512]);
write_scm_credentials(&mut abuf.0, 0, 1234, 1000, 100);
let controllen = cmsg_space_ucred();
let mhdr = make_mhdr(&abuf.0, controllen);
let result = plat::peer_pid_after_recv(0, &mhdr);
assert_eq!(result, Some((1234, 1000)));
}
#[test]
fn truncated_cmsg_length_returns_none() {
let mut abuf = AlignedBuf([0u8; 512]);
let buf = &mut abuf.0;
let hdr_size = cmsg_hdr_size();
let truncated_len: usize = hdr_size;
buf[..mem::size_of::<usize>()].copy_from_slice(&truncated_len.to_ne_bytes());
buf[mem::size_of::<usize>()..mem::size_of::<usize>() + 4]
.copy_from_slice(&plat::SOL_SOCKET.to_ne_bytes());
buf[mem::size_of::<usize>() + 4..mem::size_of::<usize>() + 8]
.copy_from_slice(&plat::SCM_CREDENTIALS.to_ne_bytes());
let controllen = cmsg_space_ucred();
let mhdr = make_mhdr(buf, controllen);
let result = plat::peer_pid_after_recv(0, &mhdr);
assert_eq!(result, None, "truncated cmsg must not produce a pid");
}
#[test]
fn unknown_cmsg_type_returns_none() {
let mut abuf = AlignedBuf([0u8; 512]);
let buf = &mut abuf.0;
let hdr_size = cmsg_hdr_size();
let cmsg_len: usize = hdr_size + mem::size_of::<plat::Ucred>();
buf[..mem::size_of::<usize>()].copy_from_slice(&cmsg_len.to_ne_bytes());
buf[mem::size_of::<usize>()..mem::size_of::<usize>() + 4]
.copy_from_slice(&plat::SOL_SOCKET.to_ne_bytes());
let wrong_type: i32 = 99;
buf[mem::size_of::<usize>() + 4..mem::size_of::<usize>() + 8]
.copy_from_slice(&wrong_type.to_ne_bytes());
let controllen = cmsg_space_ucred();
let mhdr = make_mhdr(buf, controllen);
let result = plat::peer_pid_after_recv(0, &mhdr);
assert_eq!(result, None, "unknown cmsg_type must not produce a pid");
}
#[test]
fn multiple_cmsgs_finds_credentials_in_second() {
let space = cmsg_space_ucred();
let mut abuf = AlignedBuf([0u8; 512]);
let buf = &mut abuf.0;
let hdr_size = cmsg_hdr_size();
let cmsg_len: usize = hdr_size + mem::size_of::<plat::Ucred>();
buf[..mem::size_of::<usize>()].copy_from_slice(&cmsg_len.to_ne_bytes());
buf[mem::size_of::<usize>()..mem::size_of::<usize>() + 4]
.copy_from_slice(&plat::SOL_SOCKET.to_ne_bytes());
let wrong_type: i32 = 99;
buf[mem::size_of::<usize>() + 4..mem::size_of::<usize>() + 8]
.copy_from_slice(&wrong_type.to_ne_bytes());
write_scm_credentials(buf, space, 5678, 2000, 200);
let controllen = space * 2;
let mhdr = make_mhdr(buf, controllen);
let result = plat::peer_pid_after_recv(0, &mhdr);
assert_eq!(result, Some((5678, 2000)));
}
#[test]
fn trailing_padding_does_not_confuse_walker() {
let mut abuf = AlignedBuf([0u8; 512]);
write_scm_credentials(&mut abuf.0, 0, 999, 42, 42);
let controllen = 128;
let mhdr = make_mhdr(&abuf.0, controllen);
let result = plat::peer_pid_after_recv(0, &mhdr);
assert_eq!(result, Some((999, 42)));
}
#[test]
fn bsd_shape_buffer_returns_pid_euid() {
use super::super::platform::bsd::{BsdCmsg, Cmsgcred, Cmsghdr, Msghdr};
use core::mem;
let bsd_hdr_size = <BsdCmsg as CmsgPlatform>::cmsg_hdr_size();
assert_eq!(bsd_hdr_size, cmsg_align(mem::size_of::<Cmsghdr>()));
assert_eq!(bsd_hdr_size, 16);
let total = bsd_hdr_size + mem::size_of::<Cmsgcred>();
let aligned_total = cmsg_align(total);
#[repr(align(8))]
struct AlignedBuf([u8; 256]);
let mut buf_wrapper = AlignedBuf([0u8; 256]);
let buf = &mut buf_wrapper.0[..aligned_total];
let cmsg_len: u32 = total as u32;
let sol_socket: i32 = 0xffff; let scm_creds: i32 = 0x03; buf[0..4].copy_from_slice(&cmsg_len.to_ne_bytes());
buf[4..8].copy_from_slice(&sol_socket.to_ne_bytes());
buf[8..12].copy_from_slice(&scm_creds.to_ne_bytes());
let expected_pid: i32 = 9999;
let expected_uid: u32 = 33;
let expected_euid: u32 = 1500;
let cred_off = bsd_hdr_size;
buf[cred_off..cred_off + 4].copy_from_slice(&expected_pid.to_ne_bytes());
buf[cred_off + 4..cred_off + 8].copy_from_slice(&expected_uid.to_ne_bytes());
buf[cred_off + 8..cred_off + 12].copy_from_slice(&expected_euid.to_ne_bytes());
let mhdr = Msghdr {
msg_name: core::ptr::null_mut(),
msg_namelen: 0,
_pad1: 0,
msg_iov: core::ptr::null_mut(),
msg_iovlen: 0,
_pad2: 0,
msg_control: buf.as_mut_ptr() as *mut _,
msg_controllen: aligned_total as u32,
msg_flags: 0,
};
let result = find_credential::<BsdCmsg>(&mhdr);
assert_eq!(result, Some((expected_pid as u32, expected_euid)));
}
#[test]
fn illumos_shape_buffer_returns_pid_euid() {
use super::super::platform::illumos::{Cmsghdr, IllumosCmsg, Msghdr};
use core::mem;
let illumos_hdr_size = <IllumosCmsg as CmsgPlatform>::cmsg_hdr_size();
assert_eq!(illumos_hdr_size, cmsg_align(mem::size_of::<Cmsghdr>()));
assert_eq!(illumos_hdr_size, 16);
let payload_len = 8usize;
let total = illumos_hdr_size + payload_len;
let aligned_total = cmsg_align(total);
#[repr(align(8))]
struct AlignedBuf([u8; 256]);
let mut buf_wrapper = AlignedBuf([0u8; 256]);
let buf = &mut buf_wrapper.0[..aligned_total];
let cmsg_len: u32 = total as u32;
let sol_socket: i32 = 0xffff; let scm_ucred: i32 = 0x1012; buf[0..4].copy_from_slice(&cmsg_len.to_ne_bytes());
buf[4..8].copy_from_slice(&sol_socket.to_ne_bytes());
buf[8..12].copy_from_slice(&scm_ucred.to_ne_bytes());
let expected_pid: i32 = 4321;
let expected_uid: u32 = 500;
let pay_off = illumos_hdr_size;
buf[pay_off..pay_off + 4].copy_from_slice(&expected_pid.to_ne_bytes());
buf[pay_off + 4..pay_off + 8].copy_from_slice(&expected_uid.to_ne_bytes());
let mhdr = Msghdr {
msg_name: core::ptr::null_mut(),
msg_namelen: 0,
_pad1: 0,
msg_iov: core::ptr::null_mut(),
msg_iovlen: 0,
_pad2: 0,
msg_control: buf.as_mut_ptr() as *mut _,
msg_controllen: aligned_total as u32,
msg_flags: 0,
};
let result = find_credential::<IllumosCmsg>(&mhdr);
assert_eq!(result, Some((expected_pid as u32, expected_uid)));
}
#[test]
fn illumos_opaque_extraction_returns_none_on_invalid() {
use super::super::platform::illumos::{IllumosCmsg, Msghdr};
let illumos_hdr_size = <IllumosCmsg as CmsgPlatform>::cmsg_hdr_size();
let payload_len = 8usize;
let total = illumos_hdr_size + payload_len;
let aligned_total = cmsg_align(total);
#[repr(align(8))]
struct AlignedBuf([u8; 256]);
let mut buf_wrapper = AlignedBuf([0u8; 256]);
let buf = &mut buf_wrapper.0[..aligned_total];
let cmsg_len: u32 = total as u32;
let sol_socket: i32 = 0xffff;
let scm_ucred: i32 = 0x1012;
buf[0..4].copy_from_slice(&cmsg_len.to_ne_bytes());
buf[4..8].copy_from_slice(&sol_socket.to_ne_bytes());
buf[8..12].copy_from_slice(&scm_ucred.to_ne_bytes());
let neg_pid: i32 = -1;
let uid: u32 = 1000;
let pay_off = illumos_hdr_size;
buf[pay_off..pay_off + 4].copy_from_slice(&neg_pid.to_ne_bytes());
buf[pay_off + 4..pay_off + 8].copy_from_slice(&uid.to_ne_bytes());
let mhdr = Msghdr {
msg_name: core::ptr::null_mut(),
msg_namelen: 0,
_pad1: 0,
msg_iov: core::ptr::null_mut(),
msg_iovlen: 0,
_pad2: 0,
msg_control: buf.as_mut_ptr() as *mut _,
msg_controllen: aligned_total as u32,
msg_flags: 0,
};
let result = find_credential::<IllumosCmsg>(&mhdr);
assert_eq!(result, None, "negative pid must produce None");
}
#[test]
fn illumos_walker_rejects_truncated_cmsg() {
use super::super::platform::illumos::{IllumosCmsg, Msghdr};
let illumos_hdr_size = <IllumosCmsg as CmsgPlatform>::cmsg_hdr_size();
let truncated_len: u32 = (illumos_hdr_size - 1) as u32;
#[repr(align(8))]
struct AlignedBuf([u8; 256]);
let mut buf_wrapper = AlignedBuf([0u8; 256]);
let buf = &mut buf_wrapper.0[..illumos_hdr_size + 8];
buf[0..4].copy_from_slice(&truncated_len.to_ne_bytes());
let sol_socket: i32 = 0xffff;
let scm_ucred: i32 = 0x1012;
buf[4..8].copy_from_slice(&sol_socket.to_ne_bytes());
buf[8..12].copy_from_slice(&scm_ucred.to_ne_bytes());
let mhdr = Msghdr {
msg_name: core::ptr::null_mut(),
msg_namelen: 0,
_pad1: 0,
msg_iov: core::ptr::null_mut(),
msg_iovlen: 0,
_pad2: 0,
msg_control: buf.as_mut_ptr() as *mut _,
msg_controllen: buf.len() as u32,
msg_flags: 0,
};
let result = find_credential::<IllumosCmsg>(&mhdr);
assert_eq!(
result, None,
"truncated illumos cmsg must not produce a pid"
);
}
#[test]
fn zero_cmsg_len_does_not_infinite_loop() {
let mut abuf = AlignedBuf([0u8; 512]);
let buf = &mut abuf.0;
let zero_len: usize = 0;
buf[..mem::size_of::<usize>()].copy_from_slice(&zero_len.to_ne_bytes());
buf[mem::size_of::<usize>()..mem::size_of::<usize>() + 4]
.copy_from_slice(&plat::SOL_SOCKET.to_ne_bytes());
buf[mem::size_of::<usize>() + 4..mem::size_of::<usize>() + 8]
.copy_from_slice(&plat::SCM_CREDENTIALS.to_ne_bytes());
let mhdr = make_mhdr(buf, buf.len());
let result = plat::peer_pid_after_recv(0, &mhdr);
assert_eq!(result, None);
}
}