use std::ffi::c_void;
use std::io;
use std::mem::size_of;
use std::mem::size_of_val;
use std::os::windows::ffi::OsStrExt;
use std::path::Path;
use std::ptr::addr_of;
use std::ptr::null_mut;
use windows_sys::Win32::Foundation::CloseHandle;
use windows_sys::Win32::Foundation::ERROR_SUCCESS;
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::Foundation::LocalFree;
use windows_sys::Win32::Security::ACCESS_ALLOWED_ACE;
use windows_sys::Win32::Security::ACE_HEADER;
use windows_sys::Win32::Security::ACL;
use windows_sys::Win32::Security::ACL_REVISION;
use windows_sys::Win32::Security::ACL_SIZE_INFORMATION;
use windows_sys::Win32::Security::AclSizeInformation;
use windows_sys::Win32::Security::AddAccessAllowedAceEx;
use windows_sys::Win32::Security::Authorization::ConvertStringSidToSidW;
use windows_sys::Win32::Security::Authorization::GetNamedSecurityInfoW;
use windows_sys::Win32::Security::Authorization::SE_FILE_OBJECT;
use windows_sys::Win32::Security::Authorization::SetNamedSecurityInfoW;
use windows_sys::Win32::Security::CopySid;
use windows_sys::Win32::Security::CreateWellKnownSid;
use windows_sys::Win32::Security::DACL_SECURITY_INFORMATION;
use windows_sys::Win32::Security::EqualSid;
use windows_sys::Win32::Security::GetAce;
use windows_sys::Win32::Security::GetAclInformation;
use windows_sys::Win32::Security::GetLengthSid;
use windows_sys::Win32::Security::GetTokenInformation;
use windows_sys::Win32::Security::INHERIT_ONLY_ACE;
use windows_sys::Win32::Security::InitializeAcl;
use windows_sys::Win32::Security::IsValidSid;
use windows_sys::Win32::Security::OWNER_SECURITY_INFORMATION;
use windows_sys::Win32::Security::PROTECTED_DACL_SECURITY_INFORMATION;
use windows_sys::Win32::Security::PSECURITY_DESCRIPTOR;
use windows_sys::Win32::Security::PSID;
use windows_sys::Win32::Security::SECURITY_MAX_SID_SIZE;
use windows_sys::Win32::Security::TOKEN_QUERY;
use windows_sys::Win32::Security::TOKEN_USER;
use windows_sys::Win32::Security::TokenUser;
use windows_sys::Win32::Security::WinBuiltinAdministratorsSid;
use windows_sys::Win32::Security::WinLocalSystemSid;
use windows_sys::Win32::Storage::FileSystem::DELETE;
use windows_sys::Win32::Storage::FileSystem::FILE_ALL_ACCESS;
use windows_sys::Win32::Storage::FileSystem::FILE_APPEND_DATA;
use windows_sys::Win32::Storage::FileSystem::FILE_DELETE_CHILD;
use windows_sys::Win32::Storage::FileSystem::FILE_WRITE_ATTRIBUTES;
use windows_sys::Win32::Storage::FileSystem::FILE_WRITE_DATA;
use windows_sys::Win32::Storage::FileSystem::FILE_WRITE_EA;
use windows_sys::Win32::Storage::FileSystem::WRITE_DAC;
use windows_sys::Win32::Storage::FileSystem::WRITE_OWNER;
use windows_sys::Win32::System::SystemServices::ACCESS_ALLOWED_ACE_TYPE;
use windows_sys::Win32::System::SystemServices::ACCESS_ALLOWED_CALLBACK_ACE_TYPE;
use windows_sys::Win32::System::SystemServices::ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE;
use windows_sys::Win32::System::SystemServices::ACCESS_ALLOWED_COMPOUND_ACE_TYPE;
use windows_sys::Win32::System::SystemServices::ACCESS_ALLOWED_OBJECT_ACE_TYPE;
use windows_sys::Win32::System::Threading::GetCurrentProcess;
use windows_sys::Win32::System::Threading::OpenProcessToken;
const GENERIC_WRITE: u32 = 0x4000_0000;
const GENERIC_ALL: u32 = 0x1000_0000;
const FILE_MUTATING_RIGHTS: u32 = FILE_WRITE_DATA
| FILE_APPEND_DATA
| FILE_WRITE_EA
| FILE_WRITE_ATTRIBUTES
| FILE_DELETE_CHILD
| DELETE
| WRITE_DAC
| WRITE_OWNER
| GENERIC_WRITE
| GENERIC_ALL;
const DIRECTORY_MUTATING_RIGHTS: u32 = FILE_WRITE_EA
| FILE_WRITE_ATTRIBUTES
| FILE_DELETE_CHILD
| DELETE
| WRITE_DAC
| WRITE_OWNER
| GENERIC_WRITE
| GENERIC_ALL;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum AclObjectKind {
File,
Directory,
}
impl AclObjectKind {
fn mutating_rights(self) -> u32 {
match self {
Self::File => FILE_MUTATING_RIGHTS,
Self::Directory => DIRECTORY_MUTATING_RIGHTS,
}
}
}
pub(crate) fn path_acl_is_trusted(
path: &Path,
object_kind: AclObjectKind,
system_scope: bool,
) -> io::Result<bool> {
let path = wide_path(path);
let mut owner: PSID = null_mut();
let mut dacl: *mut ACL = null_mut();
let mut descriptor: PSECURITY_DESCRIPTOR = null_mut();
let status = unsafe {
GetNamedSecurityInfoW(
path.as_ptr(),
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
&mut owner,
null_mut(),
&mut dacl,
null_mut(),
&mut descriptor,
)
};
if status != ERROR_SUCCESS {
return Err(io::Error::from_raw_os_error(status as i32));
}
let _descriptor = LocalDescriptor(descriptor);
if owner.is_null() || dacl.is_null() {
return Ok(false);
}
let sids = TrustedSids::load()?;
if !sids.is_trusted(owner, system_scope) {
return Ok(false);
}
let mut information = ACL_SIZE_INFORMATION::default();
if unsafe {
GetAclInformation(
dacl,
&mut information as *mut _ as *mut c_void,
size_of_val(&information) as u32,
AclSizeInformation,
)
} == 0
{
return Err(io::Error::last_os_error());
}
for index in 0..information.AceCount {
let mut raw_ace = null_mut();
if unsafe { GetAce(dacl, index, &mut raw_ace) } == 0 {
return Err(io::Error::last_os_error());
}
let header = unsafe { &*(raw_ace as *const ACE_HEADER) };
if u32::from(header.AceFlags) & INHERIT_ONLY_ACE != 0 {
continue;
}
match u32::from(header.AceType) {
ACCESS_ALLOWED_ACE_TYPE => {
if usize::from(header.AceSize) < size_of::<ACCESS_ALLOWED_ACE>() {
return Ok(false);
}
let ace = unsafe { &*(raw_ace as *const ACCESS_ALLOWED_ACE) };
if ace.Mask & object_kind.mutating_rights() != 0 {
let sid = addr_of!(ace.SidStart) as PSID;
if unsafe { IsValidSid(sid) } == 0 || !sids.is_trusted(sid, system_scope) {
return Ok(false);
}
}
}
ACCESS_ALLOWED_OBJECT_ACE_TYPE
| ACCESS_ALLOWED_COMPOUND_ACE_TYPE
| ACCESS_ALLOWED_CALLBACK_ACE_TYPE
| ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE => {
return Ok(false);
}
_ => {}
}
}
Ok(true)
}
pub fn make_path_private(path: &Path) -> io::Result<()> {
let sids = TrustedSids::load()?;
let principals = [sids.current(), sids.system(), sids.administrators()];
set_path_dacl(path, &principals)
}
fn set_path_dacl(path: &Path, principals: &[PSID]) -> io::Result<()> {
let entries = principals
.iter()
.copied()
.map(|sid| {
AclEntry {
flags: 0,
mask: FILE_ALL_ACCESS,
sid,
}
})
.collect::<Vec<_>>();
set_path_dacl_entries(path, &entries)
}
struct AclEntry {
flags: u32,
mask: u32,
sid: PSID,
}
fn set_path_dacl_entries(path: &Path, entries: &[AclEntry]) -> io::Result<()> {
let acl_bytes = size_of::<ACL>()
+ entries
.iter()
.map(|entry| {
size_of::<ACCESS_ALLOWED_ACE>() - size_of::<u32>()
+ unsafe { GetLengthSid(entry.sid) as usize }
})
.sum::<usize>();
let mut storage = vec![0_u32; acl_bytes.div_ceil(size_of::<u32>())];
let acl = storage.as_mut_ptr() as *mut ACL;
if unsafe { InitializeAcl(acl, acl_bytes as u32, ACL_REVISION) } == 0 {
return Err(io::Error::last_os_error());
}
for entry in entries {
if unsafe { AddAccessAllowedAceEx(acl, ACL_REVISION, entry.flags, entry.mask, entry.sid) }
== 0
{
return Err(io::Error::last_os_error());
}
}
let path = wide_path(path);
let status = unsafe {
SetNamedSecurityInfoW(
path.as_ptr(),
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
null_mut(),
null_mut(),
acl,
null_mut(),
)
};
if status == ERROR_SUCCESS {
Ok(())
} else {
Err(io::Error::from_raw_os_error(status as i32))
}
}
struct TrustedSids {
token_user: Vec<usize>,
system: Vec<u32>,
administrators: Vec<u32>,
trusted_installer: Vec<u32>,
}
impl TrustedSids {
fn load() -> io::Result<Self> {
let mut token: HANDLE = null_mut();
if unsafe { OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) } == 0 {
return Err(io::Error::last_os_error());
}
let token = OwnedHandle(token);
let mut token_bytes = 0;
unsafe {
GetTokenInformation(token.0, TokenUser, null_mut(), 0, &mut token_bytes);
}
if token_bytes < size_of::<TOKEN_USER>() as u32 {
return Err(io::Error::last_os_error());
}
let mut token_user = vec![0_usize; (token_bytes as usize).div_ceil(size_of::<usize>())];
if unsafe {
GetTokenInformation(
token.0,
TokenUser,
token_user.as_mut_ptr() as *mut c_void,
token_bytes,
&mut token_bytes,
)
} == 0
{
return Err(io::Error::last_os_error());
}
let current = unsafe { (*(token_user.as_ptr() as *const TOKEN_USER)).User.Sid };
if unsafe { IsValidSid(current) } == 0 {
return Err(io::Error::last_os_error());
}
Ok(Self {
token_user,
system: well_known_sid(WinLocalSystemSid)?,
administrators: well_known_sid(WinBuiltinAdministratorsSid)?,
trusted_installer: string_sid(
"S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464",
)?,
})
}
fn current(&self) -> PSID {
unsafe { (*(self.token_user.as_ptr() as *const TOKEN_USER)).User.Sid }
}
fn system(&self) -> PSID {
self.system.as_ptr() as PSID
}
fn administrators(&self) -> PSID {
self.administrators.as_ptr() as PSID
}
fn trusted_installer(&self) -> PSID {
self.trusted_installer.as_ptr() as PSID
}
fn is_trusted(&self, sid: PSID, system_scope: bool) -> bool {
let equal = |candidate| unsafe { EqualSid(sid, candidate) != 0 };
equal(self.system())
|| equal(self.administrators())
|| equal(self.trusted_installer())
|| (!system_scope && equal(self.current()))
}
}
fn string_sid(value: &str) -> io::Result<Vec<u32>> {
let wide: Vec<u16> = value.encode_utf16().chain(Some(0)).collect();
let mut allocated: PSID = null_mut();
if unsafe { ConvertStringSidToSidW(wide.as_ptr(), &mut allocated) } == 0 {
return Err(io::Error::last_os_error());
}
let allocated = LocalSid(allocated);
let size = unsafe { GetLengthSid(allocated.0) };
let mut sid = vec![0_u32; (size as usize).div_ceil(size_of::<u32>())];
if unsafe { CopySid(size, sid.as_mut_ptr() as PSID, allocated.0) } == 0 {
return Err(io::Error::last_os_error());
}
Ok(sid)
}
fn well_known_sid(kind: i32) -> io::Result<Vec<u32>> {
let mut size = SECURITY_MAX_SID_SIZE;
let mut sid = vec![0_u32; (size as usize).div_ceil(size_of::<u32>())];
if unsafe { CreateWellKnownSid(kind, null_mut(), sid.as_mut_ptr() as PSID, &mut size) } == 0 {
Err(io::Error::last_os_error())
} else {
Ok(sid)
}
}
fn wide_path(path: &Path) -> Vec<u16> {
path.as_os_str().encode_wide().chain(Some(0)).collect()
}
struct OwnedHandle(HANDLE);
impl Drop for OwnedHandle {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe {
CloseHandle(self.0);
}
}
}
}
struct LocalDescriptor(PSECURITY_DESCRIPTOR);
impl Drop for LocalDescriptor {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe {
LocalFree(self.0);
}
}
}
}
struct LocalSid(PSID);
impl Drop for LocalSid {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe {
LocalFree(self.0);
}
}
}
}
#[cfg(test)]
mod tests {
use windows_sys::Win32::Security::CONTAINER_INHERIT_ACE;
use windows_sys::Win32::Security::INHERIT_ONLY_ACE;
use windows_sys::Win32::Security::OBJECT_INHERIT_ACE;
use windows_sys::Win32::Security::WinWorldSid;
use windows_sys::Win32::Storage::FileSystem::FILE_ADD_FILE;
use windows_sys::Win32::Storage::FileSystem::FILE_ADD_SUBDIRECTORY;
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_EXECUTE;
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_READ;
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_WRITE;
use super::*;
fn set_world_access(path: &Path, mask: u32, flags: u32) {
let sids = TrustedSids::load().unwrap();
let world = well_known_sid(WinWorldSid).unwrap();
let entries = [
AclEntry {
flags: 0,
mask: FILE_ALL_ACCESS,
sid: sids.current(),
},
AclEntry {
flags: 0,
mask: FILE_ALL_ACCESS,
sid: sids.system(),
},
AclEntry {
flags: 0,
mask: FILE_ALL_ACCESS,
sid: sids.administrators(),
},
AclEntry {
flags,
mask,
sid: world.as_ptr() as PSID,
},
];
set_path_dacl_entries(path, &entries).unwrap();
}
#[test]
fn private_acl_is_accepted_for_the_current_user() {
let directory = tempfile::tempdir().unwrap();
make_path_private(directory.path()).unwrap();
assert!(path_acl_is_trusted(directory.path(), AclObjectKind::Directory, false).unwrap());
}
#[test]
fn writable_acl_for_an_untrusted_principal_is_rejected() {
let directory = tempfile::tempdir().unwrap();
set_world_access(directory.path(), FILE_ALL_ACCESS, 0);
assert!(!path_acl_is_trusted(directory.path(), AclObjectKind::Directory, false).unwrap());
}
#[test]
fn directory_add_subdirectory_does_not_grant_file_append_access() {
let directory = tempfile::tempdir().unwrap();
let file = directory.path().join("endpoint.exe");
std::fs::write(&file, "endpoint").unwrap();
set_world_access(directory.path(), FILE_ADD_SUBDIRECTORY, 0);
set_world_access(&file, FILE_APPEND_DATA, 0);
assert!(path_acl_is_trusted(directory.path(), AclObjectKind::Directory, false).unwrap());
assert!(!path_acl_is_trusted(&file, AclObjectKind::File, false).unwrap());
}
#[test]
fn directory_add_file_does_not_grant_file_write_data_access() {
let directory = tempfile::tempdir().unwrap();
let file = directory.path().join("endpoint.exe");
std::fs::write(&file, "endpoint").unwrap();
set_world_access(directory.path(), FILE_ADD_FILE, 0);
set_world_access(&file, FILE_WRITE_DATA, 0);
assert!(path_acl_is_trusted(directory.path(), AclObjectKind::Directory, false).unwrap());
assert!(!path_acl_is_trusted(&file, AclObjectKind::File, false).unwrap());
}
#[test]
fn directory_rights_that_can_alter_existing_components_are_rejected() {
let directory = tempfile::tempdir().unwrap();
let rights = [
("delete child", FILE_DELETE_CHILD),
("delete", DELETE),
("write DACL", WRITE_DAC),
("write owner", WRITE_OWNER),
("write extended attributes", FILE_WRITE_EA),
("write attributes", FILE_WRITE_ATTRIBUTES),
("generic write", GENERIC_WRITE),
("generic all", GENERIC_ALL),
];
for (name, mask) in rights {
set_world_access(directory.path(), mask, 0);
assert!(
!path_acl_is_trusted(directory.path(), AclObjectKind::Directory, false).unwrap(),
"{name} was accepted"
);
}
}
#[test]
fn inherit_only_access_is_ignored_until_it_becomes_effective() {
let directory = tempfile::tempdir().unwrap();
let modify = FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE;
set_world_access(
directory.path(),
modify,
CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE | INHERIT_ONLY_ACE,
);
assert!(path_acl_is_trusted(directory.path(), AclObjectKind::Directory, false).unwrap());
let child = directory.path().join("child");
std::fs::create_dir(&child).unwrap();
assert!(!path_acl_is_trusted(&child, AclObjectKind::Directory, false).unwrap());
}
}