Skip to content
This repository has been archived by the owner on May 15, 2018. It is now read-only.

WIP Implementation of peer credentials. #13

Merged
merged 7 commits into from
Jun 22, 2017
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ use tokio_io::{IoStream, AsyncRead, AsyncWrite};

mod frame;
pub use frame::{UnixDatagramFramed, UnixDatagramCodec};
mod ucred;
pub use ucred::UCred;

fn would_block() -> io::Error {
io::Error::new(io::ErrorKind::WouldBlock, "would block")
Expand Down Expand Up @@ -250,6 +252,7 @@ impl UnixStream {
let (a, b) = try!(mio_uds::UnixStream::pair());
let a = try!(UnixStream::new(a, handle));
let b = try!(UnixStream::new(b, handle));

Ok((a, b))
}

Expand Down Expand Up @@ -279,6 +282,11 @@ impl UnixStream {
self.io.get_ref().peer_addr()
}

/// Returns effective credentials of the process which called `connect` or `socketpair`.
pub fn peer_cred(&self) -> io::Result<UCred> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this be added via an extension trait if it's platform-specific?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was thinking about it too. I'm not sure if Rust runs on any platform that has UnixStream but no way of getting peer credentials. Solaris seems to have different implementation, but I have no machine to test it.

If I wanted to use a trait how to name it? LinuxAndMacPeerCredentials seems long to me. I wouldn't separate them provided there can be a common trait for both. (And I'm actually currently working on code that should be Linux/macOS.) On the other hand, Linux has possibility to provide pid too.

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you think all other unices have an implementation of this function? If that's the case then this should just remove the #[cfg].

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Digging into the topic more I found that:

  • On Linux, getsockopt should be used
  • BSD families (including macOS) use getpeereid
  • Solaris uses getpeerucred

I know of no other unix used in the wild with Rust.

So maybe we can just remove #[cfg]. However, it'd be nice to implement this for Solaris too. But as I said, I have nothing to test it on.

Also, macos cfg will have to be changed to #[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))]

What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

This either needs to be ungated to cause compile failures on other platforms to necessitate an implementation, or it needs an extension trait. I don't mind which, but there's no middle between those tw.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, I'll go with ungated. Gating pushes the compilation error to consumer, so there'd still be the error. If you have some explanation about why compile error in this crate is better than at consumer, I'm interested in learning it. (I personally don't mind using the one you choose.)

Copy link
Contributor

Choose a reason for hiding this comment

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

If we don't have a cfg here then we're claiming it's available on all unices, both in the code and in the documentation. At that point we need an assertion as such, which is to attempt to compile the code on all platforms.

ucred::get_peer_cred(self)
}

/// Returns the value of the `SO_ERROR` option.
pub fn take_error(&self) -> io::Result<Option<io::Error>> {
self.io.get_ref().take_error()
Expand Down
76 changes: 76 additions & 0 deletions src/ucred.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use libc::{uid_t, gid_t};

/// Credentials of a process
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct UCred {
/// UID (user ID) of the process
pub uid: uid_t,
/// GID (group ID) of the process
pub gid: gid_t,
}

#[cfg(target_os = "linux")]
pub use self::impl_linux::get_peer_cred;

#[cfg(target_os = "macos")]
pub use self::impl_macos::get_peer_cred;

#[cfg(target_os = "linux")]
pub mod impl_linux {
use libc::{getsockopt, SOL_SOCKET, SO_PEERCRED, c_void};
use std::{io, mem};
use UnixStream;
use std::os::unix::io::AsRawFd;

use libc::ucred;

pub fn get_peer_cred(sock: &UnixStream) -> io::Result<super::UCred> {
unsafe {
let raw_fd = sock.as_raw_fd();

let mut ucred = ucred { pid: 0, uid: 0, gid: 0 };

let ucred_size = mem::size_of::<ucred>();

// These paranoid checks should be optimized-out
assert!(mem::size_of::<u32>() <= mem::size_of::<usize>());
assert!(ucred_size <= u32::max_value() as usize);

let mut ucred_size = ucred_size as u32;

let ret = getsockopt(raw_fd, SOL_SOCKET, SO_PEERCRED, &mut ucred as *mut ucred as *mut c_void, &mut ucred_size);
if ret == 0 && ucred_size as usize == mem::size_of::<ucred>() {
Ok(super::UCred {
uid: ucred.uid,
gid: ucred.gid,
})
} else {
Err(io::Error::last_os_error())
}
}
}
}

#[cfg(any(target_os = "macos", target_os = "ios", target_os = "freebsd"))]
pub mod impl_macos {
use libc::getpeereid;
use std::{io, mem};
use UnixStream;
use std::os::unix::io::AsRawFd;

pub fn get_peer_cred(sock: &UnixStream) -> io::Result<super::UCred> {
unsafe {
let raw_fd = sock.as_raw_fd();

let mut cred: super::UCred = mem::uninitialized();
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this use zeroed? Seems like there's no reason to use the unsafer version here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It could be. But I see no point. My reasoning is that the code is already unsafe so I see no point in zeroing it. However, If you have good reason to do it, I'd probably go with explicit safe version (pid: 0, uid: 0, gid: 0).

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's use zeroed here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why? Is that some kind of standard?

Copy link
Contributor

Choose a reason for hiding this comment

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

Because I see no reason to use uninitialized, please change it to zeroed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I meant zeroed instead of ucred { pid: 0, uid: 0, gid: 0 }.

I guess that syscall only overwrites it, so the reason would be performance. I agree it's negligible, so I accept zeroig. I'm just interested to know whether there is some difference between using zeroed and zeroing "manually".

Copy link
Contributor

Choose a reason for hiding this comment

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

Nah those are the same. With libc I tend to use zeroed because there's often random hidden fields or fields used for padding, but either way is fine in this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, OK. I prefer "safe" version in this case.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can this be udpated to using UCred { ... }?


let ret = getpeereid(raw_fd, &mut cred.uid, &mut cred.gid);

if ret == 0 {
Ok(cred)
} else {
Err(io::Error::last_os_error())
}
}
}
}