| 1 | //! Existing Windows current-user token/SID custody, shared by owned storage and IPC. |
| 2 | |
| 3 | use anyhow::{Context, Result, bail}; |
| 4 | use std::os::windows::io::FromRawHandle as _; |
| 5 | |
| 6 | #[cfg(windows)] |
| 7 | pub struct CurrentWindowsUser { |
| 8 | _token: std::os::windows::io::OwnedHandle, |
| 9 | token_info: Vec<usize>, |
| 10 | } |
| 11 | |
| 12 | #[cfg(windows)] |
| 13 | impl CurrentWindowsUser { |
| 14 | pub fn open() -> Result<Self> { |
| 15 | // SAFETY: Windows supplies a process pseudo-handle; no ownership transfer. |
| 16 | Self::from_process(unsafe { windows_sys::Win32::System::Threading::GetCurrentProcess() }) |
| 17 | } |
| 18 | |
| 19 | fn from_process(process: windows_sys::Win32::Foundation::HANDLE) -> Result<Self> { |
| 20 | use windows_sys::Win32::Foundation::{CloseHandle, GetLastError, HANDLE}; |
| 21 | use windows_sys::Win32::Security::{ |
| 22 | GetTokenInformation, TOKEN_QUERY, TOKEN_USER, TokenUser, |
| 23 | }; |
| 24 | use windows_sys::Win32::System::Threading::OpenProcessToken; |
| 25 | |
| 26 | let mut token: HANDLE = std::ptr::null_mut(); |
| 27 | // SAFETY: the pseudo-process handle is valid and `token` is writable. |
| 28 | if unsafe { OpenProcessToken(process, TOKEN_QUERY, &mut token) } == 0 { |
| 29 | return Err(std::io::Error::last_os_error()) |
| 30 | .context("opening current Windows user token"); |
| 31 | } |
| 32 | let mut needed = 0; |
| 33 | // SAFETY: a null buffer/zero length asks for the required size. |
| 34 | let _ = |
| 35 | unsafe { GetTokenInformation(token, TokenUser, std::ptr::null_mut(), 0, &mut needed) }; |
| 36 | if needed < std::mem::size_of::<TOKEN_USER>() as u32 || needed > 65536 { |
| 37 | let error = std::io::Error::from_raw_os_error(unsafe { GetLastError() } as i32); |
| 38 | // SAFETY: the token is owned on this error path. |
| 39 | unsafe { CloseHandle(token) }; |
| 40 | return Err(error).context("sizing current Windows user token information"); |
| 41 | } |
| 42 | let words = (needed as usize).div_ceil(std::mem::size_of::<usize>()); |
| 43 | let mut token_info = vec![0usize; words]; |
| 44 | // SAFETY: the aligned buffer contains at least `needed` writable bytes. |
| 45 | if unsafe { |
| 46 | GetTokenInformation( |
| 47 | token, |
| 48 | TokenUser, |
| 49 | token_info.as_mut_ptr().cast(), |
| 50 | needed, |
| 51 | &mut needed, |
| 52 | ) |
| 53 | } == 0 |
| 54 | { |
| 55 | let error = std::io::Error::last_os_error(); |
| 56 | // SAFETY: the token is owned on this error path. |
| 57 | unsafe { CloseHandle(token) }; |
| 58 | return Err(error).context("reading current Windows user token information"); |
| 59 | } |
| 60 | let user = unsafe { &*token_info.as_ptr().cast::<TOKEN_USER>() }; |
| 61 | if user.User.Sid.is_null() { |
| 62 | // SAFETY: the token is owned on this error path. |
| 63 | unsafe { CloseHandle(token) }; |
| 64 | bail!("current Windows user token has no SID"); |
| 65 | } |
| 66 | // SAFETY: the token was opened above and is transferred exactly once. |
| 67 | let token = unsafe { std::os::windows::io::OwnedHandle::from_raw_handle(token) }; |
| 68 | Ok(Self { |
| 69 | _token: token, |
| 70 | token_info, |
| 71 | }) |
| 72 | } |
| 73 | |
| 74 | pub fn sid_string(&self) -> Result<String> { |
| 75 | use windows_sys::Win32::Security::Authorization::ConvertSidToStringSidW; |
| 76 | let mut text = std::ptr::null_mut(); |
| 77 | // SAFETY: the validated SID remains held and the output is writable. |
| 78 | if unsafe { ConvertSidToStringSidW(self.sid(), &mut text) } == 0 { |
| 79 | return Err(std::io::Error::last_os_error()).context("encoding current Windows SID"); |
| 80 | } |
| 81 | let _text = WindowsLocalAllocation(text.cast()); |
| 82 | anyhow::ensure!(!text.is_null(), "Windows SID text unavailable"); |
| 83 | let mut units = Vec::new(); |
| 84 | for index in 0..192 { |
| 85 | let unit = unsafe { *text.add(index) }; |
| 86 | if unit == 0 { |
| 87 | return String::from_utf16(&units).map_err(Into::into); |
| 88 | } |
| 89 | units.push(unit); |
| 90 | } |
| 91 | bail!("Windows SID text exceeds its fixed bound") |
| 92 | } |
| 93 | |
| 94 | pub fn sid(&self) -> windows_sys::Win32::Security::PSID { |
| 95 | use windows_sys::Win32::Security::TOKEN_USER; |
| 96 | // SAFETY: the aligned token buffer remains owned by `self`. |
| 97 | unsafe { (*self.token_info.as_ptr().cast::<TOKEN_USER>()).User.Sid } |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | #[cfg(windows)] |
| 102 | pub(crate) struct WindowsLocalAllocation(pub(crate) *mut core::ffi::c_void); |
| 103 | |
| 104 | #[cfg(windows)] |
| 105 | impl Drop for WindowsLocalAllocation { |
| 106 | fn drop(&mut self) { |
| 107 | if !self.0.is_null() { |
| 108 | // SAFETY: Windows allocated this block for a LocalFree caller. |
| 109 | unsafe { windows_sys::Win32::Foundation::LocalFree(self.0) }; |
| 110 | } |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | /// The actual kernel-selected peer process and token, held across control admission. |
| 115 | /// A display PID or a same-user SID is never sufficient to select an owner. |
| 116 | pub struct WindowsPeerProcess { |
| 117 | process: std::os::windows::io::OwnedHandle, |
| 118 | user: CurrentWindowsUser, |
| 119 | pid: u32, |
| 120 | start: String, |
| 121 | } |
| 122 | |
| 123 | impl WindowsPeerProcess { |
| 124 | pub fn open_current_user(pid: u32) -> Result<Self> { |
| 125 | use std::os::windows::io::AsRawHandle as _; |
| 126 | use windows_sys::Win32::Storage::FileSystem::SYNCHRONIZE; |
| 127 | use windows_sys::Win32::System::Threading::{ |
| 128 | GetProcessId, OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION, |
| 129 | }; |
| 130 | anyhow::ensure!(pid > 0, "invalid Windows peer PID"); |
| 131 | let process = |
| 132 | unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, 0, pid) }; |
| 133 | anyhow::ensure!(!process.is_null(), "Windows peer process unavailable"); |
| 134 | let process = unsafe { std::os::windows::io::OwnedHandle::from_raw_handle(process) }; |
| 135 | anyhow::ensure!( |
| 136 | unsafe { GetProcessId(process.as_raw_handle()) } == pid, |
| 137 | "Windows kernel peer PID changed" |
| 138 | ); |
| 139 | let user = CurrentWindowsUser::from_process(process.as_raw_handle())?; |
| 140 | let start = process_creation(process.as_raw_handle())?; |
| 141 | let value = Self { |
| 142 | process, |
| 143 | user, |
| 144 | pid, |
| 145 | start, |
| 146 | }; |
| 147 | value.check_current_user()?; |
| 148 | Ok(value) |
| 149 | } |
| 150 | pub fn pid(&self) -> u32 { |
| 151 | self.pid |
| 152 | } |
| 153 | pub fn start(&self) -> &str { |
| 154 | &self.start |
| 155 | } |
| 156 | pub fn principal(&self) -> Result<String> { |
| 157 | self.user.sid_string() |
| 158 | } |
| 159 | pub fn check_current_user(&self) -> Result<()> { |
| 160 | use std::os::windows::io::AsRawHandle as _; |
| 161 | use windows_sys::Win32::Security::EqualSid; |
| 162 | use windows_sys::Win32::System::Threading::GetExitCodeProcess; |
| 163 | let mut status = 0; |
| 164 | anyhow::ensure!( |
| 165 | unsafe { GetExitCodeProcess(self.process.as_raw_handle(), &mut status) } != 0 |
| 166 | && status == 259, |
| 167 | "Windows peer process has exited" |
| 168 | ); |
| 169 | let current = CurrentWindowsUser::open()?; |
| 170 | let peer = CurrentWindowsUser::from_process(self.process.as_raw_handle())?; |
| 171 | anyhow::ensure!( |
| 172 | unsafe { EqualSid(current.sid(), peer.sid()) } != 0 |
| 173 | && unsafe { EqualSid(self.user.sid(), peer.sid()) } != 0, |
| 174 | "Windows peer principal changed" |
| 175 | ); |
| 176 | anyhow::ensure!( |
| 177 | process_creation(self.process.as_raw_handle())? == self.start, |
| 178 | "Windows peer generation changed" |
| 179 | ); |
| 180 | Ok(()) |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | fn process_creation(process: windows_sys::Win32::Foundation::HANDLE) -> Result<String> { |
| 185 | use windows_sys::Win32::Foundation::FILETIME; |
| 186 | use windows_sys::Win32::System::Threading::GetProcessTimes; |
| 187 | let (mut creation, mut exit, mut kernel, mut user) = ( |
| 188 | FILETIME { |
| 189 | dwLowDateTime: 0, |
| 190 | dwHighDateTime: 0, |
| 191 | }, |
| 192 | FILETIME { |
| 193 | dwLowDateTime: 0, |
| 194 | dwHighDateTime: 0, |
| 195 | }, |
| 196 | FILETIME { |
| 197 | dwLowDateTime: 0, |
| 198 | dwHighDateTime: 0, |
| 199 | }, |
| 200 | FILETIME { |
| 201 | dwLowDateTime: 0, |
| 202 | dwHighDateTime: 0, |
| 203 | }, |
| 204 | ); |
| 205 | anyhow::ensure!( |
| 206 | unsafe { GetProcessTimes(process, &mut creation, &mut exit, &mut kernel, &mut user) } != 0, |
| 207 | "Windows process creation identity unavailable" |
| 208 | ); |
| 209 | Ok(format!( |
| 210 | "windows:{}:{}", |
| 211 | creation.dwHighDateTime, creation.dwLowDateTime |
| 212 | )) |
| 213 | } |
| 214 | |
| 215 | /// One current-user ACL builder, immediately adopted by private storage and |
| 216 | /// named-pipe creation. Both commit the same protected owner/DACL policy. |
| 217 | pub struct OwnerOnlyAcl { |
| 218 | user: CurrentWindowsUser, |
| 219 | acl: WindowsLocalAllocation, |
| 220 | } |
| 221 | impl OwnerOnlyAcl { |
| 222 | pub fn new(access: u32, inherit_to_children: bool) -> Result<Self> { |
| 223 | use windows_sys::Win32::Security::Authorization::{ |
| 224 | EXPLICIT_ACCESS_W, SET_ACCESS, SetEntriesInAclW, TRUSTEE_IS_SID, TRUSTEE_IS_USER, |
| 225 | TRUSTEE_W, |
| 226 | }; |
| 227 | use windows_sys::Win32::Security::{NO_INHERITANCE, SUB_CONTAINERS_AND_OBJECTS_INHERIT}; |
| 228 | let user = CurrentWindowsUser::open()?; |
| 229 | let entry = EXPLICIT_ACCESS_W { |
| 230 | grfAccessPermissions: access, |
| 231 | grfAccessMode: SET_ACCESS, |
| 232 | grfInheritance: if inherit_to_children { |
| 233 | SUB_CONTAINERS_AND_OBJECTS_INHERIT |
| 234 | } else { |
| 235 | NO_INHERITANCE |
| 236 | }, |
| 237 | Trustee: TRUSTEE_W { |
| 238 | pMultipleTrustee: std::ptr::null_mut(), |
| 239 | MultipleTrusteeOperation: 0, |
| 240 | TrusteeForm: TRUSTEE_IS_SID, |
| 241 | TrusteeType: TRUSTEE_IS_USER, |
| 242 | ptstrName: user.sid().cast(), |
| 243 | }, |
| 244 | }; |
| 245 | let mut acl = std::ptr::null_mut(); |
| 246 | let status = unsafe { SetEntriesInAclW(1, &entry, std::ptr::null(), &mut acl) }; |
| 247 | anyhow::ensure!( |
| 248 | status == 0, |
| 249 | "building current-user-only ACL failed ({status})" |
| 250 | ); |
| 251 | Ok(Self { |
| 252 | user, |
| 253 | acl: WindowsLocalAllocation(acl.cast()), |
| 254 | }) |
| 255 | } |
| 256 | pub(crate) fn user_sid(&self) -> windows_sys::Win32::Security::PSID { |
| 257 | self.user.sid() |
| 258 | } |
| 259 | pub(crate) fn acl(&self) -> *mut windows_sys::Win32::Security::ACL { |
| 260 | self.acl.0.cast() |
| 261 | } |
| 262 | pub fn with_security_attributes<T>( |
| 263 | &self, |
| 264 | create: impl FnOnce(*mut core::ffi::c_void) -> Result<T>, |
| 265 | ) -> Result<T> { |
| 266 | use windows_sys::Win32::Security::{ |
| 267 | InitializeSecurityDescriptor, SE_DACL_PROTECTED, SECURITY_ATTRIBUTES, |
| 268 | SECURITY_DESCRIPTOR, SetSecurityDescriptorControl, SetSecurityDescriptorDacl, |
| 269 | SetSecurityDescriptorOwner, |
| 270 | }; |
| 271 | let mut descriptor = SECURITY_DESCRIPTOR::default(); |
| 272 | let pointer = (&mut descriptor as *mut SECURITY_DESCRIPTOR).cast(); |
| 273 | anyhow::ensure!( |
| 274 | unsafe { InitializeSecurityDescriptor(pointer, 1) } != 0, |
| 275 | "initializing owner security descriptor" |
| 276 | ); |
| 277 | anyhow::ensure!( |
| 278 | unsafe { SetSecurityDescriptorOwner(pointer, self.user.sid(), 0) } != 0, |
| 279 | "setting owner SID" |
| 280 | ); |
| 281 | anyhow::ensure!( |
| 282 | unsafe { SetSecurityDescriptorDacl(pointer, 1, self.acl(), 0) } != 0, |
| 283 | "setting owner-only DACL" |
| 284 | ); |
| 285 | anyhow::ensure!( |
| 286 | unsafe { SetSecurityDescriptorControl(pointer, SE_DACL_PROTECTED, SE_DACL_PROTECTED) } |
| 287 | != 0, |
| 288 | "protecting owner-only DACL" |
| 289 | ); |
| 290 | let mut attributes = SECURITY_ATTRIBUTES { |
| 291 | nLength: std::mem::size_of::<SECURITY_ATTRIBUTES>() as u32, |
| 292 | lpSecurityDescriptor: pointer, |
| 293 | bInheritHandle: 0, |
| 294 | }; |
| 295 | create((&mut attributes as *mut SECURITY_ATTRIBUTES).cast()) |
| 296 | } |
| 297 | } |
| 298 |