unix_cred/
lib.rs

1//! # unix-cred
2//!
3//! `unix-cred` provides simple, cross-platform interfaces to read peer credentials from Unix
4//! sockets. (OS-specific interfaces are also exposed if the extra functionality is necessary).
5//!
6//! # Stream vs. Datagram sockets
7//!
8//! Some platforms support reading peer credentials from datagram sockets using ancillary messages.
9//! Currently, `unix-cred` does not support this; only stream sockets are supported.
10//!
11//! # Which credentials am I getting?
12//!
13//! On all currently supported platforms, both of the following are true:
14//!
15//! 1. The UID and GID returned by these interfaces are the *effective* UID/GID, not the real or
16//!    saved UID/GID.
17//! 2. The credentials returned are cached at the time that the `connect()`/`socketpair()` call was
18//!    made. (So if the process later drops privileges, or passes the file descriptor to an
19//!    unprivileged process, it will still be shown as having elevated privileges.)
20//!
21//! # What are the other modules I see in this crate?
22//!
23//! The `ucred` and `xucred` modules expose the OS-specific interfaces. `ucred` provides the
24//! Linux/OpenBSD/NetBSD interface, and `xucred` provides the macOS/FreeBSD/DragonFlyBSD interface.
25//!
26//! `ucred` is not particularly useful; in most cases you should use `get_peer_ids()` or
27//! `get_peer_pid_ids()`, which are more cross-platform. However, `xucred` can be helpful since it
28//! provides access to the process's full supplementary group list.
29
30use std::io;
31use std::os::unix::net::UnixStream;
32use std::os::unix::prelude::*;
33
34mod constants;
35mod util;
36
37#[cfg(any(target_os = "linux", target_os = "openbsd", target_os = "netbsd"))]
38pub mod ucred;
39#[cfg(any(
40    target_os = "freebsd",
41    target_os = "dragonfly",
42    target_os = "macos",
43    target_os = "ios"
44))]
45pub mod xucred;
46
47#[allow(clippy::needless_return)]
48#[inline]
49unsafe fn get_peer_ids_raw(sockfd: RawFd) -> io::Result<(libc::uid_t, libc::gid_t)> {
50    #[cfg(any(target_os = "linux", target_os = "openbsd", target_os = "netbsd"))]
51    {
52        let cred = ucred::get_ucred_raw(sockfd)?;
53        return Ok((cred.uid, cred.gid));
54    }
55
56    #[cfg(any(
57        target_os = "freebsd",
58        target_os = "dragonfly",
59        target_os = "macos",
60        target_os = "ios"
61    ))]
62    {
63        let cred = xucred::get_xucred_raw(sockfd)?;
64        return Ok((cred.uid(), cred.gid()));
65    }
66}
67
68/// Get the UID and GID of the given socket's peer.
69#[inline]
70pub fn get_peer_ids(sock: &UnixStream) -> io::Result<(libc::uid_t, libc::gid_t)> {
71    unsafe { get_peer_ids_raw(sock.as_raw_fd()) }
72}
73
74#[cfg(any(
75    target_os = "linux",
76    target_os = "openbsd",
77    target_os = "netbsd",
78    target_os = "freebsd",
79))]
80#[allow(clippy::needless_return)]
81#[inline]
82unsafe fn get_peer_pid_ids_raw(
83    sockfd: RawFd,
84) -> io::Result<(Option<libc::pid_t>, libc::uid_t, libc::gid_t)> {
85    #[cfg(any(target_os = "linux", target_os = "openbsd", target_os = "netbsd"))]
86    {
87        let cred = ucred::get_ucred_raw(sockfd)?;
88        return Ok((Some(cred.pid), cred.uid, cred.gid));
89    }
90
91    #[cfg(target_os = "freebsd")]
92    {
93        let cred = xucred::get_xucred_raw(sockfd)?;
94        return Ok((cred.pid(), cred.uid(), cred.gid()));
95    }
96}
97
98/// Get the PID, UID, and GID of the given socket's peer.
99///
100/// This only works on Linux, OpenBSD, NetBSD, and FreeBSD 13+. On other operating systems, this
101/// function is not available. On FreeBSD 12 and earlier, the returned PID is always `None`.
102#[cfg(any(
103    target_os = "linux",
104    target_os = "openbsd",
105    target_os = "netbsd",
106    target_os = "freebsd",
107))]
108#[inline]
109pub fn get_peer_pid_ids(
110    sock: &UnixStream,
111) -> io::Result<(Option<libc::pid_t>, libc::uid_t, libc::gid_t)> {
112    unsafe { get_peer_pid_ids_raw(sock.as_raw_fd()) }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn test_get_peer_ids() {
121        let (a, b) = UnixStream::pair().unwrap();
122
123        let (auid, agid) = get_peer_ids(&a).unwrap();
124        assert_eq!(auid, unsafe { libc::getuid() });
125        assert_eq!(agid, unsafe { libc::getgid() });
126
127        let (buid, bgid) = get_peer_ids(&b).unwrap();
128        assert_eq!(buid, unsafe { libc::getuid() });
129        assert_eq!(bgid, unsafe { libc::getgid() });
130    }
131
132    #[test]
133    fn test_get_peer_ids_bad_fd() {
134        assert_eq!(
135            get_peer_ids(unsafe { &UnixStream::from_raw_fd(libc::c_int::MAX) })
136                .unwrap_err()
137                .raw_os_error(),
138            Some(libc::EBADF),
139        );
140
141        let file = std::fs::File::open(std::env::current_exe().unwrap()).unwrap();
142        assert_eq!(
143            get_peer_ids(unsafe { &UnixStream::from_raw_fd(file.as_raw_fd()) })
144                .unwrap_err()
145                .raw_os_error(),
146            Some(libc::ENOTSOCK),
147        );
148    }
149
150    #[cfg(any(
151        target_os = "linux",
152        target_os = "openbsd",
153        target_os = "netbsd",
154        target_os = "freebsd",
155    ))]
156    #[test]
157    fn test_get_peer_pid_ids() {
158        let (a, b) = UnixStream::pair().unwrap();
159
160        let (apid, auid, agid) = get_peer_pid_ids(&a).unwrap();
161        assert_eq!(apid, get_expected_pid());
162        assert_eq!(auid, unsafe { libc::getuid() });
163        assert_eq!(agid, unsafe { libc::getgid() });
164
165        let (bpid, buid, bgid) = get_peer_pid_ids(&b).unwrap();
166        assert_eq!(bpid, get_expected_pid());
167        assert_eq!(buid, unsafe { libc::getuid() });
168        assert_eq!(bgid, unsafe { libc::getgid() });
169    }
170
171    #[cfg(any(
172        target_os = "linux",
173        target_os = "openbsd",
174        target_os = "netbsd",
175        target_os = "freebsd",
176    ))]
177    #[test]
178    fn test_get_peer_pid_ids_bad_fd() {
179        assert_eq!(
180            get_peer_pid_ids(unsafe { &UnixStream::from_raw_fd(libc::c_int::MAX) })
181                .unwrap_err()
182                .raw_os_error(),
183            Some(libc::EBADF),
184        );
185
186        let file = std::fs::File::open(std::env::current_exe().unwrap()).unwrap();
187        assert_eq!(
188            get_peer_pid_ids(unsafe { &UnixStream::from_raw_fd(file.as_raw_fd()) })
189                .unwrap_err()
190                .raw_os_error(),
191            Some(libc::ENOTSOCK),
192        );
193    }
194
195    #[cfg(any(
196        target_os = "linux",
197        target_os = "openbsd",
198        target_os = "netbsd",
199        target_os = "freebsd",
200    ))]
201    fn get_expected_pid() -> Option<libc::pid_t> {
202        #[cfg(target_os = "freebsd")]
203        if !util::has_cr_pid() {
204            return None;
205        }
206
207        Some(unsafe { libc::getpid() })
208    }
209}