uds_fork/
credentials.rs

1#![allow(
2    clippy::match_ref_pats, // looks more optimized with long array
3    clippy::needless_borrowed_reference,
4)]
5
6#[cfg(any(target_os="linux", target_os="android"))]
7use std::os::fd::AsFd;
8use std::{io, fmt};
9use std::num::NonZeroU32;
10use std::io::ErrorKind::*;
11#[cfg(any(
12    target_os="linux", target_os="android",
13    target_os="freebsd", target_os="dragonfly", target_vendor="apple",
14    target_os="openbsd", target_os="netbsd"
15))]
16use std::mem;
17#[cfg(any(target_os="illumos", target_os="solaris"))]
18use std::ptr;
19
20#[cfg(any(
21    target_os="linux", target_os="android",
22    target_os="freebsd", target_os="dragonfly", target_vendor="apple",
23    target_os="openbsd", target_os="netbsd"
24))]
25use libc::{getsockopt, c_void, socklen_t};
26#[cfg(any(target_os="linux", target_os="android"))]
27use libc::{pid_t, uid_t, gid_t, getpid, getuid, geteuid, getgid, getegid};
28#[cfg(any(target_os="linux", target_os="android"))]
29use libc::{ucred, SOL_SOCKET, SO_PEERCRED, SO_PEERSEC};
30#[cfg(any(target_os="freebsd", target_os="dragonfly", target_vendor="apple"))]
31use libc::{xucred, XUCRED_VERSION, LOCAL_PEERCRED};
32#[cfg(target_vendor="apple")]
33use libc::SOL_LOCAL; // Apple is for once the one that does the right thing!
34#[cfg(target_os="openbsd")]
35use libc::{sockpeercred, SOL_SOCKET, SO_PEERCRED};
36#[cfg(target_os="netbsd")]
37use libc::{unpcbid, LOCAL_PEEREID};
38#[cfg(any(target_os="illumos", target_os="solaris"))]
39use libc::{getpeerucred, ucred_free, ucred_t};
40#[cfg(any(target_os="illumos", target_os="solaris"))]
41use libc::{ucred_geteuid, ucred_getegid, ucred_getpid, ucred_getgroups, uid_t, gid_t, pid_t};
42
43/// Credentials to be sent with `send_ancillary()`.
44///
45/// Only on Linux (& Android) does one need to send credentials, and on other
46/// operating systems this struct is ignored.
47#[derive(Clone,Copy, PartialEq,Eq, Debug)]
48#[allow(unused)] // not used yet
49pub enum SendCredentials 
50{
51    Effective,
52    Real,
53    Custom{ pid: u32, uid: u32, gid: u32 }
54}
55
56#[cfg(any(target_os="linux", target_os="android"))]
57impl SendCredentials 
58{
59    pub fn into_raw(self) -> ucred 
60    {
61        let mut ucred: ucred = unsafe { mem::zeroed() };
62        let (pid, uid, gid) = match self {
63            SendCredentials::Effective => unsafe { (getpid(), geteuid(), getegid()) },
64            SendCredentials::Real => unsafe { (getpid(), getuid(), getgid()) },
65            SendCredentials::Custom{pid, uid, gid} => (pid as pid_t, uid as uid_t, gid as gid_t),
66        };
67
68        ucred.pid = pid;
69        ucred.uid = uid;
70        ucred.gid = gid;
71
72        return ucred;
73    }
74}
75
76
77
78#[cfg(any(target_os="linux", target_os="android"))]
79pub 
80fn selinux_context<FD: AsFd>(fd: FD, buffer: &mut[u8]) -> Result<usize, io::Error> 
81{
82    let ptr = buffer.as_mut_ptr() as *mut c_void;
83    let mut capacity = buffer.len().min(socklen_t::max_value() as usize) as socklen_t;
84
85    let res = 
86        unsafe 
87        {
88            use std::os::fd::AsRawFd;
89
90            getsockopt(fd.as_fd().as_raw_fd(), SOL_SOCKET, SO_PEERSEC, ptr, &mut capacity)    
91        };
92    
93    return 
94        match res 
95        {
96            -1 => Err(io::Error::last_os_error()),
97            _ => Ok(capacity as usize),
98        };
99}
100
101#[cfg(not(any(target_os="linux", target_os="android")))]
102pub fn selinux_context(_fd: RawFd,  _buffer: &mut[u8]) -> Result<usize, io::Error> {
103    Err(io::Error::new(Other, "not available"))
104}
105
106
107
108/// Credentials of the peer process when it called `connect()`, `accept()` or `pair()`.
109///
110/// User and group IDs can be misleading if the peer side of the socket
111/// has been transfered to another process or the peer has changed privileges.  
112/// PID is almost impossible to use correctly, as the peer might have
113/// terminated and the pid reused, or as for euid, the socket has been sent
114/// to another process (via fd-passing or forking).
115///
116/// What information is received varies from OS to OS:
117///
118/// * Linux, OpenBSD and NetBSD provides process ID, effective user ID
119///   and effective group id.
120/// * macOS, FreeBSD and DragonFly BSD provides effective user ID
121///   and group memberships. (The first group is also the effective group ID.)
122///   [FreeBSD 13+ will also provide process ID](https://www.freebsd.org/cgi/man.cgi?query=unix&sektion=0&manpath=FreeBSD+13-current&format=html).
123/// * Illumos and Solaris provide [more than one could possibly want](https://illumos.org/man/3C/ucred)
124///   (the `LinuxLike` variant is most likely returned).
125///
126/// Current limitations of this crate:
127///
128/// * Illumos and Solaris provides enough information to fill out
129///   both variants, but obviously only one can be returned.
130/// * FreeBSD 13 will also provide pid, but this crate doesn't detect that.
131#[derive(Clone,Copy, PartialEq,Eq)]
132pub enum ConnCredentials {
133    LinuxLike{ pid: NonZeroU32, euid: u32, egid: u32 },
134    MacOsLike{ euid: u32, number_of_groups: u8, groups: [u32; 16/*what libc uses for all OSes*/] },
135}
136impl ConnCredentials {
137    /// Get the process ID of the initial peer of a connection.
138    ///
139    /// This is currently only available on Linux & Android,
140    /// but will in the future also be available on OpenBSD and NetBSD,
141    /// and possibly also FreeBSD and Solaris.
142    pub fn pid(&self) -> Option<NonZeroU32> {
143        match self {
144            &ConnCredentials::LinuxLike{ pid, .. } => Some(pid),
145            &ConnCredentials::MacOsLike{ .. } => None,
146        }
147    }
148    /// Get the effective user ID of the initial peer of a connection.
149    ///
150    /// This is provided by any supported OS.
151    pub fn euid(&self) -> u32 {
152        match self {
153            &ConnCredentials::LinuxLike{ euid, .. } => euid,
154            &ConnCredentials::MacOsLike{ euid, .. } => euid,
155        }
156    }
157    /// Get the effective group ID of the initial peer of a connection.
158    ///
159    /// * On Linux, Android, OpenBSD and NetBSD,
160    ///   `egid` from the `LinuxLike` variant is returned.
161    /// * On FreeBSD, DragonFly BSD, macOS & other Apple platforms,
162    ///   `groups[0]` from the `MacOsLike` variant is returned
163    ///   (except in the unlikely case that `number_of_groups` is zero).
164    // Sources for that the first group is egid: `<sys/ucred.h>` for
165    // [macOS](https://github.com/apple/darwin-xnu/blob/cc0ca6d1af34cf5daee3673d1b0d770538f19ca5/bsd/sys/ucred.h#L140),
166    // [FreeBSD](https://svnweb.freebsd.org/base/stable/11/sys/sys/ucred.h?revision=331722&view=markup#l93),
167    // [DragonFly BSD](http://gitweb.dragonflybsd.org/dragonfly.git/blob/91dc43dd1215cf13344c65a8f9478bfd31b95814:/sys/sys/ucred.h#l77).
168    // Used by the implementation of `getpeereid()` for
169    // [FreeBSD](https://svnweb.freebsd.org/base/head/lib/libc/gen/getpeereid.c?view=markup),
170    // [DragonFly BSD](http://gitweb.dragonflybsd.org/dragonfly.git/blob/HEAD:/lib/libc/gen/getpeereid.c#l77),
171    // [macOS](https://opensource.apple.com/source/Libc/Libc-1082.50.1/gen/FreeBSD/getpeereid.c.auto.html)
172    // TODO remove None case before 0.2
173    pub fn egid(&self) -> Option<u32> {
174        match self {
175            &ConnCredentials::LinuxLike{ egid, .. } => Some(egid),
176            &ConnCredentials::MacOsLike{ number_of_groups: 1..=255, groups, .. } => Some(groups[0]),
177            &ConnCredentials::MacOsLike{ number_of_groups: 0, .. } => None,
178        }
179    }
180    /// Get the groups that the initial peer of a connection was a mamber of.
181    ///
182    /// This is only available on FreeBSD and macOS (in the future also
183    /// DragonFly BSD), and an empty slice is returned on other OSes.
184    pub fn groups(&self) -> &[u32] {
185        match self {
186            &ConnCredentials::LinuxLike{ .. } => &[],
187            &ConnCredentials::MacOsLike{ number_of_groups: n @ 0..=15, ref groups, .. } => {
188                &groups[..(n as usize)]
189            },
190            &ConnCredentials::MacOsLike{ number_of_groups: 16..=255, ref groups, .. } => groups,
191        }
192    }
193}
194impl fmt::Debug for ConnCredentials {
195    fn fmt(&self,  fmtr: &mut fmt::Formatter) -> Result<(), fmt::Error> {
196        let mut repr = fmtr.debug_struct("ConnCredentials");
197        match self {
198            &ConnCredentials::LinuxLike{ ref pid, ref euid, ref egid } => {
199                repr.field("pid", pid);
200                repr.field("euid", euid);
201                repr.field("egid", egid);
202            }
203            &ConnCredentials::MacOsLike{ ref euid, number_of_groups, ref groups } => {
204                repr.field("euid", euid);
205                let number_of_groups = (number_of_groups as usize).min(groups.len());
206                repr.field("groups", &&groups[..number_of_groups]);
207            }
208        }
209        repr.finish()
210    }
211}
212
213
214#[cfg(any(target_os="linux", target_os="android"))]
215pub 
216fn peer_credentials<FD: AsFd>(conn: FD) -> Result<ConnCredentials, io::Error> 
217{
218    use std::os::fd::AsRawFd;
219
220    let mut ucred: ucred = unsafe { mem::zeroed() };
221
222    let ptr = &mut ucred as *mut ucred as *mut c_void;
223    let mut size = mem::size_of::<ucred>() as socklen_t;
224
225    let res = 
226        unsafe
227        { 
228            getsockopt(conn.as_fd().as_raw_fd(), SOL_SOCKET, SO_PEERCRED, ptr, &mut size)
229        };
230
231    return 
232        if res == -1 
233        {
234            Err(io::Error::last_os_error())
235        } 
236        else if let Some(pid) = NonZeroU32::new(ucred.pid as u32) 
237        {
238            Ok(ConnCredentials::LinuxLike{ pid, euid: ucred.uid as u32, egid: ucred.gid as u32 })
239        } 
240        else 
241        {
242            Err(io::Error::new(NotConnected, "socket is not a connection"))
243        };
244    
245}
246
247#[cfg(any(target_os="freebsd", target_os="dragonfly", target_vendor="apple"))]
248pub fn peer_credentials(conn: RawFd) -> Result<ConnCredentials, io::Error> {
249    let mut xucred: xucred = unsafe { mem::zeroed() };
250    xucred.cr_version = XUCRED_VERSION;
251    xucred.cr_ngroups = xucred.cr_groups.len() as _;
252    // initialize to values that don't signify root, to reduce severity of bugs
253    xucred.cr_uid = !0;
254    for group_slot in &mut xucred.cr_groups {
255        *group_slot = !0;
256    }
257    #[cfg(any(target_os="freebsd", target_os="dragonfly"))]
258    const PEERCRED_SOCKET_LEVEL: i32 = 0; // yes literal zero: not SOL_SOCKET, and SOL_LOCAL is not a thing
259    #[cfg(target_vendor="apple")]
260    use SOL_LOCAL as PEERCRED_SOCKET_LEVEL;
261    unsafe {
262        let ptr = &mut xucred as *mut xucred as *mut c_void;
263        let mut size = mem::size_of::<xucred>() as socklen_t;
264        match getsockopt(conn, PEERCRED_SOCKET_LEVEL, LOCAL_PEERCRED, ptr, &mut size) {
265            -1 => Err(io::Error::last_os_error()),
266            _ if xucred.cr_version != XUCRED_VERSION => {
267                Err(io::Error::new(InvalidData, "unknown version of peer credentials"))
268            },
269            _ => {
270                let mut groups = [u32::max_value(); 16]; // set all unused group slots to ~0
271                let filled_groups = xucred.cr_groups.iter().take(xucred.cr_ngroups as usize);
272                for (&src, dst) in filled_groups.zip(&mut groups) {
273                    *dst = src.into();
274                }
275                Ok(ConnCredentials::MacOsLike {
276                    euid: xucred.cr_uid.into(),
277                    number_of_groups: xucred.cr_ngroups as u8,
278                    groups: groups,
279                })
280            }
281        }
282    }
283}
284
285#[cfg(target_os="openbsd")]
286pub fn peer_credentials(conn: RawFd) -> Result<ConnCredentials, io::Error> {
287    let mut sockpeercred: sockpeercred = unsafe { mem::zeroed() };
288    unsafe {
289        let ptr = &mut sockpeercred as *mut sockpeercred as *mut c_void;
290        let mut size = mem::size_of::<sockpeercred>() as socklen_t;
291        if getsockopt(conn, SOL_SOCKET, SO_PEERCRED, ptr, &mut size) == -1 {
292            Err(io::Error::last_os_error())
293        } else if let Some(pid) = NonZeroU32::new(sockpeercred.pid as u32) {
294            Ok(ConnCredentials::LinuxLike {
295                pid,
296                euid: sockpeercred.uid as u32,
297                egid: sockpeercred.gid as u32,
298            })
299        } else {
300            Err(io::Error::new(InvalidData, "the returned pid is zero"))
301        }
302    }
303}
304
305#[cfg(target_os="netbsd")]
306pub fn peer_credentials(conn: RawFd) -> Result<ConnCredentials, io::Error> {
307    let mut unpcbid: unpcbid = unsafe { mem::zeroed() };
308    unsafe {
309        let ptr = &mut unpcbid as *mut unpcbid as *mut c_void;
310        let mut size = mem::size_of::<unpcbid>() as socklen_t;
311        // `man unix` describes it as a socket-level option, but 0 is what works
312        if getsockopt(conn, 0, LOCAL_PEEREID, ptr, &mut size) == -1 {
313            Err(io::Error::last_os_error())
314        } else if let Some(pid) = NonZeroU32::new(unpcbid.unp_pid as u32) {
315            Ok(ConnCredentials::LinuxLike {
316                pid,
317                euid: unpcbid.unp_euid as u32,
318                egid: unpcbid.unp_egid as u32,
319            })
320        } else {
321            Err(io::Error::new(InvalidData, "the returned pid is zero"))
322        }
323    }
324}
325
326#[cfg(any(target_os="illumos", target_os="solaris"))]
327pub fn peer_credentials(conn: RawFd) -> Result<ConnCredentials, io::Error> {
328    struct UcredAlloc(*mut ucred_t);
329    impl Drop for UcredAlloc {
330        fn drop(&mut self) {
331            unsafe {
332                if self.0 != ptr::null_mut() {
333                    ucred_free(self.0);
334                }
335            }
336        }
337    }
338    unsafe {
339        let mut ucred = UcredAlloc(ptr::null_mut());
340        if getpeerucred(conn, &mut ucred.0) == -1 {
341            Err(io::Error::last_os_error())
342        } else if ucred.0 == ptr::null_mut() {
343            Err(io::Error::new(NotConnected, "socket is not a connection"))
344        } else {
345            let euid = ucred_geteuid(ucred.0 as *const _);
346            let egid = ucred_getegid(ucred.0 as *const _);
347            let pid = ucred_getpid(ucred.0 as *const _);
348            let mut groups_ptr: *const gid_t = ptr::null_mut();
349            let ngroups = ucred_getgroups(ucred.0 as *const _, &mut groups_ptr);
350            // https://illumos.org/man/3C/ucred says -1 is returned on error,
351            // but the types in libc are u32
352            if euid != -1i32 as uid_t  &&  egid != -1i32 as gid_t
353            &&  pid != -1i32 as pid_t  &&  pid != 0 {
354                Ok(ConnCredentials::LinuxLike {
355                    pid: NonZeroU32::new(pid as u32).unwrap(), // already checked
356                    euid: euid as u32,
357                    egid: egid as u32,
358                })
359            } else if euid != -1i32 as uid_t  &&  ngroups > 0  &&  groups_ptr != ptr::null() {
360                let mut groups = [u32::max_value(); 16];
361                let number_of_groups = ngroups.min(16) as u8;
362                for i in 0..number_of_groups {
363                    groups[i as usize] = *groups_ptr.offset(i as isize);
364                }
365                Ok(ConnCredentials::MacOsLike {
366                    euid: euid as u32,
367                    number_of_groups,
368                    groups,
369                })
370            } else if euid != -1i32 as uid_t  &&  egid != -1i32 as gid_t {
371                let mut groups = [u32::max_value(); 16];
372                groups[0] = egid as u32;
373                Ok(ConnCredentials::MacOsLike {
374                    euid: euid as u32,
375                    number_of_groups: 1,
376                    groups,
377                })
378            } else {
379                Err(io::Error::new(Other, "Not enough information was available"))
380            }
381        }
382    }
383}
384
385#[cfg(not(any(
386    target_os="linux", target_os="android",
387    target_os="freebsd", target_os="dragonfly", target_vendor="apple",
388    target_os="openbsd", target_os="netbsd",
389    target_os="illumos", target_os="solaris",
390)))]
391pub fn peer_credentials(_: RawFd) -> Result<ConnCredentials, io::Error> {
392    Err(io::Error::new(Other, "Not available"))
393}
394
395
396
397#[cfg(any(target_os="linux", target_os="android"))]
398pub type RawReceivedCredentials = libc::ucred;
399
400
401/// Process credentials received through `recv_ancillary()`.
402///
403/// What information is returned varies from OS to OS:
404///
405/// * On Linux (& Android) the information has to be explicitly sent by the
406///   peer through `send_ancillary()` or `sendmsg()`, but is validated by the
407///   kernel.  
408///   Peer chooses whether to send effective or real uid or gid, unless root
409///   in which case it can send whatever it wants.
410/// * On FreeBSD, NetBSD, DragonFly BSD, Illumos and likely macOS it is provided
411///   by the OS automatically when the socket option is set.
412/// * OpenBSD doesn't appear to support receiving credentials.
413#[derive(Clone,Copy, PartialEq,Eq,Hash, Debug)]
414pub struct ReceivedCredentials 
415{
416    #[cfg(any(target_os="linux", target_os="android", target_os="dragonfly"))]
417    pid: u32,
418    #[cfg(any(target_os="linux", target_os="android"))]
419    uid: u32,
420    #[cfg(any(target_os="linux", target_os="android"))]
421    gid: u32,
422
423    #[cfg(any(
424        target_os="freebsd", target_os="netbsd", target_os="dragonfly",
425        target_os="illumos", target_os="solaris", target_os="macos",
426    ))]
427    real_uid: u32,
428    #[cfg(any(
429        target_os="freebsd", target_os="netbsd", target_os="dragonfly",
430        target_os="illumos", target_os="solaris", target_os="macos",
431    ))]
432    effective_uid: u32,
433    #[cfg(any(
434        target_os="freebsd", target_os="netbsd", target_os="dragonfly",
435        target_os="illumos", target_os="solaris", target_os="macos",
436    ))]
437    real_gid: u32,
438    #[cfg(any(
439        target_os="freebsd", target_os="netbsd",
440        target_os="illumos", target_os="solaris", target_os="macos",
441    ))]
442    effective_gid: u32,
443    #[cfg(any(
444        target_os="freebsd", target_os="netbsd", target_os="dragonfly",
445        target_os="illumos", target_os="solaris", target_os="macos",
446    ))]
447    groups: [u32; 5],
448}
449
450#[allow(unused)] // TODO
451impl ReceivedCredentials 
452{
453    #[cfg(any(target_os="linux", target_os="android"))]
454    pub(crate) fn from_raw(creds: libc::ucred) -> Self 
455    {
456        ReceivedCredentials 
457        {
458            pid: creds.pid as u32,
459            uid: creds.uid as u32,
460            gid: creds.gid as u32,
461        }
462    }
463
464    /// The pid of the peer.
465    ///
466    /// This information is only available on Linux, Android and DragonFly BSD.
467    pub 
468    fn pid(&self) -> Option<u32> 
469    {
470        #[cfg(any(target_os="linux", target_os="android", target_os="dragonfly"))] {
471            Some(self.pid)
472        }
473        #[cfg(not(any(target_os="linux", target_os="android", target_os="dragonfly")))] {
474            None
475        }
476    }
477
478    pub 
479    fn effective_or_sent_uid(&self) -> u32 
480    {
481        #[cfg(any(target_os="linux", target_os="android"))] {
482            self.uid
483        }
484        #[cfg(any(
485            target_os="freebsd", target_os="netbsd", target_os="dragonfly",
486            target_os="illumos", target_os="solaris", target_os="macos",
487        ))] {
488            self.effective_uid
489        }
490        #[cfg(not(any(
491            target_os="linux", target_os="android",
492            target_os="freebsd", target_os="netbsd", target_os="dragonfly",
493            target_os="illumos", target_os="solaris", target_os="macos",
494        )))] {
495            unreachable!("struct cannot be created on unsupported OSes")
496        }
497    }
498
499    pub 
500    fn real_or_sent_uid(&self) -> u32 
501    {
502        #[cfg(any(target_os="linux", target_os="android"))] 
503        {
504            self.uid
505        }
506        #[cfg(any(
507            target_os="freebsd", target_os="netbsd", target_os="dragonfly",
508            target_os="illumos", target_os="solaris", target_os="macos",
509        ))] 
510        {
511            self.real_uid
512        }
513        #[cfg(not(any(
514            target_os="linux", target_os="android",
515            target_os="freebsd", target_os="netbsd", target_os="dragonfly",
516            target_os="illumos", target_os="solaris", target_os="macos",
517        )))] 
518        {
519            unreachable!("struct cannot be created on unsupported OSes")
520        }
521    }
522    pub fn effective_or_sent_gid(&self) -> Option<u32> {
523        #[cfg(any(target_os="linux", target_os="android"))] {
524            Some(self.gid)
525        }
526        #[cfg(any(
527            target_os="freebsd", target_os="netbsd",
528            target_os="illumos", target_os="solaris", target_os="macos",
529        ))] {
530            Some(self.effective_gid)
531        }
532        #[cfg(not(any(
533            target_os="linux", target_os="android",
534            target_os="freebsd", target_os="netbsd",
535            target_os="illumos", target_os="solaris", target_os="macos",
536        )))] {
537            None
538        }
539    }
540    
541    pub fn real_or_sent_gid(&self) -> u32 
542    {
543        #[cfg(any(target_os="linux", target_os="android"))] 
544        {
545            self.gid
546        }
547        #[cfg(any(
548            target_os="freebsd", target_os="netbsd", target_os="dragonfly",
549            target_os="illumos", target_os="solaris", target_os="macos",
550        ))] 
551        {
552            self.real_gid
553        }
554        #[cfg(not(any(
555            target_os="linux", target_os="android",
556            target_os="freebsd", target_os="netbsd", target_os="dragonfly",
557            target_os="illumos", target_os="solaris", target_os="macos",
558        )))] 
559        {
560            unreachable!("struct cannot be created on unsupported OSes")
561        }
562    }
563    /// Get the peer's group memberships.
564    ///
565    /// This information is only available on macOS, the BSDs and and Illumos.
566    /// On other operating systems an empty slice is returned.
567    pub 
568    fn groups(&self) -> &[u32] 
569    {
570        #[cfg(any(
571            target_os="freebsd", target_os="netbsd", target_os="dragonfly",
572            target_os="illumos", target_os="solaris", target_os="macos",
573        ))] 
574        {
575            &self.groups[..]
576        }
577        #[cfg(not(any(
578            target_os="freebsd", target_os="netbsd", target_os="dragonfly",
579            target_os="illumos", target_os="solaris", target_os="macos",
580        )))] 
581        {
582            &[]
583        }
584    }
585}