#![allow(unsafe_code)]
use std::{
fs::File,
io,
mem::{size_of, zeroed},
os::windows::{ffi::OsStrExt, io::AsRawHandle, io::FromRawHandle},
path::Path,
ptr::{null, null_mut},
};
use windows_sys::Win32::{
Foundation::{CloseHandle, LocalFree, ERROR_ALREADY_EXISTS, HANDLE, INVALID_HANDLE_VALUE},
Security::{
AddAccessAllowedAceEx,
Authorization::{GetSecurityInfo, SetSecurityInfo, SE_FILE_OBJECT},
EqualSid, GetAce, GetLengthSid, GetTokenInformation, InitializeAcl,
InitializeSecurityDescriptor, IsWellKnownSid, SetSecurityDescriptorControl,
SetSecurityDescriptorDacl, SetSecurityDescriptorOwner, TokenUser,
WinBuiltinAdministratorsSid, WinLocalSystemSid, ACCESS_ALLOWED_ACE, ACE_HEADER, ACL,
ACL_REVISION, CONTAINER_INHERIT_ACE, DACL_SECURITY_INFORMATION, OBJECT_INHERIT_ACE,
OWNER_SECURITY_INFORMATION, PROTECTED_DACL_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR,
PSID, SECURITY_ATTRIBUTES, SECURITY_DESCRIPTOR, SE_DACL_PROTECTED, TOKEN_QUERY, TOKEN_USER,
},
Storage::FileSystem::{
CreateDirectoryW, CreateFileW, GetFileInformationByHandle, GetFileType,
BY_HANDLE_FILE_INFORMATION, CREATE_NEW, FILE_ALL_ACCESS, FILE_ATTRIBUTE_DIRECTORY,
FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_REPARSE_POINT, FILE_FLAG_BACKUP_SEMANTICS,
FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE,
FILE_TYPE_DISK, OPEN_EXISTING, READ_CONTROL, WRITE_DAC,
},
System::Threading::{GetCurrentProcess, OpenProcessToken},
};
struct Handle(HANDLE);
impl Drop for Handle {
fn drop(&mut self) {
unsafe { CloseHandle(self.0) };
}
}
fn wide(path: &Path) -> io::Result<Vec<u16>> {
let path = std::path::absolute(path)?;
let mut value: Vec<u16> = path.as_os_str().encode_wide().collect();
if value.contains(&0) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"path contains NUL",
));
}
let verbatim: Vec<u16> = r"\\?\".encode_utf16().collect();
let device: Vec<u16> = r"\\.\".encode_utf16().collect();
if !value.starts_with(&verbatim) && !value.starts_with(&device) {
value = if value.starts_with(&[b'\\' as u16, b'\\' as u16]) {
r"\\?\UNC\"
.encode_utf16()
.chain(value.into_iter().skip(2))
.collect()
} else {
verbatim.into_iter().chain(value).collect()
};
}
value.push(0);
Ok(value)
}
struct User(Vec<usize>);
impl User {
fn current() -> io::Result<Self> {
let mut token = null_mut();
if unsafe { OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) } == 0 {
return Err(io::Error::last_os_error());
}
let token = Handle(token);
let mut bytes = 0;
unsafe { GetTokenInformation(token.0, TokenUser, null_mut(), 0, &mut bytes) };
if (bytes as usize) < size_of::<TOKEN_USER>() {
return Err(io::Error::last_os_error());
}
let mut user = Self(vec![0; (bytes as usize).div_ceil(size_of::<usize>())]);
if unsafe {
GetTokenInformation(
token.0,
TokenUser,
user.0.as_mut_ptr().cast(),
bytes,
&mut bytes,
)
} == 0
{
return Err(io::Error::last_os_error());
}
Ok(user)
}
fn sid(&self) -> PSID {
unsafe { (*(self.0.as_ptr().cast::<TOKEN_USER>())).User.Sid }
}
}
struct OwnerAcl(Vec<u32>);
impl OwnerAcl {
fn new(user: &User, directory: bool) -> io::Result<Self> {
let sid_bytes = unsafe { GetLengthSid(user.sid()) } as usize;
let bytes =
size_of::<ACL>() + size_of::<ACCESS_ALLOWED_ACE>() - size_of::<u32>() + sid_bytes;
let mut acl = Self(vec![0; bytes.div_ceil(size_of::<u32>())]);
let flags = if directory {
OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE
} else {
0
};
if unsafe {
InitializeAcl(
acl.as_mut_ptr(),
(acl.0.len() * size_of::<u32>()) as u32,
ACL_REVISION,
) == 0
|| AddAccessAllowedAceEx(
acl.as_mut_ptr(),
ACL_REVISION,
flags,
FILE_ALL_ACCESS,
user.sid(),
) == 0
} {
return Err(io::Error::last_os_error());
}
Ok(acl)
}
fn as_mut_ptr(&mut self) -> *mut ACL {
self.0.as_mut_ptr().cast()
}
}
fn with_security<T>(
directory: bool,
create: impl FnOnce(&SECURITY_ATTRIBUTES) -> io::Result<T>,
) -> io::Result<T> {
let user = User::current()?;
let mut acl = OwnerAcl::new(&user, directory)?;
let mut descriptor: SECURITY_DESCRIPTOR = unsafe { zeroed() };
let descriptor_ptr = (&mut descriptor as *mut SECURITY_DESCRIPTOR).cast();
if unsafe {
InitializeSecurityDescriptor(descriptor_ptr, 1) == 0
|| SetSecurityDescriptorOwner(descriptor_ptr, user.sid(), 0) == 0
|| SetSecurityDescriptorDacl(descriptor_ptr, 1, acl.as_mut_ptr(), 0) == 0
|| SetSecurityDescriptorControl(descriptor_ptr, SE_DACL_PROTECTED, SE_DACL_PROTECTED)
== 0
} {
return Err(io::Error::last_os_error());
}
create(&SECURITY_ATTRIBUTES {
nLength: size_of::<SECURITY_ATTRIBUTES>() as u32,
lpSecurityDescriptor: descriptor_ptr,
bInheritHandle: 0,
})
}
pub fn create_private_file(path: &Path) -> io::Result<File> {
let name = wide(path)?;
with_security(false, |attributes| {
let raw = unsafe {
CreateFileW(
name.as_ptr(),
FILE_ALL_ACCESS,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
attributes,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT,
null_mut(),
)
};
if raw == INVALID_HANDLE_VALUE {
return Err(io::Error::last_os_error());
}
Ok(unsafe { File::from_raw_handle(raw) })
})
}
fn open_directory(path: &Path, access: u32) -> io::Result<Handle> {
let name = wide(path)?;
let raw = unsafe {
CreateFileW(
name.as_ptr(),
access,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
null(),
OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
null_mut(),
)
};
if raw == INVALID_HANDLE_VALUE {
return Err(io::Error::last_os_error());
}
let handle = Handle(raw);
let attributes = disk_attributes(handle.0)?;
if attributes & FILE_ATTRIBUTE_DIRECTORY == 0 || attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0
{
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"not an ordinary directory",
));
}
Ok(handle)
}
fn disk_attributes(handle: HANDLE) -> io::Result<u32> {
let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { zeroed() };
if unsafe { GetFileInformationByHandle(handle, &mut info) } == 0 {
return Err(io::Error::last_os_error());
}
if unsafe { GetFileType(handle) } != FILE_TYPE_DISK {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"not a disk file",
));
}
Ok(info.dwFileAttributes)
}
fn create_all(path: &Path) -> io::Result<bool> {
if path.as_os_str().is_empty() {
return Ok(false);
}
match std::fs::symlink_metadata(path) {
Ok(_) => return open_directory(path, READ_CONTROL).map(|_| false),
Err(error) if error.kind() == io::ErrorKind::NotFound => {}
Err(error) => return Err(error),
}
if let Some(parent) = path.parent() {
create_all(parent)?;
}
let name = wide(path)?;
with_security(true, |attributes| {
if unsafe { CreateDirectoryW(name.as_ptr(), attributes) } != 0 {
return Ok(true);
}
let error = io::Error::last_os_error();
if error.raw_os_error() == Some(ERROR_ALREADY_EXISTS as i32) {
open_directory(path, READ_CONTROL).map(|_| false)
} else {
Err(error)
}
})
}
pub fn create_private_dir(path: &Path) -> io::Result<()> {
if create_all(path)? {
return Ok(());
}
let handle = open_directory(path, READ_CONTROL | WRITE_DAC)?;
let user = User::current()?;
let security = Security::query(handle.0)?;
security.verify_owner(&user)?;
let mut acl = OwnerAcl::new(&user, true)?;
let error = unsafe {
SetSecurityInfo(
handle.0,
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
null_mut(),
null_mut(),
acl.as_mut_ptr(),
null(),
)
};
if error != 0 {
return Err(io::Error::from_raw_os_error(error as i32));
}
Ok(())
}
struct Security {
descriptor: PSECURITY_DESCRIPTOR,
owner: PSID,
dacl: *mut ACL,
}
impl Drop for Security {
fn drop(&mut self) {
unsafe { LocalFree(self.descriptor) };
}
}
fn insecure(reason: &str) -> io::Error {
io::Error::new(io::ErrorKind::PermissionDenied, reason)
}
impl Security {
fn query(handle: HANDLE) -> io::Result<Self> {
let mut security = Self {
descriptor: null_mut(),
owner: null_mut(),
dacl: null_mut(),
};
let error = unsafe {
GetSecurityInfo(
handle,
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
&mut security.owner,
null_mut(),
&mut security.dacl,
null_mut(),
&mut security.descriptor,
)
};
if error != 0 {
return Err(io::Error::from_raw_os_error(error as i32));
}
Ok(security)
}
fn verify_owner(&self, user: &User) -> io::Result<()> {
let trusted = !self.owner.is_null()
&& unsafe {
EqualSid(self.owner, user.sid()) != 0
|| IsWellKnownSid(self.owner, WinLocalSystemSid) != 0
|| IsWellKnownSid(self.owner, WinBuiltinAdministratorsSid) != 0
};
if !trusted {
return Err(insecure(
"owner is not the current user, SYSTEM or Administrators",
));
}
Ok(())
}
fn verify(&self, user: &User) -> io::Result<()> {
self.verify_owner(user)?;
if self.dacl.is_null() {
return Err(insecure("null DACL grants unrestricted access"));
}
let count = unsafe { (*self.dacl).AceCount };
for index in 0..u32::from(count) {
let mut raw = null_mut();
if unsafe { GetAce(self.dacl, index, &mut raw) } == 0 {
return Err(io::Error::last_os_error());
}
let header = unsafe { &*raw.cast::<ACE_HEADER>() };
match header.AceType {
1 => continue,
0 => {}
_ => return Err(insecure("unsupported DACL entry type")),
}
if usize::from(header.AceSize) < size_of::<ACCESS_ALLOWED_ACE>() {
return Err(insecure("truncated DACL entry"));
}
let ace = unsafe { &*raw.cast::<ACCESS_ALLOWED_ACE>() };
let sid: PSID = std::ptr::addr_of!(ace.SidStart).cast_mut().cast();
let trusted = unsafe {
EqualSid(sid, user.sid()) != 0
|| IsWellKnownSid(sid, WinLocalSystemSid) != 0
|| IsWellKnownSid(sid, WinBuiltinAdministratorsSid) != 0
};
if ace.Mask != 0 && !trusted {
return Err(insecure("DACL grants access to another user or group"));
}
}
Ok(())
}
}
pub fn verify_owner_only(file: &File) -> io::Result<()> {
let handle = file.as_raw_handle();
if disk_attributes(handle)? & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT) != 0 {
return Err(insecure("not an ordinary file"));
}
Security::query(handle)?.verify(&User::current()?)
}
pub fn verify_private_dir(path: &Path) -> io::Result<()> {
let handle = open_directory(path, READ_CONTROL)?;
Security::query(handle.0)?.verify(&User::current()?)
}
#[cfg(any(test, feature = "test-support"))]
pub mod test_support;
#[cfg(test)]
mod tests;