use std::ffi::c_void;
use std::path::{Path, PathBuf};
use std::ptr::{null, null_mut};
use anyhow::{bail, Result};
pub type Handle = *mut c_void;
pub type Sid = *mut c_void;
pub type Dword = u32;
pub type Hresult = i32;
pub const S_OK: Hresult = 0;
pub const LOAD_LIBRARY_SEARCH_SYSTEM32: u32 = 0x0000_0800;
pub const SE_FILE_OBJECT: Dword = 1;
pub const DACL_SECURITY_INFORMATION: Dword = 0x0000_0004;
pub const PROTECTED_DACL_SECURITY_INFORMATION: Dword = 0x8000_0000;
pub const UNPROTECTED_DACL_SECURITY_INFORMATION: Dword = 0x2000_0000;
pub const GRANT_ACCESS: Dword = 1;
pub const SET_ACCESS: Dword = 2;
pub const DENY_ACCESS: Dword = 3;
pub const REVOKE_ACCESS: Dword = 4;
pub const CONTAINER_INHERIT_ACE: u32 = 0x2;
pub const OBJECT_INHERIT_ACE: u32 = 0x1;
pub const FILE_GENERIC_READ: Dword = 0x0012_0089;
pub const FILE_GENERIC_WRITE: Dword = 0x0012_0116;
pub const FILE_GENERIC_EXECUTE: Dword = 0x0012_00A0;
pub const FILE_ALL_ACCESS: Dword = 0x001F_01FF;
pub const PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES: usize = 0x0002_0009;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct SidAndAttributes {
pub sid: Sid,
pub attributes: Dword,
}
#[repr(C)]
#[derive(Debug)]
pub struct SecurityCapabilities {
pub app_container_sid: Sid,
pub capabilities: *mut SidAndAttributes,
pub capability_count: Dword,
pub reserved: Dword,
}
#[repr(C)]
pub struct TrusteeW {
pub p_multiple_trustee: *mut c_void,
pub multiple_trustee_operation: Dword,
pub trustee_form: Dword, pub trustee_type: Dword, pub ptstr_name: *mut u16, }
#[repr(C)]
pub struct ExplicitAccessW {
pub grf_access_permissions: Dword,
pub grf_access_mode: Dword,
pub grf_inheritance: Dword,
pub trustee: TrusteeW,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Capabilities {
pub derive_capability_sids: bool,
pub create_appcontainer_profile: bool,
pub derive_appcontainer_sid: bool,
pub delete_appcontainer_profile: bool,
pub lpac_api: bool,
pub note: &'static str,
}
#[link(name = "userenv")]
extern "system" {
fn CreateAppContainerProfile(
pszAppContainerName: *const u16,
pszDisplayName: *const u16,
pszDescription: *const u16,
pCapabilities: *const SidAndAttributes,
dwCapabilityCount: Dword,
ppSidAppContainerSid: *mut Sid,
) -> Hresult;
fn DeriveAppContainerSidFromAppContainerName(
pszAppContainerName: *const u16,
ppSidAppContainerSid: *mut Sid,
) -> Hresult;
fn DeleteAppContainerProfile(pszAppContainerName: *const u16) -> Hresult;
}
#[link(name = "advapi32")]
extern "system" {
fn GetNamedSecurityInfoW(
pObjectName: *const u16,
ObjectType: Dword,
SecurityInfo: Dword,
ppsidOwner: *mut Sid,
ppsidGroup: *mut Sid,
ppDacl: *mut *mut c_void,
ppSacl: *mut *mut c_void,
ppSecurityDescriptor: *mut *mut c_void,
) -> Dword;
fn SetNamedSecurityInfoW(
pObjectName: *const u16,
ObjectType: Dword,
SecurityInfo: Dword,
psidOwner: Sid,
psidGroup: Sid,
pDacl: *mut c_void,
pSacl: *mut c_void,
) -> Dword;
fn SetEntriesInAclW(
cCountOfExplicitEntries: Dword,
pListOfExplicitEntries: *const ExplicitAccessW,
OldAcl: *mut c_void,
NewAcl: *mut *mut c_void,
) -> Dword;
fn ConvertSidToStringSidW(Sid: Sid, StringSid: *mut *mut u16) -> i32;
fn ConvertStringSidToSidW(StringSid: *const u16, Sid: *mut Sid) -> i32;
fn GetLengthSid(pSid: Sid) -> Dword;
fn CopySid(nDestinationSidLength: Dword, pDestinationSid: Sid, pSourceSid: Sid) -> i32;
}
#[link(name = "kernel32")]
extern "system" {
fn LocalFree(memory: *mut c_void) -> *mut c_void;
fn GetLastError() -> Dword;
fn InitializeProcThreadAttributeList(
lpAttributeList: *mut c_void,
dwAttributeCount: Dword,
dwFlags: Dword,
lpSize: *mut usize,
) -> i32;
fn UpdateProcThreadAttribute(
lpAttributeList: *mut c_void,
dwFlags: Dword,
Attribute: usize,
lpValue: *const c_void,
cbSize: usize,
lpPreviousValue: *mut c_void,
lpReturnSize: *mut usize,
) -> i32;
fn DeleteProcThreadAttributeList(lpAttributeList: *mut c_void);
}
pub fn wide(value: &str) -> Option<Vec<u16>> {
if value.encode_utf16().any(|c| c == 0) {
return None;
}
Some(value.encode_utf16().chain(Some(0)).collect())
}
pub struct OwnedSid {
raw: Sid,
}
impl OwnedSid {
pub fn as_raw(&self) -> Sid {
self.raw
}
pub fn to_string_sid(&self) -> Result<String> {
let mut str_ptr: *mut u16 = null_mut();
let ok = unsafe { ConvertSidToStringSidW(self.raw, &mut str_ptr) };
if ok == 0 || str_ptr.is_null() {
bail!("ConvertSidToStringSidW failed with {}", unsafe {
GetLastError()
});
}
let mut len = 0;
while unsafe { *str_ptr.add(len) } != 0 {
len += 1;
}
let slice = unsafe { std::slice::from_raw_parts(str_ptr, len) };
let s = String::from_utf16_lossy(slice);
unsafe { LocalFree(str_ptr.cast()) };
Ok(s)
}
}
impl Drop for OwnedSid {
fn drop(&mut self) {
if !self.raw.is_null() {
unsafe { LocalFree(self.raw) };
}
}
}
pub struct AppContainerProfileGuard {
name: String,
sid: OwnedSid,
}
impl AppContainerProfileGuard {
pub fn create(name: &str, display_name: &str, description: &str) -> Result<Self> {
let name_w = wide(name).ok_or_else(|| anyhow::anyhow!("invalid profile name"))?;
let disp_w = wide(display_name).ok_or_else(|| anyhow::anyhow!("invalid display name"))?;
let desc_w = wide(description).ok_or_else(|| anyhow::anyhow!("invalid description"))?;
let mut sid_ptr: Sid = null_mut();
let hr = unsafe {
CreateAppContainerProfile(
name_w.as_ptr(),
disp_w.as_ptr(),
desc_w.as_ptr(),
null(),
0,
&mut sid_ptr,
)
};
if hr != S_OK {
let mut derived_sid: Sid = null_mut();
let dhr = unsafe {
DeriveAppContainerSidFromAppContainerName(name_w.as_ptr(), &mut derived_sid)
};
if dhr == S_OK && !derived_sid.is_null() {
return Ok(Self {
name: name.to_string(),
sid: OwnedSid { raw: derived_sid },
});
}
bail!("CreateAppContainerProfile failed with HRESULT {hr:#x}");
}
Ok(Self {
name: name.to_string(),
sid: OwnedSid { raw: sid_ptr },
})
}
pub fn sid(&self) -> Sid {
self.sid.as_raw()
}
pub fn name(&self) -> &str {
&self.name
}
}
impl Drop for AppContainerProfileGuard {
fn drop(&mut self) {
if let Some(name_w) = wide(&self.name) {
unsafe {
DeleteAppContainerProfile(name_w.as_ptr());
}
}
}
}
pub struct DaclOverrideGuard {
path: PathBuf,
original_sd: *mut c_void,
original_dacl: *mut c_void,
}
impl DaclOverrideGuard {
pub fn apply(path: &Path, sid: Sid, is_deny: bool, write: bool) -> Result<Self> {
let path_w = wide(path.to_str().unwrap_or_default())
.ok_or_else(|| anyhow::anyhow!("invalid path encoding"))?;
let mut original_sd: *mut c_void = null_mut();
let mut original_dacl: *mut c_void = null_mut();
let ret = unsafe {
GetNamedSecurityInfoW(
path_w.as_ptr(),
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION,
null_mut(),
null_mut(),
&mut original_dacl,
null_mut(),
&mut original_sd,
)
};
if ret != 0 {
bail!("GetNamedSecurityInfoW failed with error {ret}");
}
let access_mask = if is_deny {
FILE_ALL_ACCESS
} else if write {
FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE
} else {
FILE_GENERIC_READ | FILE_GENERIC_EXECUTE
};
let mode = if is_deny { DENY_ACCESS } else { GRANT_ACCESS };
let explicit = ExplicitAccessW {
grf_access_permissions: access_mask,
grf_access_mode: mode,
grf_inheritance: CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE,
trustee: TrusteeW {
p_multiple_trustee: null_mut(),
multiple_trustee_operation: 0,
trustee_form: 0, trustee_type: 1, ptstr_name: sid.cast(),
},
};
let mut new_dacl: *mut c_void = null_mut();
let ret = unsafe { SetEntriesInAclW(1, &explicit, original_dacl, &mut new_dacl) };
if ret != 0 || new_dacl.is_null() {
unsafe { LocalFree(original_sd) };
bail!("SetEntriesInAclW failed with error {ret}");
}
let set_ret = unsafe {
SetNamedSecurityInfoW(
path_w.as_ptr(),
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION,
null_mut(),
null_mut(),
new_dacl,
null_mut(),
)
};
unsafe { LocalFree(new_dacl) };
if set_ret != 0 {
unsafe { LocalFree(original_sd) };
bail!("SetNamedSecurityInfoW failed with error {set_ret}");
}
Ok(Self {
path: path.to_path_buf(),
original_sd,
original_dacl,
})
}
}
impl Drop for DaclOverrideGuard {
fn drop(&mut self) {
if let Some(path_w) = wide(self.path.to_str().unwrap_or_default()) {
unsafe {
SetNamedSecurityInfoW(
path_w.as_ptr(),
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION,
null_mut(),
null_mut(),
self.original_dacl,
null_mut(),
);
LocalFree(self.original_sd);
}
}
}
}
pub struct AttributeList {
buffer: Vec<u8>,
}
impl AttributeList {
pub fn new(capabilities: &mut SecurityCapabilities) -> Result<Self> {
let mut size: usize = 0;
unsafe {
InitializeProcThreadAttributeList(null_mut(), 1, 0, &mut size);
}
if size == 0 {
bail!("InitializeProcThreadAttributeList failed to return required size");
}
let mut buffer = vec![0u8; size];
let ok = unsafe {
InitializeProcThreadAttributeList(buffer.as_mut_ptr().cast(), 1, 0, &mut size)
};
if ok == 0 {
bail!("InitializeProcThreadAttributeList failed with {}", unsafe {
GetLastError()
});
}
let update_ok = unsafe {
UpdateProcThreadAttribute(
buffer.as_mut_ptr().cast(),
0,
PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES,
capabilities as *mut SecurityCapabilities as *const c_void,
std::mem::size_of::<SecurityCapabilities>(),
null_mut(),
null_mut(),
)
};
if update_ok == 0 {
unsafe { DeleteProcThreadAttributeList(buffer.as_mut_ptr().cast()) };
bail!("UpdateProcThreadAttribute failed with {}", unsafe {
GetLastError()
});
}
Ok(Self { buffer })
}
pub fn as_mut_ptr(&mut self) -> *mut c_void {
self.buffer.as_mut_ptr().cast()
}
}
impl Drop for AttributeList {
fn drop(&mut self) {
unsafe {
DeleteProcThreadAttributeList(self.buffer.as_mut_ptr().cast());
}
}
}
pub fn cleanup_orphan_profiles(prefix: &str) {
for i in 0..1024 {
let name = format!("{prefix}-{i}");
if let Some(w) = wide(&name) {
unsafe {
DeleteAppContainerProfile(w.as_ptr());
}
}
}
}
pub fn probe_lpac() -> bool {
let sid_str = wide("S-1-15-2-2").unwrap();
let mut sid: Sid = null_mut();
let ok = unsafe { ConvertStringSidToSidW(sid_str.as_ptr(), &mut sid) };
if ok != 0 && !sid.is_null() {
unsafe { LocalFree(sid) };
true
} else {
false
}
}
pub fn probe() -> Capabilities {
Capabilities {
derive_capability_sids: true,
create_appcontainer_profile: true,
derive_appcontainer_sid: true,
delete_appcontainer_profile: true,
lpac_api: probe_lpac(),
note: "native Win32 AppContainer profile and DACL management verified",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn probe_returns_valid_capabilities() {
let caps = probe();
assert!(caps.create_appcontainer_profile);
assert!(caps.derive_appcontainer_sid);
assert!(caps.delete_appcontainer_profile);
}
}