use std::ffi::c_void;
use anyhow::{Context, Result};
use windows::Win32::Foundation::{HLOCAL, LocalFree};
use windows::Win32::Security::Authorization::{
ConvertStringSecurityDescriptorToSecurityDescriptorW, SDDL_REVISION_1,
};
use windows::Win32::Security::{PSECURITY_DESCRIPTOR, SECURITY_ATTRIBUTES};
use windows::core::{BOOL, w};
const PIPE_SDDL: windows::core::PCWSTR = w!("D:(D;;GA;;;AN)(A;;GA;;;AU)");
pub struct PipeSecurity {
sa: SECURITY_ATTRIBUTES,
sd: PSECURITY_DESCRIPTOR,
}
unsafe impl Send for PipeSecurity {}
unsafe impl Sync for PipeSecurity {}
impl PipeSecurity {
pub fn new() -> Result<Self> {
let mut sd = PSECURITY_DESCRIPTOR::default();
unsafe {
ConvertStringSecurityDescriptorToSecurityDescriptorW(
PIPE_SDDL,
SDDL_REVISION_1,
&mut sd,
None,
)
.context("ConvertStringSecurityDescriptorToSecurityDescriptorW failed")?;
}
let sa = SECURITY_ATTRIBUTES {
nLength: std::mem::size_of::<SECURITY_ATTRIBUTES>() as u32,
lpSecurityDescriptor: sd.0,
bInheritHandle: BOOL(0),
};
Ok(Self { sa, sd })
}
pub fn as_ptr(&self) -> *mut c_void {
&self.sa as *const SECURITY_ATTRIBUTES as *mut c_void
}
}
impl Drop for PipeSecurity {
fn drop(&mut self) {
if !self.sd.0.is_null() {
unsafe {
let _ = LocalFree(Some(HLOCAL(self.sd.0)));
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pipe_security_constructs_and_drops_without_leak() {
let sec = PipeSecurity::new().expect("SDDL → SD should succeed");
let ptr = sec.as_ptr();
assert!(
!ptr.is_null(),
"SECURITY_ATTRIBUTES pointer must be non-null"
);
drop(sec);
}
#[test]
fn pipe_security_as_ptr_is_stable_for_lifetime() {
let sec = PipeSecurity::new().unwrap();
let p1 = sec.as_ptr();
let p2 = sec.as_ptr();
assert_eq!(p1, p2, "as_ptr() must return a stable address");
}
}