Get Windows Handler in Rust

So I am trying to convet this piece of C# code to Rust.

 private bool CreateAnonymousPipeEveryoneAccess(ref IntPtr hReadPipe, ref IntPtr hWritePipe)
    {
        SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
        sa.Length = Marshal.SizeOf(sa);
        sa.lpSecurityDescriptor = IntPtr.Zero;
        sa.bInheritHandle = true;
        if (CreatePipe(out hReadPipe, out hWritePipe, ref sa, (uint)BUFFER_SIZE_PIPE))
            return true;
        return false;
    }


private void RunasSetupStdHandlesForProcess(ref STARTUPINFO startupInfo, out IntPtr hOutputWrite, out IntPtr hErrorWrite, out IntPtr hOutputRead) {
        IntPtr hOutputReadTmpLocal = IntPtr.Zero;
        IntPtr hOutputWriteLocal = IntPtr.Zero;
        IntPtr hErrorWriteLocal = IntPtr.Zero;
        IntPtr hOutputReadLocal = IntPtr.Zero;
        IntPtr socketLocal = IntPtr.Zero;
       
        IntPtr hCurrentProcess = Process.GetCurrentProcess().Handle;
        if (!CreateAnonymousPipeEveryoneAccess(ref hOutputReadTmpLocal, ref hOutputWriteLocal))
                throw new RunasCsException("CreatePipe", true);
        if (!DuplicateHandle(hCurrentProcess, hOutputWriteLocal, hCurrentProcess, out hErrorWriteLocal, 0, true, DUPLICATE_SAME_ACCESS))
                throw new RunasCsException("DuplicateHandle stderr write pipe", true);
        if (!DuplicateHandle(hCurrentProcess, hOutputReadTmpLocal, hCurrentProcess, out hOutputReadLocal, 0, false, DUPLICATE_SAME_ACCESS))
                throw new RunasCsException("DuplicateHandle stdout read pipe", true);
        CloseHandle(hOutputReadTmpLocal);
        hOutputReadTmpLocal = IntPtr.Zero;
        UInt32 PIPE_NOWAIT = 0x00000001;
        if (!SetNamedPipeHandleState(hOutputReadLocal, ref PIPE_NOWAIT, IntPtr.Zero, IntPtr.Zero))
                throw new RunasCsException("SetNamedPipeHandleState", true);
        
        startupInfo.dwFlags = Startf_UseStdHandles;
        startupInfo.hStdOutput = hOutputWriteLocal;
        startupInfo.hStdError = hErrorWriteLocal;
      
        hOutputWrite = hOutputWriteLocal;
        hErrorWrite = hErrorWriteLocal;
        hOutputRead = hOutputReadLocal;
  
    }

I presume the Process.GetCurrentProcess().Handle returns the Windows handler for the process.
I do know that std::process::id do have the function id to return the handler id of current process, so I came up with h_current_process = HANDLE(id() as isize);.

All in all:

fn create_anonymous_pipe_everyone_access(h_read_pipe: *const u32, h_write_pipe: *const u32) -> bool {
    const SA: SECURITY_ATTRIBUTES = SECURITY_ATTRIBUTES{
        nLength: size_of::<SECURITY_ATTRIBUTES>() as u32,
        lpSecurityDescriptor: ptr::null_mut(),
        bInheritHandle: BOOL(1),
    };

    unsafe {
        const BUFFER_SIZE_PIPE: u32 = 1048576;
        return CreatePipe(&mut HANDLE(h_read_pipe as isize), &mut HANDLE(h_write_pipe as isize),
                          Some(&SA), BUFFER_SIZE_PIPE).as_bool();
    }
}

fn runas_setup_std_handles_for_process(mut startup_info: STARTUPINFOW, mut h_output_write: *const u32, mut h_error_write: *const u32, mut h_output_read: *const u32) {
    let h_output_read_tmp_local0: u32 = 0;
    let h_output_read_tmp_local: *const u32 = &h_output_read_tmp_local0;
    let h_output_write_local0: u32 = 0;
    let h_output_write_local: *const u32 = &h_output_write_local0;
    let h_error_write_local0: u32 = 0;
    let h_error_write_local: *const u32 = &h_error_write_local0;
    let h_output_read_local0: u32 = 0;
    let h_output_read_local: *const u32 = &h_output_read_local0;

    let mut h_current_process = HANDLE(id() as isize);
    println!("Currrent process: {}", id());
    if create_anonymous_pipe_everyone_access(h_output_read_tmp_local, h_output_write_local) {
        println!("Error Create pipe")
    }
    unsafe {
        if !DuplicateHandle(h_current_process, HANDLE(h_output_write_local as isize),
                            h_current_process, &mut HANDLE(h_error_write_local as isize),
                            0, true, DUPLICATE_SAME_ACCESS).as_bool() {
            println!("Error DuplicateHandle stderr write pipe")
        }
        if !DuplicateHandle(h_current_process, HANDLE(h_output_read_tmp_local as isize),
                            h_current_process, &mut HANDLE(h_output_read_local as isize),
                            0, true, DUPLICATE_SAME_ACCESS).as_bool() {
            println!("Error DuplicateHandle stdout read pipe")
        }
        CloseHandle(HANDLE(h_output_read_tmp_local as isize));
        const LPMODE: NAMED_PIPE_MODE = PIPE_NOWAIT;

        if !SetNamedPipeHandleState(HANDLE(h_output_read_local as isize), Some(&LPMODE), None, None ).as_bool(){
            println!("Error SetNamedPipeHandleState")
        }
    }
    startup_info.dwFlags = STARTF_USESTDHANDLES;
    startup_info.hStdOutput = HANDLE(h_output_write_local as isize);
    startup_info.hStdError = HANDLE(h_error_write_local as isize);

    h_output_write = h_error_write_local;
    h_error_write = h_error_write_local;
    h_output_read = h_output_read_local;
}

Problem is mine failed in every state possible, even the reate_anonymous_pipe_everyone_access return false, i.e. failed. Not compilation failed, but every error got printed out.
Can anyone have a look at it and what did I do wrong? Thank you.
Also, is there anyway to shorten:

    let h_output_read_tmp_local0: u32 = 0;
    let h_output_read_tmp_local: *const u32 = &h_output_read_tmp_local0;

to something one liner (if I did not do anything wrong at that part)?
Thank you.

The process ID is a sysem-wide unique ID of the process. If that could be re-interpreted as a memory address in an arbitrary address space (i.e., in any process), that would be quite weird. I don't think your assumptions are at all correct.

Just pass &0 directly to the function.

on Windows, a process ID is different from a process HANDLE, and what's more confusing, the GetCurrentProcess() function actually returns a so called "pseudo handle". it's value is -1. the id() function returns a PID, not a handle (or pseudo handle).

your code is different from the C# implementation, (although in my opinion the C# function should change to out instead of ref for the ref IntPtr hReadPipe and ref IntPtr hWritePipe parameters). a ref argument roughly can be emulated using a exclusive &mut in rust, not a shared &. but really, you should not use output parameter but instead return the result directly. for example, this is more idiomatic in rust (direct translate from the C# code, not tested; also I noticed you are calling DuplicateHandle wrong for the output pointer, I translated from the original C# code):

fn create_anonymous_pipe_everyone_access() -> Option<(HANDLE, HANDLE)> {
    const SA: SECURITY_ATTRIBUTES = SECURITY_ATTRIBUTES{
        nLength: size_of::<SECURITY_ATTRIBUTES>() as u32,
        lpSecurityDescriptor: ptr::null_mut(),
        bInheritHandle: BOOL(1),
    };

    unsafe {
        const BUFFER_SIZE_PIPE: u32 = 1048576;
        let mut read_handle = MaybeUninit::uninit();
        let mut write_handle = MaybeUninit::uninit();
        if CreatePipe(read_handle.as_mut_ptr(), write_handle.as_mut_ptr(), Some(&SA), BUFFER_SIZE_PIPE).as_bool() {
            Some((read_handle.assume_init(), write_handle.assume_init()))
        } else {
            None
        }
    }
}

fn runas_setup_std_handles_for_process(startup_info: &mut STARTUPINFOW) -> Option<(HANDLE, HANDLE, HANDLE)> {

    // the "pseudo handle" for current process is -1
    let mut h_current_process = HANDLE(-1);
    // alternatively, use "proper" API call
    // let h_current_process = unsafe { GetCurrentProcess() };
    println!("Currrent process: {}", id());
    let Some((output_read_tmp_local, output_write_local)) = create_anonymous_pipe_everyone_access() else {
        println!("Error Create pipe");
        return None;
    }

    // use this as s scratch buffer for the output pointer of Win32 API
    // HANDLE is `Copy` so `MaybeUninit` can be cloned
    let mut tmp_handle = MaybeUninit::uninit();
    let error_write_local: HANDLE;
    let output_read_local: HANDLE;
    unsafe {
        if !DuplicateHandle(h_current_process, output_write_local,
                            h_current_process, tmp_handle.as_mut_ptr(),
                            0, true, DUPLICATE_SAME_ACCESS).as_bool() {
            println!("Error DuplicateHandle stderr write pipe");
            return None;
        }
        error_write_local = tmp_handle.clone().assume_init();

        if !DuplicateHandle(h_current_process, output_read_tmp_local,
                            h_current_process, tmp_handle.as_mut_ptr(),
                            0, true, DUPLICATE_SAME_ACCESS).as_bool() {
            println!("Error DuplicateHandle stdout read pipe");
            return None;
        }
        output_read_local = tmp_handle.assume_init();

        CloseHandle(output_read_tmp_local);
        const LPMODE: NAMED_PIPE_MODE = PIPE_NOWAIT;

        if !SetNamedPipeHandleState(output_read_local, Some(&LPMODE), None, None ).as_bool(){
            println!("Error SetNamedPipeHandleState");
            return None;
        }
    }
    startup_info.dwFlags = STARTF_USESTDHANDLES;
    startup_info.hStdOutput = output_write_local;
    startup_info.hStdError = error_write_local;

    return Some((error_write_local, error_write_local, output_read_local))
}

although direct translate Win32 API calls is straight forward, but I'd suggest not to translate code function by function (or even line by line) from one language (C#) to another language (rust). you should read the original code and figure out what it does, and rewrite in the target language. because if you don't understand the original code, it's very likely you'll make mistakes translating it.

4 Likes

This has helped me greatly.
Thank you