Search code examples
winapirustwindows-rs

windows-rs GetNamedSecurityInfoW error 87


I'm trying to use windows-rs to use GetNamedSecurityInfoW microsoft api docs to read file permission information, but I keep getting error code 87 corresponds to ERROR_INVALID_PARAMETER. What have I done wrong? (I'm not experienced with rust or the windows api)

#[cfg(windows)]
pub unsafe fn get_file_perms(file: String) -> Result<()> {
    use windows_sys::core::PCWSTR;
    use windows_sys::Win32::Security::Authorization::GetNamedSecurityInfoW;

    let file_u16 = file.encode_utf16().collect::<Vec<u16>>();
    let lpfile: PCWSTR = file_u16.as_ptr() as PCWSTR;
    let acl: *mut *mut windows_sys::Win32::Security::ACL = std::ptr::null_mut();
    let security_descriptor: *mut windows_sys::Win32::Security::PSECURITY_DESCRIPTOR = std::ptr::null_mut();
    let err = GetNamedSecurityInfoW(
        lpfile,
        windows_sys::Win32::Security::Authorization::SE_FILE_OBJECT,
        windows_sys::Win32::Security::DACL_SECURITY_INFORMATION,
        std::ptr::null_mut(),
        std::ptr::null_mut(),
        acl,
        std::ptr::null_mut(),
        security_descriptor,
    );
    if err != 0
    {
        println!("{}", err);
        return Err(anyhow!("Failed to get file permissions"));
    }

    Ok(())
}`

Solution

  • GetNamedSecurityInfoW is an API call with somewhat complex semantics. Besides a description of the object, there's

    • a bitmask (SecurityInfo) describing the requested information
    • a set of output parameters (ppsidOwner, ppsidGroup, ppDacl, ppSacl) that provide access to structured data
    • the actual buffer holding the data (ppSecurityDescriptor).

    On successful return, the system allocates memory, and transfers ownership to the caller through the final parameter. Depending on the requested information (DACL_SECURITY_INFORMATION) you will have to pass the addresses of pointers to the structured data (ppDacl in this case).

    With that fixed, there are two more issues left: Making sure that the object name (pObjectName) is zero-terminated, and cleaning up the buffer the system allocated for us with a call to LocalFree. Note that any one of ppsidOwner, ppsidGroup, ppDacl, and ppSacl are valid only for as long as ppSecurityDescriptor is valid.

    The following code fixes the immediate issue:

    pub unsafe fn get_file_perms(file: String) -> Result<()> {
        use windows_sys::Win32::Security::Authorization::GetNamedSecurityInfoW;
    
        let file_u16 = file.encode_utf16().collect::<Vec<u16>>();
        // Pointers that receive the output arguments
        let mut acl = std::ptr::null_mut();
        let mut security_descriptor = std::ptr::null_mut();
        let err = GetNamedSecurityInfoW(
            file_u16.as_ptr(),
            windows_sys::Win32::Security::Authorization::SE_FILE_OBJECT,
            windows_sys::Win32::Security::DACL_SECURITY_INFORMATION,
            std::ptr::null_mut(),
            std::ptr::null_mut(),
            // Pass the *address* of the pointer
            std::ptr::addr_of_mut!(acl),
            std::ptr::null_mut(),
            // Same here
            std::ptr::addr_of_mut!(security_descriptor),
        );
        if err != 0 {
            println!("{}", err);
            return Err("Failed to get file permissions".into());
        }
    
        // At this point `acl` points into an access control list
    
        // Cleanup up resources (should really be bound to a struct with a `Drop` impl)
        windows_sys::Win32::System::Memory::LocalFree(security_descriptor as _);
    
        Ok(())
    }
    

    As far as the interface goes, you should consider taking a Path/PathBuf instead. Since you are dealing with path names, a String will unduly restrict the input to the point of not being able to encode all potential paths.

    Adding zero-termination, the function can be rewritten to this:

    pub unsafe fn get_file_perms(file: impl AsRef<Path>) -> Result<()> {
    
        let file_u16 = file
            .as_ref()
            .as_os_str()
            .encode_wide()
            .chain(once(0))
            .collect::<Vec<_>>();
    
        // ...