Skip to main content

vissue_control/
peercred.rs

1//! Same-uid check for an accepted Unix socket. No `unsafe` in this crate.
2
3use std::io;
4use std::os::fd::AsFd;
5
6use nix::unistd::Uid;
7
8/// Accept the peer when its uid matches the current process uid.
9#[must_use]
10pub fn accept_peer(uid: u32) -> bool {
11    uid == current_uid()
12}
13
14/// Real uid of this process (`getuid`), matching `Uid::current`.
15#[must_use]
16pub fn current_uid() -> u32 {
17    Uid::current().as_raw()
18}
19
20/// Peer uid from `SO_PEERCRED` on Linux, or `getpeereid` on BSD/macOS.
21///
22/// # Errors
23///
24/// Returns an error when the socket option cannot be read, or this OS cannot
25/// report peer credentials.
26pub fn peer_uid<F: AsFd>(sock: &F) -> io::Result<u32> {
27    peer_uid_impl(sock)
28}
29
30/// Whether an accepted socket may stay open.
31///
32/// `Ok(uid)` uses [`accept_peer`]. `ErrorKind::Unsupported` means this OS
33/// cannot read peer credentials: return true so dir 0700 / sock 0600 are the
34/// check. Every other IO error and a uid mismatch fail closed.
35#[must_use]
36pub fn accept_from_result(result: io::Result<u32>) -> bool {
37    match result {
38        Ok(uid) => accept_peer(uid),
39        Err(err) if err.kind() == io::ErrorKind::Unsupported => true,
40        Err(_) => false,
41    }
42}
43
44/// [`accept_from_result`] over [`peer_uid`].
45#[must_use]
46pub fn accept_socket<F: AsFd>(sock: &F) -> bool {
47    accept_from_result(peer_uid(sock))
48}
49
50#[cfg(any(target_os = "linux", target_os = "android"))]
51fn peer_uid_impl<F: AsFd>(sock: &F) -> io::Result<u32> {
52    use nix::sys::socket::{getsockopt, sockopt};
53    let creds = getsockopt(sock, sockopt::PeerCredentials)?;
54    Ok(creds.uid())
55}
56
57#[cfg(any(
58    target_os = "macos",
59    target_os = "ios",
60    target_os = "freebsd",
61    target_os = "dragonfly",
62    target_os = "openbsd",
63    target_os = "netbsd"
64))]
65fn peer_uid_impl<F: AsFd>(sock: &F) -> io::Result<u32> {
66    let (uid, _) = nix::unistd::getpeereid(sock)?;
67    Ok(uid.as_raw())
68}
69
70#[cfg(not(any(
71    target_os = "linux",
72    target_os = "android",
73    target_os = "macos",
74    target_os = "ios",
75    target_os = "freebsd",
76    target_os = "dragonfly",
77    target_os = "openbsd",
78    target_os = "netbsd"
79)))]
80fn peer_uid_impl<F: AsFd>(_sock: &F) -> io::Result<u32> {
81    Err(io::Error::new(
82        io::ErrorKind::Unsupported,
83        "peer credentials are unavailable; rely on dir 0700 / sock 0600",
84    ))
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn same_uid_is_accepted() {
93        assert!(accept_peer(current_uid()));
94    }
95
96    #[test]
97    fn other_uid_is_rejected() {
98        let other = current_uid().wrapping_add(1);
99        assert!(!accept_peer(other));
100        assert!(!accept_from_result(Ok(other)));
101    }
102
103    #[test]
104    fn unsupported_peercred_falls_back_to_mode_bits() {
105        let err = io::Error::new(io::ErrorKind::Unsupported, "no SO_PEERCRED");
106        assert!(accept_from_result(Err(err)));
107        let err = io::Error::other("getsockopt failed");
108        assert!(!accept_from_result(Err(err)));
109        assert!(accept_from_result(Ok(current_uid())));
110    }
111
112    #[test]
113    fn same_process_peer_is_accepted() {
114        use std::os::unix::net::{UnixListener, UnixStream};
115
116        let dir = tempfile::tempdir().unwrap();
117        let path = dir.path().join("peer.sock");
118        let listener = UnixListener::bind(&path).unwrap();
119        let client = UnixStream::connect(&path).unwrap();
120        let (server, _) = listener.accept().unwrap();
121        let uid = peer_uid(&server).unwrap();
122        assert_eq!(uid, current_uid());
123        assert!(accept_peer(uid));
124        assert!(accept_socket(&server));
125        assert!(accept_socket(&client));
126    }
127}