use super::super::{denied, io};
use std::{
ffi::c_void,
fs::File,
os::windows::{
ffi::OsStrExt,
io::{AsRawHandle, FromRawHandle},
},
path::Path,
ptr,
};
use windows_sys::Win32::{
Foundation::{CloseHandle, LocalFree, HANDLE, INVALID_HANDLE_VALUE},
Security::{
self,
Authorization::{
ConvertSidToStringSidW, ConvertStringSecurityDescriptorToSecurityDescriptorW,
GetSecurityInfo, SE_FILE_OBJECT,
},
},
Storage::FileSystem::{self as fs, BY_HANDLE_FILE_INFORMATION},
System::Threading::{GetCurrentProcess, OpenProcessToken},
};
struct LocalMemory(*mut c_void);
impl Drop for LocalMemory {
fn drop(&mut self) {
unsafe {
LocalFree(self.0);
}
}
}
struct Token(HANDLE);
impl Drop for Token {
fn drop(&mut self) {
unsafe {
CloseHandle(self.0);
}
}
}
pub(super) struct Identity {
buffer: Vec<usize>,
offset: usize,
}
impl Identity {
pub(super) fn current() -> io::Result<Self> {
let mut token = ptr::null_mut();
if unsafe { OpenProcessToken(GetCurrentProcess(), Security::TOKEN_QUERY, &mut token) } == 0
{
return Err(io::Error::last_os_error());
}
if token.is_null() {
return Err(denied());
}
let token = Token(token);
let mut length = 0;
unsafe {
Security::GetTokenInformation(
token.0,
Security::TokenUser,
ptr::null_mut(),
0,
&mut length,
);
}
if !(std::mem::size_of::<Security::TOKEN_USER>()..=4096).contains(&(length as usize)) {
return Err(denied());
}
let mut buffer = vec![0usize; (length as usize).div_ceil(std::mem::size_of::<usize>())];
if unsafe {
Security::GetTokenInformation(
token.0,
Security::TokenUser,
buffer.as_mut_ptr().cast(),
length,
&mut length,
)
} == 0
{
return Err(io::Error::last_os_error());
}
if length as usize > buffer.len() * std::mem::size_of::<usize>()
|| (length as usize) < std::mem::size_of::<Security::TOKEN_USER>()
{
return Err(denied());
}
let sid = unsafe { (*buffer.as_ptr().cast::<Security::TOKEN_USER>()).User.Sid };
let start = buffer.as_ptr() as usize;
let offset = (sid as usize).checked_sub(start).ok_or_else(denied)?;
let available = (length as usize).checked_sub(offset).ok_or_else(denied)?;
if available < 8 {
return Err(denied());
}
let count = unsafe { *sid.cast::<u8>().add(1) } as usize;
if count > 15 || 8 + count * 4 > available || unsafe { Security::IsValidSid(sid) } == 0 {
return Err(denied());
}
Ok(Self { buffer, offset })
}
fn sid(&self) -> *mut c_void {
unsafe {
self.buffer
.as_ptr()
.cast::<u8>()
.add(self.offset)
.cast_mut()
.cast()
}
}
fn descriptor(&self) -> io::Result<LocalMemory> {
let mut text = ptr::null_mut();
if unsafe { ConvertSidToStringSidW(self.sid(), &mut text) } == 0 {
return Err(io::Error::last_os_error());
}
let memory = LocalMemory(text.cast());
let mut length = 0;
while length < 184 && unsafe { *text.add(length) } != 0 {
length += 1;
}
if length == 184 {
return Err(denied());
}
let user = String::from_utf16(unsafe { std::slice::from_raw_parts(text, length) })
.map_err(|_| denied())?;
drop(memory);
let descriptor = format!("O:{user}D:P(A;OICI;FA;;;{user})(A;OICI;FA;;;SY)");
let text = descriptor.encode_utf16().chain(Some(0)).collect::<Vec<_>>();
let mut pointer = ptr::null_mut();
if unsafe {
ConvertStringSecurityDescriptorToSecurityDescriptorW(
text.as_ptr(),
1,
&mut pointer,
ptr::null_mut(),
)
} == 0
{
return Err(io::Error::last_os_error());
}
if pointer.is_null() {
return Err(denied());
}
Ok(LocalMemory(pointer))
}
pub(super) fn attest(&self, file: &File) -> io::Result<()> {
let mut owner = ptr::null_mut();
let mut dacl = ptr::null_mut();
let mut descriptor = ptr::null_mut();
let error = unsafe {
GetSecurityInfo(
file.as_raw_handle(),
SE_FILE_OBJECT,
Security::OWNER_SECURITY_INFORMATION | Security::DACL_SECURITY_INFORMATION,
&mut owner,
ptr::null_mut(),
&mut dacl,
ptr::null_mut(),
&mut descriptor,
)
};
if error != 0 {
return Err(io::Error::from_raw_os_error(error as i32));
}
let _descriptor = LocalMemory(descriptor);
if descriptor.is_null() || owner.is_null() || dacl.is_null() {
return Err(denied());
}
if unsafe { Security::EqualSid(owner, self.sid()) } == 0
|| unsafe { Security::IsValidAcl(dacl) } == 0
{
return Err(denied());
}
let count = unsafe { (*dacl).AceCount };
if count == 0 || count > 16 {
return Err(denied());
}
for index in 0..count {
let mut entry = ptr::null_mut();
if unsafe { Security::GetAce(dacl, u32::from(index), &mut entry) } == 0
|| entry.is_null()
{
return Err(denied());
}
let header = unsafe { &*entry.cast::<Security::ACE_HEADER>() };
if header.AceType != 0 || header.AceSize < 16 {
return Err(denied());
}
let allowed = entry.cast::<Security::ACCESS_ALLOWED_ACE>();
let sid = unsafe {
ptr::addr_of!((*allowed).SidStart)
.cast_mut()
.cast::<c_void>()
};
let subauthorities = unsafe { *sid.cast::<u8>().add(1) } as usize;
if subauthorities > 15 || 8 + subauthorities * 4 > usize::from(header.AceSize) - 8 {
return Err(denied());
}
if unsafe { Security::IsValidSid(sid) } == 0
|| !(unsafe { Security::EqualSid(sid, self.sid()) } != 0
|| unsafe { Security::IsWellKnownSid(sid, Security::WinLocalSystemSid) } != 0
|| unsafe {
Security::IsWellKnownSid(sid, Security::WinBuiltinAdministratorsSid)
} != 0)
{
return Err(denied());
}
}
Ok(())
}
}
fn wide(path: &Path) -> io::Result<Vec<u16>> {
let value = path.as_os_str().encode_wide().collect::<Vec<_>>();
if value.is_empty() || value.len() > 4096 || value.contains(&0) {
return Err(denied());
}
Ok(value.into_iter().chain(Some(0)).collect())
}
pub(super) fn open(
path: &Path,
directory: bool,
write: bool,
create: bool,
identity: &Identity,
) -> io::Result<File> {
let path = wide(path)?;
let descriptor = if create {
Some(identity.descriptor()?)
} else {
None
};
let attributes = descriptor
.as_ref()
.map(|descriptor| Security::SECURITY_ATTRIBUTES {
nLength: std::mem::size_of::<Security::SECURITY_ATTRIBUTES>() as u32,
lpSecurityDescriptor: descriptor.0,
bInheritHandle: 0,
});
let access = fs::FILE_GENERIC_READ | if write { fs::FILE_GENERIC_WRITE } else { 0 };
let flags = fs::FILE_FLAG_OPEN_REPARSE_POINT
| if directory {
fs::FILE_FLAG_BACKUP_SEMANTICS
} else {
0
};
let handle = unsafe {
fs::CreateFileW(
path.as_ptr(),
access,
fs::FILE_SHARE_READ | fs::FILE_SHARE_WRITE,
attributes.as_ref().map_or(ptr::null(), |value| value),
if create {
fs::CREATE_NEW
} else {
fs::OPEN_EXISTING
},
flags | fs::FILE_ATTRIBUTE_NORMAL,
ptr::null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE {
return Err(io::Error::last_os_error());
}
Ok(unsafe { File::from_raw_handle(handle) })
}
pub(super) fn mkdir(path: &Path, identity: &Identity) -> io::Result<()> {
let path = wide(path)?;
let descriptor = identity.descriptor()?;
let attributes = Security::SECURITY_ATTRIBUTES {
nLength: std::mem::size_of::<Security::SECURITY_ATTRIBUTES>() as u32,
lpSecurityDescriptor: descriptor.0,
bInheritHandle: 0,
};
if unsafe { fs::CreateDirectoryW(path.as_ptr(), &attributes) } == 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
pub(super) fn info(file: &File) -> io::Result<BY_HANDLE_FILE_INFORMATION> {
let mut information = BY_HANDLE_FILE_INFORMATION::default();
if unsafe { fs::GetFileInformationByHandle(file.as_raw_handle(), &mut information) } == 0 {
return Err(io::Error::last_os_error());
}
Ok(information)
}
pub(super) fn publish(source: &Path, destination: &Path, replace: bool) -> io::Result<()> {
let source = wide(source)?;
let destination = wide(destination)?;
if unsafe {
fs::MoveFileExW(
source.as_ptr(),
destination.as_ptr(),
fs::MOVEFILE_WRITE_THROUGH
| if replace {
fs::MOVEFILE_REPLACE_EXISTING
} else {
0
},
)
} == 0
{
return Err(io::Error::last_os_error());
}
Ok(())
}