Skip to main content

dolang_vfs/
security.rs

1//! Ownership identities and POSIX access-control lists.
2
3use dolang_winterop::security::{Sid, TokenGroupAttributes};
4use serde::{Deserialize, Serialize};
5#[cfg(unix)]
6use std::io;
7
8use crate::error::Result;
9pub use crate::{
10    macos_acl::{MacosAce, MacosAceFlags, MacosAceMask, MacosAceType, MacosAcl},
11    nfs4_acl::{Nfs4Ace, Nfs4AceFlags, Nfs4AceMask, Nfs4AceQualifier, Nfs4AceType, Nfs4Acl},
12    posix_acl::{PosixAce, PosixAcl, PosixAclError, PosixAclQualifier},
13};
14
15bitflags::bitflags! {
16    /// Unix read/write/execute permission bits, as granted by one class of a
17    /// [`crate::metadata::Mode`] or one entry of a POSIX ACL.
18    #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
19    pub struct Permission: u8 {
20        /// Grants read access.
21        const READ = 0o4;
22        /// Grants write access.
23        const WRITE = 0o2;
24        /// Grants execute or directory-search access.
25        const EXECUTE = 0o1;
26    }
27}
28
29/// Selects which kind of access-control list a `get_acl`-style operation
30/// should read.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
32#[non_exhaustive]
33pub enum AclKind {
34    /// POSIX.1e ACL.
35    Posix,
36    /// NFSv4 ACL.
37    Nfs4,
38    /// macOS extended ACL.
39    Macos,
40}
41
42/// A portable access-control list of any supported kind.
43///
44/// Returned by `get_acl`-style operations and accepted by `set_acl`-style
45/// ones, which infer the kind from the variant rather than taking a separate
46/// selector.
47#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
48#[non_exhaustive]
49pub enum Acl {
50    /// POSIX.1e ACL.
51    Posix(PosixAcl),
52    /// NFSv4 ACL.
53    Nfs4(Nfs4Acl),
54    /// macOS extended ACL.
55    Macos(MacosAcl),
56}
57
58impl Acl {
59    /// Returns the [`AclKind`] of this ACL.
60    pub fn kind(&self) -> AclKind {
61        match self {
62            Self::Posix(_) => AclKind::Posix,
63            Self::Nfs4(_) => AclKind::Nfs4,
64            Self::Macos(_) => AclKind::Macos,
65        }
66    }
67}
68
69/// A principal identifier of a specific kind, used with
70/// [`Vfs::resolve_principal_id`](crate::Vfs::resolve_principal_id) to
71/// convert between them (e.g. a Unix uid/gid and a macOS `guid_t`).
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
73#[non_exhaustive]
74pub enum PrincipalId {
75    /// A Unix user ID.
76    Uid(u32),
77    /// A Unix group ID.
78    Gid(u32),
79    /// A macOS principal UUID (`guid_t`).
80    Uuid(uuid::Uuid),
81}
82
83/// Selects which [`PrincipalId`] variant a `resolve_principal_id`-style
84/// operation should produce.
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
86#[non_exhaustive]
87pub enum PrincipalIdKind {
88    /// Resolve to a Unix user ID.
89    Uid,
90    /// Resolve to a Unix group ID.
91    Gid,
92    /// Resolve to a macOS principal UUID (`guid_t`).
93    Uuid,
94}
95
96/// An owner or group selected by numeric ID, account name, or Windows SID.
97#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
98#[non_exhaustive]
99pub enum OwnershipIdentity {
100    /// A platform numeric user or group ID.
101    Id(u32),
102    /// An account name resolved by the target.
103    Name(String),
104    /// A Windows security identifier.
105    Sid(Sid),
106}
107
108/// Snapshot of a VFS target's process security context.
109#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
110#[serde(transparent)]
111pub struct SecurityInfo(SecurityInfoInner);
112
113#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
114enum SecurityInfoInner {
115    /// Unix identity information.
116    Unix(UnixSecurityInfo),
117    /// Windows token information.
118    Windows(WindowsTokenInfo),
119}
120
121/// Unix identity information for a VFS target.
122#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
123pub struct UnixSecurityInfo {
124    /// Real user ID.
125    pub(crate) uid: u32,
126    /// Real group ID.
127    pub(crate) gid: u32,
128    /// Effective user ID.
129    pub(crate) euid: u32,
130    /// Effective group ID.
131    pub(crate) egid: u32,
132    /// Supplementary group IDs.
133    pub(crate) groups: Vec<u32>,
134}
135
136/// Windows token information for a VFS target.
137#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
138pub struct WindowsTokenInfo {
139    /// Whether the token has elevated administrator privileges.
140    pub(crate) is_elevated: bool,
141    /// Token user SID.
142    pub(crate) user_sid: Sid,
143    /// Token owner SID.
144    pub(crate) owner_sid: Sid,
145    /// Token primary group SID.
146    pub(crate) primary_group_sid: Sid,
147    /// Token group memberships.
148    pub(crate) groups: Vec<TokenGroup>,
149}
150
151/// A Windows token group SID and its attribute mask.
152#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
153pub struct TokenGroup {
154    /// Group security identifier.
155    pub(crate) sid: Sid,
156    /// Native group attributes bitmask.
157    pub(crate) attributes: TokenGroupAttributes,
158}
159
160/// Classification returned by Windows account-name lookup.
161#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
162#[non_exhaustive]
163pub enum SidNameUse {
164    /// User account.
165    User,
166    /// Group account.
167    Group,
168    /// Domain account.
169    Domain,
170    /// Local alias.
171    Alias,
172    /// Well-known group.
173    WellKnownGroup,
174    /// Deleted account.
175    DeletedAccount,
176    /// Invalid SID type.
177    Invalid,
178    /// Unrecognized SID type.
179    Unknown,
180    /// Computer account.
181    Computer,
182    /// Integrity label.
183    Label,
184    /// Logon session.
185    LogonSession,
186}
187
188/// A Windows SID together with its resolved account name.
189#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
190pub struct SidName {
191    /// Resolved security identifier.
192    pub(crate) sid: Sid,
193    /// Account name.
194    pub(crate) name: String,
195    /// Account domain.
196    pub(crate) domain: String,
197    /// Account classification.
198    pub(crate) kind: SidNameUse,
199}
200
201impl SecurityInfo {
202    /// Returns the Unix security information, if this is a Unix target.
203    pub fn unix(&self) -> Option<&UnixSecurityInfo> {
204        match &self.0 {
205            SecurityInfoInner::Unix(info) => Some(info),
206            _ => None,
207        }
208    }
209    /// Returns the Windows token information, if this is a Windows target.
210    pub fn windows(&self) -> Option<&WindowsTokenInfo> {
211        match &self.0 {
212            SecurityInfoInner::Windows(info) => Some(info),
213            _ => None,
214        }
215    }
216    /// Captures the security context of the current process.
217    pub fn current() -> Result<Self> {
218        #[cfg(unix)]
219        return Ok(Self(SecurityInfoInner::Unix(UnixSecurityInfo::current()?)));
220        #[cfg(windows)]
221        return Ok(Self(SecurityInfoInner::Windows(
222            WindowsTokenInfo::current()?
223        )));
224    }
225}
226
227impl UnixSecurityInfo {
228    /// Returns the real user ID.
229    pub const fn uid(&self) -> u32 {
230        self.uid
231    }
232    /// Returns the real group ID.
233    pub const fn gid(&self) -> u32 {
234        self.gid
235    }
236    /// Returns the effective user ID.
237    pub const fn effective_uid(&self) -> u32 {
238        self.euid
239    }
240    /// Returns the effective group ID.
241    pub const fn effective_gid(&self) -> u32 {
242        self.egid
243    }
244    /// Returns the supplementary group IDs.
245    pub fn groups(&self) -> &[u32] {
246        &self.groups
247    }
248}
249
250impl TokenGroup {
251    /// Returns the group security identifier.
252    pub fn sid(&self) -> &Sid {
253        &self.sid
254    }
255    /// Returns the native token-group attributes.
256    pub const fn attributes(&self) -> TokenGroupAttributes {
257        self.attributes
258    }
259}
260
261impl SidName {
262    /// Returns the resolved security identifier.
263    pub fn sid(&self) -> &Sid {
264        &self.sid
265    }
266    /// Returns the account name.
267    pub fn name(&self) -> &str {
268        &self.name
269    }
270    /// Returns the account domain.
271    pub fn domain(&self) -> &str {
272        &self.domain
273    }
274    /// Returns the account classification.
275    pub const fn kind(&self) -> SidNameUse {
276        self.kind
277    }
278}
279
280#[cfg(unix)]
281impl UnixSecurityInfo {
282    fn current() -> Result<Self> {
283        use nix::unistd::{getegid, geteuid, getgid, getuid};
284
285        let euid = geteuid();
286        let egid = getegid();
287
288        Ok(Self {
289            uid: getuid().as_raw(),
290            gid: getgid().as_raw(),
291            euid: euid.as_raw(),
292            egid: egid.as_raw(),
293            groups: current_groups(euid, egid)?,
294        })
295    }
296}
297
298#[cfg(all(unix, not(target_os = "macos")))]
299fn current_groups(_euid: nix::unistd::Uid, _egid: nix::unistd::Gid) -> Result<Vec<u32>> {
300    Ok(nix::unistd::getgroups()
301        .map_err(io::Error::from)?
302        .into_iter()
303        .map(|gid| gid.as_raw())
304        .collect())
305}
306
307#[cfg(target_os = "macos")]
308fn current_groups(euid: nix::unistd::Uid, egid: nix::unistd::Gid) -> Result<Vec<u32>> {
309    use std::{ffi::CString, ptr, slice};
310
311    // macOS limits the public getgroups/getgrouplist interfaces and resolves
312    // extended memberships through opendirectoryd. This SPI returns the full
313    // list in a libc-allocated buffer owned by the caller.
314    unsafe extern "C" {
315        fn getgrouplist_2(
316            name: *const libc::c_char,
317            base_gid: libc::gid_t,
318            groups: *mut *mut libc::gid_t,
319        ) -> i32;
320    }
321
322    let user = nix::unistd::User::from_uid(euid)
323        .map_err(io::Error::from)?
324        .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "effective user not found"))?;
325    let name = CString::new(user.name)
326        .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "user name contains NUL"))?;
327    let mut groups = ptr::null_mut();
328    let count = unsafe { getgrouplist_2(name.as_ptr(), egid.as_raw(), &mut groups) };
329    if count < 0 {
330        if !groups.is_null() {
331            unsafe { libc::free(groups.cast()) };
332        }
333        return Err(io::Error::other("getgrouplist_2 failed").into());
334    }
335    if count == 0 {
336        if !groups.is_null() {
337            unsafe { libc::free(groups.cast()) };
338        }
339        return Ok(Vec::new());
340    }
341    if count > 0 && groups.is_null() {
342        return Err(io::Error::new(
343            io::ErrorKind::InvalidData,
344            "getgrouplist_2 returned a null group list",
345        )
346        .into());
347    }
348    let result = unsafe { slice::from_raw_parts(groups, count as usize) }.to_vec();
349    unsafe { libc::free(groups.cast()) };
350    Ok(result)
351}
352
353impl WindowsTokenInfo {
354    /// Returns whether the token has elevated administrator privileges.
355    pub const fn is_elevated(&self) -> bool {
356        self.is_elevated
357    }
358    /// Returns the token user SID.
359    pub fn user_sid(&self) -> &Sid {
360        &self.user_sid
361    }
362    /// Returns the token owner SID.
363    pub fn owner_sid(&self) -> &Sid {
364        &self.owner_sid
365    }
366    /// Returns the token primary group SID.
367    pub fn primary_group_sid(&self) -> &Sid {
368        &self.primary_group_sid
369    }
370    /// Returns the token group memberships.
371    pub fn groups(&self) -> &[TokenGroup] {
372        &self.groups
373    }
374    /// Returns the logon SID identified by the token group attributes.
375    pub fn logon_sid(&self) -> Option<&Sid> {
376        self.groups
377            .iter()
378            .find(|group| group.attributes.contains(TokenGroupAttributes::LOGON_ID))
379            .map(|group| &group.sid)
380    }
381}
382
383#[cfg(windows)]
384impl WindowsTokenInfo {
385    fn current() -> Result<Self> {
386        use windows_sys::Win32::System::Threading::GetCurrentProcess;
387
388        // SAFETY: the pseudo-handle from `GetCurrentProcess` is valid for the
389        // life of the process and needs no closing.
390        unsafe { Self::from_process_handle(GetCurrentProcess()) }
391    }
392
393    /// Reads the token of the process `handle` refers to.
394    ///
395    /// # Safety
396    ///
397    /// `handle` must be a live process handle carrying at least
398    /// `PROCESS_QUERY_INFORMATION` (or `PROCESS_QUERY_LIMITED_INFORMATION`).
399    pub(crate) unsafe fn from_process_handle(
400        handle: windows_sys::Win32::Foundation::HANDLE,
401    ) -> Result<Self> {
402        use std::{
403            io, mem,
404            os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle},
405            ptr, slice,
406        };
407        use windows_sys::Win32::{
408            Foundation::HANDLE,
409            Security::{
410                GetLengthSid, GetTokenInformation, IsValidSid, PSID, TOKEN_ELEVATION, TOKEN_GROUPS,
411                TOKEN_INFORMATION_CLASS, TOKEN_OWNER, TOKEN_PRIMARY_GROUP, TOKEN_QUERY, TOKEN_USER,
412                TokenElevation, TokenGroups, TokenOwner, TokenPrimaryGroup, TokenUser,
413            },
414            System::Threading::OpenProcessToken,
415        };
416
417        fn query(token: HANDLE, class: TOKEN_INFORMATION_CLASS) -> io::Result<Vec<usize>> {
418            let mut required = 0;
419            unsafe {
420                GetTokenInformation(token, class, ptr::null_mut(), 0, &mut required);
421            }
422            if required == 0 {
423                return Err(io::Error::last_os_error());
424            }
425            let word_size = mem::size_of::<usize>();
426            let mut buffer = vec![0usize; (required as usize).div_ceil(word_size)];
427            if unsafe {
428                GetTokenInformation(
429                    token,
430                    class,
431                    buffer.as_mut_ptr().cast(),
432                    required,
433                    &mut required,
434                )
435            } == 0
436            {
437                return Err(io::Error::last_os_error());
438            }
439            Ok(buffer)
440        }
441
442        unsafe fn copy_sid(sid: PSID) -> io::Result<Sid> {
443            if sid.is_null() || unsafe { IsValidSid(sid) } == 0 {
444                return Err(io::Error::new(
445                    io::ErrorKind::InvalidData,
446                    "invalid token SID",
447                ));
448            }
449            let length = unsafe { GetLengthSid(sid) } as usize;
450            let bytes = unsafe { slice::from_raw_parts(sid.cast::<u8>(), length) };
451            Sid::from_bytes(bytes)
452                .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))
453        }
454
455        unsafe fn view<T>(buffer: &[usize]) -> &T {
456            unsafe { &*buffer.as_ptr().cast::<T>() }
457        }
458
459        let mut token = ptr::null_mut();
460        if unsafe { OpenProcessToken(handle, TOKEN_QUERY, &mut token) } == 0 {
461            return Err(io::Error::last_os_error().into());
462        }
463        let token = unsafe { OwnedHandle::from_raw_handle(token) };
464        let token = token.as_raw_handle();
465
466        let elevation = query(token, TokenElevation)?;
467        let user = query(token, TokenUser)?;
468        let owner = query(token, TokenOwner)?;
469        let primary_group = query(token, TokenPrimaryGroup)?;
470        let groups = query(token, TokenGroups)?;
471
472        let elevation = unsafe { view::<TOKEN_ELEVATION>(&elevation) };
473        let user = unsafe { copy_sid(view::<TOKEN_USER>(&user).User.Sid) }?;
474        let owner = unsafe { copy_sid(view::<TOKEN_OWNER>(&owner).Owner) }?;
475        let primary_group =
476            unsafe { copy_sid(view::<TOKEN_PRIMARY_GROUP>(&primary_group).PrimaryGroup) }?;
477        let groups_info = unsafe { view::<TOKEN_GROUPS>(&groups) };
478        let native_groups = unsafe {
479            slice::from_raw_parts(
480                groups_info.Groups.as_ptr(),
481                usize::try_from(groups_info.GroupCount).unwrap(),
482            )
483        };
484        let groups = native_groups
485            .iter()
486            .map(|group| {
487                Ok(TokenGroup {
488                    sid: unsafe { copy_sid(group.Sid) }?,
489                    attributes: TokenGroupAttributes::from_bits_retain(group.Attributes),
490                })
491            })
492            .collect::<io::Result<Vec<_>>>()?;
493
494        Ok(Self {
495            is_elevated: elevation.TokenIsElevated != 0,
496            user_sid: user,
497            owner_sid: owner,
498            primary_group_sid: primary_group,
499            groups,
500        })
501    }
502}