Skip to content

Commit

Permalink
Add UnixCredentials support on FreeBSD/DragonFly (cmsgcred/SCM_CREDS)
Browse files Browse the repository at this point in the history
  • Loading branch information
valpackett committed Apr 15, 2020
1 parent 154ff58 commit c5accbb
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 19 deletions.
88 changes: 87 additions & 1 deletion src/sys/socket/mod.rs
Expand Up @@ -189,12 +189,22 @@ cfg_if! {
if #[cfg(any(target_os = "android", target_os = "linux"))] {
/// Unix credentials of the sending process.
///
/// This struct is used with the `SO_PEERCRED` ancillary message for UNIX sockets.
/// This struct is used with the `SO_PEERCRED` ancillary message
/// and the `SCM_CREDENTIALS` control message for UNIX sockets.
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct UnixCredentials(libc::ucred);

impl UnixCredentials {
/// Creates a new instance with the credentials of the current process
pub fn new() -> Self {
UnixCredentials(libc::ucred {
pid: crate::unistd::getpid().as_raw(),
uid: crate::unistd::getuid().as_raw(),
gid: crate::unistd::getgid().as_raw(),
})
}

/// Returns the process identifier
pub fn pid(&self) -> libc::pid_t {
self.0.pid
Expand Down Expand Up @@ -222,6 +232,46 @@ cfg_if! {
self.0
}
}
} else if #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] {
/// Unix credentials of the sending process.
///
/// This struct is used with the `SCM_CREDS` ancillary message for UNIX sockets.
#[repr(transparent)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct UnixCredentials(libc::cmsgcred);

impl UnixCredentials {
/// Returns the process identifier
pub fn pid(&self) -> libc::pid_t {
self.0.cmcred_pid
}

/// Returns the real user identifier
pub fn uid(&self) -> libc::uid_t {
self.0.cmcred_uid
}

/// Returns the effective user identifier
pub fn euid(&self) -> libc::uid_t {
self.0.cmcred_euid
}

/// Returns the real group identifier
pub fn gid(&self) -> libc::gid_t {
self.0.cmcred_gid
}

/// Returns a list group identifiers (the first one being the effective GID)
pub fn groups(&self) -> &[libc::gid_t] {
unsafe { slice::from_raw_parts(self.0.cmcred_groups.as_ptr() as *const libc::gid_t, self.0.cmcred_ngroups as _) }
}
}

impl From<libc::cmsgcred> for UnixCredentials {
fn from(cred: libc::cmsgcred) -> Self {
UnixCredentials(cred)
}
}
}
}

Expand Down Expand Up @@ -369,6 +419,10 @@ pub enum ControlMessageOwned {
/// [`ControlMessage::ScmCredentials`][#enum.ControlMessage.html#variant.ScmCredentials]
#[cfg(any(target_os = "android", target_os = "linux"))]
ScmCredentials(UnixCredentials),
/// Received version of
/// [`ControlMessage::ScmCreds`][#enum.ControlMessage.html#variant.ScmCreds]
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
ScmCreds(UnixCredentials),
/// A message of type `SCM_TIMESTAMP`, containing the time the
/// packet was received by the kernel.
///
Expand Down Expand Up @@ -498,6 +552,11 @@ impl ControlMessageOwned {
let cred: libc::ucred = ptr::read_unaligned(p as *const _);
ControlMessageOwned::ScmCredentials(cred.into())
}
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
(libc::SOL_SOCKET, /* libc::SCM_CREDS */ 0x03) => {
let cred: libc::cmsgcred = ptr::read_unaligned(p as *const _);
ControlMessageOwned::ScmCreds(cred.into())
}
(libc::SOL_SOCKET, libc::SCM_TIMESTAMP) => {
let tv: libc::timeval = ptr::read_unaligned(p as *const _);
ControlMessageOwned::ScmTimestamp(TimeVal::from(tv))
Expand Down Expand Up @@ -586,6 +645,20 @@ pub enum ControlMessage<'a> {
/// [`unix(7)`](http://man7.org/linux/man-pages/man7/unix.7.html) man page.
#[cfg(any(target_os = "android", target_os = "linux"))]
ScmCredentials(&'a UnixCredentials),
/// A message of type `SCM_CREDS`, containing the pid, uid, euid, gid and groups of
/// a process connected to the socket.
///
/// This is similar to the socket options `LOCAL_CREDS` and `LOCAL_PEERCRED`, but
/// requires a process to explicitly send its credentials.
///
/// Credentials are always overwritten by the kernel, so this variant does have
/// any data, unlike the receive-side
/// [`ControlMessageOwned::ScmCreds`][#enum.ControlMessageOwned.html#variant.ScmCreds].
///
/// For further information, please refer to the
/// [`unix(4)`](https://www.freebsd.org/cgi/man.cgi?query=unix) man page.
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
ScmCreds,

/// Set IV for `AF_ALG` crypto API.
///
Expand Down Expand Up @@ -655,6 +728,11 @@ impl<'a> ControlMessage<'a> {
ControlMessage::ScmCredentials(creds) => {
&creds.0 as *const libc::ucred as *const u8
}
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
ControlMessage::ScmCreds => {
// The kernel overwrites the data, don't worry
return
}
#[cfg(any(target_os = "android", target_os = "linux"))]
ControlMessage::AlgSetIv(iv) => {
unsafe {
Expand Down Expand Up @@ -696,6 +774,10 @@ impl<'a> ControlMessage<'a> {
ControlMessage::ScmCredentials(creds) => {
mem::size_of_val(creds)
}
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
ControlMessage::ScmCreds => {
mem::size_of::<libc::cmsgcred>()
}
#[cfg(any(target_os = "android", target_os = "linux"))]
ControlMessage::AlgSetIv(iv) => {
mem::size_of::<libc::af_alg_iv>() + iv.len()
Expand All @@ -717,6 +799,8 @@ impl<'a> ControlMessage<'a> {
ControlMessage::ScmRights(_) => libc::SOL_SOCKET,
#[cfg(any(target_os = "android", target_os = "linux"))]
ControlMessage::ScmCredentials(_) => libc::SOL_SOCKET,
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
ControlMessage::ScmCreds => libc::SOL_SOCKET,
#[cfg(any(target_os = "android", target_os = "linux"))]
ControlMessage::AlgSetIv(_) | ControlMessage::AlgSetOp(_) |
ControlMessage::AlgSetAeadAssoclen(_) => libc::SOL_ALG ,
Expand All @@ -729,6 +813,8 @@ impl<'a> ControlMessage<'a> {
ControlMessage::ScmRights(_) => libc::SCM_RIGHTS,
#[cfg(any(target_os = "android", target_os = "linux"))]
ControlMessage::ScmCredentials(_) => libc::SCM_CREDENTIALS,
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
ControlMessage::ScmCreds => /* libc::SCM_CREDS */ 0x03, // https://github.com/rust-lang/libc/pull/1740
#[cfg(any(target_os = "android", target_os = "linux"))]
ControlMessage::AlgSetIv(_) => {
libc::ALG_SET_IV
Expand Down
46 changes: 28 additions & 18 deletions test/sys/test_socket.rs
Expand Up @@ -540,50 +540,60 @@ pub fn test_sendmsg_empty_cmsgs() {
}
}

#[cfg(any(target_os = "android", target_os = "linux"))]
#[cfg(any(
target_os = "android",
target_os = "linux",
target_os = "freebsd",
target_os = "dragonfly",
))]
#[test]
fn test_scm_credentials() {
use libc;
use nix::sys::uio::IoVec;
use nix::unistd::{close, getpid, getuid, getgid};
use nix::sys::socket::{socketpair, sendmsg, recvmsg, setsockopt,
AddressFamily, SockType, SockFlag,
ControlMessage, ControlMessageOwned, MsgFlags};
ControlMessage, ControlMessageOwned, MsgFlags,
UnixCredentials};
#[cfg(any(target_os = "android", target_os = "linux"))]
use nix::sys::socket::sockopt::PassCred;

let (send, recv) = socketpair(AddressFamily::Unix, SockType::Stream, None, SockFlag::empty())
.unwrap();
#[cfg(any(target_os = "android", target_os = "linux"))]
setsockopt(recv, PassCred, &true).unwrap();

{
let iov = [IoVec::from_slice(b"hello")];
let cred = libc::ucred {
pid: getpid().as_raw(),
uid: getuid().as_raw(),
gid: getgid().as_raw(),
}.into();
#[cfg(any(target_os = "android", target_os = "linux"))]
let cred = UnixCredentials::new();
#[cfg(any(target_os = "android", target_os = "linux"))]
let cmsg = ControlMessage::ScmCredentials(&cred);
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
let cmsg = ControlMessage::ScmCreds;
assert_eq!(sendmsg(send, &iov, &[cmsg], MsgFlags::empty(), None).unwrap(), 5);
close(send).unwrap();
}

{
let mut buf = [0u8; 5];
let iov = [IoVec::from_mut_slice(&mut buf[..])];
let mut cmsgspace = cmsg_space!(libc::ucred);
let mut cmsgspace = cmsg_space!(UnixCredentials);
let msg = recvmsg(recv, &iov, Some(&mut cmsgspace), MsgFlags::empty()).unwrap();
let mut received_cred = None;

for cmsg in msg.cmsgs() {
if let ControlMessageOwned::ScmCredentials(cred) = cmsg {
assert!(received_cred.is_none());
assert_eq!(cred.pid(), getpid().as_raw());
assert_eq!(cred.uid(), getuid().as_raw());
assert_eq!(cred.gid(), getgid().as_raw());
received_cred = Some(cred);
} else {
panic!("unexpected cmsg");
}
let cred = match cmsg {
#[cfg(any(target_os = "android", target_os = "linux"))]
ControlMessageOwned::ScmCredentials(cred) => cred,
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
ControlMessageOwned::ScmCreds(cred) => cred,
other => panic!("unexpected cmsg {:?}", other),
};
assert!(received_cred.is_none());
assert_eq!(cred.pid(), getpid().as_raw());
assert_eq!(cred.uid(), getuid().as_raw());
assert_eq!(cred.gid(), getgid().as_raw());
received_cred = Some(cred);
}
received_cred.expect("no creds received");
assert_eq!(msg.bytes, 5);
Expand Down

0 comments on commit c5accbb

Please sign in to comment.