#[cfg(not(windows))]
pub(super) fn restrict_to_current_user(_path: &std::path::Path) -> std::io::Result<()> {
Ok(())
}
#[cfg(windows)]
pub(super) fn restrict_to_current_user(path: &std::path::Path) -> std::io::Result<()> {
use std::os::windows::ffi::OsStrExt;
use windows_sys::Win32::Foundation::{CloseHandle, LocalFree, ERROR_SUCCESS, GENERIC_ALL};
use windows_sys::Win32::Security::Authorization::{
SetEntriesInAclW, SetNamedSecurityInfoW, EXPLICIT_ACCESS_W, NO_MULTIPLE_TRUSTEE,
SET_ACCESS, SE_FILE_OBJECT, TRUSTEE_IS_SID, TRUSTEE_IS_USER, TRUSTEE_W,
};
use windows_sys::Win32::Security::{
GetTokenInformation, TokenUser, ACL, DACL_SECURITY_INFORMATION, NO_INHERITANCE,
PROTECTED_DACL_SECURITY_INFORMATION, TOKEN_QUERY, TOKEN_USER,
};
use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
let mut wide: Vec<u16> = path.as_os_str().encode_wide().collect();
wide.push(0);
unsafe {
let mut token = std::ptr::null_mut();
if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) == 0 {
return Err(std::io::Error::last_os_error());
}
let mut needed: u32 = 0;
GetTokenInformation(token, TokenUser, std::ptr::null_mut(), 0, &mut needed);
if needed == 0 {
let err = std::io::Error::last_os_error();
CloseHandle(token);
return Err(err);
}
let mut buf = vec![0u8; needed as usize];
let ok = GetTokenInformation(
token,
TokenUser,
buf.as_mut_ptr().cast(),
needed,
&mut needed,
);
CloseHandle(token);
if ok == 0 {
return Err(std::io::Error::last_os_error());
}
let sid = (*buf.as_ptr().cast::<TOKEN_USER>()).User.Sid;
let access = EXPLICIT_ACCESS_W {
grfAccessPermissions: GENERIC_ALL,
grfAccessMode: SET_ACCESS,
grfInheritance: NO_INHERITANCE,
Trustee: TRUSTEE_W {
pMultipleTrustee: std::ptr::null_mut(),
MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE,
TrusteeForm: TRUSTEE_IS_SID,
TrusteeType: TRUSTEE_IS_USER,
ptstrName: sid.cast(),
},
};
let mut acl: *mut ACL = std::ptr::null_mut();
let rc = SetEntriesInAclW(1, &access, std::ptr::null(), &mut acl);
if rc != ERROR_SUCCESS {
return Err(std::io::Error::from_raw_os_error(rc as i32));
}
let rc = SetNamedSecurityInfoW(
wide.as_ptr(),
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
std::ptr::null_mut(),
std::ptr::null_mut(),
acl,
std::ptr::null(),
);
LocalFree(acl.cast());
if rc != ERROR_SUCCESS {
return Err(std::io::Error::from_raw_os_error(rc as i32));
}
}
Ok(())
}
#[cfg(test)]
mod restrict_permissions_tests {
use super::restrict_to_current_user;
#[test]
#[cfg(not(windows))]
fn restriction_is_a_noop_on_unix_where_chmod_already_applies() {
let dir = tempfile::tempdir().expect("tempdir");
let file = dir.path().join("config.toml");
std::fs::write(&file, "x = 1").expect("write");
assert!(restrict_to_current_user(&file).is_ok());
assert!(restrict_to_current_user(dir.path()).is_ok());
}
#[test]
#[cfg(windows)]
fn restriction_installs_a_protected_single_ace_dacl() {
use std::os::windows::ffi::OsStrExt;
use windows_sys::Win32::Foundation::{LocalFree, ERROR_SUCCESS};
use windows_sys::Win32::Security::Authorization::{GetNamedSecurityInfoW, SE_FILE_OBJECT};
use windows_sys::Win32::Security::{
AclSizeInformation, GetAclInformation, GetSecurityDescriptorControl, ACL,
ACL_SIZE_INFORMATION, DACL_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR,
SE_DACL_PROTECTED,
};
let dir = tempfile::tempdir().expect("tempdir");
let file = dir.path().join("config.toml");
std::fs::write(&file, "x = 1").expect("write");
restrict_to_current_user(&file).expect("restriction applies");
let mut wide: Vec<u16> = file.as_os_str().encode_wide().collect();
wide.push(0);
unsafe {
let mut dacl: *mut ACL = std::ptr::null_mut();
let mut sd: PSECURITY_DESCRIPTOR = std::ptr::null_mut();
let rc = GetNamedSecurityInfoW(
wide.as_ptr(),
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION,
std::ptr::null_mut(),
std::ptr::null_mut(),
&mut dacl,
std::ptr::null_mut(),
&mut sd,
);
assert_eq!(rc, ERROR_SUCCESS, "reading the DACL back must succeed");
let mut control: u16 = 0;
let mut revision: u32 = 0;
let ok = GetSecurityDescriptorControl(sd, &mut control, &mut revision);
assert_ne!(ok, 0, "descriptor control must be readable");
let mut info = ACL_SIZE_INFORMATION {
AceCount: 0,
AclBytesInUse: 0,
AclBytesFree: 0,
};
let ok = GetAclInformation(
dacl,
(&mut info as *mut ACL_SIZE_INFORMATION).cast(),
std::mem::size_of::<ACL_SIZE_INFORMATION>() as u32,
AclSizeInformation,
);
assert_ne!(ok, 0, "ACL size information must be readable");
LocalFree(sd.cast());
assert_ne!(
control & SE_DACL_PROTECTED,
0,
"DACL must be protected from parent-directory inheritance"
);
assert_eq!(info.AceCount, 1, "DACL must grant exactly one trustee");
}
}
}