wininskit 0.1.1

Thin checked wrappers over the Win32 an installer needs: elevation, ACLs, services, the Restart Manager, the registry and shortcuts.
use std::{mem, path::Path, ptr};

use windows_sys::Win32::{
    Foundation::{ERROR_SUCCESS, LocalFree},
    Security::{
        ACCESS_ALLOWED_ACE, ACE_HEADER, ACL,
        Authorization::{
            EXPLICIT_ACCESS_W, GRANT_ACCESS, GetNamedSecurityInfoW, NO_MULTIPLE_TRUSTEE,
            SE_FILE_OBJECT, SetEntriesInAclW, TRUSTEE_IS_GROUP, TRUSTEE_IS_SID, TRUSTEE_W,
        },
        CONTAINER_INHERIT_ACE, CreateWellKnownSid, DACL_SECURITY_INFORMATION, EqualSid, GetAce,
        InitializeSecurityDescriptor, OBJECT_INHERIT_ACE, PSECURITY_DESCRIPTOR, PSID,
        SECURITY_DESCRIPTOR, SECURITY_MAX_SID_SIZE, SetFileSecurityW, SetSecurityDescriptorDacl,
        WinBuiltinUsersSid,
    },
    Storage::FileSystem::FILE_ALL_ACCESS,
    System::SystemServices::ACCESS_ALLOWED_ACE_TYPE,
};

use crate::{
    error::{Error, Result},
    wide::wide_path,
};

/// Windows spells "everything" this way for a file object.
const GENERIC_ALL: u32 = 0x1000_0000;
const SECURITY_DESCRIPTOR_REVISION: u32 = 1;
/// Both flags, so the entry reaches every directory and file underneath.
const INHERIT_ALL: u32 = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE;

/// Grants BUILTIN\Users full access to `path`, inherited by everything created
/// under it.
///
/// Written to cost the same whether the folder is empty or already holds an
/// install.
///
/// `SetNamedSecurityInfo`, the obvious call, walks every file already under the
/// directory and rewrites its inherited entry. That is instant on a fresh
/// install and linear on a re-install: measured at about 66 microseconds a
/// file, so roughly five seconds over an install of eighty thousand. There is
/// no way to parallelise that - it happens inside the one call - so it is
/// avoided instead, twice over.
///
/// First, if the entry is already on the directory there is nothing to do, and
/// on a re-install it always is. Second, the entry is written with
/// `SetFileSecurity`, which sets the descriptor on the directory alone.
/// Inheritance is applied by the filesystem when a file is created, so
/// everything the install then unpacks picks it up regardless, and files left
/// by a previous install already carry it.
///
/// Worth knowing what this grants: any standard user can then replace files the
/// engine runs. It matches what the product has always shipped, and narrowing
/// it to the directories that actually need writing is a separate decision.
pub fn grant_users_full_access(path: &Path) -> Result<()> {
    let wide = wide_path(path);

    unsafe {
        // BUILTIN\Users, built rather than parsed so no string form is involved.
        let mut sid_buffer = [0u8; SECURITY_MAX_SID_SIZE as usize];
        let mut sid_size = sid_buffer.len() as u32;
        if CreateWellKnownSid(
            WinBuiltinUsersSid,
            ptr::null_mut(),
            sid_buffer.as_mut_ptr().cast::<core::ffi::c_void>(),
            &mut sid_size,
        ) == 0
        {
            return Err(Error::last("CreateWellKnownSid"));
        }
        let sid: PSID = sid_buffer.as_mut_ptr().cast();

        // The existing DACL, so the new entry is added to it rather than
        // replacing the administrator and SYSTEM entries.
        let mut existing_dacl: *mut ACL = ptr::null_mut();
        let mut descriptor: PSECURITY_DESCRIPTOR = ptr::null_mut();
        let status = GetNamedSecurityInfoW(
            wide.as_ptr(),
            SE_FILE_OBJECT,
            DACL_SECURITY_INFORMATION,
            ptr::null_mut(),
            ptr::null_mut(),
            &mut existing_dacl,
            ptr::null_mut(),
            &mut descriptor,
        );
        if status != ERROR_SUCCESS {
            return Err(Error::code("GetNamedSecurityInfoW", status));
        }
        let _descriptor = LocalGuard(descriptor);

        if already_granted(existing_dacl, sid) {
            return Ok(());
        }

        let mut access: EXPLICIT_ACCESS_W = mem::zeroed();
        access.grfAccessPermissions = GENERIC_ALL;
        access.grfAccessMode = GRANT_ACCESS;
        access.grfInheritance = INHERIT_ALL;
        access.Trustee = TRUSTEE_W {
            pMultipleTrustee: ptr::null_mut(),
            MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE,
            TrusteeForm: TRUSTEE_IS_SID,
            TrusteeType: TRUSTEE_IS_GROUP,
            ptstrName: sid.cast(),
        };

        let mut merged: *mut ACL = ptr::null_mut();
        let status = SetEntriesInAclW(1, &access, existing_dacl, &mut merged);
        if status != ERROR_SUCCESS {
            return Err(Error::code("SetEntriesInAclW", status));
        }
        let _merged = LocalGuard(merged.cast());

        // A descriptor of our own carrying just the DACL. SetFileSecurity wants
        // one of these rather than the pieces.
        let mut fresh: SECURITY_DESCRIPTOR = mem::zeroed();
        let fresh_ptr: PSECURITY_DESCRIPTOR = (&raw mut fresh).cast();
        if InitializeSecurityDescriptor(fresh_ptr, SECURITY_DESCRIPTOR_REVISION) == 0 {
            return Err(Error::last("InitializeSecurityDescriptor"));
        }
        if SetSecurityDescriptorDacl(fresh_ptr, 1, merged, 0) == 0 {
            return Err(Error::last("SetSecurityDescriptorDacl"));
        }
        if SetFileSecurityW(wide.as_ptr(), DACL_SECURITY_INFORMATION, fresh_ptr) == 0 {
            return Err(Error::last("SetFileSecurityW"));
        }
    }
    Ok(())
}

/// Whether the directory already carries an inheritable full-access entry for
/// this trustee.
///
/// The mask is checked against `FILE_ALL_ACCESS` as well as `GENERIC_ALL`
/// because a generic right is mapped to the specific ones when it is stored, so
/// an entry written as `GENERIC_ALL` reads back as `FILE_ALL_ACCESS`.
unsafe fn already_granted(dacl: *const ACL, sid: PSID) -> bool {
    if dacl.is_null() {
        return false;
    }
    let count = unsafe { (*dacl).AceCount };
    for index in 0..count as u32 {
        let mut ace: *mut core::ffi::c_void = ptr::null_mut();
        if unsafe { GetAce(dacl, index, &mut ace) } == 0 || ace.is_null() {
            continue;
        }
        let header = unsafe { &*(ace as *const ACE_HEADER) };
        if u32::from(header.AceType) != ACCESS_ALLOWED_ACE_TYPE {
            continue;
        }
        if u32::from(header.AceFlags) & INHERIT_ALL != INHERIT_ALL {
            continue;
        }
        let allowed = unsafe { &*(ace as *const ACCESS_ALLOWED_ACE) };
        let grants_everything =
            allowed.Mask & FILE_ALL_ACCESS == FILE_ALL_ACCESS || allowed.Mask & GENERIC_ALL != 0;
        if !grants_everything {
            continue;
        }
        // SidStart is the first four bytes of the trustee, not a pointer to it.
        let ace_sid: PSID = (&raw const allowed.SidStart).cast_mut().cast();
        if unsafe { EqualSid(ace_sid, sid) } != 0 {
            return true;
        }
    }
    false
}

/// Frees a buffer the security API allocated, on every path out.
struct LocalGuard(*mut core::ffi::c_void);

impl Drop for LocalGuard {
    fn drop(&mut self) {
        if !self.0.is_null() {
            unsafe { LocalFree(self.0) };
        }
    }
}