diff options
Diffstat (limited to 'vendor/gix-sec/src')
-rw-r--r-- | vendor/gix-sec/src/identity.rs | 183 | ||||
-rw-r--r-- | vendor/gix-sec/src/lib.rs | 60 | ||||
-rw-r--r-- | vendor/gix-sec/src/permission.rs | 54 | ||||
-rw-r--r-- | vendor/gix-sec/src/trust.rs | 56 |
4 files changed, 353 insertions, 0 deletions
diff --git a/vendor/gix-sec/src/identity.rs b/vendor/gix-sec/src/identity.rs new file mode 100644 index 000000000..7c3df38bd --- /dev/null +++ b/vendor/gix-sec/src/identity.rs @@ -0,0 +1,183 @@ +use std::path::Path; + +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +/// An account based identity +pub struct Account { + /// The user's name + pub username: String, + /// The user's password + pub password: String, +} + +/// Returns true if the given `path` is owned by the user who is executing the current process. +/// +/// Note that this method is very specific to avoid having to deal with any operating system types. +pub fn is_path_owned_by_current_user(path: impl AsRef<Path>) -> std::io::Result<bool> { + impl_::is_path_owned_by_current_user(path) +} + +#[cfg(not(windows))] +mod impl_ { + use std::path::Path; + + pub fn is_path_owned_by_current_user(path: impl AsRef<Path>) -> std::io::Result<bool> { + fn owner_from_path(path: impl AsRef<Path>) -> std::io::Result<u32> { + use std::os::unix::fs::MetadataExt; + let meta = std::fs::symlink_metadata(path)?; + Ok(meta.uid()) + } + + fn owner_of_current_process() -> std::io::Result<u32> { + // SAFETY: there is no documented possibility for failure + #[allow(unsafe_code)] + let uid = unsafe { libc::geteuid() }; + Ok(uid) + } + use std::str::FromStr; + + let owner_of_path = owner_from_path(path)?; + let owner_of_process = owner_of_current_process()?; + if owner_of_path == owner_of_process { + Ok(true) + } else if let Some(sudo_uid) = + std::env::var_os("SUDO_UID").and_then(|val| val.to_str().and_then(|val_str| u32::from_str(val_str).ok())) + { + Ok(owner_of_path == sudo_uid) + } else { + Ok(false) + } + } +} + +#[cfg(windows)] +mod impl_ { + use std::path::Path; + + fn err(msg: impl Into<String>) -> std::io::Error { + std::io::Error::new(std::io::ErrorKind::Other, msg.into()) + } + + pub fn is_path_owned_by_current_user(path: impl AsRef<Path>) -> std::io::Result<bool> { + use windows::{ + core::{Error, PCWSTR}, + Win32::{ + Foundation::{CloseHandle, BOOL, HANDLE, PSID}, + Security::{ + Authorization::{GetNamedSecurityInfoW, SE_FILE_OBJECT}, + CheckTokenMembership, EqualSid, GetTokenInformation, IsWellKnownSid, TokenOwner, + WinBuiltinAdministratorsSid, OWNER_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, TOKEN_OWNER, + TOKEN_QUERY, + }, + System::{ + Memory::LocalFree, + Threading::{GetCurrentProcess, GetCurrentThread, OpenProcessToken, OpenThreadToken}, + }, + }, + }; + + let mut err_msg = None; + let mut is_owned = false; + let path = path.as_ref(); + + if !path.exists() { + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("{:?} does not exist.", path), + )); + } + + // Home is not actually owned by the corresponding user + // but it can be considered de-facto owned by the user + // Ignore errors here and just do the regular checks below + if gix_path::realpath(path).ok() == dirs::home_dir() { + return Ok(true); + } + + #[allow(unsafe_code)] + unsafe { + let mut folder_owner = PSID::default(); + let mut pdescriptor = PSECURITY_DESCRIPTOR::default(); + let result = GetNamedSecurityInfoW( + PCWSTR(to_wide_path(path).as_ptr()), + SE_FILE_OBJECT, + OWNER_SECURITY_INFORMATION, + Some(&mut folder_owner), + None, + None, + None, + &mut pdescriptor, + ); + + // Workaround for https://github.com/microsoft/win32metadata/issues/884 + if result.is_ok() { + let mut token = HANDLE::default(); + // Use the current thread token if possible, otherwise open the process token + OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, true, &mut token) + .ok() + .or_else(|_| OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token).ok())?; + + let mut buffer_size = 0; + let mut buffer = Vec::<u8>::new(); + GetTokenInformation(token, TokenOwner, None, 0, &mut buffer_size); + if buffer_size != 0 { + buffer.resize(buffer_size as usize, 0); + if GetTokenInformation( + token, + TokenOwner, + Some(buffer.as_mut_ptr() as *mut std::ffi::c_void), + buffer_size, + &mut buffer_size, + ) + .as_bool() + { + let token_owner = buffer.as_ptr() as *const TOKEN_OWNER; + let token_owner = (*token_owner).Owner; + + is_owned = EqualSid(folder_owner, token_owner).as_bool(); + + // Admin-group owned folders are considered owned by the current user, if they are in the admin group + if !is_owned && IsWellKnownSid(token_owner, WinBuiltinAdministratorsSid).as_bool() { + let mut is_member = BOOL::default(); + // TODO: re-use the handle + match CheckTokenMembership(HANDLE::default(), token_owner, &mut is_member).ok() { + Err(e) => err_msg = Some(format!("Couldn't check if user is an administrator: {}", e)), + Ok(()) => is_owned = is_member.as_bool(), + } + } + } else { + err_msg = format!( + "Couldn't get actual token information for current process with err: {}", + Error::from_win32() + ) + .into(); + } + } else { + err_msg = format!( + "Couldn't get token information size info for current process with err: {}", + Error::from_win32() + ) + .into(); + } + CloseHandle(token); + } else { + err_msg = format!( + "Couldn't get security information for path '{}' with err {}", + path.display(), + Error::from_win32() + ) + .into(); + } + LocalFree(pdescriptor.0 as isize); + } + + err_msg.map(|msg| Err(err(msg))).unwrap_or(Ok(is_owned)) + } + + fn to_wide_path(path: impl AsRef<Path>) -> Vec<u16> { + use std::os::windows::ffi::OsStrExt; + let mut wide_path: Vec<_> = path.as_ref().as_os_str().encode_wide().collect(); + wide_path.push(0); + wide_path + } +} diff --git a/vendor/gix-sec/src/lib.rs b/vendor/gix-sec/src/lib.rs new file mode 100644 index 000000000..2f35d98cf --- /dev/null +++ b/vendor/gix-sec/src/lib.rs @@ -0,0 +1,60 @@ +//! A shared trust model for `gitoxide` crates. +//! +//! ## Feature Flags +#![cfg_attr( + feature = "document-features", + cfg_attr(doc, doc = ::document_features::document_features!()) +)] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +// `unsafe_code` not forbidden because we need to interact with the libc +#![deny(missing_docs, rust_2018_idioms, unsafe_code)] + +use std::fmt::{Display, Formatter}; + +/// A way to specify how 'safe' we feel about a resource, typically about a git repository. +#[derive(Copy, Clone, Ord, PartialOrd, PartialEq, Eq, Debug, Hash)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub enum Trust { + /// Caution is warranted when using the resource. + Reduced, + /// We have no doubts that this resource means no harm and it can be used at will. + Full, +} + +/// +pub mod trust; + +/// Allow, deny or forbid using a resource or performing an action. +#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Hash)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub enum Permission { + /// Fail outright when trying to load a resource or performing an action. + Forbid, + /// Ignore resources or try to avoid performing an operation. + Deny, + /// Allow loading a resource or performing an action. + Allow, +} + +/// +pub mod permission; + +bitflags::bitflags! { + /// Whether something can be read or written. + #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] + pub struct ReadWrite: u8 { + /// The item can be read. + const READ = 1 << 0; + /// The item can be written + const WRITE = 1 << 1; + } +} + +impl Display for ReadWrite { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} + +/// Various types to identify entities. +pub mod identity; diff --git a/vendor/gix-sec/src/permission.rs b/vendor/gix-sec/src/permission.rs new file mode 100644 index 000000000..5bd5f2c32 --- /dev/null +++ b/vendor/gix-sec/src/permission.rs @@ -0,0 +1,54 @@ +use std::fmt::{Debug, Display, Formatter}; + +use crate::Permission; + +/// An error to use if an operation cannot proceed due to insufficient permissions. +/// +/// It's up to the implementation to decide which permission is required for an operation, and which one +/// causes errors. +#[derive(Debug)] +pub struct Error<R: std::fmt::Debug> { + /// The resource which cannot be used. + pub resource: R, +} + +impl<R> Display for Error<R> +where + R: std::fmt::Debug, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Not allowed to handle resource {:?}: permission denied", + self.resource + ) + } +} + +impl<R> std::error::Error for Error<R> where R: std::fmt::Debug {} + +impl Permission { + /// Return true if this instance is `Permission::Allow`. + pub fn is_allowed(&self) -> bool { + matches!(self, Permission::Allow) + } + /// Check this permissions and produce a reply to indicate if the `resource` can be used and in which way. + /// + /// Only if this permission is set to `Allow` will the resource be usable. + pub fn check<R: std::fmt::Debug>(&self, resource: R) -> Result<Option<R>, Error<R>> { + match self { + Permission::Allow => Ok(Some(resource)), + Permission::Deny => Ok(None), + Permission::Forbid => Err(Error { resource }), + } + } + + /// Like [`check()`][Self::check()], but degenerates the type to an option to make it more useful in cases where + /// `Forbid` shouldn't abort the entire operation. + pub fn check_opt<R: std::fmt::Debug>(&self, resource: R) -> Option<R> { + match self { + Permission::Allow => Some(resource), + Permission::Deny | Permission::Forbid => None, + } + } +} diff --git a/vendor/gix-sec/src/trust.rs b/vendor/gix-sec/src/trust.rs new file mode 100644 index 000000000..274c5b780 --- /dev/null +++ b/vendor/gix-sec/src/trust.rs @@ -0,0 +1,56 @@ +use crate::Trust; + +impl Trust { + /// Derive `Full` trust if `path` is owned by the user executing the current process, or `Reduced` trust otherwise. + pub fn from_path_ownership(path: impl AsRef<std::path::Path>) -> std::io::Result<Self> { + Ok(if crate::identity::is_path_owned_by_current_user(path.as_ref())? { + Trust::Full + } else { + Trust::Reduced + }) + } +} + +/// A trait to help creating default values based on a trust level. +pub trait DefaultForLevel { + /// Produce a default value for the given trust `level`. + fn default_for_level(level: Trust) -> Self; +} + +/// Associate instructions for how to deal with various `Trust` levels as they are encountered in the wild. +pub struct Mapping<T> { + /// The value for fully trusted resources. + pub full: T, + /// The value for resources with reduced trust. + pub reduced: T, +} + +impl<T> Default for Mapping<T> +where + T: DefaultForLevel, +{ + fn default() -> Self { + Mapping { + full: T::default_for_level(Trust::Full), + reduced: T::default_for_level(Trust::Reduced), + } + } +} + +impl<T> Mapping<T> { + /// Obtain the value for the given trust `level`. + pub fn by_level(&self, level: Trust) -> &T { + match level { + Trust::Full => &self.full, + Trust::Reduced => &self.reduced, + } + } + + /// Obtain the value for the given `level` once. + pub fn into_value_by_level(self, level: Trust) -> T { + match level { + Trust::Full => self.full, + Trust::Reduced => self.reduced, + } + } +} |