subc-os 0.1.16

Operating-system primitives for the subc daemon that need unsafe code, behind a safe API: per-process identity, signalling, memory and CPU usage, and the launch-nonce descriptor handoff.
Documentation
use super::super::{call_error, User};
use std::{
    ffi::c_void,
    io,
    mem::size_of,
    ptr::{addr_of, null_mut},
};
use windows_sys::Win32::{
    Foundation::LocalFree,
    Security::{
        Authorization::{
            ConvertSecurityDescriptorToStringSecurityDescriptorW, ConvertSidToStringSidW,
            ConvertStringSecurityDescriptorToSecurityDescriptorW, GetSecurityInfo, SDDL_REVISION_1,
            SE_KERNEL_OBJECT,
        },
        GetAce, GetSecurityDescriptorControl, GetSecurityDescriptorDacl, ACCESS_ALLOWED_ACE,
        ACE_HEADER, DACL_SECURITY_INFORMATION, PSID, SE_DACL_PROTECTED,
    },
    System::Threading::GetCurrentProcess,
};

struct Allocation(*mut c_void);

impl Drop for Allocation {
    fn drop(&mut self) {
        // SAFETY: Each allocation came from a successful LocalAlloc-backed API.
        unsafe { LocalFree(self.0) };
    }
}

fn allocated_text(pointer: *mut u16) -> String {
    let _allocation = Allocation(pointer.cast());
    // SAFETY: The conversion API returned a live, NUL-terminated UTF-16 string.
    unsafe {
        let length = (0..).take_while(|&i| *pointer.add(i) != 0).count();
        String::from_utf16(std::slice::from_raw_parts(pointer, length)).unwrap()
    }
}

fn sid_string(sid: PSID) -> io::Result<String> {
    let mut text = null_mut();
    // SAFETY: sid is valid and live, and text receives an allocated string.
    if unsafe { ConvertSidToStringSidW(sid, &mut text) } == 0 {
        return Err(call_error(
            "ConvertSidToStringSidW(process trustee)",
            io::Error::last_os_error(),
        ));
    }
    Ok(allocated_text(text))
}

pub(super) fn current_process_sddl() -> io::Result<String> {
    let mut raw = null_mut();
    // SAFETY: The pseudo process handle retains READ_CONTROL; raw receives an
    // allocated snapshot of the applied descriptor, not the builder's buffers.
    let error = unsafe {
        GetSecurityInfo(
            GetCurrentProcess(),
            SE_KERNEL_OBJECT,
            DACL_SECURITY_INFORMATION,
            null_mut(),
            null_mut(),
            null_mut(),
            null_mut(),
            &mut raw,
        )
    };
    if error != 0 {
        return Err(call_error(
            "GetSecurityInfo(process DACL readback)",
            io::Error::from_raw_os_error(error as i32),
        ));
    }
    let descriptor = Allocation(raw);
    let mut text = null_mut();
    // SAFETY: The readback descriptor remains live and text is writable.
    if unsafe {
        ConvertSecurityDescriptorToStringSecurityDescriptorW(
            descriptor.0,
            SDDL_REVISION_1,
            DACL_SECURITY_INFORMATION,
            &mut text,
            null_mut(),
        )
    } == 0
    {
        return Err(call_error(
            "ConvertSecurityDescriptorToStringSecurityDescriptorW(process DACL)",
            io::Error::last_os_error(),
        ));
    }
    Ok(allocated_text(text))
}

/// Parse the child's native readback independently of the production ACL builder.
/// Check the descriptor, not an expected SDDL string generated by the same code.
pub(super) fn assert_exact_protected_dacl(sddl: &str) {
    let text: Vec<u16> = sddl.encode_utf16().chain(Some(0)).collect();
    let mut raw = null_mut();
    // SAFETY: The SDDL is terminated and raw receives an allocated descriptor.
    assert_ne!(
        unsafe {
            ConvertStringSecurityDescriptorToSecurityDescriptorW(
                text.as_ptr(),
                SDDL_REVISION_1,
                &mut raw,
                null_mut(),
            )
        },
        0,
        "parse child DACL {sddl}: {}",
        io::Error::last_os_error()
    );
    let descriptor = Allocation(raw);
    let mut control = 0;
    let mut revision = 0;
    // SAFETY: The parsed descriptor is live and both outputs are writable.
    assert_ne!(
        unsafe { GetSecurityDescriptorControl(descriptor.0, &mut control, &mut revision) },
        0,
        "GetSecurityDescriptorControl: {}",
        io::Error::last_os_error()
    );
    assert_ne!(
        control & SE_DACL_PROTECTED,
        0,
        "process DACL must be protected: {sddl}"
    );
    let mut present = 0;
    let mut defaulted = 0;
    let mut dacl = null_mut();
    // SAFETY: The parsed descriptor retains the returned ACL throughout checks.
    assert_ne!(
        unsafe { GetSecurityDescriptorDacl(descriptor.0, &mut present, &mut dacl, &mut defaulted) },
        0,
        "GetSecurityDescriptorDacl: {}",
        io::Error::last_os_error()
    );
    assert_ne!(present, 0, "process DACL must be present: {sddl}");
    assert!(
        !dacl.is_null(),
        "null process DACL grants unrestricted access: {sddl}"
    );
    // SAFETY: The conversion returned a valid ACL in the retained descriptor.
    assert_eq!(
        unsafe { (*dacl).AceCount },
        2,
        "exactly two process ACEs: {sddl}"
    );
    let user = User::current().expect("token user for process DACL assertion");
    let mut trustees = Vec::new();
    for index in 0..2 {
        let mut raw_ace = null_mut();
        // SAFETY: The assertion above established AceCount == 2, so indices 0
        // and 1 exist in this ACL, which the retained descriptor keeps alive.
        assert_ne!(
            unsafe { GetAce(dacl, index, &mut raw_ace) },
            0,
            "GetAce(process DACL): {}",
            io::Error::last_os_error()
        );
        // SAFETY: Every ACE starts with ACE_HEADER, regardless of its payload.
        let header = unsafe { &*raw_ace.cast::<ACE_HEADER>() };
        assert_eq!(header.AceType, 0, "ACCESS_ALLOWED ACE: {sddl}");
        assert!(usize::from(header.AceSize) >= size_of::<ACCESS_ALLOWED_ACE>());
        assert_eq!(header.AceFlags, 0, "non-inheriting explicit ACE: {sddl}");
        // SAFETY: The checked type and size establish an ACCESS_ALLOWED payload.
        let ace = unsafe { &*raw_ace.cast::<ACCESS_ALLOWED_ACE>() };
        // Independent numeric mask: limited query (0x1000), synchronize
        // (0x100000), terminate (0x1), with no other process or standard rights.
        assert_eq!(
            ace.Mask, 0x0010_1001,
            "only daemon rights in each ACE: {sddl}"
        );
        let sid = addr_of!(ace.SidStart).cast_mut().cast();
        trustees.push(sid_string(sid).expect("process ACE trustee SID"));
    }
    trustees.sort();
    let mut expected = vec![sid_string(user.sid()).unwrap(), "S-1-3-4".to_owned()];
    expected.sort();
    assert_eq!(
        trustees, expected,
        "token user and OW (OWNER RIGHTS) trustees: {sddl}"
    );
}