use crate::atomic::{atomic_write_with, AtomicOptions};
use crate::error::{FsError, Result};
use std::path::{Path, PathBuf};
pub const FILE_MODE_OWNER_ONLY: u32 = 0o600;
pub const DIR_MODE_OWNER_ONLY: u32 = 0o700;
pub const EXECUTABLE_MODE_OWNER_ONLY: u32 = 0o700;
pub fn restrict_owner_only(path: &Path) -> Result<()> {
restrict_owner_only_impl(path)
}
#[cfg(unix)]
fn restrict_owner_only_impl(path: &Path) -> Result<()> {
use std::os::unix::fs::PermissionsExt;
let meta = match std::fs::symlink_metadata(path) {
Ok(meta) => meta,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()),
Err(e) => return Err(FsError::io(path, e)),
};
if meta.file_type().is_symlink() {
return Err(FsError::Symlink(path.to_path_buf()));
}
let mode = if meta.is_dir() {
DIR_MODE_OWNER_ONLY
} else {
FILE_MODE_OWNER_ONLY
};
if meta.permissions().mode() & 0o7777 != mode {
std::fs::set_permissions(path, std::fs::Permissions::from_mode(mode))
.map_err(|e| FsError::io(path, e))?;
}
if cfg!(target_os = "macos")
&& (meta.is_file() || meta.is_dir())
&& crate::acl::existing_has_allow_acl(path)?
{
return Err(FsError::UnsafeAcl(path.to_path_buf()));
}
Ok(())
}
#[cfg(not(any(unix, windows)))]
fn restrict_owner_only_impl(_path: &Path) -> Result<()> {
Ok(())
}
pub fn is_owner_only(path: &Path) -> Result<bool> {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let meta = std::fs::symlink_metadata(path).map_err(|e| FsError::io(path, e))?;
if meta.permissions().mode() & 0o077 != 0 {
return Ok(false);
}
if cfg!(target_os = "macos") && (meta.is_file() || meta.is_dir()) {
return Ok(!crate::acl::existing_has_allow_acl(path)?);
}
Ok(true)
}
#[cfg(windows)]
{
let file = open_owner_only_handle(path, false)?;
Ok(private_trust_handle(&file, path, true)? == Trust::Private)
}
#[cfg(not(any(unix, windows)))]
{
let _ = path;
Ok(true)
}
}
pub fn is_owner_only_strict(path: &Path) -> Result<bool> {
#[cfg(windows)]
{
let file = open_owner_only_handle(path, false)?;
is_owner_only_handle(&file, path)
}
#[cfg(not(windows))]
{
is_owner_only(path)
}
}
pub fn verify_owner_only(path: &Path) -> Result<()> {
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
let meta = std::fs::symlink_metadata(path).map_err(|e| FsError::io(path, e))?;
if meta.file_type().is_symlink() {
return Err(FsError::Symlink(path.to_path_buf()));
}
if meta.uid() != unsafe { libc::geteuid() } {
return Err(FsError::NotOwned(path.to_path_buf()));
}
if is_owner_only(path)? {
Ok(())
} else {
Err(FsError::TooPermissive(path.to_path_buf()))
}
}
#[cfg(windows)]
{
let file = open_owner_only_handle(path, false)?;
match private_trust_handle(&file, path, true)? {
Trust::Private => Ok(()),
Trust::ForeignOwner => Err(FsError::NotOwned(path.to_path_buf())),
Trust::ForeignAccess => Err(FsError::TooPermissive(path.to_path_buf())),
}
}
#[cfg(not(any(unix, windows)))]
{
let _ = path;
Ok(())
}
}
#[cfg(any(windows, test))]
#[cfg_attr(not(windows), allow(dead_code))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Trust {
Private,
ForeignOwner,
ForeignAccess,
}
#[cfg(any(windows, test))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum AceKind {
Allow,
Deny,
Other,
}
#[cfg(any(windows, test))]
fn ace_kind(ace_type: u8) -> AceKind {
match ace_type {
0 | 9 => AceKind::Allow,
1 | 6 | 10 | 12 => AceKind::Deny,
_ => AceKind::Other,
}
}
#[cfg(any(windows, test))]
const INHERIT_ONLY_ACE: u8 = 0x08;
#[cfg(any(windows, test))]
fn trustee_is_acceptable(
is_current_user: bool,
authority: u64,
sub_authorities: &[u32],
ace_flags: u8,
) -> bool {
if is_current_user {
return true;
}
match (authority, sub_authorities) {
(5, [18]) | (5, [32, 544]) => true,
(3, [0]) => ace_flags & INHERIT_ONLY_ACE != 0,
_ => false,
}
}
#[cfg(any(windows, test))]
fn ace_is_acceptable(ace_type: u8, ace_flags: u8, trustee: Option<(bool, u64, &[u32])>) -> bool {
match ace_kind(ace_type) {
AceKind::Deny => true,
AceKind::Other => false,
AceKind::Allow => match trustee {
Some((is_user, authority, subs)) => {
trustee_is_acceptable(is_user, authority, subs, ace_flags)
}
None => false,
},
}
}
pub fn ensure_private_dir(dir: &Path) -> Result<()> {
if dir.as_os_str().is_empty() {
return Err(FsError::io(
dir,
std::io::Error::new(std::io::ErrorKind::InvalidInput, "empty path"),
));
}
let mut missing: Vec<PathBuf> = Vec::new();
for ancestor in dir.ancestors() {
if ancestor.as_os_str().is_empty() {
break;
}
match ancestor.symlink_metadata() {
Ok(_) => break,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
missing.push(ancestor.to_path_buf());
}
Err(error) => return Err(FsError::io(ancestor, error)),
}
}
for path in missing.iter().rev() {
create_private_dir_one(path)?;
}
let meta = std::fs::symlink_metadata(dir).map_err(|e| FsError::io(dir, e))?;
if meta.file_type().is_symlink() {
return Err(FsError::Symlink(dir.to_path_buf()));
}
if !meta.is_dir() {
return Err(FsError::NotADirectory(dir.to_path_buf()));
}
restrict_owner_only(dir)
}
fn create_private_dir_one(path: &Path) -> Result<()> {
#[cfg(unix)]
let result = {
use std::os::unix::fs::DirBuilderExt;
if let Some(parent) = path.parent().filter(|p| !p.as_os_str().is_empty()) {
crate::acl::guard_parent_path(parent, path)?;
}
let made = std::fs::DirBuilder::new()
.mode(DIR_MODE_OWNER_ONLY)
.create(path);
if made.is_ok() {
if let Err(error) = crate::acl::refuse_acl_path(path) {
let _ = std::fs::remove_dir(path);
return Err(error);
}
}
made
};
#[cfg(windows)]
let result = create_dir_owner_only(path);
#[cfg(not(any(unix, windows)))]
let result = std::fs::create_dir(path);
let created = match result {
Ok(()) => true,
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => false,
Err(e) => return Err(FsError::io(path, e)),
};
#[cfg(not(windows))]
let _ = created;
#[cfg(windows)]
if !created {
restrict_owner_only(path)?;
}
#[cfg(not(any(unix, windows)))]
restrict_owner_only(path)?;
Ok(())
}
pub fn write_private_file(path: &Path, bytes: &[u8]) -> Result<()> {
use std::io::Write;
atomic_write_with(path, AtomicOptions::private(), |f| f.write_all(bytes))
}
pub fn write_private_text(path: &Path, text: &str) -> Result<()> {
write_private_file(path, text.as_bytes())
}
pub fn write_private_executable(path: &Path, bytes: &[u8]) -> Result<()> {
let mut writer = crate::atomic::AtomicWriter::create(path, AtomicOptions::private())?;
writer.write_all(bytes)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let permissions = std::fs::Permissions::from_mode(EXECUTABLE_MODE_OWNER_ONLY);
if let Err(e) = writer.file_mut().set_permissions(permissions) {
let temp = writer.temp_path().to_path_buf();
return Err(FsError::io(temp, e));
}
}
writer.commit()
}
pub fn create_private_new(path: &Path) -> Result<std::fs::File> {
if let Some(name) = path.file_name() {
crate::atomic::reject_reserved_name(&name.to_string_lossy())?;
}
create_private_new_impl(path)
}
#[cfg(windows)]
fn create_private_new_impl(path: &Path) -> Result<std::fs::File> {
create_new_owner_only(path).map_err(|e| FsError::io(path, e))
}
#[cfg(not(windows))]
fn create_private_new_impl(path: &Path) -> Result<std::fs::File> {
let mut options = std::fs::OpenOptions::new();
options.write(true).create_new(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
options
.mode(FILE_MODE_OWNER_ONLY)
.custom_flags(libc::O_NOFOLLOW | libc::O_CLOEXEC);
}
#[cfg(unix)]
crate::acl::guard_parent_path(
path.parent()
.filter(|p| !p.as_os_str().is_empty())
.unwrap_or(Path::new(".")),
path,
)?;
let file = options.open(path).map_err(|e| FsError::io(path, e))?;
#[cfg(unix)]
if let Err(error) = crate::acl::refuse_acl(&file, path) {
drop(file);
let _ = std::fs::remove_file(path);
return Err(error);
}
#[cfg(not(unix))]
restrict_owner_only(path)?;
Ok(file)
}
pub fn sweep_owner_only(dir: &Path, owned_names: &[&str]) -> Result<usize> {
restrict_owner_only(dir)?;
let mut narrowed = 0;
let entries = match std::fs::read_dir(dir) {
Ok(entries) => entries,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(0),
Err(e) => return Err(FsError::io(dir, e)),
};
for entry in entries.flatten() {
let name = entry.file_name();
if owned_names.iter().any(|owned| name == *owned)
&& (cfg!(windows) || !is_owner_only(&entry.path())?)
{
restrict_owner_only(&entry.path())?;
narrowed += 1;
}
}
Ok(narrowed)
}
#[cfg(windows)]
fn restrict_owner_only_impl(path: &Path) -> crate::error::Result<()> {
let file = match open_owner_only_handle(path, true) {
Ok(file) => file,
Err(error) if error.is_not_found() => return Ok(()),
Err(error) => return Err(error),
};
native_restrict_owner_only(&file, path)
}
#[cfg(windows)]
fn current_process_token_user(path: &Path) -> crate::error::Result<Vec<usize>> {
current_process_token_info(path, 1) }
#[cfg(windows)]
fn current_process_token_info(path: &Path, class: u32) -> crate::error::Result<Vec<usize>> {
use std::ffi::c_void;
type Handle = *mut c_void;
const TOKEN_QUERY: u32 = 0x0008;
let class_id = class;
#[link(name = "advapi32")]
unsafe extern "system" {
fn OpenProcessToken(process: Handle, access: u32, token: *mut Handle) -> i32;
fn GetTokenInformation(
token: Handle,
class: u32,
buffer: *mut c_void,
len: u32,
needed: *mut u32,
) -> i32;
}
#[link(name = "kernel32")]
unsafe extern "system" {
fn GetCurrentProcess() -> Handle;
fn CloseHandle(handle: Handle) -> i32;
}
unsafe {
let mut token = std::ptr::null_mut();
if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) == 0 {
return Err(crate::error::FsError::io(
path,
std::io::Error::last_os_error(),
));
}
let mut needed = 0;
let _ = GetTokenInformation(token, class_id, std::ptr::null_mut(), 0, &mut needed);
if needed == 0 {
let error = std::io::Error::last_os_error();
let _ = CloseHandle(token);
return Err(crate::error::FsError::io(path, error));
}
let words = (needed as usize).div_ceil(std::mem::size_of::<usize>());
let mut token_info = vec![0usize; words];
if GetTokenInformation(
token,
class_id,
token_info.as_mut_ptr().cast(),
needed,
&mut needed,
) == 0
{
let error = std::io::Error::last_os_error();
let _ = CloseHandle(token);
return Err(crate::error::FsError::io(path, error));
}
let _ = CloseHandle(token);
Ok(token_info)
}
}
#[cfg(any(windows, test))]
const SE_GROUP_ENABLED: u32 = 0x0000_0004;
#[cfg(any(windows, test))]
const SE_GROUP_USE_FOR_DENY_ONLY: u32 = 0x0000_0010;
#[cfg(any(windows, test))]
const SE_GROUP_OWNER: u32 = 0x0000_0008;
#[cfg(any(windows, test))]
fn sid_is_broad(authority: u64, sub_authorities: &[u32]) -> bool {
matches!(
(authority, sub_authorities),
(1, [0]) | (2, [0]) | (5, [4]) | (5, [11]) | (5, [32, 545])
)
}
#[cfg(any(windows, test))]
fn owner_is_trusted<S: PartialEq>(
owner: &S,
user: &S,
groups: &[(S, u32)],
is_broad: &dyn Fn(&S) -> bool,
) -> bool {
if owner == user {
return true;
}
if is_broad(owner) {
return false;
}
groups.iter().any(|(sid, attributes)| {
sid == owner
&& attributes & SE_GROUP_ENABLED != 0
&& attributes & SE_GROUP_OWNER != 0
&& attributes & SE_GROUP_USE_FOR_DENY_ONLY == 0
})
}
#[cfg(windows)]
fn owner_matches_token(
owner: *mut std::ffi::c_void,
user: *mut std::ffi::c_void,
path: &Path,
) -> crate::error::Result<bool> {
use std::ffi::c_void;
#[repr(C)]
struct SidAndAttributes {
sid: *mut c_void,
attributes: u32,
}
#[repr(C)]
struct TokenGroups {
count: u32,
groups: [SidAndAttributes; 1],
}
#[link(name = "advapi32")]
unsafe extern "system" {
fn EqualSid(left: *mut c_void, right: *mut c_void) -> i32;
}
const TOKEN_GROUPS_CLASS: u32 = 2;
if owner.is_null() {
return Ok(false);
}
if unsafe { EqualSid(owner, user) } != 0 {
return Ok(true);
}
let buffer = current_process_token_info(path, TOKEN_GROUPS_CLASS)?;
let header = buffer.as_ptr().cast::<TokenGroups>();
let (count, first) = unsafe { ((*header).count as usize, (*header).groups.as_ptr()) };
let groups: Vec<(u8, u32)> = (0..count)
.map(|index| {
let entry = unsafe { &*first.add(index) };
let same = unsafe { EqualSid(owner, entry.sid) } != 0;
(if same { 1 } else { 2 }, entry.attributes)
})
.collect();
let owner_broad = unsafe {
let bytes = owner.cast::<u8>();
let count = (*bytes.add(1)).min(15) as usize;
let mut authority = 0u64;
for i in 0..6 {
authority = (authority << 8) | u64::from(*bytes.add(2 + i));
}
let subs: Vec<u32> = (0..count)
.map(|i| std::ptr::read_unaligned(bytes.add(8 + 4 * i).cast::<u32>()))
.collect();
sid_is_broad(authority, &subs)
};
Ok(owner_is_trusted(&1u8, &0u8, &groups, &|_| owner_broad))
}
#[cfg(windows)]
pub(crate) fn current_process_sid_string(path: &Path) -> crate::error::Result<String> {
use std::ffi::c_void;
#[link(name = "advapi32")]
unsafe extern "system" {
fn ConvertSidToStringSidW(sid: *mut c_void, string_sid: *mut *mut u16) -> i32;
}
#[link(name = "kernel32")]
unsafe extern "system" {
fn LocalFree(memory: *mut c_void) -> *mut c_void;
}
let token_info = current_process_token_user(path)?;
unsafe {
let sid = *(token_info.as_ptr().cast::<*mut c_void>());
let mut sid_text = std::ptr::null_mut();
if ConvertSidToStringSidW(sid, &mut sid_text) == 0 {
return Err(crate::error::FsError::io(
path,
std::io::Error::last_os_error(),
));
}
let sid_len = (0..).take_while(|&i| *sid_text.add(i) != 0).count();
let sid_string = String::from_utf16_lossy(std::slice::from_raw_parts(sid_text, sid_len));
let _ = LocalFree(sid_text.cast());
Ok(sid_string)
}
}
#[cfg(windows)]
fn open_owner_only_handle(path: &Path, repair: bool) -> crate::error::Result<std::fs::File> {
use std::ffi::c_void;
use std::os::windows::ffi::OsStrExt;
use std::os::windows::io::{AsRawHandle, FromRawHandle};
#[repr(C)]
struct AttributeTagInfo {
attributes: u32,
tag: u32,
}
#[link(name = "kernel32")]
unsafe extern "system" {
fn CreateFileW(
name: *const u16,
access: u32,
share: u32,
security: *const c_void,
disposition: u32,
flags: u32,
template: *mut c_void,
) -> *mut c_void;
fn GetFileInformationByHandleEx(
file: *mut c_void,
class: i32,
info: *mut c_void,
size: u32,
) -> i32;
}
const WRITE_DAC: u32 = 0x0004_0000;
const WRITE_OWNER: u32 = 0x0008_0000;
const ERROR_ACCESS_DENIED: i32 = 5;
const FILE_SHARE_READ_WRITE_DELETE: u32 = 0x7;
const READ_CONTROL: u32 = 0x0002_0000;
const FILE_READ_ATTRIBUTES: u32 = 0x80;
const OPEN_EXISTING: u32 = 3;
const FILE_FLAG_BACKUP_SEMANTICS: u32 = 0x0200_0000;
const FILE_FLAG_OPEN_REPARSE_POINT: u32 = 0x0020_0000;
const FILE_ATTRIBUTE_TAG_INFO: i32 = 9;
const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x400;
let wide: Vec<u16> = path.as_os_str().encode_wide().chain(Some(0)).collect();
if wide[..wide.len() - 1].contains(&0) {
return Err(FsError::BadName(path.display().to_string()));
}
let read_access = READ_CONTROL | FILE_READ_ATTRIBUTES;
let accesses: &[u32] = if repair {
&[
read_access | WRITE_DAC | WRITE_OWNER,
read_access | WRITE_DAC,
]
} else {
&[read_access]
};
let mut raw = std::ptr::null_mut();
for (index, access) in accesses.iter().enumerate() {
raw = unsafe {
CreateFileW(
wide.as_ptr(),
*access,
FILE_SHARE_READ_WRITE_DELETE,
std::ptr::null(),
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
std::ptr::null_mut(),
)
};
if raw as isize != -1 && !raw.is_null() {
break;
}
let error = std::io::Error::last_os_error();
if !(index + 1 < accesses.len() && error.raw_os_error() == Some(ERROR_ACCESS_DENIED)) {
return Err(FsError::io(path, error));
}
raw = std::ptr::null_mut();
}
if raw as isize == -1 || raw.is_null() {
return Err(FsError::io(path, std::io::Error::last_os_error()));
}
let file = unsafe { std::fs::File::from_raw_handle(raw) };
let mut info = AttributeTagInfo {
attributes: 0,
tag: 0,
};
if unsafe {
GetFileInformationByHandleEx(
file.as_raw_handle(),
FILE_ATTRIBUTE_TAG_INFO,
(&mut info as *mut AttributeTagInfo).cast(),
std::mem::size_of::<AttributeTagInfo>() as u32,
)
} == 0
{
return Err(FsError::io(path, std::io::Error::last_os_error()));
}
if info.attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
return Err(FsError::Symlink(path.to_path_buf()));
}
Ok(file)
}
#[cfg(windows)]
fn require_persistent_acls(file: &std::fs::File, shown: &Path) -> crate::error::Result<()> {
use std::os::windows::io::AsRawHandle;
#[link(name = "kernel32")]
unsafe extern "system" {
fn GetVolumeInformationByHandleW(
file: *mut std::ffi::c_void,
volume_name: *mut u16,
volume_name_size: u32,
serial: *mut u32,
max_component: *mut u32,
flags: *mut u32,
filesystem_name: *mut u16,
filesystem_name_size: u32,
) -> i32;
}
const FILE_PERSISTENT_ACLS: u32 = 0x8;
let mut flags = 0;
if unsafe {
GetVolumeInformationByHandleW(
file.as_raw_handle(),
std::ptr::null_mut(),
0,
std::ptr::null_mut(),
std::ptr::null_mut(),
&mut flags,
std::ptr::null_mut(),
0,
)
} == 0
{
return Err(FsError::io(shown, std::io::Error::last_os_error()));
}
if flags & FILE_PERSISTENT_ACLS == 0 {
return Err(FsError::Unavailable(
"Windows filesystem lacks persistent ACLs",
));
}
Ok(())
}
#[cfg(windows)]
pub(crate) fn is_owner_only_handle(
file: &std::fs::File,
shown: &Path,
) -> crate::error::Result<bool> {
private_trust_handle(file, shown, false).map(|trust| trust == Trust::Private)
}
#[cfg(windows)]
fn private_trust_handle(
file: &std::fs::File,
shown: &Path,
tolerant: bool,
) -> crate::error::Result<Trust> {
use std::ffi::c_void;
use std::os::windows::io::AsRawHandle;
#[repr(C)]
struct AceHeader {
ace_type: u8,
ace_flags: u8,
ace_size: u16,
}
#[repr(C)]
struct AclSizeInformation {
ace_count: u32,
acl_bytes_in_use: u32,
acl_bytes_free: u32,
}
#[link(name = "advapi32")]
unsafe extern "system" {
fn GetSecurityInfo(
object: *mut c_void,
object_type: u32,
information: u32,
owner: *mut *mut c_void,
group: *mut *mut c_void,
dacl: *mut *mut c_void,
sacl: *mut *mut c_void,
descriptor: *mut *mut c_void,
) -> u32;
fn EqualSid(left: *mut c_void, right: *mut c_void) -> i32;
fn GetSecurityDescriptorControl(
descriptor: *mut c_void,
control: *mut u16,
revision: *mut u32,
) -> i32;
fn IsValidAcl(acl: *mut c_void) -> i32;
fn GetAclInformation(
acl: *mut c_void,
information: *mut c_void,
length: u32,
class: u32,
) -> i32;
fn GetAce(acl: *mut c_void, index: u32, ace: *mut *mut c_void) -> i32;
fn IsValidSid(sid: *mut c_void) -> i32;
fn GetLengthSid(sid: *mut c_void) -> u32;
}
#[link(name = "kernel32")]
unsafe extern "system" {
fn LocalFree(memory: *mut c_void) -> *mut c_void;
}
const SE_FILE_OBJECT: u32 = 1;
const OWNER_SECURITY_INFORMATION: u32 = 0x1;
const DACL_SECURITY_INFORMATION: u32 = 0x4;
const SE_DACL_PROTECTED: u16 = 0x1000;
const ACL_SIZE_INFORMATION: u32 = 2;
const ACCESS_ALLOWED_ACE_TYPE: u8 = 0;
const OBJECT_INHERIT_ACE: u8 = 0x1;
const CONTAINER_INHERIT_ACE: u8 = 0x2;
const FILE_ALL_ACCESS: u32 = 0x001f_01ff;
const ACCESS_ALLOWED_ACE_PREFIX: usize = 8;
const SID_HEADER_SIZE: usize = 8;
const SID_MAX_SUB_AUTHORITIES: u8 = 15;
fn strict_single_ace(
dacl: *mut c_void,
count: u32,
directory: bool,
sid: *mut c_void,
shown: &Path,
) -> crate::error::Result<bool> {
if count != 1 {
return Ok(false);
}
let mut ace = std::ptr::null_mut();
if unsafe { GetAce(dacl, 0, &mut ace) } == 0 {
return Err(FsError::io(shown, std::io::Error::last_os_error()));
}
if ace.is_null() {
return Ok(false);
}
let header = unsafe { &*(ace.cast::<AceHeader>()) };
if header.ace_type != ACCESS_ALLOWED_ACE_TYPE {
return Ok(false);
}
let expected_flags = if directory {
OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE
} else {
0
};
let ace_size = usize::from(header.ace_size);
if header.ace_flags != expected_flags
|| ace_size < ACCESS_ALLOWED_ACE_PREFIX + SID_HEADER_SIZE
{
return Ok(false);
}
let bytes = ace.cast::<u8>();
let mask = unsafe { *(bytes.add(4).cast::<u32>()) };
let ace_sid = unsafe { bytes.add(ACCESS_ALLOWED_ACE_PREFIX).cast::<c_void>() };
let sub_authorities = unsafe { *bytes.add(ACCESS_ALLOWED_ACE_PREFIX + 1) };
if sub_authorities > SID_MAX_SUB_AUTHORITIES {
return Ok(false);
}
let declared_sid_size = SID_HEADER_SIZE + usize::from(sub_authorities) * 4;
if declared_sid_size > ace_size - ACCESS_ALLOWED_ACE_PREFIX {
return Ok(false);
}
if unsafe { IsValidSid(ace_sid) } == 0 {
return Ok(false);
}
let sid_length = unsafe { GetLengthSid(ace_sid) } as usize;
if sid_length != declared_sid_size || ace_size != ACCESS_ALLOWED_ACE_PREFIX + sid_length {
return Ok(false);
}
Ok(mask == FILE_ALL_ACCESS && unsafe { EqualSid(ace_sid, sid) } != 0)
}
fn read_trustee(
ace: *mut c_void,
ace_size: usize,
sid: *mut c_void,
) -> Option<(bool, u64, Vec<u32>)> {
if ace_size < ACCESS_ALLOWED_ACE_PREFIX + SID_HEADER_SIZE {
return None;
}
let bytes = ace.cast::<u8>();
let sub_authorities = unsafe { *bytes.add(ACCESS_ALLOWED_ACE_PREFIX + 1) };
if sub_authorities > SID_MAX_SUB_AUTHORITIES {
return None;
}
let declared = SID_HEADER_SIZE + usize::from(sub_authorities) * 4;
if declared > ace_size - ACCESS_ALLOWED_ACE_PREFIX {
return None;
}
let ace_sid = unsafe { bytes.add(ACCESS_ALLOWED_ACE_PREFIX) }.cast::<c_void>();
if unsafe { IsValidSid(ace_sid) } == 0 {
return None;
}
let mut authority = 0u64;
let mut subs = Vec::with_capacity(usize::from(sub_authorities));
unsafe {
for i in 0..6 {
authority =
(authority << 8) | u64::from(*bytes.add(ACCESS_ALLOWED_ACE_PREFIX + 2 + i));
}
for i in 0..usize::from(sub_authorities) {
subs.push(std::ptr::read_unaligned(
bytes
.add(ACCESS_ALLOWED_ACE_PREFIX + SID_HEADER_SIZE + 4 * i)
.cast::<u32>(),
));
}
}
let is_user = unsafe { EqualSid(ace_sid, sid) } != 0;
Some((is_user, authority, subs))
}
fn tolerant_dacl_verdict(
dacl: *mut c_void,
count: u32,
sid: *mut c_void,
shown: &Path,
) -> crate::error::Result<Trust> {
for index in 0..count {
let mut ace = std::ptr::null_mut();
if unsafe { GetAce(dacl, index, &mut ace) } == 0 {
return Err(FsError::io(shown, std::io::Error::last_os_error()));
}
if ace.is_null() {
return Ok(Trust::ForeignAccess);
}
let header = unsafe { &*(ace.cast::<AceHeader>()) };
let trustee = if ace_kind(header.ace_type) == AceKind::Allow {
read_trustee(ace, usize::from(header.ace_size), sid)
} else {
None
};
let view = trustee
.as_ref()
.map(|(user, authority, subs)| (*user, *authority, subs.as_slice()));
if !ace_is_acceptable(header.ace_type, header.ace_flags, view) {
return Ok(Trust::ForeignAccess);
}
}
Ok(Trust::Private)
}
require_persistent_acls(file, shown)?;
let token = current_process_token_user(shown)?;
let sid = unsafe { *(token.as_ptr().cast::<*mut c_void>()) };
let mut owner = std::ptr::null_mut();
let mut dacl = std::ptr::null_mut();
let mut descriptor = std::ptr::null_mut();
let result = unsafe {
GetSecurityInfo(
file.as_raw_handle(),
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
&mut owner,
std::ptr::null_mut(),
&mut dacl,
std::ptr::null_mut(),
&mut descriptor,
)
};
if result != 0 {
return Err(FsError::io(
shown,
std::io::Error::from_raw_os_error(result as i32),
));
}
let check = (|| -> crate::error::Result<Trust> {
if descriptor.is_null() || owner.is_null() {
return Ok(Trust::ForeignOwner);
}
if dacl.is_null() {
return Ok(Trust::ForeignAccess);
}
let directory = file
.metadata()
.map_err(|error| FsError::io(shown, error))?
.is_dir();
let mut control = 0;
let mut revision = 0;
if unsafe { GetSecurityDescriptorControl(descriptor, &mut control, &mut revision) } == 0 {
return Err(FsError::io(shown, std::io::Error::last_os_error()));
}
if !tolerant && control & SE_DACL_PROTECTED == 0 {
return Ok(Trust::ForeignAccess);
}
if !owner_matches_token(owner, sid, shown)? {
return Ok(Trust::ForeignOwner);
}
if unsafe { IsValidAcl(dacl) } == 0 {
return Ok(Trust::ForeignAccess);
}
let mut info = AclSizeInformation {
ace_count: 0,
acl_bytes_in_use: 0,
acl_bytes_free: 0,
};
if unsafe {
GetAclInformation(
dacl,
(&mut info as *mut AclSizeInformation).cast(),
std::mem::size_of::<AclSizeInformation>() as u32,
ACL_SIZE_INFORMATION,
)
} == 0
{
return Err(FsError::io(shown, std::io::Error::last_os_error()));
}
if tolerant {
return tolerant_dacl_verdict(dacl, info.ace_count, sid, shown);
}
let private = strict_single_ace(dacl, info.ace_count, directory, sid, shown)?;
Ok(if private {
Trust::Private
} else {
Trust::ForeignAccess
})
})();
unsafe { LocalFree(descriptor) };
check
}
#[cfg(windows)]
pub(crate) fn verify_owner_only_handle(
file: &std::fs::File,
shown: &Path,
) -> crate::error::Result<()> {
if is_owner_only_handle(file, shown)? {
Ok(())
} else {
Err(FsError::TooPermissive(shown.to_path_buf()))
}
}
#[cfg(windows)]
fn handle_owner_is(
file: &std::fs::File,
sid: *mut std::ffi::c_void,
path: &Path,
) -> crate::error::Result<bool> {
use std::ffi::c_void;
use std::os::windows::io::AsRawHandle;
#[link(name = "advapi32")]
unsafe extern "system" {
fn GetSecurityInfo(
object: *mut c_void,
object_type: u32,
information: u32,
owner: *mut *mut c_void,
group: *mut *mut c_void,
dacl: *mut *mut c_void,
sacl: *mut *mut c_void,
descriptor: *mut *mut c_void,
) -> u32;
fn EqualSid(left: *mut c_void, right: *mut c_void) -> i32;
}
#[link(name = "kernel32")]
unsafe extern "system" {
fn LocalFree(memory: *mut c_void) -> *mut c_void;
}
const SE_FILE_OBJECT: u32 = 1;
const OWNER_SECURITY_INFORMATION: u32 = 0x0000_0001;
let mut owner = std::ptr::null_mut();
let mut descriptor = std::ptr::null_mut();
let result = unsafe {
GetSecurityInfo(
file.as_raw_handle(),
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION,
&mut owner,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
&mut descriptor,
)
};
if result != 0 {
return Err(FsError::io(
path,
std::io::Error::from_raw_os_error(result as i32),
));
}
let same = !owner.is_null() && unsafe { EqualSid(owner, sid) } != 0;
unsafe { LocalFree(descriptor) };
Ok(same)
}
#[cfg(windows)]
fn native_restrict_owner_only(file: &std::fs::File, path: &Path) -> crate::error::Result<()> {
use std::ffi::c_void;
use std::os::windows::io::AsRawHandle;
const DACL_SECURITY_INFORMATION: u32 = 0x0000_0004;
const PROTECTED_DACL_SECURITY_INFORMATION: u32 = 0x8000_0000;
const SE_FILE_OBJECT: u32 = 1;
const OWNER_SECURITY_INFORMATION: u32 = 0x0000_0001;
#[link(name = "advapi32")]
unsafe extern "system" {
fn GetSecurityInfo(
object: *mut c_void,
object_type: u32,
information: u32,
owner: *mut *mut c_void,
group: *mut *mut c_void,
dacl: *mut *mut c_void,
sacl: *mut *mut c_void,
descriptor: *mut *mut c_void,
) -> u32;
fn SetSecurityInfo(
object: *mut c_void,
object_type: u32,
information: u32,
owner: *mut c_void,
group: *mut c_void,
dacl: *mut c_void,
sacl: *mut c_void,
) -> u32;
fn GetSecurityDescriptorDacl(
descriptor: *mut c_void,
present: *mut i32,
dacl: *mut *mut c_void,
defaulted: *mut i32,
) -> i32;
fn EqualSid(left: *mut c_void, right: *mut c_void) -> i32;
}
#[link(name = "kernel32")]
unsafe extern "system" {
fn LocalFree(memory: *mut c_void) -> *mut c_void;
}
require_persistent_acls(file, path)?;
let directory = file
.metadata()
.map_err(|error| FsError::io(path, error))?
.is_dir();
let token_info = current_process_token_user(path)?;
unsafe {
let sid = *(token_info.as_ptr().cast::<*mut c_void>());
let mut owner = std::ptr::null_mut();
let mut descriptor = std::ptr::null_mut();
let owner_result = GetSecurityInfo(
file.as_raw_handle(),
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION,
&mut owner,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
&mut descriptor,
);
if owner_result != 0 {
return Err(FsError::io(
path,
std::io::Error::from_raw_os_error(owner_result as i32),
));
}
let is_token_user = !owner.is_null() && EqualSid(owner, sid) != 0;
let owned = owner_matches_token(owner, sid, path);
LocalFree(descriptor);
let owned = owned?;
if !owned {
return Err(FsError::NotOwned(path.to_path_buf()));
}
let descriptor = SecurityDescriptor::owner_only(path, directory)?;
let mut present = 0;
let mut dacl = std::ptr::null_mut();
let mut defaulted = 0;
if GetSecurityDescriptorDacl(descriptor.0, &mut present, &mut dacl, &mut defaulted) == 0 {
return Err(FsError::io(path, std::io::Error::last_os_error()));
}
if present == 0 || dacl.is_null() {
return Err(FsError::Unavailable("owner-only descriptor lacks DACL"));
}
let (information, new_owner) = if is_token_user {
(
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
std::ptr::null_mut(),
)
} else {
(
OWNER_SECURITY_INFORMATION
| DACL_SECURITY_INFORMATION
| PROTECTED_DACL_SECURITY_INFORMATION,
sid,
)
};
let result = SetSecurityInfo(
file.as_raw_handle(),
SE_FILE_OBJECT,
information,
new_owner,
std::ptr::null_mut(),
dacl,
std::ptr::null_mut(),
);
if result == 0 {
verify_owner_only_handle(file, path)?;
if !is_token_user && !handle_owner_is(file, sid, path)? {
return Err(FsError::NotOwned(path.to_path_buf()));
}
Ok(())
} else {
Err(crate::error::FsError::io(
path,
std::io::Error::from_raw_os_error(result as i32),
))
}
}
}
#[cfg(windows)]
pub(crate) struct SecurityDescriptor(pub(crate) *mut std::ffi::c_void);
#[cfg(windows)]
impl SecurityDescriptor {
pub(crate) fn owner_only(shown: &Path, directory: bool) -> crate::error::Result<Self> {
use std::ffi::c_void;
#[link(name = "advapi32")]
unsafe extern "system" {
fn ConvertStringSecurityDescriptorToSecurityDescriptorW(
string: *const u16,
revision: u32,
descriptor: *mut *mut c_void,
size: *mut u32,
) -> i32;
}
let sid = current_process_sid_string(shown)?;
let inherit = if directory { "OICI" } else { "" };
let sddl: Vec<u16> = format!("O:{sid}D:P(A;{inherit};FA;;;{sid})")
.encode_utf16()
.chain(Some(0))
.collect();
let mut raw = std::ptr::null_mut();
if unsafe {
ConvertStringSecurityDescriptorToSecurityDescriptorW(
sddl.as_ptr(),
1,
&mut raw,
std::ptr::null_mut(),
)
} == 0
{
return Err(FsError::io(shown, std::io::Error::last_os_error()));
}
Ok(Self(raw))
}
}
#[cfg(windows)]
impl Drop for SecurityDescriptor {
fn drop(&mut self) {
#[link(name = "kernel32")]
unsafe extern "system" {
fn LocalFree(memory: *mut std::ffi::c_void) -> *mut std::ffi::c_void;
}
unsafe { LocalFree(self.0) };
}
}
#[cfg(windows)]
pub(crate) fn create_new_owner_only(path: &Path) -> std::io::Result<std::fs::File> {
use std::ffi::c_void;
use std::os::windows::ffi::OsStrExt;
use std::os::windows::io::FromRawHandle;
#[repr(C)]
struct SecurityAttributes {
length: u32,
descriptor: *mut c_void,
inherit_handle: i32,
}
#[link(name = "kernel32")]
unsafe extern "system" {
fn CreateFileW(
name: *const u16,
access: u32,
share: u32,
security: *const c_void,
disposition: u32,
flags: u32,
template: *mut c_void,
) -> *mut c_void;
}
const GENERIC_WRITE: u32 = 0x4000_0000;
const READ_CONTROL: u32 = 0x0002_0000;
const DELETE: u32 = 0x0001_0000;
const FILE_SHARE_READ_WRITE_DELETE: u32 = 0x7;
const CREATE_NEW: u32 = 1;
const FILE_ATTRIBUTE_NORMAL: u32 = 0x80;
let descriptor = SecurityDescriptor::owner_only(path, false).map_err(std::io::Error::other)?;
let attributes = SecurityAttributes {
length: std::mem::size_of::<SecurityAttributes>() as u32,
descriptor: descriptor.0,
inherit_handle: 0,
};
let wide: Vec<u16> = path.as_os_str().encode_wide().chain(Some(0)).collect();
if wide[..wide.len() - 1].contains(&0) {
return Err(std::io::Error::from(std::io::ErrorKind::InvalidInput));
}
let handle = unsafe {
CreateFileW(
wide.as_ptr(),
GENERIC_WRITE | READ_CONTROL | DELETE,
FILE_SHARE_READ_WRITE_DELETE,
(&attributes as *const SecurityAttributes).cast(),
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
std::ptr::null_mut(),
)
};
if handle as isize == -1 {
return Err(std::io::Error::last_os_error());
}
let file = unsafe { std::fs::File::from_raw_handle(handle) };
if let Err(error) = verify_owner_only_handle(&file, path) {
let _ = discard_unverified_owner_only(&file, path);
return Err(error.into());
}
Ok(file)
}
#[cfg(windows)]
pub(crate) fn discard_unverified_owner_only(
file: &std::fs::File,
shown: &Path,
) -> crate::error::Result<()> {
use std::os::windows::io::AsRawHandle;
#[link(name = "kernel32")]
unsafe extern "system" {
fn SetFileInformationByHandle(
file: *mut std::ffi::c_void,
class: i32,
info: *const std::ffi::c_void,
size: u32,
) -> i32;
}
const FILE_DISPOSITION_INFO: i32 = 4;
let delete: u8 = 1;
if unsafe {
SetFileInformationByHandle(
file.as_raw_handle(),
FILE_DISPOSITION_INFO,
(&delete as *const u8).cast(),
1,
)
} == 0
{
return Err(FsError::io(shown, std::io::Error::last_os_error()));
}
Ok(())
}
#[cfg(windows)]
fn create_dir_owner_only(path: &Path) -> std::io::Result<()> {
use std::ffi::c_void;
use std::os::windows::ffi::OsStrExt;
use std::os::windows::io::{AsRawHandle, FromRawHandle};
#[repr(C)]
struct UnicodeString {
length: u16,
maximum_length: u16,
buffer: *mut u16,
}
#[repr(C)]
struct ObjectAttributes {
length: u32,
root_directory: *mut c_void,
object_name: *mut UnicodeString,
attributes: u32,
security_descriptor: *mut c_void,
security_quality_of_service: *mut c_void,
}
#[repr(C)]
struct IoStatusBlock {
status: usize, information: usize,
}
#[link(name = "kernel32")]
unsafe extern "system" {
fn CreateFileW(
name: *const u16,
access: u32,
share: u32,
security: *const c_void,
disposition: u32,
flags: u32,
template: *mut c_void,
) -> *mut c_void;
fn GetFileInformationByHandleEx(
file: *mut c_void,
class: i32,
info: *mut c_void,
size: u32,
) -> i32;
}
#[link(name = "ntdll")]
unsafe extern "system" {
fn NtCreateFile(
file: *mut *mut c_void,
access: u32,
attributes: *mut ObjectAttributes,
status: *mut IoStatusBlock,
allocation_size: *const i64,
file_attributes: u32,
share: u32,
disposition: u32,
options: u32,
ea_buffer: *const c_void,
ea_length: u32,
) -> i32;
fn RtlNtStatusToDosError(status: i32) -> u32;
}
const FILE_ADD_SUBDIRECTORY: u32 = 0x4;
const READ_ATTRIBUTES: u32 = 0x80;
const READ_CONTROL: u32 = 0x0002_0000;
const SYNCHRONIZE: u32 = 0x0010_0000;
const DELETE: u32 = 0x0001_0000;
const OPEN_EXISTING: u32 = 3;
const FILE_SHARE_READ_WRITE: u32 = 0x3;
const FILE_FLAG_BACKUP_SEMANTICS: u32 = 0x0200_0000;
const FILE_FLAG_OPEN_REPARSE_POINT: u32 = 0x0020_0000;
const FILE_ATTRIBUTE_TAG_INFO: i32 = 9;
const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x400;
const FILE_ATTRIBUTE_DIRECTORY: u32 = 0x10;
const FILE_CREATE: u32 = 2;
const FILE_DIRECTORY_FILE: u32 = 0x1;
const FILE_WRITE_THROUGH: u32 = 0x2;
const FILE_SYNCHRONOUS_IO_NONALERT: u32 = 0x20;
const FILE_OPEN_REPARSE_POINT: u32 = 0x0020_0000;
#[repr(C)]
struct AttributeTagInfo {
attributes: u32,
tag: u32,
}
let parent = match path
.parent()
.filter(|parent| !parent.as_os_str().is_empty())
{
Some(parent) => parent.to_path_buf(),
None => std::env::current_dir()?,
};
let name = path
.file_name()
.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::InvalidInput))?;
let mut child: Vec<u16> = name.encode_wide().collect();
if child.is_empty()
|| child
.iter()
.any(|unit| *unit == 0 || *unit == b'\\' as u16 || *unit == b'/' as u16)
{
return Err(std::io::Error::from(std::io::ErrorKind::InvalidInput));
}
let child_bytes = u16::try_from(child.len() * 2)
.map_err(|_| std::io::Error::from(std::io::ErrorKind::InvalidInput))?;
let mut child_name = UnicodeString {
length: child_bytes,
maximum_length: child_bytes,
buffer: child.as_mut_ptr(),
};
let parent_wide: Vec<u16> = parent.as_os_str().encode_wide().chain(Some(0)).collect();
if parent_wide[..parent_wide.len() - 1].contains(&0) {
return Err(std::io::Error::from(std::io::ErrorKind::InvalidInput));
}
let mut attempt = 0;
let parent_raw = loop {
let raw = unsafe {
CreateFileW(
parent_wide.as_ptr(),
FILE_ADD_SUBDIRECTORY | READ_ATTRIBUTES | SYNCHRONIZE,
FILE_SHARE_READ_WRITE,
std::ptr::null(),
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
std::ptr::null_mut(),
)
};
if !raw.is_null() && raw as isize != -1 {
break raw;
}
let error = std::io::Error::last_os_error();
if error.raw_os_error() != Some(32) || attempt + 1 >= crate::replaced::MAX_OPEN_TRIES {
return Err(error);
}
std::thread::sleep(crate::replaced::pause_after(attempt));
attempt += 1;
};
let parent_file = unsafe { std::fs::File::from_raw_handle(parent_raw) };
let mut tag = AttributeTagInfo {
attributes: 0,
tag: 0,
};
if unsafe {
GetFileInformationByHandleEx(
parent_file.as_raw_handle(),
FILE_ATTRIBUTE_TAG_INFO,
(&mut tag as *mut AttributeTagInfo).cast(),
std::mem::size_of::<AttributeTagInfo>() as u32,
)
} == 0
{
return Err(std::io::Error::last_os_error());
}
if tag.attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
return Err(FsError::Symlink(parent).into());
}
let descriptor = SecurityDescriptor::owner_only(path, true).map_err(std::io::Error::from)?;
let mut attributes = ObjectAttributes {
length: std::mem::size_of::<ObjectAttributes>() as u32,
root_directory: parent_file.as_raw_handle(),
object_name: &mut child_name,
attributes: 0x40, security_descriptor: descriptor.0,
security_quality_of_service: std::ptr::null_mut(),
};
let mut status = IoStatusBlock {
status: 0,
information: 0,
};
let mut raw = std::ptr::null_mut();
let result = unsafe {
NtCreateFile(
&mut raw,
READ_ATTRIBUTES | READ_CONTROL | SYNCHRONIZE | DELETE,
&mut attributes,
&mut status,
std::ptr::null(),
FILE_ATTRIBUTE_DIRECTORY,
FILE_SHARE_READ_WRITE,
FILE_CREATE,
FILE_DIRECTORY_FILE
| FILE_WRITE_THROUGH
| FILE_SYNCHRONOUS_IO_NONALERT
| FILE_OPEN_REPARSE_POINT,
std::ptr::null(),
0,
)
};
if result < 0 {
return Err(std::io::Error::from_raw_os_error(
unsafe { RtlNtStatusToDosError(result) } as i32,
));
}
if raw.is_null() || raw as isize == -1 {
return Err(std::io::Error::from(std::io::ErrorKind::Other));
}
let file = unsafe { std::fs::File::from_raw_handle(raw) };
if let Err(error) = verify_owner_only_handle(&file, path) {
let _ = discard_unverified_owner_only(&file, path);
return Err(error.into());
}
Ok(())
}
#[cfg(test)]
mod owner_policy_tests {
use super::*;
const USER: &str = "S-1-5-21-1-2-3-1001";
const ADMINS: &str = "S-1-5-32-544";
const SYSTEM: &str = "S-1-5-18";
const OTHER_USER: &str = "S-1-5-21-1-2-3-1002";
const EVERYONE: &str = "S-1-1-0";
const LOCAL: &str = "S-1-2-0";
const INTERACTIVE: &str = "S-1-5-4";
const AUTH_USERS: &str = "S-1-5-11";
const USERS: &str = "S-1-5-32-545";
const ENABLED_GROUP: u32 = SE_GROUP_ENABLED | 0x1;
fn broad(sid: &&str) -> bool {
match *sid {
"S-1-1-0" => sid_is_broad(1, &[0]),
"S-1-2-0" => sid_is_broad(2, &[0]),
"S-1-5-4" => sid_is_broad(5, &[4]),
"S-1-5-11" => sid_is_broad(5, &[11]),
"S-1-5-32-545" => sid_is_broad(5, &[32, 545]),
_ => false,
}
}
fn trusted(owner: &&str, groups: &[(&str, u32)]) -> bool {
owner_is_trusted(owner, &USER, groups, &broad)
}
#[test]
fn current_user_owner_is_trusted() {
assert!(trusted(&USER, &[]));
}
#[test]
fn administrators_owner_needs_owner_and_enabled_flags() {
assert!(trusted(
&ADMINS,
&[(ADMINS, SE_GROUP_OWNER | SE_GROUP_ENABLED)]
));
assert!(!trusted(&ADMINS, &[(ADMINS, SE_GROUP_ENABLED)]));
assert!(!trusted(
&ADMINS,
&[(ADMINS, SE_GROUP_OWNER | SE_GROUP_USE_FOR_DENY_ONLY)]
));
assert!(!trusted(
&ADMINS,
&[(
ADMINS,
SE_GROUP_OWNER | SE_GROUP_ENABLED | SE_GROUP_USE_FOR_DENY_ONLY
)]
));
assert!(!trusted(&ADMINS, &[]));
}
#[test]
fn broad_groups_are_never_trusted_owners() {
for sid in [EVERYONE, LOCAL, INTERACTIVE, AUTH_USERS, USERS] {
assert!(!trusted(&sid, &[(sid, ENABLED_GROUP)]), "{sid}");
assert!(
!trusted(&sid, &[(sid, ENABLED_GROUP | SE_GROUP_OWNER)]),
"{sid}"
);
}
}
#[test]
fn broad_sid_shapes() {
assert!(sid_is_broad(1, &[0]) && sid_is_broad(5, &[32, 545]));
assert!(!sid_is_broad(5, &[32, 544]) && !sid_is_broad(5, &[18]));
}
#[test]
fn foreign_owner_is_rejected() {
let groups = [(ADMINS, SE_GROUP_OWNER | SE_GROUP_ENABLED)];
assert!(!trusted(&OTHER_USER, &groups));
assert!(!trusted(&SYSTEM, &groups));
}
}
#[cfg(test)]
mod dacl_policy_tests {
use super::*;
const SYSTEM: (u64, &[u32]) = (5, &[18]);
const ADMINS: (u64, &[u32]) = (5, &[32, 544]);
const CREATOR_OWNER: (u64, &[u32]) = (3, &[0]);
const EVERYONE: (u64, &[u32]) = (1, &[0]);
const USERS: (u64, &[u32]) = (5, &[32, 545]);
const AUTH_USERS: (u64, &[u32]) = (5, &[11]);
const INTERACTIVE: (u64, &[u32]) = (5, &[4]);
const OTHER_USER: (u64, &[u32]) = (5, &[21, 1, 2, 3, 1002]);
const ALLOW: u8 = 0;
const DENY: u8 = 1;
const INHERITED: u8 = 0x10;
const OICI: u8 = 0x03;
fn allow(trustee: (u64, &[u32]), is_user: bool, flags: u8) -> bool {
ace_is_acceptable(ALLOW, flags, Some((is_user, trustee.0, trustee.1)))
}
#[test]
fn the_current_user_system_and_administrators_are_accepted_inherited_or_explicit() {
assert!(allow(OTHER_USER, true, INHERITED | OICI));
assert!(allow(SYSTEM, false, INHERITED | OICI));
assert!(allow(ADMINS, false, INHERITED | OICI));
assert!(allow(SYSTEM, false, 0));
assert!(allow(ADMINS, false, OICI));
}
#[test]
fn another_user_or_a_broad_group_is_rejected_whatever_the_flags() {
for (name, trustee) in [
("other user", OTHER_USER),
("Everyone", EVERYONE),
("Users", USERS),
("Authenticated Users", AUTH_USERS),
("INTERACTIVE", INTERACTIVE),
] {
for flags in [0, INHERITED, OICI, INHERITED | OICI, INHERIT_ONLY_ACE] {
assert!(!allow(trustee, false, flags), "{name} flags {flags:#x}");
}
}
}
#[test]
fn creator_owner_counts_only_when_inherit_only() {
assert!(allow(CREATOR_OWNER, false, INHERIT_ONLY_ACE | OICI));
assert!(!allow(CREATOR_OWNER, false, OICI));
assert!(!allow(CREATOR_OWNER, false, 0));
}
#[test]
fn deny_aces_are_ignored_and_unknown_types_fail_closed() {
for deny in [1u8, 6, 10, 12] {
assert!(ace_is_acceptable(deny, 0, None), "deny type {deny}");
assert!(ace_is_acceptable(
deny,
0,
Some((false, EVERYONE.0, EVERYONE.1))
));
}
for other in [2u8, 3, 4, 5, 7, 8, 11, 13, 14, 15, 16, 17, 0x7f, 0xff] {
assert!(
!ace_is_acceptable(other, 0, Some((true, 5, &[18]))),
"type {other}"
);
}
assert_eq!(ace_kind(ALLOW), AceKind::Allow);
assert_eq!(ace_kind(9), AceKind::Allow);
assert_eq!(ace_kind(DENY), AceKind::Deny);
}
#[test]
fn an_allow_ace_with_an_unreadable_trustee_is_rejected() {
assert!(!ace_is_acceptable(ALLOW, 0, None));
assert!(!ace_is_acceptable(9, OICI, None));
}
#[test]
fn a_broad_trustee_cannot_pose_as_the_current_user_by_authority() {
assert!(!allow(EVERYONE, false, 0));
assert!(!allow((5, &[18, 1]), false, 0));
assert!(!allow((5, &[32, 544, 1]), false, 0));
assert!(!allow((5, &[]), false, 0));
assert!(!allow((6, &[18]), false, 0));
}
}
#[cfg(all(test, unix))]
mod verify_tests {
use super::*;
use std::os::unix::fs::PermissionsExt;
#[test]
fn a_private_file_verifies_and_a_broad_one_is_too_permissive() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("f");
std::fs::write(&path, b"x").unwrap();
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600)).unwrap();
verify_owner_only(&path).unwrap();
assert!(is_owner_only_strict(&path).unwrap());
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o640)).unwrap();
assert!(matches!(
verify_owner_only(&path),
Err(FsError::TooPermissive(_))
));
assert!(!is_owner_only_strict(&path).unwrap());
}
#[test]
fn a_symlink_is_never_followed_by_the_verify() {
let dir = tempfile::tempdir().unwrap();
let target = dir.path().join("t");
std::fs::write(&target, b"x").unwrap();
std::fs::set_permissions(&target, std::fs::Permissions::from_mode(0o600)).unwrap();
let link = dir.path().join("l");
std::os::unix::fs::symlink(&target, &link).unwrap();
assert!(matches!(verify_owner_only(&link), Err(FsError::Symlink(_))));
}
#[test]
fn a_missing_path_is_an_io_error() {
let dir = tempfile::tempdir().unwrap();
assert!(verify_owner_only(&dir.path().join("absent"))
.unwrap_err()
.is_not_found());
}
}