unix_cred/
xucred.rs

1//! The `xucred` module provides an interface to the `xucred` interface on FreeBSD, DragonflyBSD,
2//! and macOS.
3
4use std::fmt;
5
6use std::io;
7use std::os::unix::net::UnixStream;
8use std::os::unix::prelude::*;
9
10#[cfg(target_os = "freebsd")]
11mod xucred_cr {
12    #[derive(Copy, Clone)]
13    pub union XucredCr {
14        pub cr_pid: libc::pid_t,
15        _cr_unused_1: *const libc::c_void,
16    }
17
18    impl Eq for XucredCr {}
19
20    impl std::hash::Hash for XucredCr {
21        #[inline]
22        fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
23            state.write_u64(unsafe { self.cr_pid } as u64)
24        }
25    }
26
27    impl PartialEq<Self> for XucredCr {
28        #[inline]
29        fn eq(&self, other: &Self) -> bool {
30            unsafe { self.cr_pid == other.cr_pid }
31        }
32    }
33}
34
35/// Represents the credentials of a Unix socket's peer.
36#[derive(Clone, Eq, Hash, PartialEq)]
37#[repr(C)]
38pub struct Xucred {
39    cr_version: libc::c_uint,
40    cr_uid: libc::uid_t,
41    cr_ngroups: libc::c_short,
42    cr_groups: [libc::gid_t; crate::constants::XU_NGROUPS],
43    #[cfg(target_os = "freebsd")]
44    _cr: xucred_cr::XucredCr,
45    #[cfg(target_os = "dragonfly")]
46    _cr_unused1: *const libc::c_void,
47}
48
49impl Xucred {
50    /// Get the peer's effective user ID.
51    #[inline]
52    pub fn uid(&self) -> libc::uid_t {
53        self.cr_uid
54    }
55
56    /// Get the peer's effective group ID.
57    #[inline]
58    pub fn gid(&self) -> libc::gid_t {
59        self.cr_groups[0]
60    }
61
62    /// Get the peer's supplementary group list.
63    ///
64    /// On FreeBSD, this is truncated to the first 16 supplementary groups.
65    #[inline]
66    pub fn groups(&self) -> &[libc::gid_t] {
67        &self.cr_groups[..self.cr_ngroups as usize]
68    }
69
70    /// Get the peer's PID.
71    ///
72    /// This only works on FreeBSD 13+. On FreeBSD 12 and earlier, it always returns `None`.
73    #[cfg(target_os = "freebsd")]
74    #[inline]
75    pub fn pid(&self) -> Option<libc::pid_t> {
76        match unsafe { self._cr.cr_pid } {
77            0 => None,
78            pid => Some(pid),
79        }
80    }
81}
82
83impl fmt::Debug for Xucred {
84    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
85        let mut s = f.debug_struct("Xucred");
86
87        s.field("uid", &self.uid())
88            .field("gid", &self.gid())
89            .field("groups", &self.groups());
90
91        #[cfg(target_os = "freebsd")]
92        s.field("pid", &self.pid());
93
94        s.finish()
95    }
96}
97
98pub(crate) unsafe fn get_xucred_raw(sockfd: RawFd) -> io::Result<Xucred> {
99    let mut xucred: Xucred = std::mem::zeroed();
100    xucred.cr_version = libc::XUCRED_VERSION;
101
102    let len = crate::util::getsockopt_raw(
103        sockfd,
104        0,
105        libc::LOCAL_PEERCRED,
106        std::slice::from_mut(&mut xucred),
107    )?;
108
109    // We want to make sure that 1) the length matches, 2) the version number
110    // matches, 3) we have at least one GID to pull out as the primary GID, and
111    // 4) cr_ngroups isn't greater than XU_NGROUPS.
112    //
113    // Most of this is just paranoid sanity checks that should never actually
114    // happen.
115
116    if len != std::mem::size_of::<Xucred>()
117        || xucred.cr_version != libc::XUCRED_VERSION
118        || xucred.cr_ngroups < 1
119        || xucred.cr_ngroups as usize > crate::constants::XU_NGROUPS
120    {
121        return Err(io::Error::from_raw_os_error(libc::EINVAL));
122    }
123
124    Ok(xucred)
125}
126
127/// Get the credentials of the given socket's peer.
128#[inline]
129pub fn get_xucred(sock: &UnixStream) -> io::Result<Xucred> {
130    unsafe { get_xucred_raw(sock.as_raw_fd()) }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    use std::os::unix::net::UnixDatagram;
138
139    fn getgroups() -> Vec<libc::gid_t> {
140        let mut ngroups = unsafe { libc::getgroups(0, std::ptr::null_mut()) };
141        assert!(ngroups >= 0, "{:?}", io::Error::last_os_error());
142
143        let mut groups = Vec::new();
144        groups.resize(ngroups as usize, 0);
145
146        ngroups = unsafe { libc::getgroups(groups.len() as libc::c_int, groups.as_mut_ptr()) };
147        assert!(ngroups >= 0, "{:?}", io::Error::last_os_error());
148
149        groups.truncate(ngroups as usize);
150        groups
151    }
152
153    #[cfg(target_os = "freebsd")]
154    fn get_expected_pid() -> Option<libc::pid_t> {
155        if crate::util::has_cr_pid() {
156            Some(unsafe { libc::getpid() })
157        } else {
158            None
159        }
160    }
161
162    #[test]
163    fn test_get_xucred() {
164        let (a, b) = UnixStream::pair().unwrap();
165
166        let mut groups = getgroups();
167        groups.sort_unstable();
168
169        let acred = get_xucred(&a).unwrap();
170        assert_eq!(acred.uid(), unsafe { libc::geteuid() });
171        assert_eq!(acred.gid(), unsafe { libc::getegid() });
172
173        let mut agroups = Vec::from(acred.groups());
174        agroups.sort_unstable();
175        assert_eq!(agroups, groups);
176
177        #[cfg(target_os = "freebsd")]
178        assert_eq!(acred.pid(), get_expected_pid());
179
180        let bcred = get_xucred(&b).unwrap();
181        assert_eq!(bcred.uid(), unsafe { libc::geteuid() });
182        assert_eq!(bcred.gid(), unsafe { libc::getegid() });
183
184        let mut bgroups = Vec::from(bcred.groups());
185        bgroups.sort_unstable();
186        assert_eq!(bgroups, groups);
187
188        #[cfg(target_os = "freebsd")]
189        assert_eq!(bcred.pid(), get_expected_pid());
190    }
191
192    fn same_hash<T: std::hash::Hash>(a: &T, b: &T) -> bool {
193        use std::hash::{BuildHasher, Hasher};
194
195        let s = std::collections::hash_map::RandomState::new();
196
197        let mut hasher_a = s.build_hasher();
198        a.hash(&mut hasher_a);
199
200        let mut hasher_b = s.build_hasher();
201        b.hash(&mut hasher_b);
202
203        hasher_a.finish() == hasher_b.finish()
204    }
205
206    #[test]
207    fn test_xucred() {
208        let (a, b) = UnixStream::pair().unwrap();
209
210        let acred = get_xucred(&a).unwrap();
211        let bcred = get_xucred(&b).unwrap();
212
213        assert_eq!(acred, bcred);
214        assert!(same_hash(&acred, &bcred));
215
216        assert_eq!(acred, acred.clone());
217        assert!(same_hash(&acred, &acred.clone()));
218
219        let zcred: Xucred = unsafe { std::mem::zeroed() };
220
221        assert_eq!(zcred, zcred.clone());
222        assert!(same_hash(&zcred, &zcred.clone()));
223
224        assert_ne!(acred, zcred);
225        assert!(!same_hash(&acred, &zcred));
226
227        // 0 -> no PID
228        #[cfg(target_os = "freebsd")]
229        assert_eq!(zcred.pid(), None);
230
231        let mut test_cred: Xucred = unsafe { std::mem::zeroed() };
232
233        #[cfg(target_os = "freebsd")]
234        {
235            assert_eq!(
236                format!("{:?}", test_cred),
237                "Xucred { uid: 0, gid: 0, groups: [], pid: None }"
238            );
239
240            test_cred.cr_uid = 1000;
241            test_cred.cr_ngroups = 2;
242            test_cred.cr_groups[0] = 1001;
243            test_cred.cr_groups[1] = 10;
244            assert_eq!(
245                format!("{:?}", test_cred),
246                "Xucred { uid: 1000, gid: 1001, groups: [1001, 10], pid: None }"
247            );
248
249            test_cred._cr.cr_pid = 1494;
250            assert_eq!(
251                format!("{:?}", test_cred),
252                "Xucred { uid: 1000, gid: 1001, groups: [1001, 10], pid: Some(1494) }"
253            );
254        }
255
256        #[cfg(not(target_os = "freebsd"))]
257        {
258            assert_eq!(
259                format!("{:?}", test_cred),
260                "Xucred { uid: 0, gid: 0, groups: [] }"
261            );
262
263            test_cred.cr_uid = 1000;
264            test_cred.cr_ngroups = 2;
265            test_cred.cr_groups[0] = 1001;
266            test_cred.cr_groups[1] = 10;
267            assert_eq!(
268                format!("{:?}", test_cred),
269                "Xucred { uid: 1000, gid: 1001, groups: [1001, 10] }"
270            );
271        }
272    }
273
274    #[test]
275    fn test_get_xucred_error() {
276        let dir = tempfile::tempdir().unwrap();
277
278        let sock = UnixDatagram::bind(dir.path().join("sock")).unwrap();
279
280        assert_eq!(
281            get_xucred(unsafe { &UnixStream::from_raw_fd(sock.as_raw_fd()) })
282                .unwrap_err()
283                .raw_os_error(),
284            Some(libc::EINVAL),
285        );
286    }
287}