gix_sec/
identity.rs

1use std::path::Path;
2
3#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
4#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
5/// An account based identity
6pub struct Account {
7    /// The user's name
8    pub username: String,
9    /// The user's password
10    pub password: String,
11}
12
13/// Returns true if the given `path` is owned by the user who is executing the current process.
14///
15/// Note that this method is very specific to avoid having to deal with any operating system types.
16pub fn is_path_owned_by_current_user(path: &Path) -> std::io::Result<bool> {
17    impl_::is_path_owned_by_current_user(path)
18}
19
20// Wasi doesn't have a concept of a user, so this is implicitly true.
21#[cfg(target_os = "wasi")]
22mod impl_ {
23    pub fn is_path_owned_by_current_user(_path: &std::path::Path) -> std::io::Result<bool> {
24        Ok(true)
25    }
26}
27
28#[cfg(all(not(windows), not(target_os = "wasi")))]
29mod impl_ {
30    use std::path::Path;
31
32    pub fn is_path_owned_by_current_user(path: &Path) -> std::io::Result<bool> {
33        fn owner_from_path(path: &Path) -> std::io::Result<u32> {
34            use std::os::unix::fs::MetadataExt;
35            let meta = std::fs::symlink_metadata(path)?;
36            Ok(meta.uid())
37        }
38
39        fn owner_of_current_process() -> std::io::Result<u32> {
40            // SAFETY: there is no documented possibility for failure
41            #[allow(unsafe_code)]
42            let uid = unsafe { libc::geteuid() };
43            Ok(uid)
44        }
45        use std::str::FromStr;
46
47        let owner_of_path = owner_from_path(path)?;
48        let owner_of_process = owner_of_current_process()?;
49        if owner_of_path == owner_of_process {
50            Ok(true)
51        } else if let Some(sudo_uid) =
52            std::env::var_os("SUDO_UID").and_then(|val| val.to_str().and_then(|val_str| u32::from_str(val_str).ok()))
53        {
54            Ok(owner_of_path == sudo_uid)
55        } else {
56            Ok(false)
57        }
58    }
59}
60
61#[cfg(windows)]
62mod impl_ {
63    use std::{
64        io,
65        mem::MaybeUninit,
66        os::windows::io::{FromRawHandle as _, OwnedHandle},
67        path::Path,
68        ptr,
69    };
70
71    macro_rules! error {
72        ($msg:expr) => {{
73            let inner = io::Error::last_os_error();
74            error!(inner, $msg);
75        }};
76        ($inner:expr, $msg:expr) => {{
77            return Err(io::Error::new($inner.kind(), $msg));
78        }};
79    }
80
81    pub fn is_path_owned_by_current_user(path: &Path) -> io::Result<bool> {
82        use windows_sys::Win32::{
83            Foundation::{GetLastError, LocalFree, ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS},
84            Security::{
85                Authorization::{GetNamedSecurityInfoW, SE_FILE_OBJECT},
86                CheckTokenMembership, EqualSid, GetTokenInformation, IsWellKnownSid, TokenOwner,
87                WinBuiltinAdministratorsSid, OWNER_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, TOKEN_OWNER,
88                TOKEN_QUERY,
89            },
90            System::Threading::{GetCurrentProcess, GetCurrentThread, OpenProcessToken, OpenThreadToken},
91        };
92
93        if !path.exists() {
94            return Err(io::Error::new(
95                io::ErrorKind::NotFound,
96                format!("{path:?} does not exist."),
97            ));
98        }
99
100        // Home is not actually owned by the corresponding user
101        // but it can be considered de-facto owned by the user
102        // Ignore errors here and just do the regular checks below
103        if gix_path::realpath(path).ok() == gix_path::env::home_dir() {
104            return Ok(true);
105        }
106
107        #[allow(unsafe_code)]
108        unsafe {
109            let (folder_owner, descriptor) = {
110                let mut folder_owner = MaybeUninit::uninit();
111                let mut pdescriptor = MaybeUninit::uninit();
112                let result = GetNamedSecurityInfoW(
113                    to_wide_path(path).as_ptr(),
114                    SE_FILE_OBJECT,
115                    OWNER_SECURITY_INFORMATION,
116                    folder_owner.as_mut_ptr(),
117                    ptr::null_mut(),
118                    ptr::null_mut(),
119                    ptr::null_mut(),
120                    pdescriptor.as_mut_ptr(),
121                );
122
123                if result != ERROR_SUCCESS {
124                    let inner = io::Error::from_raw_os_error(result as _);
125                    error!(
126                        inner,
127                        format!(
128                            "Couldn't get security information for path '{}' with err {inner}",
129                            path.display()
130                        )
131                    );
132                }
133
134                (folder_owner.assume_init(), pdescriptor.assume_init())
135            };
136
137            struct Descriptor(PSECURITY_DESCRIPTOR);
138
139            impl Drop for Descriptor {
140                fn drop(&mut self) {
141                    #[allow(unsafe_code)]
142                    // SAFETY: syscall only invoked if we have a valid descriptor
143                    unsafe {
144                        LocalFree(self.0 as _);
145                    }
146                }
147            }
148
149            let _descriptor = Descriptor(descriptor);
150
151            let token = {
152                let mut token = MaybeUninit::uninit();
153
154                // Use the current thread token if possible, otherwise open the process token
155                if OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, 1, token.as_mut_ptr()) == 0
156                    && OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token.as_mut_ptr()) == 0
157                {
158                    error!("Couldn't acquire thread or process token");
159                }
160                token.assume_init()
161            };
162
163            let _owned_token = OwnedHandle::from_raw_handle(token as _);
164
165            let buf = 'token_buf: {
166                let mut buffer_size = 36;
167                let mut heap_buf = vec![0; 36];
168
169                loop {
170                    if GetTokenInformation(
171                        token,
172                        TokenOwner,
173                        heap_buf.as_mut_ptr().cast(),
174                        heap_buf.len() as _,
175                        &mut buffer_size,
176                    ) != 0
177                    {
178                        break 'token_buf heap_buf;
179                    }
180
181                    if GetLastError() != ERROR_INSUFFICIENT_BUFFER {
182                        error!("Couldn't acquire token ownership");
183                    }
184
185                    heap_buf.resize(buffer_size as _, 0);
186                }
187            };
188
189            let token_owner = (*buf.as_ptr().cast::<TOKEN_OWNER>()).Owner;
190
191            // If the current user is the owner of the parent folder then they also
192            // own this file
193            if EqualSid(folder_owner, token_owner) != 0 {
194                return Ok(true);
195            }
196
197            // Admin-group owned folders are considered owned by the current user, if they are in the admin group
198            if IsWellKnownSid(token_owner, WinBuiltinAdministratorsSid) == 0 {
199                return Ok(false);
200            }
201
202            let mut is_member = 0;
203            if CheckTokenMembership(0, token_owner, &mut is_member) == 0 {
204                error!("Couldn't check if user is an administrator");
205            }
206
207            Ok(is_member != 0)
208        }
209    }
210
211    fn to_wide_path(path: impl AsRef<Path>) -> Vec<u16> {
212        use std::os::windows::ffi::OsStrExt;
213        let mut wide_path: Vec<_> = path.as_ref().as_os_str().encode_wide().collect();
214        wide_path.push(0);
215        wide_path
216    }
217}