use super::{EffectiveAccess, PrivateFs, Writes};
use std::ffi::c_void;
use std::fs::File;
use std::io;
use std::os::windows::ffi::OsStrExt;
use std::os::windows::io::FromRawHandle;
use std::path::Path;
use std::ptr;
use windows_sys::Win32::Foundation::{
CloseHandle, LocalFree, GENERIC_READ, GENERIC_WRITE, HANDLE, INVALID_HANDLE_VALUE,
};
use windows_sys::Win32::Security::Authorization::{
GetNamedSecurityInfoW, SetEntriesInAclW, SetNamedSecurityInfoW, EXPLICIT_ACCESS_W,
NO_MULTIPLE_TRUSTEE, SET_ACCESS, SE_FILE_OBJECT, TRUSTEE_IS_SID, TRUSTEE_IS_UNKNOWN, TRUSTEE_W,
};
use windows_sys::Win32::Security::{
AclSizeInformation, EqualSid, GetAce, GetAclInformation, GetTokenInformation,
InitializeSecurityDescriptor, LookupAccountSidW, SetSecurityDescriptorControl,
SetSecurityDescriptorDacl, TokenUser, ACCESS_ALLOWED_ACE, ACL, ACL_SIZE_INFORMATION,
DACL_SECURITY_INFORMATION, NO_INHERITANCE, PROTECTED_DACL_SECURITY_INFORMATION,
PSECURITY_DESCRIPTOR, PSID, SECURITY_ATTRIBUTES, SECURITY_DESCRIPTOR,
SECURITY_DESCRIPTOR_CONTROL, SE_DACL_PROTECTED, TOKEN_QUERY, TOKEN_USER,
};
use windows_sys::Win32::Storage::FileSystem::{
CreateDirectoryW, CreateFileW, CREATE_ALWAYS, CREATE_NEW, FILE_ALL_ACCESS,
FILE_ATTRIBUTE_NORMAL, FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_SHARE_DELETE,
FILE_SHARE_MODE, FILE_SHARE_READ, FILE_WRITE_DATA,
};
use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
const ACCESS_ALLOWED_ACE_TYPE: u8 = 0;
const SHARE_WHILE_WRITING: FILE_SHARE_MODE = FILE_SHARE_READ | FILE_SHARE_DELETE;
#[derive(Debug, Clone, Copy, Default)]
pub struct WindowsPrivateFs;
impl WindowsPrivateFs {
pub const fn new() -> Self {
Self
}
}
impl crate::sealed::Sealed for WindowsPrivateFs {}
impl PrivateFs for WindowsPrivateFs {
fn create_dir(&self, path: &Path) -> io::Result<()> {
let sid = current_user_sid()?;
let acl = one_ace_dacl(sid.as_psid(), FILE_ALL_ACCESS)?;
let mut sd = protected_descriptor(&acl)?;
let sa = SECURITY_ATTRIBUTES {
nLength: size_of::<SECURITY_ATTRIBUTES>() as u32,
lpSecurityDescriptor: sd.as_mut_ptr(),
bInheritHandle: 0,
};
let wide = wide(path);
if unsafe { CreateDirectoryW(wide.as_ptr(), &sa) } == 0 {
let err = io::Error::last_os_error();
if err.kind() == io::ErrorKind::AlreadyExists {
return self.harden_existing(path);
}
return Err(err);
}
Ok(())
}
fn create_file_new(&self, path: &Path, writes: Writes) -> io::Result<File> {
let sid = current_user_sid()?;
let acl = one_ace_dacl(sid.as_psid(), FILE_ALL_ACCESS)?;
let mut sd = protected_descriptor(&acl)?;
let sa = SECURITY_ATTRIBUTES {
nLength: size_of::<SECURITY_ATTRIBUTES>() as u32,
lpSecurityDescriptor: sd.as_mut_ptr(),
bInheritHandle: 0,
};
let wide = wide(path);
let access = match writes {
Writes::FromStart => GENERIC_READ | GENERIC_WRITE,
Writes::Append => FILE_GENERIC_WRITE & !FILE_WRITE_DATA,
};
let handle = unsafe {
CreateFileW(
wide.as_ptr(),
access,
SHARE_WHILE_WRITING,
&sa,
CREATE_NEW,
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 as _) })
}
fn create_file_truncate(&self, path: &Path) -> io::Result<File> {
let sid = current_user_sid()?;
let acl = one_ace_dacl(sid.as_psid(), FILE_ALL_ACCESS)?;
let mut sd = protected_descriptor(&acl)?;
let sa = SECURITY_ATTRIBUTES {
nLength: size_of::<SECURITY_ATTRIBUTES>() as u32,
lpSecurityDescriptor: sd.as_mut_ptr(),
bInheritHandle: 0,
};
let wide = wide(path);
let handle = unsafe {
CreateFileW(
wide.as_ptr(),
GENERIC_READ | GENERIC_WRITE,
SHARE_WHILE_WRITING,
&sa,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
ptr::null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE {
return Err(io::Error::last_os_error());
}
self.harden_existing(path)?;
Ok(unsafe { File::from_raw_handle(handle as _) })
}
fn harden_existing(&self, path: &Path) -> io::Result<()> {
let sid = current_user_sid()?;
let acl = one_ace_dacl(sid.as_psid(), FILE_ALL_ACCESS)?;
let mut wide = wide(path);
let rc = unsafe {
SetNamedSecurityInfoW(
wide.as_mut_ptr(),
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
ptr::null_mut(),
ptr::null_mut(),
acl.as_ptr(),
ptr::null_mut(),
)
};
if rc != 0 {
return Err(io::Error::from_raw_os_error(rc as i32));
}
Ok(())
}
fn effective_access(&self, path: &Path) -> io::Result<EffectiveAccess> {
let me = current_user_sid()?;
let mut wide = wide(path);
let mut dacl: *mut ACL = ptr::null_mut();
let mut sd: PSECURITY_DESCRIPTOR = ptr::null_mut();
let rc = unsafe {
GetNamedSecurityInfoW(
wide.as_mut_ptr(),
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION,
ptr::null_mut(),
ptr::null_mut(),
&mut dacl,
ptr::null_mut(),
&mut sd,
)
};
if rc != 0 {
return Err(io::Error::from_raw_os_error(rc as i32));
}
let owned = LocalOwned(sd);
let other_readers = read_grants_other_than(dacl, &me)?;
drop(owned);
Ok(EffectiveAccess {
owner_only: other_readers.is_empty(),
other_readers,
})
}
}
fn read_grants_other_than(dacl: *const ACL, me: &OwnedSid) -> io::Result<Vec<String>> {
if dacl.is_null() {
return Ok(vec!["everyone (the object has a NULL DACL)".to_string()]);
}
let mut info = ACL_SIZE_INFORMATION {
AceCount: 0,
AclBytesInUse: 0,
AclBytesFree: 0,
};
if unsafe {
GetAclInformation(
dacl,
(&raw mut info).cast(),
size_of::<ACL_SIZE_INFORMATION>() as u32,
AclSizeInformation,
)
} == 0
{
return Err(io::Error::last_os_error());
}
let mut out = Vec::new();
for i in 0..info.AceCount {
let mut ace: *mut c_void = ptr::null_mut();
if unsafe { GetAce(dacl, i, &mut ace) } == 0 {
return Err(io::Error::last_os_error());
}
let header = unsafe { *(ace as *const u8) };
if header != ACCESS_ALLOWED_ACE_TYPE {
continue; }
let allowed = unsafe { &*(ace as *const ACCESS_ALLOWED_ACE) };
if allowed.Mask & FILE_GENERIC_READ == 0 {
continue;
}
let sid = (&raw const allowed.SidStart) as PSID;
if unsafe { EqualSid(sid, me.as_psid()) } != 0 {
continue;
}
out.push(account_name(sid));
}
Ok(out)
}
fn account_name(sid: PSID) -> String {
let mut name = [0u16; 256];
let mut domain = [0u16; 256];
let mut name_len = name.len() as u32;
let mut domain_len = domain.len() as u32;
let mut kind = 0i32;
let ok = unsafe {
LookupAccountSidW(
ptr::null(),
sid,
name.as_mut_ptr(),
&mut name_len,
domain.as_mut_ptr(),
&mut domain_len,
&mut kind,
)
};
if ok == 0 {
return "an unresolvable SID".to_string();
}
String::from_utf16_lossy(&name[..name_len as usize])
}
struct OwnedSid(Vec<u8>);
impl OwnedSid {
fn as_psid(&self) -> PSID {
unsafe { (*(self.0.as_ptr() as *const TOKEN_USER)).User.Sid }
}
}
fn current_user_sid() -> io::Result<OwnedSid> {
let mut token: HANDLE = ptr::null_mut();
if unsafe { OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) } == 0 {
return Err(io::Error::last_os_error());
}
let mut needed = 0u32;
unsafe { GetTokenInformation(token, TokenUser, ptr::null_mut(), 0, &mut needed) };
let mut buf = vec![0u8; needed as usize];
let ok = unsafe {
GetTokenInformation(
token,
TokenUser,
buf.as_mut_ptr().cast(),
needed,
&mut needed,
)
};
unsafe { CloseHandle(token) };
if ok == 0 {
return Err(io::Error::last_os_error());
}
Ok(OwnedSid(buf))
}
struct OwnedAcl(*mut ACL);
impl OwnedAcl {
fn as_ptr(&self) -> *const ACL {
self.0
}
}
impl Drop for OwnedAcl {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe { LocalFree(self.0.cast()) };
}
}
}
fn one_ace_dacl(sid: PSID, access: u32) -> io::Result<OwnedAcl> {
let ea = EXPLICIT_ACCESS_W {
grfAccessPermissions: access,
grfAccessMode: SET_ACCESS,
grfInheritance: NO_INHERITANCE,
Trustee: TRUSTEE_W {
pMultipleTrustee: ptr::null_mut(),
MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE,
TrusteeForm: TRUSTEE_IS_SID,
TrusteeType: TRUSTEE_IS_UNKNOWN,
ptstrName: sid.cast(),
},
};
let mut acl: *mut ACL = ptr::null_mut();
let rc = unsafe { SetEntriesInAclW(1, &ea, ptr::null(), &mut acl) };
if rc != 0 {
return Err(io::Error::from_raw_os_error(rc as i32));
}
Ok(OwnedAcl(acl))
}
struct Descriptor(Box<SECURITY_DESCRIPTOR>);
impl Descriptor {
fn as_mut_ptr(&mut self) -> *mut c_void {
(&raw mut *self.0).cast()
}
}
fn protected_descriptor(acl: &OwnedAcl) -> io::Result<Descriptor> {
let mut sd: Box<SECURITY_DESCRIPTOR> = Box::new(unsafe { std::mem::zeroed() });
let ptr = (&raw mut *sd).cast();
if unsafe { InitializeSecurityDescriptor(ptr, 1) } == 0 {
return Err(io::Error::last_os_error());
}
if unsafe { SetSecurityDescriptorDacl(ptr, 1, acl.as_ptr() as *mut ACL, 0) } == 0 {
return Err(io::Error::last_os_error());
}
if unsafe {
SetSecurityDescriptorControl(
ptr,
SE_DACL_PROTECTED as SECURITY_DESCRIPTOR_CONTROL,
SE_DACL_PROTECTED as SECURITY_DESCRIPTOR_CONTROL,
)
} == 0
{
return Err(io::Error::last_os_error());
}
Ok(Descriptor(sd))
}
pub(crate) struct OwnerOnlyAttributes {
sa: Box<SECURITY_ATTRIBUTES>,
_sd: Descriptor,
_acl: OwnedAcl,
}
impl OwnerOnlyAttributes {
pub(crate) fn as_ptr(&mut self) -> *mut c_void {
(&raw mut *self.sa).cast()
}
}
pub(crate) fn owner_only_attributes() -> io::Result<OwnerOnlyAttributes> {
let sid = current_user_sid()?;
let acl = one_ace_dacl(sid.as_psid(), FILE_ALL_ACCESS)?;
let mut sd = protected_descriptor(&acl)?;
let sa = Box::new(SECURITY_ATTRIBUTES {
nLength: size_of::<SECURITY_ATTRIBUTES>() as u32,
lpSecurityDescriptor: sd.as_mut_ptr(),
bInheritHandle: 0,
});
Ok(OwnerOnlyAttributes {
sa,
_sd: sd,
_acl: acl,
})
}
struct LocalOwned(*mut c_void);
impl Drop for LocalOwned {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe { LocalFree(self.0) };
}
}
}
fn wide(path: &Path) -> Vec<u16> {
path.as_os_str().encode_wide().chain(Some(0)).collect()
}
#[cfg(test)]
pub(crate) fn dacl_is_protected(path: &Path) -> io::Result<bool> {
use windows_sys::Win32::Security::GetSecurityDescriptorControl;
let mut wide = wide(path);
let mut sd: PSECURITY_DESCRIPTOR = ptr::null_mut();
let mut dacl: *mut ACL = ptr::null_mut();
let rc = unsafe {
GetNamedSecurityInfoW(
wide.as_mut_ptr(),
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION,
ptr::null_mut(),
ptr::null_mut(),
&mut dacl,
ptr::null_mut(),
&mut sd,
)
};
if rc != 0 {
return Err(io::Error::from_raw_os_error(rc as i32));
}
let owned = LocalOwned(sd);
let mut control: SECURITY_DESCRIPTOR_CONTROL = 0;
let mut revision = 0u32;
let ok = unsafe { GetSecurityDescriptorControl(sd, &mut control, &mut revision) };
drop(owned);
if ok == 0 {
return Err(io::Error::last_os_error());
}
Ok(control & (SE_DACL_PROTECTED as SECURITY_DESCRIPTOR_CONTROL) != 0)
}
#[cfg(test)]
pub(crate) fn allow_inheritance(path: &Path) -> io::Result<()> {
let mut wide = wide(path);
let rc = unsafe {
SetNamedSecurityInfoW(
wide.as_mut_ptr(),
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION
| windows_sys::Win32::Security::UNPROTECTED_DACL_SECURITY_INFORMATION,
ptr::null_mut(),
ptr::null_mut(),
ptr::null(),
ptr::null_mut(),
)
};
if rc != 0 {
return Err(io::Error::from_raw_os_error(rc as i32));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_private_object_is_detached_from_inherited_aces() {
let scratch = std::env::temp_dir().join(format!("hotl-dacl-{}", std::process::id()));
crate::PRIVATE_FS.create_dir(&scratch).unwrap();
assert!(dacl_is_protected(&scratch).unwrap());
let file = scratch.join("secret");
drop(
crate::PRIVATE_FS
.create_file_new(&file, crate::privatefs::Writes::FromStart)
.unwrap(),
);
assert!(dacl_is_protected(&file).unwrap());
allow_inheritance(&file).unwrap();
crate::PRIVATE_FS.harden_existing(&file).unwrap();
assert!(dacl_is_protected(&file).unwrap());
let _ = std::fs::remove_dir_all(&scratch);
}
}