Skip to content

Implementation of peer credentials for Unix sockets #75148

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

Merged
merged 10 commits into from
Sep 15, 2020
12 changes: 12 additions & 0 deletions library/std/src/sys/unix/ext/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ pub mod process;
pub mod raw;
pub mod thread;

#[unstable(feature = "peer_credentials_unix_socket", issue = "42839", reason = "unstable")]
#[cfg(any(
target_os = "android",
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "openbsd"
))]
pub mod ucred;

/// A prelude for conveniently writing platform-specific code.
///
/// Includes all extension traits, and some important type definitions.
Expand Down
51 changes: 51 additions & 0 deletions library/std/src/sys/unix/ext/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,29 @@ use crate::sys::{self, cvt};
use crate::sys_common::{self, AsInner, FromInner, IntoInner};
use crate::time::Duration;

#[cfg(any(
target_os = "android",
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "openbsd"
))]
use crate::os::unix::ucred;

#[unstable(feature = "peer_credentials_unix_socket", issue = "42839", reason = "unstable")]
#[cfg(any(
target_os = "android",
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "openbsd"
))]
pub use ucred::UCred;

#[cfg(any(
target_os = "linux",
target_os = "android",
Expand Down Expand Up @@ -405,6 +428,34 @@ impl UnixStream {
SocketAddr::new(|addr, len| unsafe { libc::getpeername(*self.0.as_inner(), addr, len) })
}

/// Gets the peer credentials for this Unix domain socket.
///
/// # Examples
///
/// ```no_run
/// #![feature(peer_credentials_unix_socket)]
/// use std::os::unix::net::UnixStream;
///
/// fn main() -> std::io::Result<()> {
/// let socket = UnixStream::connect("/tmp/sock")?;
/// let peer_cred = socket.peer_cred().expect("Couldn't get peer credentials");
/// Ok(())
/// }
/// ```
#[unstable(feature = "peer_credentials_unix_socket", issue = "42839", reason = "unstable")]
#[cfg(any(
target_os = "android",
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "openbsd"
))]
pub fn peer_cred(&self) -> io::Result<UCred> {
ucred::peer_cred(self)
}

/// Sets the read timeout for the socket.
///
/// If the provided value is [`None`], then [`read`] calls will block
Expand Down
97 changes: 97 additions & 0 deletions library/std/src/sys/unix/ext/ucred.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//! Unix peer credentials.

// NOTE: Code in this file is heavily based on work done in PR 13 from the tokio-uds repository on
// GitHub.
//
// For reference, the link is here: https://github.com/tokio-rs/tokio-uds/pull/13
// Credit to Martin Habovštiak (GitHub username Kixunil) and contributors for this work.

use libc::{gid_t, pid_t, uid_t};

/// Credentials for a UNIX process for credentials passing.
#[unstable(feature = "peer_credentials_unix_socket", issue = "42839", reason = "unstable")]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct UCred {
/// The UID part of the peer credential. This is the effective UID of the process at the domain
/// socket's endpoint.
pub uid: uid_t,
/// The GID part of the peer credential. This is the effective GID of the process at the domain
/// socket's endpoint.
pub gid: gid_t,
Copy link
Contributor

Choose a reason for hiding this comment

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

something_t types are not used in std often (at all?), is this a good idea?

Personally I'd love seeing use of newtypes as done in nix, but not sure if that should go to std.

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 did a quick grep of the codebase and I can see a few places where these are used... I'll leave it as it is for now! If you would strongly like this to change feel free to follow up. 🙂

/// The PID part of the peer credential. This field is optional because the PID part of the
/// peer credentials is not supported on every platform. On platforms where the mechanism to
/// discover the PID exists, this field will be populated to the PID of the process at the
/// domain socket's endpoint. Otherwise, it will be set to None.
pub pid: Option<pid_t>,
Copy link
Member

Choose a reason for hiding this comment

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

Could we get doc comments for each of the public fields? In particular the Option<pid_t> should be explained in the docs.

}

#[cfg(any(target_os = "android", target_os = "linux"))]
pub use self::impl_linux::peer_cred;

#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "openbsd"
))]
pub use self::impl_bsd::peer_cred;

#[cfg(any(target_os = "linux", target_os = "android"))]
pub mod impl_linux {
use super::UCred;
use crate::os::unix::io::AsRawFd;
use crate::os::unix::net::UnixStream;
use crate::{io, mem};
use libc::{c_void, getsockopt, socklen_t, ucred, SOL_SOCKET, SO_PEERCRED};

pub fn peer_cred(socket: &UnixStream) -> io::Result<UCred> {
let ucred_size = mem::size_of::<ucred>();

// Trivial sanity checks.
assert!(mem::size_of::<u32>() <= mem::size_of::<usize>());
assert!(ucred_size <= u32::MAX as usize);

let mut ucred_size = ucred_size as socklen_t;
let mut ucred: ucred = ucred { pid: 1, uid: 1, gid: 1 };
Copy link
Contributor

Choose a reason for hiding this comment

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

Oooh, nice defensive programming using 1 instead of 0! ❤️


unsafe {
let ret = getsockopt(
socket.as_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>() {

This comment was marked as resolved.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed. 😄

Ok(UCred { uid: ucred.uid, gid: ucred.gid, pid: Some(ucred.pid) })
} else {
Err(io::Error::last_os_error())
}
}
}
}

#[cfg(any(
target_os = "dragonfly",
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "openbsd"
))]
pub mod impl_bsd {
use super::UCred;
use crate::io;
use crate::os::unix::io::AsRawFd;
use crate::os::unix::net::UnixStream;

pub fn peer_cred(socket: &UnixStream) -> io::Result<UCred> {
let mut cred = UCred { uid: 1, gid: 1, pid: None };
unsafe {
let ret = libc::getpeereid(socket.as_raw_fd(), &mut cred.uid, &mut cred.gid);

if ret == 0 { Ok(cred) } else { Err(io::Error::last_os_error()) }
}
}
}
25 changes: 25 additions & 0 deletions library/std/src/sys/unix/ext/ucred/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use crate::os::unix::net::UnixStream;
use libc::{getegid, geteuid};

#[test]
#[cfg(any(
target_os = "android",
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "openbsd"
))]
fn test_socket_pair() {
// Create two connected sockets and get their peer credentials. They should be equal.
let (sock_a, sock_b) = UnixStream::pair().unwrap();
let (cred_a, cred_b) = (sock_a.peer_cred().unwrap(), sock_b.peer_cred().unwrap());
assert_eq!(cred_a, cred_b);

// Check that the UID and GIDs match up.
let uid = unsafe { geteuid() };
let gid = unsafe { getegid() };
assert_eq!(cred_a.uid, uid);
assert_eq!(cred_a.gid, gid);
}
1 change: 0 additions & 1 deletion src/bootstrap/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,6 @@ def build_bootstrap(self):
# See also: <https://github.com/rust-lang/rust/issues/70208>.
if "CARGO_BUILD_TARGET" in env:
del env["CARGO_BUILD_TARGET"]
env["RUSTC_BOOTSTRAP"] = '1'
env["CARGO_TARGET_DIR"] = build_dir
env["RUSTC"] = self.rustc()
env["LD_LIBRARY_PATH"] = os.path.join(self.bin_root(), "lib") + \
Expand Down
2 changes: 0 additions & 2 deletions src/bootstrap/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,6 @@
//! More documentation can be found in each respective module below, and you can
//! also check out the `src/bootstrap/README.md` file for more information.

#![feature(drain_filter)]

use std::cell::{Cell, RefCell};
use std::collections::{HashMap, HashSet};
use std::env;
Expand Down
6 changes: 4 additions & 2 deletions src/bootstrap/tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,15 @@ impl Step for ToolBuild {
"the following dependencies are duplicated although they \
have the same features enabled:"
);
for (id, cur, prev) in duplicates.drain_filter(|(_, cur, prev)| cur.2 == prev.2) {
let (same, different): (Vec<_>, Vec<_>) =
duplicates.into_iter().partition(|(_, cur, prev)| cur.2 == prev.2);
for (id, cur, prev) in same {
println!(" {}", id);
// same features
println!(" `{}` ({:?})\n `{}` ({:?})", cur.0, cur.1, prev.0, prev.1);
}
println!("the following dependencies have different features:");
for (id, cur, prev) in duplicates {
for (id, cur, prev) in different {
println!(" {}", id);
let cur_features: HashSet<_> = cur.2.into_iter().collect();
let prev_features: HashSet<_> = prev.2.into_iter().collect();
Expand Down