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