urma-node 0.6.0

Optional URMA archive node: Litecoin follower, record archive, libp2p overlay and loopback service
Documentation
use crate::{
    config,
    install::Error,
    platform::{Descriptor, Exposure, Paths, Sddl},
};
use std::{
    ffi::{OsStr, c_void},
    fs, io,
    mem::{size_of, zeroed},
    os::windows::ffi::OsStrExt,
    path::Path,
    ptr,
};
use windows_sys::Win32::{
    Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE, LUID, LocalFree},
    Security::{
        ACCESS_ALLOWED_ACE, ACE_HEADER, ACL, AdjustTokenPrivileges,
        Authorization::{ConvertStringSidToSidW, GetSecurityInfo, SE_FILE_OBJECT},
        DACL_SECURITY_INFORMATION, EqualSid, GetAce, IsWellKnownSid, LUID_AND_ATTRIBUTES,
        LookupPrivilegeValueW, OWNER_SECURITY_INFORMATION, PROTECTED_DACL_SECURITY_INFORMATION,
        PSECURITY_DESCRIPTOR, PSID, SE_BACKUP_NAME, SE_PRIVILEGE_ENABLED, SE_RESTORE_NAME,
        SE_TAKE_OWNERSHIP_NAME, SetKernelObjectSecurity, TOKEN_ADJUST_PRIVILEGES, TOKEN_PRIVILEGES,
        TOKEN_QUERY, WinBuiltinAdministratorsSid, WinLocalSystemSid,
    },
    Storage::FileSystem::{
        BY_HANDLE_FILE_INFORMATION, CreateFileW, FILE_ATTRIBUTE_DIRECTORY,
        FILE_ATTRIBUTE_REPARSE_POINT, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT,
        FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE,
        GetFileInformationByHandle, OPEN_EXISTING, READ_CONTROL, WRITE_DAC, WRITE_OWNER,
    },
    System::Threading::{GetCurrentProcess, OpenProcessToken},
};

pub struct Acl;

impl Acl {
    const OWNER: &'static str = "O:BA";
    const TRUSTED_INSTALLER: &'static str =
        "S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464";
    const BINARY: &'static str = "urma-node.exe";
    const WRITE_RIGHTS: u32 = 0x0000_0002
        | 0x0000_0004
        | 0x0000_0010
        | 0x0000_0100
        | 0x0001_0000
        | 0x0004_0000
        | 0x0008_0000
        | 0x1000_0000
        | 0x4000_0000;
    const ALLOWED: u32 = 0;
    const INHERIT_ONLY: u32 = 0x08;
    const NOT_ALL_ASSIGNED: i32 = 1300;
}

struct Handle(HANDLE);

impl Drop for Handle {
    fn drop(&mut self) {
        unsafe { CloseHandle(self.0) };
    }
}

struct Local(*mut c_void);

impl Drop for Local {
    fn drop(&mut self) {
        unsafe { LocalFree(self.0) };
    }
}

fn wide(text: &OsStr) -> Vec<u16> {
    text.encode_wide().chain([0]).collect()
}

fn refuse(path: &Path, why: &str) -> Error {
    Error::Failed(format!("refusing {}: {why}", path.display()))
}

fn open(path: &Path, access: u32) -> Result<Handle, Error> {
    let name = wide(path.as_os_str());
    let handle = unsafe {
        CreateFileW(
            name.as_ptr(),
            access,
            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
            ptr::null(),
            OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
            ptr::null_mut(),
        )
    };
    match handle {
        INVALID_HANDLE_VALUE => Err(io::Error::last_os_error().into()),
        handle => Ok(Handle(handle)),
    }
}

fn information(handle: &Handle) -> Result<BY_HANDLE_FILE_INFORMATION, Error> {
    let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { zeroed() };
    match unsafe { GetFileInformationByHandle(handle.0, &mut info) } {
        0 => Err(io::Error::last_os_error().into()),
        _ => Ok(info),
    }
}

fn plain(path: &Path, info: &BY_HANDLE_FILE_INFORMATION) -> Result<bool, Error> {
    if info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
        return Err(refuse(path, "it is a reparse point"));
    }
    let directory = info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0;
    if !directory && info.nNumberOfLinks != 1 {
        return Err(refuse(path, "it has more than one hard link"));
    }
    Ok(directory)
}

fn security(handle: &Handle, what: u32) -> Result<(PSID, *mut ACL, Local), Error> {
    let mut owner: PSID = ptr::null_mut();
    let mut dacl: *mut ACL = ptr::null_mut();
    let mut descriptor: PSECURITY_DESCRIPTOR = ptr::null_mut();
    let status = unsafe {
        GetSecurityInfo(
            handle.0,
            SE_FILE_OBJECT,
            what,
            &mut owner,
            ptr::null_mut(),
            &mut dacl,
            ptr::null_mut(),
            &mut descriptor,
        )
    };
    let descriptor = Local(descriptor);
    match status {
        0 => Ok((owner, dacl, descriptor)),
        code => {
            Err(io::Error::from_raw_os_error(i32::try_from(code).map_err(io::Error::other)?).into())
        }
    }
}

fn administrative(sid: PSID) -> bool {
    unsafe {
        IsWellKnownSid(sid, WinLocalSystemSid) != 0
            || IsWellKnownSid(sid, WinBuiltinAdministratorsSid) != 0
    }
}

fn trusted_installer() -> Result<Local, Error> {
    let text = wide(OsStr::new(Acl::TRUSTED_INSTALLER));
    let mut sid: PSID = ptr::null_mut();
    match unsafe { ConvertStringSidToSidW(text.as_ptr(), &mut sid) } {
        0 => Err(io::Error::last_os_error().into()),
        _ => Ok(Local(sid)),
    }
}

fn apply(handle: &Handle, sddl: &str) -> Result<(), Error> {
    let descriptor = Descriptor::parse(&format!("{}{sddl}", Acl::OWNER))?;
    let what = OWNER_SECURITY_INFORMATION
        | DACL_SECURITY_INFORMATION
        | PROTECTED_DACL_SECURITY_INFORMATION;
    match unsafe { SetKernelObjectSecurity(handle.0, what, descriptor.raw()) } {
        0 => Err(io::Error::last_os_error().into()),
        _ => Ok(()),
    }
}

fn enable_privileges() -> Result<(), Error> {
    let mut token: HANDLE = ptr::null_mut();
    let opened = unsafe {
        OpenProcessToken(
            GetCurrentProcess(),
            TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
            &mut token,
        )
    };
    if opened == 0 {
        return Err(io::Error::last_os_error().into());
    }
    let token = Handle(token);
    for name in [SE_BACKUP_NAME, SE_RESTORE_NAME, SE_TAKE_OWNERSHIP_NAME] {
        let mut luid: LUID = unsafe { zeroed() };
        if unsafe { LookupPrivilegeValueW(ptr::null(), name, &mut luid) } == 0 {
            return Err(io::Error::last_os_error().into());
        }
        let state = TOKEN_PRIVILEGES {
            PrivilegeCount: 1,
            Privileges: [LUID_AND_ATTRIBUTES {
                Luid: luid,
                Attributes: SE_PRIVILEGE_ENABLED,
            }],
        };
        let size = u32::try_from(size_of::<TOKEN_PRIVILEGES>()).map_err(io::Error::other)?;
        let adjusted = unsafe {
            AdjustTokenPrivileges(token.0, 0, &state, size, ptr::null_mut(), ptr::null_mut())
        };
        let error = io::Error::last_os_error();
        let unassigned = error
            .raw_os_error()
            .iter()
            .any(|code| *code == Acl::NOT_ALL_ASSIGNED);
        if adjusted == 0 || unassigned {
            return Err(error.into());
        }
    }
    Ok(())
}

fn template(paths: &Paths, path: &Path, directory: bool, data: bool) -> String {
    if path == paths.token {
        return Sddl::file(Exposure::Readers);
    }
    if path == paths.peer_key {
        return Sddl::file(Exposure::Private);
    }
    if path == paths.settings || path == paths.config {
        return Sddl::file(Exposure::Public);
    }
    match (directory, data) {
        (true, true) => Sddl::data_directory(),
        (false, true) => Sddl::file(Exposure::Private),
        (true, false) => Sddl::settings_directory(),
        (false, false) => Sddl::file(Exposure::Public),
    }
}

fn squatted(paths: &Paths, path: &Path, handle: &Handle) -> Result<bool, Error> {
    let known = [
        &paths.settings,
        &paths.config,
        &paths.token,
        &paths.peer_key,
    ];
    if !known.iter().any(|item| item.as_path() == path) {
        return Ok(false);
    }
    let (owner, _dacl, _descriptor) = security(handle, OWNER_SECURITY_INFORMATION)?;
    Ok(!administrative(owner))
}

fn walk(paths: &Paths, dir: &Path, data: bool) -> Result<(), Error> {
    for entry in fs::read_dir(dir)? {
        let path = entry?.path();
        let handle = open(
            &path,
            READ_CONTROL | WRITE_DAC | WRITE_OWNER | FILE_READ_ATTRIBUTES,
        )?;
        let directory = plain(&path, &information(&handle)?)?;
        if squatted(paths, &path, &handle)? {
            drop(handle);
            fs::remove_file(&path)?;
            continue;
        }
        let data = data || path == paths.data;
        apply(&handle, &template(paths, &path, directory, data))?;
        drop(handle);
        if directory {
            walk(paths, &path, data)?;
        }
    }
    Ok(())
}

pub fn reclaim(paths: &Paths) -> Result<(), Error> {
    if !paths.state_dir.try_exists()? {
        return Ok(());
    }
    enable_privileges()?;
    let root = &paths.state_dir;
    let handle = open(
        root,
        READ_CONTROL | WRITE_DAC | WRITE_OWNER | FILE_READ_ATTRIBUTES,
    )?;
    if !plain(root, &information(&handle)?)? {
        return Err(refuse(root, "it is not a directory"));
    }
    apply(&handle, &Sddl::settings_directory())?;
    drop(handle);
    walk(paths, root, false)
}

fn writers_are_trusted(dacl: *mut ACL, installer: PSID) -> Result<bool, Error> {
    if dacl.is_null() {
        return Ok(false);
    }
    let count = unsafe { (*dacl).AceCount };
    for index in 0..u32::from(count) {
        let mut ace: *mut c_void = ptr::null_mut();
        if unsafe { GetAce(dacl, index, &mut ace) } == 0 {
            return Err(io::Error::last_os_error().into());
        }
        let header = unsafe { *ace.cast::<ACE_HEADER>() };
        let applies = u32::from(header.AceFlags) & Acl::INHERIT_ONLY == 0;
        if u32::from(header.AceType) != Acl::ALLOWED || !applies {
            continue;
        }
        let allowed = ace.cast::<ACCESS_ALLOWED_ACE>();
        let mask = unsafe { (*allowed).Mask };
        let sid: PSID = unsafe { ptr::addr_of_mut!((*allowed).SidStart).cast() };
        let trusted = administrative(sid) || unsafe { EqualSid(sid, installer) } != 0;
        if mask & Acl::WRITE_RIGHTS != 0 && !trusted {
            return Ok(false);
        }
    }
    Ok(true)
}

pub fn trusted_binary(binary: &Path) -> Result<(), Error> {
    let expected = config::program_files()
        .join(config::WINDOWS_PRODUCT_DIR)
        .join(Acl::BINARY);
    let refusal = || {
        Error::Failed(format!(
            "refusing to register {}: the service binary must be {}, owned by SYSTEM, \
             Administrators or TrustedInstaller and writable by nobody else",
            binary.display(),
            expected.display()
        ))
    };
    let same = match (binary.to_str(), expected.to_str()) {
        (Some(binary), Some(expected)) => binary.eq_ignore_ascii_case(expected),
        (Some(..) | None, Some(..) | None) => return Err(refusal()),
    };
    if !same {
        return Err(refusal());
    }
    let Some(parent) = binary.parent() else {
        return Err(refusal());
    };
    let installer = trusted_installer()?;
    let file = trusted_object(binary, false, installer.0)?;
    let directory = trusted_object(parent, true, installer.0)?;
    match file && directory {
        true => Ok(()),
        false => Err(refusal()),
    }
}

fn trusted_object(path: &Path, directory: bool, installer: PSID) -> Result<bool, Error> {
    let handle = open(path, READ_CONTROL | FILE_READ_ATTRIBUTES)?;
    if plain(path, &information(&handle)?)? != directory {
        return Ok(false);
    }
    let (owner, dacl, _descriptor) = security(
        &handle,
        OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
    )?;
    let owned = administrative(owner) || unsafe { EqualSid(owner, installer) } != 0;
    Ok(owned && writers_are_trusted(dacl, installer)?)
}

pub fn administered(path: &Path) -> Result<(), Error> {
    let handle = open(path, READ_CONTROL | FILE_READ_ATTRIBUTES)?;
    if !plain(path, &information(&handle)?)? {
        return Err(refuse(path, "it is not a directory"));
    }
    let (owner, _dacl, _descriptor) = security(&handle, OWNER_SECURITY_INFORMATION)?;
    match administrative(owner) {
        true => Ok(()),
        false => Err(refuse(path, "it is not owned by SYSTEM or Administrators")),
    }
}