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.
This code is incorrect in two ways.
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 theVec, that will be a double-free.The allocation must have been allocated by the Rust global allocator:
Whenever you are using an
unsafefunction, 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 usingstd::slice::from_raw_parts, then callto_vec()on that slice to copy the data into a newVec. Then, you canLocalFreethe 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 theDereftrait to provide data access and theDroptrait to deallocate when it is no longer needed.