Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add UnixCredentials support on FreeBSD/DragonFly #1216

Merged
merged 1 commit into from May 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Expand Up @@ -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"
Expand Down
90 changes: 89 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"))] {
valpackett marked this conversation as resolved.
Show resolved Hide resolved
/// 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 {
valpackett marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -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))
Expand Down Expand Up @@ -603,6 +662,20 @@ pub enum ControlMessage<'a> {
/// [`unix(7)`](http://man7.org/linux/man-pages/man7/unix.7.html) man page.
valpackett marked this conversation as resolved.
Show resolved Hide resolved
#[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 @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this result in cmsg_data being uninitialized? That way might lead to UB. I think that you should zero it instead.

}
#[cfg(any(target_os = "android", target_os = "linux"))]
ControlMessage::AlgSetIv(iv) => {
let af_alg_iv = libc::af_alg_iv {
Expand Down Expand Up @@ -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::<libc::cmsgcred>()
}
#[cfg(any(target_os = "android", target_os = "linux"))]
ControlMessage::AlgSetIv(iv) => {
mem::size_of::<libc::af_alg_iv>() + iv.len()
Expand All @@ -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,
Expand All @@ -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
Expand Down
46 changes: 28 additions & 18 deletions test/sys/test_socket.rs
Expand Up @@ -752,50 +752,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