From b2a9c4f18c3e2314bf737ef842e5d5c7de94f33a Mon Sep 17 00:00:00 2001 From: Greg V Date: Wed, 15 Apr 2020 01:16:01 +0300 Subject: [PATCH] Add UnixCredentials support on FreeBSD/DragonFly (cmsgcred/SCM_CREDS) --- CHANGELOG.md | 2 + Cargo.toml | 2 +- src/sys/socket/mod.rs | 90 ++++++++++++++++++++++++++++++++++++++++- test/sys/test_socket.rs | 46 ++++++++++++--------- 4 files changed, 120 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4794c11c9f..a9c28b4a90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). receive offload (GRO) ([#1209](https://github.com/nix-rust/nix/pull/1209)) - Added support for `sendmmsg` and `recvmmsg` calls (#[1208](https://github.com/nix-rust/nix/pull/1208)) +- Added support for `SCM_CREDS` messages (`UnixCredentials`) on FreeBSD/DragonFly + (#[1216](https://github.com/nix-rust/nix/pull/1216)) ### Changed - Changed `fallocate` return type from `c_int` to `()` (#[1201](https://github.com/nix-rust/nix/pull/1201)) diff --git a/Cargo.toml b/Cargo.toml index 746cefc838..1277fbc434 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ exclude = [ ] [dependencies] -libc = { version = "0.2.69", features = [ "extra_traits" ] } +libc = { git = "https://github.com/rust-lang/libc/", features = [ "extra_traits" ] } bitflags = "1.1" cfg-if = "0.1.10" void = "1.0.2" diff --git a/src/sys/socket/mod.rs b/src/sys/socket/mod.rs index 9b770fa82b..1f627614f0 100644 --- a/src/sys/socket/mod.rs +++ b/src/sys/socket/mod.rs @@ -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 @@ -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 for UnixCredentials { + fn from(cred: libc::cmsgcred) -> Self { + UnixCredentials(cred) + } + } } } @@ -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. /// @@ -510,6 +564,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) => { + 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)) @@ -603,6 +662,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. /// @@ -682,6 +755,13 @@ 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, we just zero it + // to make sure it's not uninitialized memory + unsafe { ptr::write_bytes(cmsg_data, 0, self.len()) }; + return + } #[cfg(any(target_os = "android", target_os = "linux"))] ControlMessage::AlgSetIv(iv) => { let af_alg_iv = libc::af_alg_iv { @@ -738,6 +818,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::() + } #[cfg(any(target_os = "android", target_os = "linux"))] ControlMessage::AlgSetIv(iv) => { mem::size_of::() + iv.len() @@ -763,6 +847,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, @@ -777,6 +863,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, #[cfg(any(target_os = "android", target_os = "linux"))] ControlMessage::AlgSetIv(_) => { libc::ALG_SET_IV diff --git a/test/sys/test_socket.rs b/test/sys/test_socket.rs index ae5ac66c16..20ff8d78d3 100644 --- a/test/sys/test_socket.rs +++ b/test/sys/test_socket.rs @@ -752,29 +752,36 @@ 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(); } @@ -782,20 +789,23 @@ fn test_scm_credentials() { { 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);