#![allow(unsafe_code)]
use std::io;
use std::ptr;
use windows_sys::Win32::Foundation::{CloseHandle, FALSE, HANDLE, HLOCAL, LocalFree};
use windows_sys::Win32::Security::Authorization::{
ConvertSidToStringSidW, ConvertStringSecurityDescriptorToSecurityDescriptorW, SDDL_REVISION_1,
};
use windows_sys::Win32::Security::{
GetTokenInformation, PSECURITY_DESCRIPTOR, SECURITY_ATTRIBUTES, TOKEN_QUERY, TOKEN_USER,
TokenUser,
};
use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
use windows_sys::core::PWSTR;
pub struct PipeSecurityDescriptor {
descriptor: PSECURITY_DESCRIPTOR,
attrs: SECURITY_ATTRIBUTES,
}
impl PipeSecurityDescriptor {
pub fn current_user_only() -> io::Result<Self> {
let sid_str = current_user_sid_string()?;
let sddl: Vec<u16> = format!("O:{sid_str}G:{sid_str}D:P(A;;GA;;;{sid_str})")
.encode_utf16()
.chain(std::iter::once(0))
.collect();
let mut descriptor: PSECURITY_DESCRIPTOR = ptr::null_mut();
let ok = unsafe {
ConvertStringSecurityDescriptorToSecurityDescriptorW(
sddl.as_ptr(),
SDDL_REVISION_1,
&mut descriptor,
ptr::null_mut(),
)
};
if ok == 0 {
return Err(io::Error::last_os_error());
}
let attrs = SECURITY_ATTRIBUTES {
nLength: std::mem::size_of::<SECURITY_ATTRIBUTES>() as u32,
lpSecurityDescriptor: descriptor,
bInheritHandle: FALSE,
};
Ok(Self { descriptor, attrs })
}
pub fn as_attrs_ptr(&mut self) -> *mut std::ffi::c_void {
(&mut self.attrs) as *mut SECURITY_ATTRIBUTES as *mut std::ffi::c_void
}
}
impl Drop for PipeSecurityDescriptor {
fn drop(&mut self) {
if !self.descriptor.is_null() {
unsafe {
LocalFree(self.descriptor as HLOCAL);
}
self.descriptor = ptr::null_mut();
}
}
}
fn current_user_sid_string() -> io::Result<String> {
unsafe {
let process: HANDLE = GetCurrentProcess();
let mut token: HANDLE = 0;
if OpenProcessToken(process, TOKEN_QUERY, &mut token) == 0 {
return Err(io::Error::last_os_error());
}
let mut needed: u32 = 0;
GetTokenInformation(token, TokenUser, ptr::null_mut(), 0, &mut needed);
if needed == 0 {
CloseHandle(token);
return Err(io::Error::other("GetTokenInformation: zero-size response"));
}
let mut buf = vec![0u8; needed as usize];
let ok = GetTokenInformation(
token,
TokenUser,
buf.as_mut_ptr().cast(),
needed,
&mut needed,
);
if ok == 0 {
let err = io::Error::last_os_error();
CloseHandle(token);
return Err(err);
}
let token_user = &*(buf.as_ptr() as *const TOKEN_USER);
let sid_ptr = token_user.User.Sid;
let mut sid_str_ptr: PWSTR = ptr::null_mut();
let ok = ConvertSidToStringSidW(sid_ptr, &mut sid_str_ptr);
CloseHandle(token);
if ok == 0 || sid_str_ptr.is_null() {
return Err(io::Error::last_os_error());
}
let s = wstr_to_string(sid_str_ptr);
LocalFree(sid_str_ptr as HLOCAL);
s
}
}
unsafe fn wstr_to_string(ptr: PWSTR) -> io::Result<String> {
if ptr.is_null() {
return Err(io::Error::other("null SID string"));
}
let mut len = 0usize;
while unsafe { *ptr.add(len) } != 0 {
len += 1;
}
let slice = unsafe { std::slice::from_raw_parts(ptr, len) };
String::from_utf16(slice)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("UTF-16: {e}")))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn current_user_sid_is_well_formed() {
let s = current_user_sid_string().unwrap();
assert!(
s.starts_with("S-1-"),
"SID should start with S-1- prefix, got {s:?}"
);
assert!(
s.matches('-').count() >= 2,
"SID has too few components: {s:?}"
);
}
#[test]
fn pipe_security_descriptor_constructs() {
let _sd = PipeSecurityDescriptor::current_user_only().unwrap();
}
#[tokio::test]
async fn pipe_with_hardened_dacl_accepts_self() {
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions};
let path = format!(r"\\.\pipe\inferd-sddl-test-{}", std::process::id());
let mut sd = PipeSecurityDescriptor::current_user_only().unwrap();
let server = unsafe {
ServerOptions::new()
.first_pipe_instance(true)
.create_with_security_attributes_raw(&path, sd.as_attrs_ptr())
}
.expect("bind hardened pipe");
let client_path = path.clone();
let client = tokio::spawn(async move {
for _ in 0..10 {
match ClientOptions::new().open(&client_path) {
Ok(c) => return c,
Err(_) => tokio::time::sleep(std::time::Duration::from_millis(20)).await,
}
}
panic!("client failed to connect to hardened pipe");
});
server.connect().await.expect("server.connect");
let mut client = client.await.unwrap();
client.write_all(b"x").await.unwrap();
let mut buf = [0u8; 1];
let mut server_io = server;
let n = server_io.read(&mut buf).await.unwrap();
assert_eq!(n, 1);
assert_eq!(&buf, b"x");
}
}