Delta_Fore
Delta_Fore

Reputation: 3271

Calling the GetUserName WinAPI function with a mutable string doesn't populate the string

This appears to partially work but I cannot get the string value to print

pub fn test() {
    let mut buf: Vec<u16> = vec![0; 64];
    let mut sz: DWORD = 0;
    unsafe {
        advapi32::GetUserNameW(buf.as_mut_ptr(), &mut sz);
    }
    let str1 = OsString::from_wide(&buf).into_string().unwrap();
    println!("Here: {} {}", sz, str1);
}

Prints:

Here: 10

When I expect it to also print

Here: 10 <username>

As a test, the C version

TCHAR buf[100];
DWORD sz;
GetUserName(buf, &sz);

seems to populate buf fine.

Upvotes: 1

Views: 2211

Answers (1)

Shepmaster
Shepmaster

Reputation: 431529

GetUserName

You should re-read the API documentation for GetUserName to recall how the arguments work:

lpnSize [in, out]

On input, this variable specifies the size of the lpBuffer buffer, in TCHARs. On output, the variable receives the number of TCHARs copied to the buffer, including the terminating null character. If lpBuffer is too small, the function fails and GetLastError returns ERROR_INSUFFICIENT_BUFFER. This parameter receives the required buffer size, including the terminating null character.

TL;DR:

  • On input: caller tells the API how many spaces the buffer has.
  • On success: API tells the caller how many spaces were used.
  • On failure: API tells the caller how many spaces were needed.

C version

This has a fixed-size stack-allocated array of 100 TCHARs.

This code is broken and unsafe because sz is uninitialized. This allows the API to write an undefined number of characters to a buffer that's only 100 long. If the username is over 100 characters, you've just introduced a security hole into your program.

Rust version

The Rust code is broken in a much better way. sz is set to zero, which means "you may write zero entries of data", so it writes zero entries. Thus, the Vec buffer is full of zeros and the resulting string is empty. The buffer is reported too small to receive the username, so GetUserNameW sets sz to the number of characters that the buffer needs to have allocated.

What to do

One "fix" would be to set sz to the length of your array. However, this is likely to have over- or under-allocated the buffer.

If you are ok with a truncated string (and I'm not sure if TCHAR strings can be split arbitrarily, I know UTF-8 cannot), then it would be better to use a fixed-size array like the C code.

If you want to more appropriately allocate memory to call this type of WinAPI function, see What is the right way to allocate data to pass to an FFI call?.

extern crate advapi32;
extern crate winapi;

use std::ptr;

fn get_user_name() -> String {
    unsafe {
        let mut size = 0;
        let retval = advapi32::GetUserNameW(ptr::null_mut(), &mut size);
        assert_eq!(retval, 0, "Should have failed");

        let mut username = Vec::with_capacity(size as usize);
        let retval = advapi32::GetUserNameW(username.as_mut_ptr(), &mut size);
        assert_ne!(retval, 0, "Perform better error handling");
        assert!((size as usize) <= username.capacity());
        username.set_len(size as usize);

        // Beware: This leaves the trailing NUL character in the final string,
        // you may want to remove it!
        String::from_utf16(&username).unwrap()
    }
}

fn main() {
    println!("{:?}", get_user_name()); // "IEUser\u{0}"
}

Upvotes: 10

Related Questions