diff --git a/gitoxide-core/src/lib.rs b/gitoxide-core/src/lib.rs index 2246e237492..5ea0c093cbf 100644 --- a/gitoxide-core/src/lib.rs +++ b/gitoxide-core/src/lib.rs @@ -84,6 +84,16 @@ pub mod repository; mod discover; pub use discover::discover; +pub fn trust(paths: &[std::path::PathBuf], mut out: impl std::io::Write) -> anyhow::Result<()> { + let trust_width = "Reduced".len(); + for path in paths { + let trust = gix::sec::Trust::from_path_ownership(path)?; + let trust = format!("{trust:?}"); + writeln!(out, "{trust: anyhow::Result<()> { if format != OutputFormat::Human { bail!("JSON output isn't supported"); diff --git a/gix-sec/src/identity.rs b/gix-sec/src/identity.rs index 420aa8963ed..09965b3ec14 100644 --- a/gix-sec/src/identity.rs +++ b/gix-sec/src/identity.rs @@ -63,9 +63,9 @@ mod impl_ { #[cfg(windows)] mod impl_ { use std::{ - io, + io, mem, mem::MaybeUninit, - os::windows::io::{FromRawHandle as _, OwnedHandle}, + os::windows::io::{AsRawHandle as _, FromRawHandle as _, OwnedHandle}, path::Path, ptr, }; @@ -76,18 +76,92 @@ mod impl_ { error!(inner, $msg); }}; ($inner:expr, $msg:expr) => {{ - return Err(io::Error::new($inner.kind(), $msg)); + return Err(io::Error::new($inner.kind(), format!("{}: {}", $msg, $inner))); }}; } + fn token_information( + token: windows_sys::Win32::Foundation::HANDLE, + class: i32, + class_name: &'static str, + subject: &'static str, + path: &Path, + ) -> io::Result> { + use windows_sys::Win32::{ + Foundation::{GetLastError, ERROR_INSUFFICIENT_BUFFER}, + Security::GetTokenInformation, + }; + + #[allow(unsafe_code)] + unsafe { + let mut buffer_size = 36; + let mut heap_buf = vec![0; 36]; + + loop { + if GetTokenInformation( + token, + class, + heap_buf.as_mut_ptr().cast(), + heap_buf.len() as _, + &mut buffer_size, + ) != 0 + { + return Ok(heap_buf); + } + + if GetLastError() != ERROR_INSUFFICIENT_BUFFER { + error!(format!( + "Couldn't acquire {class_name} for the {subject} while checking ownership of '{}'", + path.display() + )); + } + + heap_buf.resize(buffer_size as _, 0); + } + } + } + + /// Read a fixed-size token information record of type `T` with `GetTokenInformation`. + /// + /// Use this for token information classes whose result fits exactly into `T`. + fn fixed_size_token_information( + token: windows_sys::Win32::Foundation::HANDLE, + class: i32, + class_name: &'static str, + subject: &'static str, + path: &Path, + ) -> io::Result { + use windows_sys::Win32::Security::GetTokenInformation; + + #[allow(unsafe_code)] + unsafe { + let mut info = MaybeUninit::::uninit(); + let mut returned_size = 0; + if GetTokenInformation( + token, + class, + info.as_mut_ptr().cast(), + mem::size_of::() as u32, + &mut returned_size, + ) == 0 + { + error!(format!( + "Couldn't acquire {class_name} for the {subject} while checking ownership of '{}'", + path.display() + )); + } + Ok(info.assume_init()) + } + } + pub fn is_path_owned_by_current_user(path: &Path) -> io::Result { use windows_sys::Win32::{ - Foundation::{GetLastError, LocalFree, ERROR_INSUFFICIENT_BUFFER, ERROR_INVALID_FUNCTION, ERROR_SUCCESS}, + Foundation::{LocalFree, ERROR_INVALID_FUNCTION, ERROR_SUCCESS}, Security::{ Authorization::{GetNamedSecurityInfoW, SE_FILE_OBJECT}, - CheckTokenMembership, EqualSid, GetTokenInformation, IsWellKnownSid, TokenOwner, - WinBuiltinAdministratorsSid, OWNER_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, TOKEN_OWNER, - TOKEN_QUERY, + CheckTokenMembership, EqualSid, IsWellKnownSid, TokenElevationType, TokenElevationTypeLimited, + TokenLinkedToken, TokenUser, WinBuiltinAdministratorsSid, OWNER_SECURITY_INFORMATION, + PSECURITY_DESCRIPTOR, TOKEN_ELEVATION_TYPE, TOKEN_LINKED_TOKEN, TOKEN_QUERY, TOKEN_USER, }, System::Threading::{GetCurrentProcess, GetCurrentThread, OpenProcessToken, OpenThreadToken}, }; @@ -131,10 +205,7 @@ mod impl_ { let inner = io::Error::from_raw_os_error(result as _); error!( inner, - format!( - "Couldn't get security information for path '{}' with err {inner}", - path.display() - ) + format!("Couldn't get security information for path '{}'", path.display()) ); } @@ -162,53 +233,68 @@ mod impl_ { if OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, 1, token.as_mut_ptr()) == 0 && OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token.as_mut_ptr()) == 0 { - error!("Couldn't acquire thread or process token"); + error!(format!( + "Couldn't acquire a thread or process token while checking ownership of '{}'", + path.display() + )); } token.assume_init() }; let _owned_token = OwnedHandle::from_raw_handle(token as _); - let buf = 'token_buf: { - let mut buffer_size = 36; - let mut heap_buf = vec![0; 36]; - - loop { - if GetTokenInformation( - token, - TokenOwner, - heap_buf.as_mut_ptr().cast(), - heap_buf.len() as _, - &mut buffer_size, - ) != 0 - { - break 'token_buf heap_buf; - } + let user_info_buf = token_information(token, TokenUser, "TokenUser", "current token", path)?; + let token_user_info = ptr::read_unaligned(user_info_buf.as_ptr().cast::()); + let token_user = token_user_info.User.Sid; - if GetLastError() != ERROR_INSUFFICIENT_BUFFER { - error!("Couldn't acquire token ownership"); - } + if EqualSid(folder_owner, token_user) != 0 { + return Ok(true); + } - heap_buf.resize(buffer_size as _, 0); - } - }; + // Admin-group owned folders are considered owned by the current user, if they are in the admin group. + if IsWellKnownSid(folder_owner, WinBuiltinAdministratorsSid) == 0 { + return Ok(false); + } - let token_owner = (*buf.as_ptr().cast::()).Owner; + let mut is_member = 0; + if CheckTokenMembership(std::ptr::null_mut(), folder_owner, &mut is_member) == 0 { + error!(format!( + "Couldn't check whether the current token is in the Administrators group while checking ownership of '{}'", + path.display() + )); + } - // If the current user is the owner of the parent folder then they also - // own this file - if EqualSid(folder_owner, token_owner) != 0 { + if is_member != 0 { return Ok(true); } - // Admin-group owned folders are considered owned by the current user, if they are in the admin group - if IsWellKnownSid(token_owner, WinBuiltinAdministratorsSid) == 0 { + let elevation_type = fixed_size_token_information::( + token, + TokenElevationType, + "TokenElevationType", + "current token", + path, + )?; + if elevation_type != TokenElevationTypeLimited { return Ok(false); } + let linked_token_info = fixed_size_token_information::( + token, + TokenLinkedToken, + "TokenLinkedToken", + "limited current token", + path, + )?; + let linked_token = linked_token_info.LinkedToken; + let linked_token = OwnedHandle::from_raw_handle(linked_token as _); + let mut is_member = 0; - if CheckTokenMembership(std::ptr::null_mut(), token_owner, &mut is_member) == 0 { - error!("Couldn't check if user is an administrator"); + if CheckTokenMembership(linked_token.as_raw_handle() as _, folder_owner, &mut is_member) == 0 { + error!(format!( + "Couldn't check whether the linked elevated token is in the Administrators group while checking ownership of '{}'", + path.display() + )); } Ok(is_member != 0) diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 12edbd7cc27..d02d376762a 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -758,6 +758,15 @@ pub fn main() -> Result<()> { None, move |_progress, out, _err| core::discover(&repository_path, out), ), + free::Subcommands::Trust { paths } => prepare_and_run( + "trust", + trace, + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| core::trust(&paths, out), + ), free::Subcommands::CommitGraph(cmd) => match cmd { free::commitgraph::Subcommands::Verify { path, statistics } => prepare_and_run( "commitgraph-verify", diff --git a/src/plumbing/options/free.rs b/src/plumbing/options/free.rs index 631d481227f..6ce2b59defc 100644 --- a/src/plumbing/options/free.rs +++ b/src/plumbing/options/free.rs @@ -16,6 +16,12 @@ pub enum Subcommands { Index(index::Platform), /// Show information about repository discovery and when opening a repository at the current path. Discover, + /// Show the trust level implied by the ownership of one or more paths. + Trust { + /// The paths whose ownership-derived trust should be shown. + #[clap(required = true)] + paths: Vec, + }, } ///