Skip to content

Commit 7bbe44c

Browse files
committed
refactor so that the windows implementation can happen (#386)
For posterity, using the windows API is absolutely terrible and I hope I don't have to do it again anytime soon. More importantly, I hope that it works nonetheless.
1 parent a58d2cf commit 7bbe44c

File tree

4 files changed

+150
-49
lines changed

4 files changed

+150
-49
lines changed

Cargo.lock

Lines changed: 49 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

git-sec/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ serde = { version = "1.0.114", optional = true, default-features = false, featur
2222
[target.'cfg(not(windows))'.dependencies]
2323
libc = "0.2.123"
2424

25+
[target.'cfg(windows)'.dependencies]
26+
windows = { version = "0.35.0", features = ["Win32_System_Threading", "Win32_Foundation", "Win32_Security", "Win32_Security_Authorization"] }
2527

2628
[dev-dependencies]
2729
tempfile = "3.3.0"

git-sec/src/lib.rs

Lines changed: 95 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,6 @@
33
44
/// Various types to identify entities.
55
pub mod identity {
6-
/// A unix user id as obtained from the file system.
7-
#[cfg(not(windows))]
8-
pub type UserId = u32;
9-
10-
/// A windows [security identifier](https://docs.microsoft.com/en-us/windows/security/identity-protection/access-control/security-identifiers)
11-
/// in its stringified form.
12-
#[cfg(windows)]
13-
pub type UserId = String;
146

157
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
168
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
@@ -25,56 +17,119 @@ pub mod identity {
2517
use std::borrow::Cow;
2618
use std::path::Path;
2719

28-
/// Obtain the owner of the given `path`.
29-
pub fn from_path(path: Cow<'_, Path>) -> std::io::Result<UserId> {
30-
impl_::from_path(path)
31-
}
32-
33-
/// Obtain the of the currently running process.
34-
pub fn from_process() -> Result<UserId, from_process::Error> {
35-
impl_::from_process()
36-
}
37-
20+
/// Returns true if the given `path` is owned by the user who is executing the current process.
3821
///
39-
pub mod from_process {
40-
use crate::identity::impl_;
41-
42-
/// The error returned by [from_process()][super::from_process()].
43-
pub type Error = impl_::FromProcessError;
22+
/// Note that this method is very specific to avoid having to deal with any operating system types.
23+
pub fn is_path_owned_by_current_user(path: Cow<'_, Path>) -> std::io::Result<bool> {
24+
impl_::is_path_owned_by_current_user(path)
4425
}
4526

4627
#[cfg(not(windows))]
4728
mod impl_ {
48-
use crate::identity::UserId;
4929
use std::borrow::Cow;
5030
use std::path::Path;
5131

52-
pub fn from_path(path: Cow<'_, Path>) -> std::io::Result<UserId> {
53-
use std::os::unix::fs::MetadataExt;
54-
let meta = std::fs::symlink_metadata(path)?;
55-
Ok(meta.uid())
56-
}
32+
pub fn is_path_owned_by_current_user(path: Cow<'_, Path>) -> std::io::Result<bool> {
33+
fn from_path(path: Cow<'_, 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+
}
5738

58-
pub type FromProcessError = std::convert::Infallible;
59-
pub fn from_process() -> Result<UserId, FromProcessError> {
60-
// SAFETY: there is no documented possibility for failure
61-
#[allow(unsafe_code)]
62-
let uid = unsafe { libc::geteuid() };
63-
Ok(uid)
39+
fn from_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+
46+
Ok(from_path(path)? == from_process()?)
6447
}
6548
}
6649

6750
#[cfg(windows)]
6851
mod impl_ {
69-
use crate::identity::UserId;
7052
use std::borrow::Cow;
7153
use std::path::Path;
7254

73-
pub fn from_path(path: Cow<'_, Path>) -> std::io::Result<UserId> {
74-
todo!("unix")
55+
fn err(msg: &str) -> std::io::Error {
56+
std::io::Error::new(std::io::ErrorKind::Other, msg)
7557
}
76-
pub fn from_process() -> std::io::Result<UserId> {
77-
todo!("process")
58+
59+
pub fn is_path_owned_by_current_user(path: Cow<'_, Path>) -> std::io::Result<bool> {
60+
use windows::Win32::{
61+
Foundation::{CloseHandle, ERROR_SUCCESS, HANDLE, PSID},
62+
Security,
63+
Security::Authorization::SE_FILE_OBJECT,
64+
System::Threading,
65+
};
66+
let mut handle = HANDLE::default();
67+
let mut descriptor = Security::PSECURITY_DESCRIPTOR::default();
68+
let mut err_msg = None;
69+
let mut is_owned = false;
70+
71+
#[allow(unsafe_code)]
72+
unsafe {
73+
Threading::OpenProcessToken(Threading::GetCurrentProcess(), Security::TOKEN_QUERY, &mut handle)
74+
.ok()
75+
.map_err(|_| err("Failed to open process token"))?;
76+
77+
let mut len = 0_u32;
78+
if Security::GetTokenInformation(&handle, Security::TokenUser, std::ptr::null_mut(), 0, &mut len)
79+
.as_bool()
80+
{
81+
let mut token_user = Security::TOKEN_USER::default();
82+
if Security::GetTokenInformation(
83+
&handle,
84+
Security::TokenUser,
85+
&mut token_user as *mut _ as *mut std::ffi::c_void,
86+
len,
87+
&mut len,
88+
)
89+
.as_bool()
90+
{
91+
// NOTE: we avoid to copy the sid or cache it in any way for now, even though it should be possible
92+
// with a custom allocation/vec/box and it's just very raw. Can the `windows` crate do better?
93+
// When/If yes, then let's improve this.
94+
if Security::IsValidSid(token_user.User.Sid).as_bool() {
95+
use std::os::windows::ffi::OsStrExt;
96+
let mut wide_path: Vec<_> = path.as_ref().as_os_str().encode_wide().collect();
97+
// err = GetNamedSecurityInfoW(wpath, SE_FILE_OBJECT,
98+
// OWNER_SECURITY_INFORMATION |
99+
// DACL_SECURITY_INFORMATION,
100+
// &sid, NULL, NULL, NULL, &descriptor);
101+
let mut path_sid = PSID::default();
102+
let res = Security::Authorization::GetNamedSecurityInfoW(
103+
windows::core::PCWSTR(wide_path.as_mut_ptr()),
104+
SE_FILE_OBJECT,
105+
Security::OWNER_SECURITY_INFORMATION | Security::DACL_SECURITY_INFORMATION,
106+
&mut path_sid,
107+
std::ptr::null_mut(),
108+
std::ptr::null_mut(),
109+
std::ptr::null_mut(),
110+
&mut descriptor,
111+
);
112+
113+
if res == ERROR_SUCCESS.0 && Security::IsValidSid(path_sid).as_bool() {
114+
is_owned = Security::EqualSid(path_sid, token_user.User.Sid).as_bool();
115+
} else {
116+
err_msg = "couldn't get owner for path or it wasn't valid".into();
117+
}
118+
} else {
119+
err_msg = "owner id of current process wasn't set or valid".into();
120+
}
121+
} else {
122+
err_msg = "Could not get information about the token user".into();
123+
}
124+
} else {
125+
err_msg = "Could not get token information for length of token user".into();
126+
}
127+
CloseHandle(handle);
128+
if !descriptor.is_invalid() {
129+
windows::core::heap_free(descriptor.0);
130+
}
131+
}
132+
err_msg.map(|msg| Err(err(msg))).unwrap_or(Ok(is_owned))
78133
}
79134
}
80135
}

git-sec/tests/identity/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
mod uid {
22
#[test]
3-
fn from_path() {
4-
let dir = tempfile::tempdir().unwrap();
5-
let owner = git_sec::identity::from_path(dir.path().into()).unwrap();
6-
assert_eq!(owner, git_sec::identity::from_process().unwrap());
3+
fn from_path() -> crate::Result {
4+
let dir = tempfile::tempdir()?;
5+
assert!(git_sec::identity::is_path_owned_by_current_user(dir.path().into())?);
6+
Ok(())
77
}
88
}

0 commit comments

Comments
 (0)