Search code examples
winapirustlow-leveldpapi

Decrypting Data with CryptUnprotectData in Rust Leads to STATUS_HEAP_CORRUPTION Error


I'm working on a Rust function that receives a &[u8] and decrypts it using CryptUnprotectData from the WinAPI. Here's the function in question:

fn decrypt(data: &[u8]) -> Result<Vec<u8>, String> {}

I've successfully implemented this function, and it successfully decrypts the data using DPAPI.

However, I've encountered an issue when trying to free the buffer passed to CryptUnprotectData. I'm getting the following error:

error: process didn't exit successfully: `target\debug\main.exe` (exit code: 0xc0000374, STATUS_HEAP_CORRUPTION)

Here's the code for the function:

use winapi::{
    um::{
        winbase,
        dpapi,
        wincrypt
    },
    shared::minwindef
};

fn decrypt(keydpapi: &[u8]) -> Result<Vec<u8>, String> {
    // https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptunprotectdata
    // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-localfree
    // https://docs.rs/winapi/latest/winapi/um/dpapi/index.html
    // https://docs.rs/winapi/latest/winapi/um/winbase/fn.LocalFree.html

    let mut data_in = wincrypt::DATA_BLOB {
        cbData: keydpapi.len() as minwindef::DWORD,
        pbData: keydpapi.as_ptr() as *mut minwindef::BYTE,
    };
    let mut data_out = wincrypt::DATA_BLOB {
        cbData: 0,
        pbData: ptr::null_mut()
    };
    let result = unsafe {
        dpapi::CryptUnprotectData(
            &mut data_in,
            ptr::null_mut(),
            ptr::null_mut(),
            ptr::null_mut(),
            ptr::null_mut(),
            0,
            &mut data_out
        )
    };
    if result == 0 {
        return Err("CryptUnprotectData failed".to_string())
    };
    if data_out.pbData.is_null() {
        return Err("CryptUnprotectData returned a null pointer".to_string());
    }
    
    let decrypted_data = unsafe {
        Vec::from_raw_parts(data_out.pbData, data_out.cbData as usize, data_out.cbData as usize)
    };
    unsafe {
        winbase::LocalFree(data_out.pbData as minwindef::HLOCAL); // error occured here
    };
    Ok(decrypted_data)
}

I'd appreciate any insights or solutions to resolve this STATUS_HEAP_CORRUPTION error.


Solution

  • let decrypted_data = unsafe {
        Vec::from_raw_parts(data_out.pbData, data_out.cbData as usize, data_out.cbData as usize)
    };
    unsafe {
        winbase::LocalFree(data_out.pbData as minwindef::HLOCAL); // error occured here
    };
    

    This code is incorrect in two ways.

    1. Vec::from_raw_parts() takes ownership of the allocation pointer provided; that is, it takes responsibility for freeing the allocation. If you free the allocation after creating the Vec, that will be a double-free.

      The ownership of ptr is effectively transferred to the Vec<T> which may then deallocate, reallocate or change the contents of memory pointed to by the pointer at will. Ensure that nothing else uses the pointer after calling this function.

    2. The allocation must have been allocated by the Rust global allocator:

      • ptr must have been allocated using the global allocator, such as via the alloc::alloc function.

    Whenever you are using an unsafe function, you must read the safety requirements and follow all of them.

    Instead of Vec::from_raw_parts, you should construct a non-owning slice reference using std::slice::from_raw_parts, then call to_vec() on that slice to copy the data into a new Vec. Then, you can LocalFree the system-created allocation.

    If you don't want to copy the data, then you must return a type that owns the Windows allocation — not a Rust Vec. I'm not familiar with Windows bindings, so perhaps such a type already exists, but if it does not, then write your own — a struct containing the pointer, and implementing the Deref trait to provide data access and the Drop trait to deallocate when it is no longer needed.