0

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(())
}`
Paul Dempsey
  • 639
  • 3
  • 19
t348575
  • 674
  • 8
  • 19
  • 1
    possible error in last parameter - security_descriptor - this is pointer to pointer and must not be 0, but look like you past 0 here – RbMm May 09 '22 at 09:29
  • The first argument is *"a pointer to a null-terminated string"*. If `file` isn't null-terminated, then `file_u16` won't be either. This is setting up the following API call to read out of bounds. – IInspectable May 09 '22 at 09:41
  • @IInspectable RbMm both your comments together fixed the issues. (Rust strings are not null terminated) – t348575 May 09 '22 at 09:50
  • As an aside, when you're dealing with path names, you shouldn't be using `String`/`&str`. Either one can represent only a subset of valid NTFS path names. `Path`/`PathBuf` are a better pick, as they store data as `OsString`/`OsStr` internally, allowing to pass path names around that do not consist of valid UTF-16 sequences. If you want to prevent conversions altogether, you can use `Vec`/`&[u16]` as well. – IInspectable May 09 '22 at 10:07

1 Answers1

0

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<_>>();

    // ...
IInspectable
  • 46,945
  • 8
  • 85
  • 181