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.
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.
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 theVec<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.
The allocation must have been allocated by the Rust global allocator:
ptr
must have been allocated using the global allocator, such as via thealloc::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.